Merge branch 'main' into watch
19
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "bada-frame",
|
||||
"version": "0.9.1",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
@ -22,8 +22,6 @@
|
|||
"@mui/x-date-pickers": "^5.0.0-alpha.6",
|
||||
"@sentry/nextjs": "^6.7.1",
|
||||
"@stripe/stripe-js": "^1.13.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
||||
"@typescript-eslint/parser": "^4.25.0",
|
||||
"axios": "^0.21.3",
|
||||
"bip39": "^3.0.4",
|
||||
"bootstrap": "^4.5.2",
|
||||
|
@ -31,16 +29,11 @@
|
|||
"chrono-node": "^2.2.6",
|
||||
"comlink": "^4.3.0",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-plugin-import": "^2.23.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"exifr": "^7.1.3",
|
||||
"ffmpeg-wasm": "file:./thirdparty/ffmpeg-wasm",
|
||||
"file-type": "^16.5.3",
|
||||
"file-type": "^16.5.4",
|
||||
"formik": "^2.1.5",
|
||||
"heic-convert": "^1.2.4",
|
||||
"http-proxy-middleware": "^1.0.5",
|
||||
"is-electron": "^2.2.0",
|
||||
"jszip": "3.7.1",
|
||||
"libsodium-wrappers": "^0.7.8",
|
||||
|
@ -58,8 +51,6 @@
|
|||
"react-top-loading-bar": "^2.0.1",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.6",
|
||||
"react-window-infinite-loader": "^1.0.5",
|
||||
"scrypt-js": "^3.0.1",
|
||||
"styled-components": "^5.3.5",
|
||||
"workbox-precaching": "^6.1.5",
|
||||
"workbox-recipes": "^6.1.5",
|
||||
|
@ -83,11 +74,17 @@
|
|||
"@types/react-window-infinite-loader": "^1.0.3",
|
||||
"@types/styled-components": "^5.1.25",
|
||||
"@types/yup": "^0.29.7",
|
||||
"@typescript-eslint/eslint-plugin": "^4.25.0",
|
||||
"@typescript-eslint/parser": "^4.25.0",
|
||||
"babel-plugin-styled-components": "^1.11.1",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.23.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"husky": "^7.0.1",
|
||||
"lint-staged": "^11.1.2",
|
||||
"prettier": "2.3.2",
|
||||
|
|
Before Width: | Height: | Size: 157 B |
Before Width: | Height: | Size: 11 KiB |
|
@ -1,28 +0,0 @@
|
|||
export const BLACK_THUMBNAIL_BASE64 =
|
||||
'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB' +
|
||||
'AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQ' +
|
||||
'EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARC' +
|
||||
'ACWASwDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUF' +
|
||||
'BAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk' +
|
||||
'6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztL' +
|
||||
'W2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAA' +
|
||||
'AAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVY' +
|
||||
'nLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImK' +
|
||||
'kpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oAD' +
|
||||
'AMBAAIRAxEAPwD/AD/6ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' +
|
||||
'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' +
|
||||
'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAC' +
|
||||
'gAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
|
||||
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' +
|
||||
'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' +
|
||||
'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' +
|
||||
'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' +
|
||||
'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA' +
|
||||
'KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' +
|
||||
'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
|
||||
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' +
|
||||
'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAK' +
|
||||
'ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA' +
|
||||
'KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
|
||||
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
|
||||
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k=';
|
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
BIN
public/images/family-plan/1x.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
public/images/family-plan/2x.png
Normal file
After Width: | Height: | Size: 197 KiB |
Before Width: | Height: | Size: 408 KiB After Width: | Height: | Size: 408 KiB |
BIN
public/images/gallery-locked/1x.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
public/images/gallery-locked/2x.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
public/images/gallery-locked/3x.png
Normal file
After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 8.6 KiB |
BIN
public/images/onboarding-lock/1x.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
public/images/onboarding-lock/2x.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
public/images/onboarding-lock/3x.png
Normal file
After Width: | Height: | Size: 101 KiB |
BIN
public/images/onboarding-safe/1x.png
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
public/images/onboarding-safe/2x.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
public/images/onboarding-safe/3x.png
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
public/images/onboarding-sync/1x.png
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
public/images/onboarding-sync/2x.png
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
public/images/onboarding-sync/3x.png
Normal file
After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 38 KiB |
BIN
public/images/subscription-card-background/1x.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
public/images/subscription-card-background/2x.png
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
public/images/subscription-card-background/3x.png
Normal file
After Width: | Height: | Size: 422 KiB |
Before Width: | Height: | Size: 472 B |
|
@ -3,17 +3,17 @@
|
|||
"name": "ente | encrypted photo storage",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/images/ente-192.png",
|
||||
"src": "/images/ente/192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/images/ente-256.png",
|
||||
"src": "/images/ente/256.png",
|
||||
"type": "image/png",
|
||||
"sizes": "256x256"
|
||||
},
|
||||
{
|
||||
"src": "/images/ente-512.png",
|
||||
"src": "/images/ente/512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<img src='/icon.svg' />
|
||||
<img src="/images/ente.svg" />
|
||||
</nav>
|
||||
<div>
|
||||
<h2>seems like you are offline :(</h2>
|
||||
|
|
Before Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 157 B |
10
src/components/Badge.tsx
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { Paper, styled } from '@mui/material';
|
||||
import { CSSProperties } from '@mui/styled-engine';
|
||||
|
||||
export const Badge = styled(Paper)(({ theme }) => ({
|
||||
padding: '2px 4px',
|
||||
backgroundColor: theme.palette.glass.main,
|
||||
color: theme.palette.glass.contrastText,
|
||||
textTransform: 'uppercase',
|
||||
...(theme.typography.mini as CSSProperties),
|
||||
}));
|
|
@ -1,18 +1,17 @@
|
|||
import { Formik, FormikHelpers } from 'formik';
|
||||
import React, { useContext, useRef, useState } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import constants from 'utils/strings/constants';
|
||||
import SubmitButton from 'components/SubmitButton';
|
||||
import router from 'next/router';
|
||||
import { changeEmail, getOTTForEmailChange } from 'services/userService';
|
||||
import { AppContext, FLASH_MESSAGE_TYPE } from 'pages/_app';
|
||||
import { changeEmail, sendOTTForEmailChange } from 'services/userService';
|
||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||
import { PAGES } from 'constants/pages';
|
||||
import { TextField } from '@mui/material';
|
||||
import { Alert, TextField } from '@mui/material';
|
||||
import Container from './Container';
|
||||
import LinkButton from './pages/gallery/LinkButton';
|
||||
import { Alert } from 'react-bootstrap';
|
||||
import FormPaperFooter from './Form/FormPaper/Footer';
|
||||
import { sleep } from 'utils/common';
|
||||
|
||||
interface formValues {
|
||||
email: string;
|
||||
|
@ -23,9 +22,9 @@ function ChangeEmailForm() {
|
|||
const [loading, setLoading] = useState(false);
|
||||
const [ottInputVisible, setShowOttInputVisibility] = useState(false);
|
||||
const ottInputRef = useRef(null);
|
||||
const appContext = useContext(AppContext);
|
||||
const [email, setEmail] = useState(null);
|
||||
const [showMessage, setShowMessage] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
const requestOTT = async (
|
||||
{ email }: formValues,
|
||||
|
@ -33,7 +32,7 @@ function ChangeEmailForm() {
|
|||
) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await getOTTForEmailChange(email);
|
||||
await sendOTTForEmailChange(email);
|
||||
setEmail(email);
|
||||
setShowOttInputVisibility(true);
|
||||
setShowMessage(true);
|
||||
|
@ -54,15 +53,14 @@ function ChangeEmailForm() {
|
|||
setLoading(true);
|
||||
await changeEmail(email, ott);
|
||||
setData(LS_KEYS.USER, { ...getData(LS_KEYS.USER), email });
|
||||
appContext.setDisappearingFlashMessage({
|
||||
message: constants.EMAIL_UDPATE_SUCCESSFUL,
|
||||
type: FLASH_MESSAGE_TYPE.SUCCESS,
|
||||
});
|
||||
setLoading(false);
|
||||
setSuccess(true);
|
||||
await sleep(1000);
|
||||
router.push(PAGES.GALLERY);
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
setFieldError('ott', `${constants.INCORRECT_CODE}`);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const goToGallery = () => router.push(PAGES.GALLERY);
|
||||
|
@ -83,15 +81,13 @@ function ChangeEmailForm() {
|
|||
onSubmit={!ottInputVisible ? requestOTT : requestEmailChange}>
|
||||
{({ values, errors, handleChange, handleSubmit }) => (
|
||||
<>
|
||||
{showMessage && (
|
||||
<Alert
|
||||
variant="success"
|
||||
show={showMessage}
|
||||
style={{ paddingBottom: 0 }}
|
||||
transition
|
||||
dismissible
|
||||
color="accent"
|
||||
onClose={() => setShowMessage(false)}>
|
||||
{constants.EMAIL_SENT({ email })}
|
||||
</Alert>
|
||||
)}
|
||||
<form noValidate onSubmit={handleSubmit}>
|
||||
<Container>
|
||||
<TextField
|
||||
|
@ -121,6 +117,7 @@ function ChangeEmailForm() {
|
|||
/>
|
||||
)}
|
||||
<SubmitButton
|
||||
success={success}
|
||||
sx={{ mt: 2 }}
|
||||
loading={loading}
|
||||
buttonText={
|
||||
|
|
|
@ -11,7 +11,7 @@ export const CopyButtonWrapper = styled(IconButton)`
|
|||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
margin: ${({ theme }) => theme.spacing(1)};
|
||||
margin-top: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const CodeWrapper = styled('div')`
|
||||
|
|
|
@ -7,13 +7,28 @@ import OverflowMenu from 'components/OverflowMenu/menu';
|
|||
export interface CollectionSortProps {
|
||||
setCollectionSortBy: (sortBy: COLLECTION_SORT_BY) => void;
|
||||
activeSortBy: COLLECTION_SORT_BY;
|
||||
nestedInDialog?: boolean;
|
||||
disableBG?: boolean;
|
||||
}
|
||||
|
||||
export default function CollectionSort(props: CollectionSortProps) {
|
||||
return (
|
||||
<OverflowMenu
|
||||
ariaControls="collection-sort"
|
||||
triggerButtonIcon={<SortIcon />}>
|
||||
triggerButtonIcon={<SortIcon />}
|
||||
menuPaperProps={{
|
||||
sx: {
|
||||
backgroundColor: (theme) =>
|
||||
props.nestedInDialog &&
|
||||
theme.palette.background.overPaper,
|
||||
},
|
||||
}}
|
||||
triggerButtonProps={{
|
||||
sx: {
|
||||
background: (theme) =>
|
||||
!props.disableBG && theme.palette.fill.dark,
|
||||
},
|
||||
}}>
|
||||
<CollectionSortOptions {...props} />
|
||||
</OverflowMenu>
|
||||
);
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import { COLLECTION_SORT_BY } from 'constants/collection';
|
||||
import TickIcon from '@mui/icons-material/Done';
|
||||
import { CollectionSortProps } from '.';
|
||||
import { OverflowMenuContext } from 'contexts/overflowMenu';
|
||||
import { OverflowMenuOption } from 'components/OverflowMenu/option';
|
||||
import { SvgIcon } from '@mui/material';
|
||||
|
||||
const SortByOptionCreator =
|
||||
({ setCollectionSortBy, activeSortBy }: CollectionSortProps) =>
|
||||
(props: { sortBy: COLLECTION_SORT_BY; children: any }) => {
|
||||
const { close } = useContext(OverflowMenuContext);
|
||||
|
||||
const handleClick = () => {
|
||||
setCollectionSortBy(props.sortBy);
|
||||
close();
|
||||
};
|
||||
|
||||
return (
|
||||
<OverflowMenuOption
|
||||
onClick={handleClick}
|
||||
startIcon={activeSortBy === props.sortBy && <TickIcon />}>
|
||||
endIcon={
|
||||
activeSortBy === props.sortBy ? <TickIcon /> : <SvgIcon />
|
||||
}>
|
||||
{props.children}
|
||||
</OverflowMenuOption>
|
||||
);
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
import { Box } from '@mui/material';
|
||||
import { Typography } from '@mui/material';
|
||||
import constants from 'utils/strings/constants';
|
||||
import React from 'react';
|
||||
import CollectionCard from '../CollectionCard';
|
||||
import { CollectionSummary } from 'types/collection';
|
||||
import { AllCollectionTileText } from '../styledComponents';
|
||||
import { AllCollectionTile, AllCollectionTileText } from '../styledComponents';
|
||||
|
||||
interface Iprops {
|
||||
collectionTile: any;
|
||||
collectionSummary: CollectionSummary;
|
||||
onCollectionClick: (collectionID: number) => void;
|
||||
}
|
||||
|
||||
export default function AllCollectionCard({
|
||||
collectionTile,
|
||||
onCollectionClick,
|
||||
collectionSummary,
|
||||
}: Iprops) {
|
||||
return (
|
||||
<CollectionCard
|
||||
collectionTile={collectionTile}
|
||||
collectionTile={AllCollectionTile}
|
||||
latestFile={collectionSummary.latestFile}
|
||||
onClick={() => onCollectionClick(collectionSummary.id)}>
|
||||
<AllCollectionTileText>
|
||||
<Box fontWeight={'bold'}>{collectionSummary.name}</Box>
|
||||
<Box>{constants.PHOTO_COUNT(collectionSummary.fileCount)}</Box>
|
||||
<Typography>{collectionSummary.name}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{constants.PHOTO_COUNT(collectionSummary.fileCount)}
|
||||
</Typography>
|
||||
</AllCollectionTileText>
|
||||
</CollectionCard>
|
||||
);
|
|
@ -1,28 +1,22 @@
|
|||
import React from 'react';
|
||||
import { DialogContent } from '@mui/material';
|
||||
import { FlexWrapper } from 'components/Container';
|
||||
import AllCollectionCard from './CollectionCard';
|
||||
import AllCollectionCard from './collectionCard';
|
||||
import { CollectionSummary } from 'types/collection';
|
||||
|
||||
interface Iprops {
|
||||
collectionSummaries: CollectionSummary[];
|
||||
onCollectionClick: (id?: number) => void;
|
||||
collectionTile: any;
|
||||
}
|
||||
export default function AllCollectionContent({
|
||||
collectionSummaries,
|
||||
onCollectionClick,
|
||||
collectionTile,
|
||||
}: Iprops) {
|
||||
return (
|
||||
<DialogContent>
|
||||
<FlexWrapper
|
||||
style={{
|
||||
flexWrap: 'wrap',
|
||||
}}>
|
||||
<FlexWrapper flexWrap="wrap" gap={0.5}>
|
||||
{collectionSummaries.map((collectionSummary) => (
|
||||
<AllCollectionCard
|
||||
collectionTile={collectionTile}
|
||||
onCollectionClick={onCollectionClick}
|
||||
collectionSummary={collectionSummary}
|
||||
key={collectionSummary.id}
|
||||
|
|
|
@ -2,22 +2,33 @@ import { Dialog, Slide, styled } from '@mui/material';
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const AllCollectionContainer = styled(Dialog)(({ theme }) => ({
|
||||
export const AllCollectionDialog = styled(Dialog)<{
|
||||
position: 'flex-start' | 'center' | 'flex-end';
|
||||
}>(({ theme, position }) => ({
|
||||
'& .MuiDialog-container': {
|
||||
justifyContent: 'flex-end',
|
||||
justifyContent: position,
|
||||
},
|
||||
'& .MuiPaper-root': {
|
||||
maxWidth: '498px',
|
||||
maxWidth: '494px',
|
||||
},
|
||||
'& .MuiDialogTitle-root': {
|
||||
padding: theme.spacing(3, 2),
|
||||
padding: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1),
|
||||
},
|
||||
'& .MuiDialogContent-root': {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
[theme.breakpoints.down(559)]: {
|
||||
'& .MuiPaper-root': {
|
||||
width: '324px',
|
||||
},
|
||||
'& .MuiDialogContent-root': {
|
||||
padding: 6,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
AllCollectionContainer.propTypes = {
|
||||
AllCollectionDialog.propTypes = {
|
||||
children: PropTypes.node,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
|
@ -1,6 +1,10 @@
|
|||
import React from 'react';
|
||||
import { DialogTitle, IconButton, Typography } from '@mui/material';
|
||||
import { SpaceBetweenFlex } from 'components/Container';
|
||||
import { Box, DialogTitle, Stack, Typography } from '@mui/material';
|
||||
import {
|
||||
FlexWrapper,
|
||||
FluidContainer,
|
||||
IconButtonWithBG,
|
||||
} from 'components/Container';
|
||||
import CollectionSort from 'components/Collections/AllCollections/CollectionSort';
|
||||
import constants from 'utils/strings/constants';
|
||||
import Close from '@mui/icons-material/Close';
|
||||
|
@ -13,23 +17,28 @@ export default function AllCollectionsHeader({
|
|||
}) {
|
||||
return (
|
||||
<DialogTitle>
|
||||
<SpaceBetweenFlex>
|
||||
<Typography variant="subtitle">
|
||||
<FlexWrapper>
|
||||
<FluidContainer mr={1.5}>
|
||||
<Box>
|
||||
<Typography variant="h3">
|
||||
{constants.ALL_ALBUMS}
|
||||
</Typography>
|
||||
<IconButton onClick={onClose}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</SpaceBetweenFlex>
|
||||
<SpaceBetweenFlex>
|
||||
<Typography variant="subtitle" color={'text.secondary'}>
|
||||
<Typography variant="body2" color={'text.secondary'}>
|
||||
{`${collectionCount} ${constants.ALBUMS}`}
|
||||
</Typography>
|
||||
</Box>
|
||||
</FluidContainer>
|
||||
<Stack direction="row" spacing={1.5}>
|
||||
<CollectionSort
|
||||
activeSortBy={collectionSortBy}
|
||||
setCollectionSortBy={setCollectionSortBy}
|
||||
nestedInDialog
|
||||
/>
|
||||
</SpaceBetweenFlex>
|
||||
<IconButtonWithBG onClick={onClose}>
|
||||
<Close />
|
||||
</IconButtonWithBG>
|
||||
</Stack>
|
||||
</FlexWrapper>
|
||||
</DialogTitle>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,70 +1,60 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import { COLLECTION_SORT_BY } from 'constants/collection';
|
||||
import { sortCollectionSummaries } from 'services/collectionService';
|
||||
import {
|
||||
Transition,
|
||||
AllCollectionContainer,
|
||||
} from 'components/Collections/AllCollections/Container';
|
||||
import { useLocalState } from 'hooks/useLocalState';
|
||||
import { LS_KEYS } from 'utils/storage/localStorage';
|
||||
AllCollectionDialog,
|
||||
} from 'components/Collections/AllCollections/dialog';
|
||||
import AllCollectionsHeader from './header';
|
||||
import { CollectionSummaries } from 'types/collection';
|
||||
import { CollectionSummary } from 'types/collection';
|
||||
import AllCollectionContent from './content';
|
||||
import { AllCollectionTile } from '../styledComponents';
|
||||
import { isSystemCollection } from 'utils/collection';
|
||||
import { AppContext } from 'pages/_app';
|
||||
|
||||
interface Iprops {
|
||||
isOpen: boolean;
|
||||
close: () => void;
|
||||
collectionSummaries: CollectionSummaries;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
collectionSummaries: CollectionSummary[];
|
||||
setActiveCollection: (id?: number) => void;
|
||||
collectionSortBy: COLLECTION_SORT_BY;
|
||||
setCollectionSortBy: (v: COLLECTION_SORT_BY) => void;
|
||||
}
|
||||
|
||||
const LeftSlideTransition = Transition('up');
|
||||
|
||||
export default function AllCollections(props: Iprops) {
|
||||
const { collectionSummaries, isOpen, close, setActiveCollection } = props;
|
||||
|
||||
const [collectionSortBy, setCollectionSortBy] =
|
||||
useLocalState<COLLECTION_SORT_BY>(
|
||||
LS_KEYS.COLLECTION_SORT_BY,
|
||||
COLLECTION_SORT_BY.UPDATION_TIME_DESCENDING
|
||||
);
|
||||
|
||||
const sortedCollectionSummaries = useMemo(
|
||||
() =>
|
||||
sortCollectionSummaries(
|
||||
[...collectionSummaries.values()].filter(
|
||||
(x) => !isSystemCollection(x.type)
|
||||
),
|
||||
collectionSortBy
|
||||
),
|
||||
[collectionSortBy, collectionSummaries]
|
||||
);
|
||||
const {
|
||||
collectionSummaries,
|
||||
open,
|
||||
onClose,
|
||||
setActiveCollection,
|
||||
collectionSortBy,
|
||||
setCollectionSortBy,
|
||||
} = props;
|
||||
const { isMobile } = useContext(AppContext);
|
||||
|
||||
const onCollectionClick = (collectionID: number) => {
|
||||
setActiveCollection(collectionID);
|
||||
close();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<AllCollectionContainer
|
||||
<AllCollectionDialog
|
||||
position="flex-end"
|
||||
TransitionComponent={LeftSlideTransition}
|
||||
onClose={close}
|
||||
open={isOpen}>
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
fullScreen={isMobile}>
|
||||
<AllCollectionsHeader
|
||||
onClose={close}
|
||||
collectionCount={props.collectionSummaries.size}
|
||||
onClose={onClose}
|
||||
collectionCount={props.collectionSummaries.length}
|
||||
collectionSortBy={collectionSortBy}
|
||||
setCollectionSortBy={setCollectionSortBy}
|
||||
/>
|
||||
<Divider />
|
||||
<AllCollectionContent
|
||||
collectionTile={AllCollectionTile}
|
||||
collectionSummaries={sortedCollectionSummaries}
|
||||
collectionSummaries={collectionSummaries}
|
||||
onCollectionClick={onCollectionClick}
|
||||
/>
|
||||
</AllCollectionContainer>
|
||||
</AllCollectionDialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import React from 'react';
|
||||
import { EnteFile } from 'types/file';
|
||||
import {
|
||||
CollectionTileWrapper,
|
||||
ActiveIndicator,
|
||||
CollectionBarTile,
|
||||
CollectionBarTileText,
|
||||
} from '../styledComponents';
|
||||
import CollectionCard from '../CollectionCard';
|
||||
import TruncateText from 'components/TruncateText';
|
||||
|
||||
interface Iprops {
|
||||
active: boolean;
|
||||
latestFile: EnteFile;
|
||||
collectionName: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const CollectionCardWithActiveIndicator = React.forwardRef(
|
||||
(props: Iprops, ref: any) => {
|
||||
const { active, collectionName, ...others } = props;
|
||||
|
||||
return (
|
||||
<CollectionTileWrapper ref={ref}>
|
||||
<CollectionCard collectionTile={CollectionBarTile} {...others}>
|
||||
<CollectionBarTileText>
|
||||
<TruncateText text={collectionName} />
|
||||
</CollectionBarTileText>
|
||||
</CollectionCard>
|
||||
{active && <ActiveIndicator />}
|
||||
</CollectionTileWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default CollectionCardWithActiveIndicator;
|
|
@ -1,12 +0,0 @@
|
|||
import React from 'react';
|
||||
import constants from 'utils/strings/englishConstants';
|
||||
import { CollectionTitleWithDashedBorder } from '../styledComponents';
|
||||
|
||||
export const CreateNewCollectionTile = (props) => {
|
||||
return (
|
||||
<CollectionTitleWithDashedBorder {...props}>
|
||||
<div>{constants.NEW} </div>
|
||||
<div>{'+'}</div>
|
||||
</CollectionTitleWithDashedBorder>
|
||||
);
|
||||
};
|
|
@ -1,114 +0,0 @@
|
|||
import ScrollButton from 'components/Collections/CollectionBar/ScrollButton';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { CollectionSummaries } from 'types/collection';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { ALL_SECTION, COLLECTION_SORT_BY } from 'constants/collection';
|
||||
import { Typography } from '@mui/material';
|
||||
import {
|
||||
CollectionListBarWrapper,
|
||||
ScrollContainer,
|
||||
CollectionListWrapper,
|
||||
} from 'components/Collections/styledComponents';
|
||||
import CollectionCardWithActiveIndicator from 'components/Collections/CollectionBar/CollectionCardWithActiveIndicator';
|
||||
import useComponentScroll, { SCROLL_DIRECTION } from 'hooks/useComponentScroll';
|
||||
import useWindowSize from 'hooks/useWindowSize';
|
||||
import LinkButton from 'components/pages/gallery/LinkButton';
|
||||
import { SpaceBetweenFlex } from 'components/Container';
|
||||
import { sortCollectionSummaries } from 'services/collectionService';
|
||||
|
||||
interface IProps {
|
||||
activeCollection?: number;
|
||||
setActiveCollection: (id?: number) => void;
|
||||
collectionSummaries: CollectionSummaries;
|
||||
showAllCollections: () => void;
|
||||
}
|
||||
|
||||
export default function CollectionListBar(props: IProps) {
|
||||
const {
|
||||
activeCollection,
|
||||
setActiveCollection,
|
||||
collectionSummaries,
|
||||
showAllCollections,
|
||||
} = props;
|
||||
|
||||
const sortedCollectionSummary = useMemo(
|
||||
() =>
|
||||
sortCollectionSummaries(
|
||||
[...collectionSummaries.values()].filter(
|
||||
(c) => c.fileCount > 0
|
||||
),
|
||||
COLLECTION_SORT_BY.UPDATION_TIME_DESCENDING
|
||||
),
|
||||
[collectionSummaries]
|
||||
);
|
||||
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const {
|
||||
componentRef,
|
||||
scrollComponent,
|
||||
hasScrollBar,
|
||||
onFarLeft,
|
||||
onFarRight,
|
||||
} = useComponentScroll({
|
||||
dependencies: [windowSize, collectionSummaries],
|
||||
});
|
||||
|
||||
const collectionChipsRef = sortedCollectionSummary.reduce(
|
||||
(refMap, collectionSummary) => {
|
||||
refMap[collectionSummary.id] = React.createRef();
|
||||
return refMap;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
collectionChipsRef[activeCollection]?.current.scrollIntoView({
|
||||
inline: 'center',
|
||||
});
|
||||
}, [activeCollection]);
|
||||
|
||||
const clickHandler = (collectionID?: number) => () => {
|
||||
setActiveCollection(collectionID ?? ALL_SECTION);
|
||||
};
|
||||
|
||||
return (
|
||||
<CollectionListBarWrapper>
|
||||
<SpaceBetweenFlex mb={1}>
|
||||
<Typography>{constants.ALBUMS}</Typography>
|
||||
{hasScrollBar && (
|
||||
<LinkButton onClick={showAllCollections}>
|
||||
{constants.VIEW_ALL_ALBUMS}
|
||||
</LinkButton>
|
||||
)}
|
||||
</SpaceBetweenFlex>
|
||||
|
||||
<CollectionListWrapper>
|
||||
{!onFarLeft && (
|
||||
<ScrollButton
|
||||
scrollDirection={SCROLL_DIRECTION.LEFT}
|
||||
onClick={scrollComponent(SCROLL_DIRECTION.LEFT)}
|
||||
/>
|
||||
)}
|
||||
<ScrollContainer ref={componentRef}>
|
||||
{sortedCollectionSummary.map((item) => (
|
||||
<CollectionCardWithActiveIndicator
|
||||
key={item.id}
|
||||
latestFile={item.latestFile}
|
||||
ref={collectionChipsRef[item.id]}
|
||||
active={activeCollection === item.id}
|
||||
onClick={clickHandler(item.id)}
|
||||
collectionName={item.name}
|
||||
/>
|
||||
))}
|
||||
</ScrollContainer>
|
||||
{!onFarRight && (
|
||||
<ScrollButton
|
||||
scrollDirection={SCROLL_DIRECTION.RIGHT}
|
||||
onClick={scrollComponent(SCROLL_DIRECTION.RIGHT)}
|
||||
/>
|
||||
)}
|
||||
</CollectionListWrapper>
|
||||
</CollectionListBarWrapper>
|
||||
);
|
||||
}
|
|
@ -1,10 +1,20 @@
|
|||
import { Typography } from '@mui/material';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { FlexWrapper } from 'components/Container';
|
||||
import React from 'react';
|
||||
import constants from 'utils/strings/constants';
|
||||
export function CollectionInfo({ name, fileCount }) {
|
||||
interface Iprops {
|
||||
name: string;
|
||||
fileCount: number;
|
||||
endIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function CollectionInfo({ name, fileCount, endIcon }: Iprops) {
|
||||
return (
|
||||
<div>
|
||||
<FlexWrapper>
|
||||
<Typography variant="subtitle">{name}</Typography>
|
||||
{endIcon && <Box ml={1.5}>{endIcon}</Box>}
|
||||
</FlexWrapper>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{constants.PHOTO_COUNT(fileCount)}
|
||||
</Typography>
|
||||
|
|
|
@ -5,7 +5,11 @@ import CollectionOptions from 'components/Collections/CollectionOptions';
|
|||
import { SetCollectionNamerAttributes } from 'components/Collections/CollectionNamer';
|
||||
import { SpaceBetweenFlex } from 'components/Container';
|
||||
import { CollectionInfoBarWrapper } from './styledComponents';
|
||||
import { isSystemCollection } from 'utils/collection';
|
||||
import { shouldShowOptions } from 'utils/collection';
|
||||
import { CollectionSummaryType } from 'constants/collection';
|
||||
import Favorite from '@mui/icons-material/FavoriteRounded';
|
||||
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
import Delete from '@mui/icons-material/Delete';
|
||||
|
||||
interface Iprops {
|
||||
activeCollection: Collection;
|
||||
|
@ -19,6 +23,7 @@ interface Iprops {
|
|||
collectionSummary: CollectionSummary;
|
||||
setCollectionNamerAttributes: SetCollectionNamerAttributes;
|
||||
activeCollection: Collection;
|
||||
activeCollectionID: number;
|
||||
showCollectionShareModal: () => void;
|
||||
redirectToAll: () => void;
|
||||
}
|
||||
|
@ -32,11 +37,33 @@ export default function CollectionInfoWithOptions({
|
|||
|
||||
const { name, type, fileCount } = collectionSummary;
|
||||
|
||||
const EndIcon = ({ type }: { type: CollectionSummaryType }) => {
|
||||
switch (type) {
|
||||
case CollectionSummaryType.favorites:
|
||||
return <Favorite />;
|
||||
case CollectionSummaryType.archived:
|
||||
case CollectionSummaryType.archive:
|
||||
return <VisibilityOff />;
|
||||
case CollectionSummaryType.trash:
|
||||
return <Delete />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<CollectionInfoBarWrapper>
|
||||
<SpaceBetweenFlex>
|
||||
<CollectionInfo name={name} fileCount={fileCount} />
|
||||
{!isSystemCollection(type) && <CollectionOptions {...props} />}
|
||||
<CollectionInfo
|
||||
name={name}
|
||||
fileCount={fileCount}
|
||||
endIcon={<EndIcon type={type} />}
|
||||
/>
|
||||
{shouldShowOptions(type) && (
|
||||
<CollectionOptions
|
||||
{...props}
|
||||
collectionSummaryType={type}
|
||||
/>
|
||||
)}
|
||||
</SpaceBetweenFlex>
|
||||
</CollectionInfoBarWrapper>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import React from 'react';
|
||||
import { EnteFile } from 'types/file';
|
||||
import {
|
||||
ActiveIndicator,
|
||||
CollectionBarTile,
|
||||
CollectionBarTileIcon,
|
||||
CollectionBarTileText,
|
||||
} from '../styledComponents';
|
||||
import CollectionCard from '../CollectionCard';
|
||||
import TruncateText from 'components/TruncateText';
|
||||
import { Box } from '@mui/material';
|
||||
import { CollectionSummaryType } from 'constants/collection';
|
||||
import Favorite from '@mui/icons-material/FavoriteRounded';
|
||||
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
|
||||
interface Iprops {
|
||||
active: boolean;
|
||||
latestFile: EnteFile;
|
||||
collectionName: string;
|
||||
collectionType: CollectionSummaryType;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const CollectionListBarCard = React.forwardRef((props: Iprops, ref: any) => {
|
||||
const { active, collectionName, collectionType, ...others } = props;
|
||||
|
||||
return (
|
||||
<Box ref={ref}>
|
||||
<CollectionCard collectionTile={CollectionBarTile} {...others}>
|
||||
<CollectionCardText collectionName={collectionName} />
|
||||
<CollectionCardIcon collectionType={collectionType} />
|
||||
</CollectionCard>
|
||||
{active && <ActiveIndicator />}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
export default CollectionListBarCard;
|
||||
|
||||
function CollectionCardText({ collectionName }) {
|
||||
return (
|
||||
<CollectionBarTileText>
|
||||
<TruncateText text={collectionName} />
|
||||
</CollectionBarTileText>
|
||||
);
|
||||
}
|
||||
|
||||
function CollectionCardIcon({ collectionType }) {
|
||||
return (
|
||||
<CollectionBarTileIcon>
|
||||
{collectionType === CollectionSummaryType.favorites && <Favorite />}
|
||||
{collectionType === CollectionSummaryType.archived && (
|
||||
<VisibilityOff />
|
||||
)}
|
||||
</CollectionBarTileIcon>
|
||||
);
|
||||
}
|
|
@ -15,7 +15,7 @@ const Wrapper = styled('button')<{ direction: SCROLL_DIRECTION }>`
|
|||
|
||||
border-radius: 50%;
|
||||
background-color: ${({ theme }) => theme.palette.background.paper};
|
||||
color: ${({ theme }) => theme.palette.text.primary};
|
||||
color: ${({ theme }) => theme.palette.primary.main};
|
||||
|
||||
${(props) =>
|
||||
props.direction === SCROLL_DIRECTION.LEFT
|
125
src/components/Collections/CollectionListBar/index.tsx
Normal file
|
@ -0,0 +1,125 @@
|
|||
import ScrollButton from 'components/Collections/CollectionListBar/ScrollButton';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { ALL_SECTION, COLLECTION_SORT_BY } from 'constants/collection';
|
||||
import { Box, IconButton, Typography } from '@mui/material';
|
||||
import {
|
||||
CollectionListBarWrapper,
|
||||
ScrollContainer,
|
||||
CollectionListWrapper,
|
||||
} from 'components/Collections/styledComponents';
|
||||
import CollectionListBarCard from 'components/Collections/CollectionListBar/CollectionCard';
|
||||
import useComponentScroll, { SCROLL_DIRECTION } from 'hooks/useComponentScroll';
|
||||
import useWindowSize from 'hooks/useWindowSize';
|
||||
import { IconButtonWithBG, SpaceBetweenFlex } from 'components/Container';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { CollectionSummary } from 'types/collection';
|
||||
import CollectionSort from '../AllCollections/CollectionSort';
|
||||
|
||||
interface IProps {
|
||||
activeCollection?: number;
|
||||
setActiveCollection: (id?: number) => void;
|
||||
collectionSummaries: CollectionSummary[];
|
||||
showAllCollections: () => void;
|
||||
collectionSortBy: COLLECTION_SORT_BY;
|
||||
setCollectionSortBy: (v: COLLECTION_SORT_BY) => void;
|
||||
}
|
||||
|
||||
export default function CollectionListBar(props: IProps) {
|
||||
const {
|
||||
activeCollection,
|
||||
setActiveCollection,
|
||||
collectionSummaries,
|
||||
showAllCollections,
|
||||
} = props;
|
||||
|
||||
const appContext = useContext(AppContext);
|
||||
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const { componentRef, scrollComponent, onFarLeft, onFarRight } =
|
||||
useComponentScroll({
|
||||
dependencies: [windowSize, collectionSummaries],
|
||||
});
|
||||
|
||||
const collectionChipsRef = collectionSummaries.reduce(
|
||||
(refMap, collectionSummary) => {
|
||||
refMap[collectionSummary.id] = React.createRef();
|
||||
return refMap;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
collectionChipsRef[activeCollection]?.current.scrollIntoView();
|
||||
}, [activeCollection]);
|
||||
|
||||
const clickHandler = (collectionID?: number) => () => {
|
||||
setActiveCollection(collectionID ?? ALL_SECTION);
|
||||
};
|
||||
|
||||
return (
|
||||
<CollectionListBarWrapper>
|
||||
<SpaceBetweenFlex mb={1}>
|
||||
<Typography>{constants.ALBUMS}</Typography>
|
||||
{appContext.isMobile && (
|
||||
<Box display="flex" alignItems={'center'} gap={1}>
|
||||
<CollectionSort
|
||||
setCollectionSortBy={props.setCollectionSortBy}
|
||||
activeSortBy={props.collectionSortBy}
|
||||
disableBG
|
||||
/>
|
||||
<IconButton onClick={showAllCollections}>
|
||||
<ExpandMore />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)}
|
||||
</SpaceBetweenFlex>
|
||||
<Box display="flex" alignItems="flex-start" gap={2}>
|
||||
<CollectionListWrapper>
|
||||
{!onFarLeft && (
|
||||
<ScrollButton
|
||||
scrollDirection={SCROLL_DIRECTION.LEFT}
|
||||
onClick={scrollComponent(SCROLL_DIRECTION.LEFT)}
|
||||
/>
|
||||
)}
|
||||
<ScrollContainer ref={componentRef}>
|
||||
{collectionSummaries.map((item) => (
|
||||
<CollectionListBarCard
|
||||
key={item.id}
|
||||
latestFile={item.latestFile}
|
||||
ref={collectionChipsRef[item.id]}
|
||||
active={activeCollection === item.id}
|
||||
onClick={clickHandler(item.id)}
|
||||
collectionType={item.type}
|
||||
collectionName={item.name}
|
||||
/>
|
||||
))}
|
||||
</ScrollContainer>
|
||||
{!onFarRight && (
|
||||
<ScrollButton
|
||||
scrollDirection={SCROLL_DIRECTION.RIGHT}
|
||||
onClick={scrollComponent(SCROLL_DIRECTION.RIGHT)}
|
||||
/>
|
||||
)}
|
||||
</CollectionListWrapper>
|
||||
{!appContext.isMobile && (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems={'center'}
|
||||
gap={1}
|
||||
height={'64px'}>
|
||||
<CollectionSort
|
||||
setCollectionSortBy={props.setCollectionSortBy}
|
||||
activeSortBy={props.collectionSortBy}
|
||||
/>
|
||||
<IconButtonWithBG onClick={showAllCollections}>
|
||||
<ExpandMore />
|
||||
</IconButtonWithBG>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</CollectionListBarWrapper>
|
||||
);
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
import constants from 'utils/strings/constants';
|
||||
import DialogBox from 'components/DialogBox';
|
||||
import SingleInputForm, {
|
||||
SingleInputFormProps,
|
||||
} from 'components/SingleInputForm';
|
||||
import DialogBoxBase from 'components/DialogBox/base';
|
||||
import { DialogContent, DialogTitle } from '@mui/material';
|
||||
|
||||
export interface CollectionNamerAttributes {
|
||||
callback: (name: string) => void;
|
||||
|
@ -39,19 +40,19 @@ export default function CollectionNamer({ attributes, ...props }: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<DialogBox
|
||||
open={props.show}
|
||||
attributes={{ title: attributes.title }}
|
||||
onClose={props.onHide}
|
||||
titleCloseButton
|
||||
maxWidth="xs">
|
||||
<DialogBoxBase open={props.show} onClose={props.onHide}>
|
||||
<DialogTitle>{attributes.title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<SingleInputForm
|
||||
callback={onSubmit}
|
||||
fieldType="text"
|
||||
buttonText={attributes.buttonText}
|
||||
placeholder={constants.ENTER_ALBUM_NAME}
|
||||
initialValue={attributes.autoFilledName}
|
||||
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}
|
||||
secondaryButtonAction={props.onHide}
|
||||
/>
|
||||
</DialogBox>
|
||||
</DialogContent>
|
||||
</DialogBoxBase>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import { OverflowMenuOption } from 'components/OverflowMenu/option';
|
||||
import React from 'react';
|
||||
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import IosShareIcon from '@mui/icons-material/IosShare';
|
||||
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
|
||||
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||
import VisibilityOnOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
|
||||
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { CollectionActions } from '.';
|
||||
|
||||
interface Iprops {
|
||||
IsArchived: boolean;
|
||||
handleCollectionAction: (
|
||||
action: CollectionActions,
|
||||
loader?: boolean
|
||||
) => (...args: any[]) => Promise<void>;
|
||||
}
|
||||
|
||||
export function AlbumCollectionOption({
|
||||
IsArchived,
|
||||
handleCollectionAction,
|
||||
}: Iprops) {
|
||||
return (
|
||||
<>
|
||||
<OverflowMenuOption
|
||||
onClick={handleCollectionAction(
|
||||
CollectionActions.SHOW_SHARE_DIALOG,
|
||||
false
|
||||
)}
|
||||
startIcon={<IosShareIcon />}>
|
||||
{constants.SHARE}
|
||||
</OverflowMenuOption>
|
||||
<OverflowMenuOption
|
||||
onClick={handleCollectionAction(
|
||||
CollectionActions.CONFIRM_DOWNLOAD,
|
||||
false
|
||||
)}
|
||||
startIcon={<FileDownloadOutlinedIcon />}>
|
||||
{constants.DOWNLOAD}
|
||||
</OverflowMenuOption>
|
||||
<OverflowMenuOption
|
||||
onClick={handleCollectionAction(
|
||||
CollectionActions.SHOW_RENAME_DIALOG,
|
||||
false
|
||||
)}
|
||||
startIcon={<EditIcon />}>
|
||||
{constants.RENAME}
|
||||
</OverflowMenuOption>
|
||||
{IsArchived ? (
|
||||
<OverflowMenuOption
|
||||
onClick={handleCollectionAction(
|
||||
CollectionActions.UNARCHIVE
|
||||
)}
|
||||
startIcon={<VisibilityOnOutlinedIcon />}>
|
||||
{constants.UNARCHIVE}
|
||||
</OverflowMenuOption>
|
||||
) : (
|
||||
<OverflowMenuOption
|
||||
onClick={handleCollectionAction(CollectionActions.ARCHIVE)}
|
||||
startIcon={<VisibilityOffOutlinedIcon />}>
|
||||
{constants.ARCHIVE}
|
||||
</OverflowMenuOption>
|
||||
)}
|
||||
<OverflowMenuOption
|
||||
startIcon={<DeleteOutlinedIcon />}
|
||||
onClick={handleCollectionAction(
|
||||
CollectionActions.CONFIRM_DELETE,
|
||||
false
|
||||
)}>
|
||||
{constants.DELETE}
|
||||
</OverflowMenuOption>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { OverflowMenuOption } from 'components/OverflowMenu/option';
|
||||
import React from 'react';
|
||||
|
||||
import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { CollectionActions } from '.';
|
||||
|
||||
interface Iprops {
|
||||
handleCollectionAction: (
|
||||
action: CollectionActions,
|
||||
loader?: boolean
|
||||
) => (...args: any[]) => Promise<void>;
|
||||
}
|
||||
|
||||
export function TrashCollectionOption({ handleCollectionAction }: Iprops) {
|
||||
return (
|
||||
<OverflowMenuOption
|
||||
color="danger"
|
||||
startIcon={<DeleteOutlinedIcon />}
|
||||
onClick={handleCollectionAction(
|
||||
CollectionActions.CONFIRM_EMPTY_TRASH,
|
||||
false
|
||||
)}>
|
||||
{constants.EMPTY_TRASH}
|
||||
</OverflowMenuOption>
|
||||
);
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
import { AlbumCollectionOption } from './AlbumCollectionOption';
|
||||
import React, { useContext } from 'react';
|
||||
import * as CollectionAPI from 'services/collectionService';
|
||||
import * as TrashService from 'services/trashService';
|
||||
import {
|
||||
changeCollectionVisibility,
|
||||
downloadAllCollectionFiles,
|
||||
} from 'utils/collection';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { SetCollectionNamerAttributes } from './CollectionNamer';
|
||||
import { SetCollectionNamerAttributes } from '../CollectionNamer';
|
||||
import { Collection } from 'types/collection';
|
||||
import { IsArchived } from 'utils/magicMetadata';
|
||||
import { GalleryContext } from 'pages/gallery';
|
||||
|
@ -13,41 +15,60 @@ import { logError } from 'utils/sentry';
|
|||
import { VISIBILITY_STATE } from 'types/magicMetadata';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import OverflowMenu from 'components/OverflowMenu/menu';
|
||||
import { OverflowMenuOption } from 'components/OverflowMenu/option';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import { CollectionSummaryType } from 'constants/collection';
|
||||
import { TrashCollectionOption } from './TrashCollectionOption';
|
||||
import MoreHoriz from '@mui/icons-material/MoreHoriz';
|
||||
|
||||
interface CollectionOptionsProps {
|
||||
setCollectionNamerAttributes: SetCollectionNamerAttributes;
|
||||
activeCollection: Collection;
|
||||
collectionSummaryType: CollectionSummaryType;
|
||||
showCollectionShareModal: () => void;
|
||||
redirectToAll: () => void;
|
||||
}
|
||||
|
||||
enum CollectionActions {
|
||||
export enum CollectionActions {
|
||||
SHOW_RENAME_DIALOG,
|
||||
RENAME,
|
||||
CONFIRM_DOWNLOAD,
|
||||
DOWNLOAD,
|
||||
ARCHIVE,
|
||||
UNARCHIVE,
|
||||
CONFIRM_DELETE,
|
||||
DELETE,
|
||||
SHOW_SHARE_DIALOG,
|
||||
CONFIRM_EMPTY_TRASH,
|
||||
EMPTY_TRASH,
|
||||
}
|
||||
|
||||
const CollectionOptions = (props: CollectionOptionsProps) => {
|
||||
const {
|
||||
activeCollection,
|
||||
collectionSummaryType,
|
||||
redirectToAll,
|
||||
setCollectionNamerAttributes,
|
||||
showCollectionShareModal,
|
||||
} = props;
|
||||
|
||||
const { startLoading, finishLoading, setDialogMessage } =
|
||||
useContext(AppContext);
|
||||
const { syncWithRemote } = useContext(GalleryContext);
|
||||
|
||||
const handleCollectionAction = (action: CollectionActions) => {
|
||||
const handleCollectionAction = (
|
||||
action: CollectionActions,
|
||||
loader = true
|
||||
) => {
|
||||
let callback;
|
||||
switch (action) {
|
||||
case CollectionActions.SHOW_RENAME_DIALOG:
|
||||
callback = showRenameCollectionModal;
|
||||
break;
|
||||
case CollectionActions.RENAME:
|
||||
callback = renameCollection;
|
||||
break;
|
||||
case CollectionActions.CONFIRM_DOWNLOAD:
|
||||
callback = confirmDownloadCollection;
|
||||
break;
|
||||
case CollectionActions.DOWNLOAD:
|
||||
callback = downloadCollection;
|
||||
break;
|
||||
|
@ -57,9 +78,21 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
|
|||
case CollectionActions.UNARCHIVE:
|
||||
callback = unArchiveCollection;
|
||||
break;
|
||||
case CollectionActions.CONFIRM_DELETE:
|
||||
callback = confirmDeleteCollection;
|
||||
break;
|
||||
case CollectionActions.DELETE:
|
||||
callback = deleteCollection;
|
||||
break;
|
||||
case CollectionActions.SHOW_SHARE_DIALOG:
|
||||
callback = showCollectionShareModal;
|
||||
break;
|
||||
case CollectionActions.CONFIRM_EMPTY_TRASH:
|
||||
callback = confirmEmptyTrash;
|
||||
break;
|
||||
case CollectionActions.EMPTY_TRASH:
|
||||
callback = emptyTrash;
|
||||
break;
|
||||
default:
|
||||
logError(
|
||||
Error('invalid collection action '),
|
||||
|
@ -70,8 +103,8 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
|
|||
}
|
||||
}
|
||||
return async (...args) => {
|
||||
startLoading();
|
||||
try {
|
||||
loader && startLoading();
|
||||
await callback(...args);
|
||||
} catch (e) {
|
||||
setDialogMessage({
|
||||
|
@ -79,10 +112,10 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
|
|||
content: constants.UNKNOWN_ERROR,
|
||||
close: { variant: 'danger' },
|
||||
});
|
||||
} finally {
|
||||
syncWithRemote(false, true);
|
||||
loader && finishLoading();
|
||||
}
|
||||
|
||||
syncWithRemote();
|
||||
finishLoading();
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -109,6 +142,12 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
|
|||
downloadAllCollectionFiles(activeCollection.id);
|
||||
};
|
||||
|
||||
const emptyTrash = async () => {
|
||||
await TrashService.emptyTrash();
|
||||
await TrashService.clearLocalTrash();
|
||||
redirectToAll();
|
||||
};
|
||||
|
||||
const showRenameCollectionModal = () => {
|
||||
setCollectionNamerAttributes({
|
||||
title: constants.RENAME_COLLECTION,
|
||||
|
@ -120,8 +159,8 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
|
|||
|
||||
const confirmDeleteCollection = () => {
|
||||
setDialogMessage({
|
||||
title: constants.CONFIRM_DELETE_COLLECTION,
|
||||
content: constants.DELETE_COLLECTION_MESSAGE(),
|
||||
title: constants.DELETE_COLLECTION_TITLE,
|
||||
content: constants.DELETE_COLLECTION_MESSAGE,
|
||||
proceed: {
|
||||
text: constants.DELETE_COLLECTION,
|
||||
action: handleCollectionAction(CollectionActions.DELETE),
|
||||
|
@ -148,42 +187,38 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
|
|||
});
|
||||
};
|
||||
|
||||
const confirmEmptyTrash = () =>
|
||||
setDialogMessage({
|
||||
title: constants.EMPTY_TRASH_TITLE,
|
||||
content: constants.EMPTY_TRASH_MESSAGE,
|
||||
|
||||
proceed: {
|
||||
action: handleCollectionAction(CollectionActions.EMPTY_TRASH),
|
||||
text: constants.EMPTY_TRASH,
|
||||
variant: 'danger',
|
||||
},
|
||||
close: { text: constants.CANCEL },
|
||||
});
|
||||
|
||||
return (
|
||||
<OverflowMenu
|
||||
ariaControls={`collection-options-${props.activeCollection.id}`}
|
||||
triggerButtonIcon={<MoreVertIcon />}
|
||||
ariaControls={'collection-options'}
|
||||
triggerButtonIcon={<MoreHoriz />}
|
||||
triggerButtonProps={{
|
||||
sx: {
|
||||
background: (theme) => theme.palette.background.paper,
|
||||
background: (theme) => theme.palette.fill.dark,
|
||||
},
|
||||
}}>
|
||||
<OverflowMenuOption onClick={showRenameCollectionModal}>
|
||||
{constants.RENAME}
|
||||
</OverflowMenuOption>
|
||||
<OverflowMenuOption onClick={showCollectionShareModal}>
|
||||
{constants.SHARE}
|
||||
</OverflowMenuOption>
|
||||
<OverflowMenuOption onClick={confirmDownloadCollection}>
|
||||
{constants.DOWNLOAD}
|
||||
</OverflowMenuOption>
|
||||
{IsArchived(activeCollection) ? (
|
||||
<OverflowMenuOption
|
||||
onClick={handleCollectionAction(
|
||||
CollectionActions.UNARCHIVE
|
||||
)}>
|
||||
{constants.UNARCHIVE}
|
||||
</OverflowMenuOption>
|
||||
{collectionSummaryType === CollectionSummaryType.trash ? (
|
||||
<TrashCollectionOption
|
||||
handleCollectionAction={handleCollectionAction}
|
||||
/>
|
||||
) : (
|
||||
<OverflowMenuOption
|
||||
onClick={handleCollectionAction(CollectionActions.ARCHIVE)}>
|
||||
{constants.ARCHIVE}
|
||||
</OverflowMenuOption>
|
||||
<AlbumCollectionOption
|
||||
IsArchived={IsArchived(activeCollection)}
|
||||
handleCollectionAction={handleCollectionAction}
|
||||
/>
|
||||
)}
|
||||
<OverflowMenuOption
|
||||
color="danger"
|
||||
onClick={confirmDeleteCollection}>
|
||||
{constants.DELETE}
|
||||
</OverflowMenuOption>
|
||||
</OverflowMenu>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
import CollectionCard from 'components/Collections/CollectionCard';
|
||||
import {
|
||||
AllCollectionTile,
|
||||
AllCollectionTileText,
|
||||
} from 'components/Collections/styledComponents';
|
||||
import React from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { CenteredFlex, Overlay } from 'components/Container';
|
||||
|
||||
const ImageContainer = styled(Overlay)`
|
||||
display: flex;
|
||||
font-size: 42px;
|
||||
`;
|
||||
|
||||
interface Iprops {
|
||||
showNextModal: () => void;
|
||||
}
|
||||
|
||||
export default function AddCollectionButton({ showNextModal }: Iprops) {
|
||||
return (
|
||||
<CollectionCard
|
||||
collectionTile={AllCollectionTile}
|
||||
onClick={() => showNextModal()}
|
||||
latestFile={null}>
|
||||
<AllCollectionTileText>
|
||||
{constants.CREATE_COLLECTION}
|
||||
</AllCollectionTileText>
|
||||
<ImageContainer>
|
||||
<CenteredFlex>+</CenteredFlex>
|
||||
</ImageContainer>
|
||||
</CollectionCard>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { Typography } from '@mui/material';
|
||||
import React from 'react';
|
||||
import CollectionCard from '../CollectionCard';
|
||||
import { CollectionSummary } from 'types/collection';
|
||||
import { AllCollectionTile, AllCollectionTileText } from '../styledComponents';
|
||||
|
||||
interface Iprops {
|
||||
collectionSummary: CollectionSummary;
|
||||
onCollectionClick: (collectionID: number) => void;
|
||||
}
|
||||
|
||||
export default function CollectionSelectorCard({
|
||||
onCollectionClick,
|
||||
collectionSummary,
|
||||
}: Iprops) {
|
||||
return (
|
||||
<CollectionCard
|
||||
collectionTile={AllCollectionTile}
|
||||
latestFile={collectionSummary.latestFile}
|
||||
onClick={() => onCollectionClick(collectionSummary.id)}>
|
||||
<AllCollectionTileText>
|
||||
<Typography>{collectionSummary.name}</Typography>
|
||||
</AllCollectionTileText>
|
||||
</CollectionCard>
|
||||
);
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import React, { useEffect, useMemo } from 'react';
|
||||
import AddCollectionButton from './AddCollectionButton';
|
||||
import React, { useContext, useEffect, useMemo } from 'react';
|
||||
import { Collection, CollectionSummaries } from 'types/collection';
|
||||
import DialogBoxBase from 'components/DialogBox/base';
|
||||
import DialogTitleWithCloseButton from 'components/DialogBox/titleWithCloseButton';
|
||||
import DialogTitleWithCloseButton from 'components/DialogBox/TitleWithCloseButton';
|
||||
import { isUploadAllowedCollection } from 'utils/collection';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { AllCollectionDialog } from 'components/Collections/AllCollections/dialog';
|
||||
import { DialogContent } from '@mui/material';
|
||||
import { FlexWrapper } from 'components/Container';
|
||||
import { CollectionSelectorTile } from 'components/Collections/styledComponents';
|
||||
import AllCollectionCard from 'components/Collections/AllCollections/CollectionCard';
|
||||
import { isSystemCollection } from 'utils/collection';
|
||||
import CollectionSelectorCard from './CollectionCard';
|
||||
import AddCollectionButton from './AddCollectionButton';
|
||||
|
||||
export interface CollectionSelectorAttributes {
|
||||
callback: (collection: Collection) => void;
|
||||
|
@ -15,9 +15,6 @@ export interface CollectionSelectorAttributes {
|
|||
title: string;
|
||||
fromCollection?: number;
|
||||
}
|
||||
export type SetCollectionSelectorAttributes = React.Dispatch<
|
||||
React.SetStateAction<CollectionSelectorAttributes>
|
||||
>;
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
|
@ -32,12 +29,14 @@ function CollectionSelector({
|
|||
collections,
|
||||
...props
|
||||
}: Props) {
|
||||
const appContext = useContext(AppContext);
|
||||
const collectionToShow = useMemo(() => {
|
||||
const personalCollectionsOtherThanFrom = [
|
||||
...collectionSummaries.values(),
|
||||
]?.filter(
|
||||
({ type, id }) =>
|
||||
id !== attributes?.fromCollection && !isSystemCollection(type)
|
||||
id !== attributes?.fromCollection &&
|
||||
isUploadAllowedCollection(type)
|
||||
);
|
||||
return personalCollectionsOtherThanFrom;
|
||||
}, [collectionSummaries, attributes]);
|
||||
|
@ -62,27 +61,24 @@ function CollectionSelector({
|
|||
props.onClose();
|
||||
};
|
||||
|
||||
const onCloseButtonClick = () => props.onClose(true);
|
||||
|
||||
return (
|
||||
<DialogBoxBase
|
||||
{...props}
|
||||
maxWidth="md"
|
||||
PaperProps={{ sx: { maxWidth: '848px' } }}>
|
||||
<DialogTitleWithCloseButton onClose={() => props.onClose(true)}>
|
||||
<AllCollectionDialog
|
||||
onClose={props.onClose}
|
||||
open={props.open}
|
||||
position="center"
|
||||
fullScreen={appContext.isMobile}>
|
||||
<DialogTitleWithCloseButton onClose={onCloseButtonClick}>
|
||||
{attributes.title}
|
||||
</DialogTitleWithCloseButton>
|
||||
<DialogContent
|
||||
sx={{
|
||||
'&&&': {
|
||||
px: 0,
|
||||
},
|
||||
}}>
|
||||
<FlexWrapper style={{ flexWrap: 'wrap' }}>
|
||||
<DialogContent>
|
||||
<FlexWrapper flexWrap="wrap" gap={0.5}>
|
||||
<AddCollectionButton
|
||||
showNextModal={attributes.showNextModal}
|
||||
/>
|
||||
{collectionToShow.map((collectionSummary) => (
|
||||
<AllCollectionCard
|
||||
collectionTile={CollectionSelectorTile}
|
||||
<CollectionSelectorCard
|
||||
onCollectionClick={handleCollectionClick}
|
||||
collectionSummary={collectionSummary}
|
||||
key={collectionSummary.id}
|
||||
|
@ -90,7 +86,7 @@ function CollectionSelector({
|
|||
))}
|
||||
</FlexWrapper>
|
||||
</DialogContent>
|
||||
</DialogBoxBase>
|
||||
</AllCollectionDialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -6,7 +6,10 @@ export const CollectionShareContainer = styled(Dialog)(({ theme }) => ({
|
|||
justifyContent: 'flex-end',
|
||||
},
|
||||
'& .MuiPaper-root': {
|
||||
maxWidth: '414px',
|
||||
width: '414px',
|
||||
},
|
||||
'& .MuiDialog-paperFullScreen': {
|
||||
maxWidth: '100%',
|
||||
},
|
||||
'& .MuiDialogTitle-root': {
|
||||
padding: theme.spacing(4, 3, 3, 4),
|
||||
|
|
|
@ -1,34 +1,37 @@
|
|||
import SingleInputForm from 'components/SingleInputForm';
|
||||
import SingleInputForm, {
|
||||
SingleInputFormProps,
|
||||
} from 'components/SingleInputForm';
|
||||
import { GalleryContext } from 'pages/gallery';
|
||||
import React, { useContext } from 'react';
|
||||
import { shareCollection } from 'services/collectionService';
|
||||
import { User } from 'types/user';
|
||||
import { sleep } from 'utils/common';
|
||||
import { handleSharingErrors } from 'utils/error';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { CollectionShareSharees } from './sharees';
|
||||
import CollectionShareSubmitButton from './submitButton';
|
||||
|
||||
export default function EmailShare({ collection }) {
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
const collectionShare = async (email, setFieldError) => {
|
||||
const collectionShare: SingleInputFormProps['callback'] = async (
|
||||
email,
|
||||
setFieldError
|
||||
) => {
|
||||
try {
|
||||
const user: User = getData(LS_KEYS.USER);
|
||||
if (email === user.email) {
|
||||
setFieldError('email', constants.SHARE_WITH_SELF);
|
||||
setFieldError(constants.SHARE_WITH_SELF);
|
||||
} else if (
|
||||
collection?.sharees?.find((value) => value.email === email)
|
||||
) {
|
||||
setFieldError('email', constants.ALREADY_SHARED(email));
|
||||
setFieldError(constants.ALREADY_SHARED(email));
|
||||
} else {
|
||||
await shareCollection(collection, email);
|
||||
await sleep(2000);
|
||||
await galleryContext.syncWithRemote(false, true);
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMessage = handleSharingErrors(e);
|
||||
setFieldError('email', errorMessage);
|
||||
setFieldError(errorMessage);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
@ -38,7 +41,11 @@ export default function EmailShare({ collection }) {
|
|||
placeholder={constants.ENTER_EMAIL}
|
||||
fieldType="email"
|
||||
buttonText={constants.SHARE}
|
||||
customSubmitButton={CollectionShareSubmitButton}
|
||||
submitButtonProps={{
|
||||
size: 'medium',
|
||||
sx: { mt: 1, mb: 2 },
|
||||
}}
|
||||
disableAutoFocus
|
||||
/>
|
||||
<CollectionShareSharees collection={collection} />
|
||||
</>
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
import EmailShare from './emailShare';
|
||||
import React from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { Collection } from 'types/collection';
|
||||
import { dialogCloseHandler } from 'components/DialogBox/base';
|
||||
import DialogTitleWithCloseButton from 'components/DialogBox/titleWithCloseButton';
|
||||
import DialogTitleWithCloseButton, {
|
||||
dialogCloseHandler,
|
||||
} from 'components/DialogBox/TitleWithCloseButton';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import { Divider } from '@mui/material';
|
||||
|
||||
import { CollectionShareContainer } from './container';
|
||||
import PublicShare from './publicShare';
|
||||
import { AppContext } from 'pages/_app';
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
onHide: () => void;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
collection: Collection;
|
||||
}
|
||||
|
||||
function CollectionShare(props: Props) {
|
||||
const { isMobile } = useContext(AppContext);
|
||||
const handleClose = dialogCloseHandler({
|
||||
onClose: props.onHide,
|
||||
onClose: props.onClose,
|
||||
});
|
||||
|
||||
if (!props.collection) {
|
||||
|
@ -27,7 +30,10 @@ function CollectionShare(props: Props) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<CollectionShareContainer open={props.show} onClose={handleClose}>
|
||||
<CollectionShareContainer
|
||||
open={props.open}
|
||||
onClose={handleClose}
|
||||
fullScreen={isMobile}>
|
||||
<DialogTitleWithCloseButton onClose={handleClose}>
|
||||
{constants.SHARE_COLLECTION}
|
||||
</DialogTitleWithCloseButton>
|
||||
|
|
|
@ -1,37 +1,36 @@
|
|||
import { Box, Typography } from '@mui/material';
|
||||
import { FlexWrapper } from 'components/Container';
|
||||
import { ButtonVariant } from 'components/pages/gallery/LinkButton';
|
||||
import { GalleryContext } from 'pages/gallery';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import {
|
||||
createShareableURL,
|
||||
deleteShareableURL,
|
||||
} from 'services/collectionService';
|
||||
import { appendCollectionKeyToShareURL } from 'utils/collection';
|
||||
import { Collection, PublicURL } from 'types/collection';
|
||||
import { handleSharingErrors } from 'utils/error';
|
||||
import constants from 'utils/strings/constants';
|
||||
import PublicShareSwitch from './switch';
|
||||
interface Iprops {
|
||||
collection: Collection;
|
||||
publicShareActive: boolean;
|
||||
setPublicShareProp: (value: PublicURL) => void;
|
||||
}
|
||||
|
||||
export default function PublicShareControl({
|
||||
publicShareUrl,
|
||||
sharableLinkError,
|
||||
collection,
|
||||
setPublicShareUrl,
|
||||
setSharableLinkError,
|
||||
}) {
|
||||
publicShareActive,
|
||||
setPublicShareProp,
|
||||
}: Iprops) {
|
||||
const appContext = useContext(AppContext);
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
const [sharableLinkError, setSharableLinkError] = useState(null);
|
||||
|
||||
const createSharableURLHelper = async () => {
|
||||
try {
|
||||
appContext.startLoading();
|
||||
const publicURL = await createShareableURL(collection);
|
||||
const sharableURL = await appendCollectionKeyToShareURL(
|
||||
publicURL.url,
|
||||
collection.key
|
||||
);
|
||||
setPublicShareUrl(sharableURL);
|
||||
galleryContext.syncWithRemote(false, true);
|
||||
setPublicShareProp(publicURL);
|
||||
} catch (e) {
|
||||
const errorMessage = handleSharingErrors(e);
|
||||
setSharableLinkError(errorMessage);
|
||||
|
@ -44,8 +43,7 @@ export default function PublicShareControl({
|
|||
try {
|
||||
appContext.startLoading();
|
||||
await deleteShareableURL(collection);
|
||||
setPublicShareUrl(null);
|
||||
galleryContext.syncWithRemote(false, true);
|
||||
setPublicShareProp(null);
|
||||
} catch (e) {
|
||||
const errorMessage = handleSharingErrors(e);
|
||||
setSharableLinkError(errorMessage);
|
||||
|
@ -69,8 +67,7 @@ export default function PublicShareControl({
|
|||
|
||||
const handleCollectionPublicSharing = () => {
|
||||
setSharableLinkError(null);
|
||||
|
||||
if (publicShareUrl) {
|
||||
if (publicShareActive) {
|
||||
confirmDisablePublicSharing();
|
||||
} else {
|
||||
createSharableURLHelper();
|
||||
|
@ -86,7 +83,7 @@ export default function PublicShareControl({
|
|||
sx={{
|
||||
ml: 2,
|
||||
}}
|
||||
checked={!!publicShareUrl}
|
||||
checked={publicShareActive}
|
||||
onChange={handleCollectionPublicSharing}
|
||||
/>
|
||||
</FlexWrapper>
|
||||
|
|
|
@ -1,51 +1,53 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { PublicURL } from 'types/collection';
|
||||
import { Collection, PublicURL } from 'types/collection';
|
||||
import { appendCollectionKeyToShareURL } from 'utils/collection';
|
||||
import PublicShareControl from './control';
|
||||
import PublicShareLink from './link';
|
||||
import PublicShareManage from './manage';
|
||||
|
||||
export default function PublicShare({ collection }) {
|
||||
const [sharableLinkError, setSharableLinkError] = useState(null);
|
||||
export default function PublicShare({
|
||||
collection,
|
||||
}: {
|
||||
collection: Collection;
|
||||
}) {
|
||||
const [publicShareUrl, setPublicShareUrl] = useState<string>(null);
|
||||
const [publicShareProp, setPublicShareProp] = useState<PublicURL>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const main = async () => {
|
||||
if (collection?.publicURLs?.[0]?.url) {
|
||||
const t = await appendCollectionKeyToShareURL(
|
||||
collection?.publicURLs?.[0]?.url,
|
||||
if (collection.publicURLs?.length) {
|
||||
setPublicShareProp(collection.publicURLs[0]);
|
||||
}
|
||||
}, [collection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (publicShareProp) {
|
||||
const url = appendCollectionKeyToShareURL(
|
||||
publicShareProp.url,
|
||||
collection.key
|
||||
);
|
||||
setPublicShareUrl(t);
|
||||
setPublicShareProp(collection?.publicURLs?.[0] as PublicURL);
|
||||
setPublicShareUrl(url);
|
||||
} else {
|
||||
setPublicShareUrl(null);
|
||||
setPublicShareProp(null);
|
||||
}
|
||||
};
|
||||
main();
|
||||
}, [collection]);
|
||||
}, [publicShareProp]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PublicShareControl
|
||||
setPublicShareUrl={setPublicShareUrl}
|
||||
setPublicShareProp={setPublicShareProp}
|
||||
collection={collection}
|
||||
publicShareUrl={publicShareUrl}
|
||||
sharableLinkError={sharableLinkError}
|
||||
setSharableLinkError={setSharableLinkError}
|
||||
publicShareActive={!!publicShareProp}
|
||||
/>
|
||||
{publicShareUrl && (
|
||||
<PublicShareLink publicShareUrl={publicShareUrl} />
|
||||
)}
|
||||
{publicShareProp && (
|
||||
<>
|
||||
<PublicShareLink publicShareUrl={publicShareUrl} />
|
||||
|
||||
<PublicShareManage
|
||||
publicShareProp={publicShareProp}
|
||||
collection={collection}
|
||||
setPublicShareProp={setPublicShareProp}
|
||||
setSharableLinkError={setSharableLinkError}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -20,7 +20,7 @@ export function ManageDeviceLimit({
|
|||
|
||||
return (
|
||||
<Box>
|
||||
<Typography>{constants.LINK_DEVICE_LIMIT}</Typography>
|
||||
<Typography mb={0.5}>{constants.LINK_DEVICE_LIMIT}</Typography>
|
||||
<Select
|
||||
menuPosition="fixed"
|
||||
options={getDeviceLimitOptions()}
|
||||
|
|
|
@ -25,7 +25,7 @@ export function ManageDownloadAccess({
|
|||
const disableFileDownload = () => {
|
||||
appContext.setDialogMessage({
|
||||
title: constants.DISABLE_FILE_DOWNLOAD,
|
||||
content: constants.DISABLE_FILE_DOWNLOAD_MESSAGE,
|
||||
content: constants.DISABLE_FILE_DOWNLOAD_MESSAGE(),
|
||||
close: { text: constants.CANCEL },
|
||||
proceed: {
|
||||
text: constants.DISABLE,
|
||||
|
@ -40,7 +40,7 @@ export function ManageDownloadAccess({
|
|||
};
|
||||
return (
|
||||
<Box>
|
||||
<Typography>{constants.FILE_DOWNLOAD}</Typography>
|
||||
<Typography mb={0.5}>{constants.FILE_DOWNLOAD}</Typography>
|
||||
<PublicShareSwitch
|
||||
checked={publicShareProp?.enableDownload ?? false}
|
||||
onChange={handleFileDownloadSetting}
|
|
@ -2,7 +2,7 @@ import { ManageLinkPassword } from './linkPassword';
|
|||
import { ManageDeviceLimit } from './deviceLimit';
|
||||
import { ManageLinkExpiry } from './linkExpiry';
|
||||
import { PublicLinkSetPassword } from '../setPassword';
|
||||
import { Stack } from '@mui/material';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { GalleryContext } from 'pages/gallery';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { updateShareableURL } from 'services/collectionService';
|
||||
|
@ -14,17 +14,17 @@ import {
|
|||
ManageSectionLabel,
|
||||
ManageSectionOptions,
|
||||
} from '../../styledComponents';
|
||||
import { ManageDownloadAccess } from './downloadAcess';
|
||||
import { ManageDownloadAccess } from './downloadAccess';
|
||||
|
||||
export default function PublicShareManage({
|
||||
publicShareProp,
|
||||
collection,
|
||||
setPublicShareProp,
|
||||
setSharableLinkError,
|
||||
}) {
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
const [changePasswordView, setChangePasswordView] = useState(false);
|
||||
const [sharableLinkError, setSharableLinkError] = useState(null);
|
||||
|
||||
const closeConfigurePassword = () => setChangePasswordView(false);
|
||||
|
||||
|
@ -33,7 +33,6 @@ export default function PublicShareManage({
|
|||
galleryContext.setBlockingLoad(true);
|
||||
const response = await updateShareableURL(req);
|
||||
setPublicShareProp(response);
|
||||
galleryContext.syncWithRemote(false, true);
|
||||
} catch (e) {
|
||||
const errorMessage = handleSharingErrors(e);
|
||||
setSharableLinkError(errorMessage);
|
||||
|
@ -59,7 +58,7 @@ export default function PublicShareManage({
|
|||
{constants.MANAGE_LINK}
|
||||
</ManageSectionLabel>
|
||||
<ManageSectionOptions>
|
||||
<Stack spacing={1}>
|
||||
<Stack spacing={1.5}>
|
||||
<ManageLinkExpiry
|
||||
collection={collection}
|
||||
publicShareProp={publicShareProp}
|
||||
|
@ -90,6 +89,17 @@ export default function PublicShareManage({
|
|||
}
|
||||
/>
|
||||
</Stack>
|
||||
{sharableLinkError && (
|
||||
<Typography
|
||||
textAlign={'center'}
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: (theme) => theme.palette.danger.main,
|
||||
mt: 0.5,
|
||||
}}>
|
||||
{sharableLinkError}
|
||||
</Typography>
|
||||
)}
|
||||
</ManageSectionOptions>
|
||||
</details>
|
||||
<PublicLinkSetPassword
|
||||
|
|
|
@ -20,7 +20,7 @@ export function ManageLinkExpiry({
|
|||
};
|
||||
return (
|
||||
<Box>
|
||||
<Typography>{constants.LINK_EXPIRY}</Typography>
|
||||
<Typography mb={0.5}>{constants.LINK_EXPIRY}</Typography>
|
||||
<Select
|
||||
menuPosition="fixed"
|
||||
options={shareExpiryOptions}
|
||||
|
|
|
@ -39,7 +39,7 @@ export function ManageLinkPassword({
|
|||
|
||||
return (
|
||||
<Box>
|
||||
<Typography> {constants.LINK_PASSWORD_LOCK}</Typography>
|
||||
<Typography mb={0.5}> {constants.LINK_PASSWORD_LOCK}</Typography>
|
||||
<PublicShareSwitch
|
||||
checked={!!publicShareProp?.passwordEnabled}
|
||||
onChange={handlePasswordChangeSetting}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import DialogBox from 'components/DialogBox';
|
||||
import SingleInputForm from 'components/SingleInputForm';
|
||||
import { Dialog, Stack, Typography } from '@mui/material';
|
||||
import SingleInputForm, {
|
||||
SingleInputFormProps,
|
||||
} from 'components/SingleInputForm';
|
||||
import React from 'react';
|
||||
import CryptoWorker from 'utils/crypto';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
||||
export function PublicLinkSetPassword({
|
||||
open,
|
||||
onClose,
|
||||
|
@ -11,13 +14,16 @@ export function PublicLinkSetPassword({
|
|||
updatePublicShareURLHelper,
|
||||
setChangePasswordView,
|
||||
}) {
|
||||
const savePassword = async (passphrase, setFieldError) => {
|
||||
const savePassword: SingleInputFormProps['callback'] = async (
|
||||
passphrase,
|
||||
setFieldError
|
||||
) => {
|
||||
if (passphrase && passphrase.trim().length >= 1) {
|
||||
await enablePublicUrlPassword(passphrase);
|
||||
setChangePasswordView(false);
|
||||
publicShareProp.passwordEnabled = true;
|
||||
} else {
|
||||
setFieldError('linkPassword', 'can not be empty');
|
||||
setFieldError('can not be empty');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -35,20 +41,26 @@ export function PublicLinkSetPassword({
|
|||
});
|
||||
};
|
||||
return (
|
||||
<DialogBox
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
PaperProps={{ sx: { maxWidth: '350px' } }}
|
||||
titleCloseButton
|
||||
attributes={{
|
||||
title: constants.PASSWORD_LOCK,
|
||||
}}>
|
||||
disablePortal
|
||||
BackdropProps={{ sx: { position: 'absolute' } }}
|
||||
sx={{ position: 'absolute' }}
|
||||
PaperProps={{ sx: { p: 1 } }}>
|
||||
<Stack spacing={3} p={1.5}>
|
||||
<Typography variant="h3" px={1} py={0.5} fontWeight={'bold'}>
|
||||
{constants.PASSWORD_LOCK}
|
||||
</Typography>
|
||||
<SingleInputForm
|
||||
callback={savePassword}
|
||||
placeholder={constants.RETURN_PASSPHRASE_HINT}
|
||||
buttonText={constants.LOCK}
|
||||
fieldType="password"
|
||||
secondaryButtonAction={onClose}
|
||||
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}
|
||||
/>
|
||||
</DialogBox>
|
||||
</Stack>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { AppContext } from 'pages/_app';
|
|||
import React, { useContext } from 'react';
|
||||
import { unshareCollection } from 'services/collectionService';
|
||||
import { Collection } from 'types/collection';
|
||||
import { sleep } from 'utils/common';
|
||||
import constants from 'utils/strings/constants';
|
||||
import ShareeRow from './row';
|
||||
|
||||
|
@ -20,7 +19,6 @@ export function CollectionShareSharees({ collection }: Iprops) {
|
|||
try {
|
||||
appContext.startLoading();
|
||||
await unshareCollection(collection, sharee.email);
|
||||
await sleep(2000);
|
||||
await galleryContext.syncWithRemote(false, true);
|
||||
} finally {
|
||||
appContext.finishLoading();
|
||||
|
|
|
@ -18,6 +18,12 @@ const ShareeRow = ({ sharee, collectionUnshare }: IProps) => {
|
|||
<SpaceBetweenFlex>
|
||||
{sharee.email}
|
||||
<OverflowMenu
|
||||
menuPaperProps={{
|
||||
sx: {
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.background.overPaper,
|
||||
},
|
||||
}}
|
||||
ariaControls={`email-share-${sharee.email}`}
|
||||
triggerButtonIcon={<MoreHorizIcon />}>
|
||||
<OverflowMenuOption
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import { FlexWrapper } from 'components/Container';
|
||||
import SubmitButton, { SubmitButtonProps } from 'components/SubmitButton';
|
||||
import React from 'react';
|
||||
export default function CollectionShareSubmitButton(props: SubmitButtonProps) {
|
||||
return (
|
||||
<FlexWrapper style={{ justifyContent: 'flex-end' }}>
|
||||
<SubmitButton {...props} size="medium" inline sx={{ my: 2 }} />
|
||||
</FlexWrapper>
|
||||
);
|
||||
}
|
|
@ -1,13 +1,20 @@
|
|||
import { Collection, CollectionSummaries } from 'types/collection';
|
||||
import CollectionListBar from 'components/Collections/CollectionBar';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import CollectionListBar from 'components/Collections/CollectionListBar';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import AllCollections from 'components/Collections/AllCollections';
|
||||
import CollectionInfoWithOptions from 'components/Collections/CollectionInfoWithOptions';
|
||||
import { ALL_SECTION } from 'constants/collection';
|
||||
import { ALL_SECTION, COLLECTION_SORT_BY } from 'constants/collection';
|
||||
import CollectionShare from 'components/Collections/CollectionShare';
|
||||
import { SetCollectionNamerAttributes } from 'components/Collections/CollectionNamer';
|
||||
import { ITEM_TYPE, TimeStampListItem } from 'components/PhotoList';
|
||||
import { hasNonEmptyCollections } from 'utils/collection';
|
||||
import {
|
||||
hasNonEmptyCollections,
|
||||
isSystemCollection,
|
||||
shouldBeShownOnCollectionBar,
|
||||
} from 'utils/collection';
|
||||
import { useLocalState } from 'hooks/useLocalState';
|
||||
import { sortCollectionSummaries } from 'services/collectionService';
|
||||
import { LS_KEYS } from 'utils/storage/localStorage';
|
||||
|
||||
interface Iprops {
|
||||
collections: Collection[];
|
||||
|
@ -33,6 +40,12 @@ export default function Collections(props: Iprops) {
|
|||
const [allCollectionView, setAllCollectionView] = useState(false);
|
||||
const [collectionShareModalView, setCollectionShareModalView] =
|
||||
useState(false);
|
||||
|
||||
const [collectionSortBy, setCollectionSortBy] =
|
||||
useLocalState<COLLECTION_SORT_BY>(
|
||||
LS_KEYS.COLLECTION_SORT_BY,
|
||||
COLLECTION_SORT_BY.UPDATION_TIME_DESCENDING
|
||||
);
|
||||
const collectionsMap = useRef<Map<number, Collection>>(new Map());
|
||||
const activeCollection = useRef<Collection>(null);
|
||||
|
||||
|
@ -50,6 +63,15 @@ export default function Collections(props: Iprops) {
|
|||
collectionsMap.current.get(activeCollectionID);
|
||||
}, [activeCollectionID, collections]);
|
||||
|
||||
const sortedCollectionSummaries = useMemo(
|
||||
() =>
|
||||
sortCollectionSummaries(
|
||||
[...collectionSummaries.values()],
|
||||
collectionSortBy
|
||||
),
|
||||
[collectionSortBy, collectionSummaries]
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
!shouldBeHidden &&
|
||||
|
@ -60,6 +82,7 @@ export default function Collections(props: Iprops) {
|
|||
activeCollectionID
|
||||
)}
|
||||
activeCollection={activeCollection.current}
|
||||
activeCollectionID={activeCollectionID}
|
||||
setCollectionNamerAttributes={
|
||||
setCollectionNamerAttributes
|
||||
}
|
||||
|
@ -69,7 +92,7 @@ export default function Collections(props: Iprops) {
|
|||
}
|
||||
/>
|
||||
),
|
||||
itemType: ITEM_TYPE.STATIC,
|
||||
itemType: ITEM_TYPE.OTHER,
|
||||
height: 68,
|
||||
}),
|
||||
[collectionSummaries, activeCollectionID, shouldBeHidden]
|
||||
|
@ -79,25 +102,37 @@ export default function Collections(props: Iprops) {
|
|||
return <></>;
|
||||
}
|
||||
|
||||
const closeAllCollections = () => setAllCollectionView(false);
|
||||
const openAllCollections = () => setAllCollectionView(true);
|
||||
const closeCollectionShare = () => setCollectionShareModalView(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CollectionListBar
|
||||
activeCollection={activeCollectionID}
|
||||
setActiveCollection={setActiveCollectionID}
|
||||
collectionSummaries={collectionSummaries}
|
||||
showAllCollections={() => setAllCollectionView(true)}
|
||||
collectionSummaries={sortedCollectionSummaries.filter((x) =>
|
||||
shouldBeShownOnCollectionBar(x.type)
|
||||
)}
|
||||
showAllCollections={openAllCollections}
|
||||
setCollectionSortBy={setCollectionSortBy}
|
||||
collectionSortBy={collectionSortBy}
|
||||
/>
|
||||
|
||||
<AllCollections
|
||||
isOpen={allCollectionView}
|
||||
close={() => setAllCollectionView(false)}
|
||||
collectionSummaries={collectionSummaries}
|
||||
open={allCollectionView}
|
||||
onClose={closeAllCollections}
|
||||
collectionSummaries={sortedCollectionSummaries.filter(
|
||||
(x) => !isSystemCollection(x.type)
|
||||
)}
|
||||
setActiveCollection={setActiveCollectionID}
|
||||
setCollectionSortBy={setCollectionSortBy}
|
||||
collectionSortBy={collectionSortBy}
|
||||
/>
|
||||
|
||||
<CollectionShare
|
||||
show={collectionShareModalView}
|
||||
onHide={() => setCollectionShareModalView(false)}
|
||||
open={collectionShareModalView}
|
||||
onClose={closeCollectionShare}
|
||||
collection={activeCollection.current}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Box } from '@mui/material';
|
||||
import { styled } from '@mui/material';
|
||||
import { Overlay } from 'components/Container';
|
||||
import { SpecialPadding } from 'styles/SpecialPadding';
|
||||
export const CollectionListWrapper = styled(Box)`
|
||||
position: relative;
|
||||
|
@ -9,10 +10,9 @@ export const CollectionListWrapper = styled(Box)`
|
|||
`;
|
||||
|
||||
export const CollectionListBarWrapper = styled(Box)`
|
||||
width: 100%;
|
||||
${SpecialPadding}
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.palette.divider};
|
||||
${SpecialPadding}
|
||||
`;
|
||||
|
||||
export const CollectionInfoBarWrapper = styled(Box)`
|
||||
|
@ -22,10 +22,11 @@ export const CollectionInfoBarWrapper = styled(Box)`
|
|||
|
||||
export const ScrollContainer = styled('div')`
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
height: 120px;
|
||||
overflow: auto;
|
||||
scroll-behavior: smooth;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
`;
|
||||
|
||||
export const CollectionTile = styled('div')`
|
||||
|
@ -39,74 +40,53 @@ export const CollectionTile = styled('div')`
|
|||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CollectionTileWrapper = styled('div')`
|
||||
margin-right: 4px;
|
||||
`;
|
||||
|
||||
export const ActiveIndicator = styled('div')`
|
||||
height: 3px;
|
||||
background-color: ${({ theme }) => theme.palette.text.primary};
|
||||
background-color: ${({ theme }) => theme.palette.primary.main};
|
||||
margin-top: 18px;
|
||||
border-radius: 2px;
|
||||
`;
|
||||
|
||||
export const Hider = styled('div')<{ hide: boolean }>`
|
||||
display: ${(props) => (props.hide ? 'none' : 'block')};
|
||||
`;
|
||||
|
||||
export const CollectionBarTile = styled(CollectionTile)`
|
||||
width: 80px;
|
||||
width: 90px;
|
||||
height: 64px;
|
||||
`;
|
||||
|
||||
export const AllCollectionTile = styled(CollectionTile)`
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
align-items: flex-start;
|
||||
margin: 2px;
|
||||
`;
|
||||
|
||||
export const CollectionTitleWithDashedBorder = styled(CollectionTile)`
|
||||
border: 1px dashed ${({ theme }) => theme.palette.grey.A200};
|
||||
`;
|
||||
|
||||
export const CollectionSelectorTile = styled(AllCollectionTile)`
|
||||
height: 192px;
|
||||
width: 192px;
|
||||
margin: 10px;
|
||||
`;
|
||||
|
||||
export const ResultPreviewTile = styled(AllCollectionTile)`
|
||||
export const ResultPreviewTile = styled(CollectionTile)`
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
export const CollectionTileTextOverlay = styled('div')`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
padding: 4px 6px;
|
||||
`;
|
||||
|
||||
export const CollectionBarTileText = styled(CollectionTileTextOverlay)`
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(0, 0, 0, 0.1) 0%,
|
||||
rgba(0, 0, 0, 0.5) 86.46%
|
||||
);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
`;
|
||||
|
||||
export const AllCollectionTileText = styled(CollectionTileTextOverlay)`
|
||||
export const CollectionBarTileText = styled(Overlay)`
|
||||
padding: 4px;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0.1) 0%,
|
||||
rgba(0, 0, 0, 0.5) 86.46%
|
||||
);
|
||||
`;
|
||||
|
||||
export const CollectionBarTileIcon = styled(Overlay)`
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
& > .MuiSvgIcon-root {
|
||||
font-size: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AllCollectionTileText = styled(Overlay)`
|
||||
padding: 8px;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
rgba(0, 0, 0, 0.1) 0%,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Box } from '@mui/material';
|
||||
import { Box, IconButton } from '@mui/material';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
const VerticallyCentered = styled(Box)`
|
||||
|
@ -19,23 +19,6 @@ export const DisclaimerContainer = styled('div')`
|
|||
font-size: 14px;
|
||||
`;
|
||||
|
||||
export const IconButton = styled('button')`
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
padding: 5px;
|
||||
color: inherit;
|
||||
margin: 0 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
`;
|
||||
|
||||
export const Row = styled('div')`
|
||||
min-height: 32px;
|
||||
display: flex;
|
||||
|
@ -80,22 +63,13 @@ export const FluidContainer = styled(FlexWrapper)`
|
|||
`;
|
||||
|
||||
export const Overlay = styled(Box)`
|
||||
display: flex;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1; ;
|
||||
`;
|
||||
|
||||
export const InvertedIconButton = styled(IconButton)`
|
||||
background-color: ${({ theme }) => theme.palette.primary.main};
|
||||
color: ${({ theme }) => theme.palette.background.default};
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.palette.grey.A100};
|
||||
}
|
||||
&:focus {
|
||||
background-color: ${({ theme }) => theme.palette.primary.main};
|
||||
}
|
||||
`;
|
||||
export const IconButtonWithBG = styled(IconButton)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.fill.dark,
|
||||
}));
|
||||
|
|
54
src/components/DialogBox/TitleWithCloseButton.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
DialogProps,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { SpaceBetweenFlex } from 'components/Container';
|
||||
|
||||
const DialogTitleWithCloseButton = (props) => {
|
||||
const { children, onClose, ...other } = props;
|
||||
|
||||
return (
|
||||
<DialogTitle {...other}>
|
||||
<SpaceBetweenFlex>
|
||||
<Typography variant="h3" fontWeight={'bold'}>
|
||||
{children}
|
||||
</Typography>
|
||||
{onClose && (
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={onClose}
|
||||
sx={{ float: 'right' }}
|
||||
color="secondary">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</SpaceBetweenFlex>
|
||||
</DialogTitle>
|
||||
);
|
||||
};
|
||||
|
||||
export default DialogTitleWithCloseButton;
|
||||
|
||||
export const dialogCloseHandler =
|
||||
({
|
||||
staticBackdrop,
|
||||
nonClosable,
|
||||
onClose,
|
||||
}: {
|
||||
staticBackdrop?: boolean;
|
||||
nonClosable?: boolean;
|
||||
onClose: () => void;
|
||||
}): DialogProps['onClose'] =>
|
||||
(_, reason) => {
|
||||
if (nonClosable) {
|
||||
// no-op
|
||||
} else if (staticBackdrop && reason === 'backdropClick') {
|
||||
// no-op
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
};
|
|
@ -1,51 +1,23 @@
|
|||
import { Dialog, DialogProps, styled } from '@mui/material';
|
||||
import { Dialog, styled } from '@mui/material';
|
||||
|
||||
const DialogBoxBase = styled(Dialog)(({ theme }) => ({
|
||||
'& .MuiDialog-paper': {
|
||||
padding: theme.spacing(2, 0),
|
||||
padding: theme.spacing(1, 1.5),
|
||||
maxWidth: '346px',
|
||||
},
|
||||
'& .MuiDialogTitle-root': {
|
||||
padding: theme.spacing(2, 3),
|
||||
padding: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(1),
|
||||
},
|
||||
'& .MuiDialogContent-root': {
|
||||
padding: theme.spacing(2, 3),
|
||||
},
|
||||
'& .MuiDialogActions-root': {
|
||||
padding: theme.spacing(2, 3),
|
||||
},
|
||||
'& .MuiDialogActions-root button': {
|
||||
fontSize: '18px',
|
||||
lineHeight: '21.78px',
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
'& .MuiDialogActions-root button:not(:first-child)': {
|
||||
marginLeft: theme.spacing(2),
|
||||
'.MuiDialogTitle-root + .MuiDialogContent-root': {
|
||||
paddingTop: 0,
|
||||
},
|
||||
'.MuiDialogTitle-root + .MuiDialogActions-root': {
|
||||
paddingTop: theme.spacing(3),
|
||||
},
|
||||
}));
|
||||
|
||||
DialogBoxBase.defaultProps = {
|
||||
fullWidth: true,
|
||||
maxWidth: 'sm',
|
||||
};
|
||||
|
||||
export const dialogCloseHandler =
|
||||
({
|
||||
staticBackdrop,
|
||||
nonClosable,
|
||||
onClose,
|
||||
}: {
|
||||
staticBackdrop?: boolean;
|
||||
nonClosable?: boolean;
|
||||
onClose: () => void;
|
||||
}): DialogProps['onClose'] =>
|
||||
(_, reason) => {
|
||||
if (nonClosable) {
|
||||
// no-op
|
||||
} else if (staticBackdrop && reason === 'backdropClick') {
|
||||
// no-op
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
export default DialogBoxBase;
|
||||
|
|
|
@ -6,10 +6,12 @@ import {
|
|||
DialogActions,
|
||||
DialogContent,
|
||||
DialogProps,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import DialogTitleWithCloseButton from './titleWithCloseButton';
|
||||
import MessageText from './messageText';
|
||||
import DialogBoxBase, { dialogCloseHandler } from './base';
|
||||
import DialogTitleWithCloseButton, {
|
||||
dialogCloseHandler,
|
||||
} from './TitleWithCloseButton';
|
||||
import DialogBoxBase from './base';
|
||||
import { DialogBoxAttributes } from 'types/dialogBox';
|
||||
|
||||
type IProps = React.PropsWithChildren<
|
||||
|
@ -59,7 +61,9 @@ export default function DialogBox({
|
|||
{(children || attributes?.content) && (
|
||||
<DialogContent>
|
||||
{children || (
|
||||
<MessageText>{attributes.content}</MessageText>
|
||||
<Typography color="text.secondary">
|
||||
{attributes.content}
|
||||
</Typography>
|
||||
)}
|
||||
</DialogContent>
|
||||
)}
|
||||
|
@ -68,6 +72,7 @@ export default function DialogBox({
|
|||
<>
|
||||
{attributes.close && (
|
||||
<Button
|
||||
size="large"
|
||||
color={attributes.close?.variant ?? 'secondary'}
|
||||
onClick={() => {
|
||||
attributes.close.action &&
|
||||
|
@ -79,6 +84,7 @@ export default function DialogBox({
|
|||
)}
|
||||
{attributes.proceed && (
|
||||
<Button
|
||||
size="large"
|
||||
color={attributes.proceed?.variant}
|
||||
onClick={() => {
|
||||
attributes.proceed.action();
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import { DialogContentText, styled } from '@mui/material';
|
||||
|
||||
const MessageText = styled(DialogContentText)(({ theme }) => ({
|
||||
paddingBottom: theme.spacing(2),
|
||||
fontSize: '20px',
|
||||
lineHeight: '24.2px',
|
||||
}));
|
||||
|
||||
export default MessageText;
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react';
|
||||
import { DialogTitle, IconButton, Typography } from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { SpaceBetweenFlex } from 'components/Container';
|
||||
|
||||
const DialogTitleWithCloseButton = (props) => {
|
||||
const { children, onClose, ...other } = props;
|
||||
|
||||
return (
|
||||
<DialogTitle {...other}>
|
||||
<SpaceBetweenFlex>
|
||||
<Typography variant="title">{children}</Typography>
|
||||
{onClose && (
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
onClick={onClose}
|
||||
sx={{ float: 'right' }}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</SpaceBetweenFlex>
|
||||
</DialogTitle>
|
||||
);
|
||||
};
|
||||
|
||||
export default DialogTitleWithCloseButton;
|
|
@ -24,7 +24,12 @@ export default function EmptyScreen({ openUploader }) {
|
|||
</div>
|
||||
) : (
|
||||
<>
|
||||
<img height={150} src="/images/gallery.png" />
|
||||
<img
|
||||
height={150}
|
||||
src="/images/gallery-locked/1x.png"
|
||||
srcSet="/images/gallery-locked/2x.png 2x,
|
||||
/images/gallery-locked/3x.png 3x"
|
||||
/>
|
||||
<Typography color="text.secondary" mt={2}>
|
||||
{constants.UPLOAD_FIRST_PHOTO_DESCRIPTION()}
|
||||
</Typography>
|
||||
|
|
|
@ -50,6 +50,14 @@ const EnteDateTimePicker = ({
|
|||
'.MuiPickersToolbar-penIconButton': {
|
||||
display: 'none',
|
||||
},
|
||||
'.MuiDialog-paper': { width: '320px' },
|
||||
'.MuiClockPicker-root': {
|
||||
position: 'relative',
|
||||
minHeight: '292px',
|
||||
},
|
||||
'.PrivatePickersSlideTransition-root': {
|
||||
minHeight: '200px',
|
||||
},
|
||||
},
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
import React from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
|
||||
const LogoImage = styled('img')`
|
||||
height: 18px;
|
||||
padding: 0 3px;
|
||||
margin: 3px 0;
|
||||
`;
|
||||
|
||||
interface Iprops {
|
||||
height?: string;
|
||||
width?: string;
|
||||
}
|
||||
|
||||
export function EnteLogo({ height, width }: Iprops) {
|
||||
return <LogoImage style={{ width, height }} alt="logo" src="/icon.svg" />;
|
||||
export function EnteLogo(props) {
|
||||
return (
|
||||
<LogoImage height={18} alt="logo" src="/images/ente.svg" {...props} />
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import { Button, DialogActions, DialogContent, Stack } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { ExportStats } from 'types/export';
|
||||
import { formatDateTime } from 'utils/file';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { Label, Row, Value } from './Container';
|
||||
import { FlexWrapper, Label, Value } from './Container';
|
||||
import { ComfySpan } from './ExportInProgress';
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
onHide: () => void;
|
||||
exportFolder: string;
|
||||
exportSize: string;
|
||||
lastExportTime: number;
|
||||
exportStats: ExportStats;
|
||||
updateExportFolder: (newFolder: string) => void;
|
||||
exportFiles: () => void;
|
||||
retryFailed: () => void;
|
||||
}
|
||||
|
@ -22,70 +18,58 @@ export default function ExportFinished(props: Props) {
|
|||
const totalFiles = props.exportStats.failed + props.exportStats.success;
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
borderBottom: '1px solid #444',
|
||||
marginBottom: '20px',
|
||||
padding: '0 5%',
|
||||
}}>
|
||||
<Row>
|
||||
<Label width="35%">{constants.LAST_EXPORT_TIME}</Label>
|
||||
<Value width="65%">
|
||||
<DialogContent>
|
||||
<Stack spacing={2.5}>
|
||||
<FlexWrapper>
|
||||
<Label width="40%">{constants.LAST_EXPORT_TIME}</Label>
|
||||
<Value width="60%">
|
||||
{formatDateTime(props.lastExportTime)}
|
||||
</Value>
|
||||
</Row>
|
||||
<Row>
|
||||
<Label width="60%">
|
||||
</FlexWrapper>
|
||||
<FlexWrapper>
|
||||
<Label width="40%">
|
||||
{constants.SUCCESSFULLY_EXPORTED_FILES}
|
||||
</Label>
|
||||
<Value width="40%">
|
||||
<Value width="60%">
|
||||
<ComfySpan>
|
||||
{props.exportStats.success} / {totalFiles}
|
||||
</ComfySpan>
|
||||
</Value>
|
||||
</Row>
|
||||
</FlexWrapper>
|
||||
{props.exportStats.failed > 0 && (
|
||||
<Row>
|
||||
<Label width="60%">
|
||||
<FlexWrapper>
|
||||
<Label width="40%">
|
||||
{constants.FAILED_EXPORTED_FILES}
|
||||
</Label>
|
||||
<Value width="35%">
|
||||
<Value width="60%">
|
||||
<ComfySpan>
|
||||
{props.exportStats.failed} / {totalFiles}
|
||||
</ComfySpan>
|
||||
</Value>
|
||||
</Row>
|
||||
</FlexWrapper>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
}}>
|
||||
<Button
|
||||
block
|
||||
variant={'outline-secondary'}
|
||||
onClick={props.onHide}>
|
||||
{constants.CLOSE}
|
||||
</Button>
|
||||
<div style={{ width: '30px' }} />
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{props.exportStats.failed !== 0 ? (
|
||||
<Button
|
||||
block
|
||||
variant={'outline-danger'}
|
||||
size="large"
|
||||
color="accent"
|
||||
onClick={props.retryFailed}>
|
||||
{constants.RETRY_EXPORT_}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
block
|
||||
variant={'outline-success'}
|
||||
size="large"
|
||||
color="primary"
|
||||
onClick={props.exportFiles}>
|
||||
{constants.EXPORT_AGAIN}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Button color="secondary" size="large" onClick={props.onHide}>
|
||||
{constants.CLOSE}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import React from 'react';
|
||||
import { Button, ProgressBar } from 'react-bootstrap';
|
||||
import { ExportProgress } from 'types/export';
|
||||
import { styled } from '@mui/material';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { ExportStage } from 'constants/export';
|
||||
import VerticallyCentered, { FlexWrapper } from './Container';
|
||||
import { ProgressBar } from 'react-bootstrap';
|
||||
|
||||
export const ComfySpan = styled('span')`
|
||||
word-spacing: 1rem;
|
||||
|
@ -11,10 +18,6 @@ export const ComfySpan = styled('span')`
|
|||
`;
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
onHide: () => void;
|
||||
exportFolder: string;
|
||||
exportSize: string;
|
||||
exportStage: ExportStage;
|
||||
exportProgress: ExportProgress;
|
||||
resumeExport: () => void;
|
||||
|
@ -25,66 +28,59 @@ interface Props {
|
|||
export default function ExportInProgress(props: Props) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '30px',
|
||||
padding: '0 5%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
}}>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<DialogContent>
|
||||
<VerticallyCentered>
|
||||
<Box mb={1.5}>
|
||||
<ComfySpan>
|
||||
{' '}
|
||||
{props.exportProgress.current} /{' '}
|
||||
{props.exportProgress.total}{' '}
|
||||
</ComfySpan>{' '}
|
||||
<span style={{ marginLeft: '10px' }}>
|
||||
<span>
|
||||
{' '}
|
||||
files exported{' '}
|
||||
{props.exportStage === ExportStage.PAUSED && `(paused)`}
|
||||
{props.exportStage === ExportStage.PAUSED &&
|
||||
`(paused)`}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ width: '100%', marginBottom: '30px' }}>
|
||||
</Box>
|
||||
<FlexWrapper px={1}>
|
||||
<ProgressBar
|
||||
style={{ width: '100%' }}
|
||||
now={Math.round(
|
||||
(props.exportProgress.current * 100) /
|
||||
props.exportProgress.total
|
||||
)}
|
||||
animated={!(props.exportStage === ExportStage.PAUSED)}
|
||||
animated={
|
||||
!(props.exportStage === ExportStage.PAUSED)
|
||||
}
|
||||
variant="upload-progress-bar"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
}}>
|
||||
</FlexWrapper>
|
||||
</VerticallyCentered>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
{props.exportStage === ExportStage.PAUSED ? (
|
||||
<Button
|
||||
block
|
||||
variant={'outline-secondary'}
|
||||
onClick={props.resumeExport}>
|
||||
size="large"
|
||||
onClick={props.resumeExport}
|
||||
color="accent">
|
||||
{constants.RESUME}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
block
|
||||
variant={'outline-secondary'}
|
||||
onClick={props.pauseExport}>
|
||||
size="large"
|
||||
onClick={props.pauseExport}
|
||||
color="primary">
|
||||
{constants.PAUSE}
|
||||
</Button>
|
||||
)}
|
||||
<div style={{ width: '30px' }} />
|
||||
<Button
|
||||
block
|
||||
variant={'outline-danger'}
|
||||
onClick={props.cancelExport}>
|
||||
size="large"
|
||||
onClick={props.cancelExport}
|
||||
color="secondary">
|
||||
{constants.CANCEL}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,35 +1,18 @@
|
|||
import { DeadCenter } from 'pages/gallery';
|
||||
import { Button, DialogActions, DialogContent } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import constants from 'utils/strings/constants';
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
onHide: () => void;
|
||||
updateExportFolder: (newFolder: string) => void;
|
||||
exportFolder: string;
|
||||
startExport: () => void;
|
||||
exportSize: string;
|
||||
selectExportDirectory: () => void;
|
||||
}
|
||||
export default function ExportInit(props: Props) {
|
||||
export default function ExportInit({ startExport }: Props) {
|
||||
return (
|
||||
<>
|
||||
<DeadCenter>
|
||||
<Button
|
||||
variant="outline-success"
|
||||
size="lg"
|
||||
style={{
|
||||
padding: '6px 3em',
|
||||
margin: '0 20px',
|
||||
marginBottom: '20px',
|
||||
flex: 1,
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
onClick={props.startExport}>
|
||||
<DialogContent>
|
||||
<DialogActions>
|
||||
<Button size="large" color="accent" onClick={startExport}>
|
||||
{constants.START}
|
||||
</Button>
|
||||
</DeadCenter>
|
||||
</>
|
||||
</DialogActions>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,42 +1,43 @@
|
|||
import isElectron from 'is-electron';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import exportService from 'services/exportService';
|
||||
import { ExportProgress, ExportStats } from 'types/export';
|
||||
import { getLocalFiles } from 'services/fileService';
|
||||
import { User } from 'types/user';
|
||||
import { styled } from '@mui/material';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
Divider,
|
||||
Stack,
|
||||
styled,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import { sleep } from 'utils/common';
|
||||
import { getExportRecordFileUID } from 'utils/export';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { Label, Row, Value } from './Container';
|
||||
import { FlexWrapper, Label, Value } from './Container';
|
||||
import ExportFinished from './ExportFinished';
|
||||
import ExportInit from './ExportInit';
|
||||
import ExportInProgress from './ExportInProgress';
|
||||
import FolderIcon from './icons/FolderIcon';
|
||||
import InProgressIcon from './icons/InProgressIcon';
|
||||
import DialogBox from './DialogBox';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import { ExportStage, ExportType } from 'constants/export';
|
||||
|
||||
const FolderIconWrapper = styled('div')`
|
||||
width: 15%;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
padding: 3px;
|
||||
border: 1px solid #444;
|
||||
border-radius: 15%;
|
||||
&:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
`;
|
||||
import EnteSpinner from './EnteSpinner';
|
||||
import DialogTitleWithCloseButton from './DialogBox/TitleWithCloseButton';
|
||||
import MoreHoriz from '@mui/icons-material/MoreHoriz';
|
||||
import OverflowMenu from './OverflowMenu/menu';
|
||||
import { OverflowMenuOption } from './OverflowMenu/option';
|
||||
import { convertBytesToHumanReadable } from 'utils/billing';
|
||||
import { CustomError } from 'utils/error';
|
||||
import { getLocalUserDetails } from 'utils/user';
|
||||
|
||||
const ExportFolderPathContainer = styled('span')`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 200px;
|
||||
width: 100%;
|
||||
|
||||
/* Beginning of string */
|
||||
direction: rtl;
|
||||
|
@ -46,9 +47,9 @@ const ExportFolderPathContainer = styled('span')`
|
|||
interface Props {
|
||||
show: boolean;
|
||||
onHide: () => void;
|
||||
usage: string;
|
||||
}
|
||||
export default function ExportModal(props: Props) {
|
||||
const userDetails = useMemo(() => getLocalUserDetails(), []);
|
||||
const [exportStage, setExportStage] = useState(ExportStage.INIT);
|
||||
const [exportFolder, setExportFolder] = useState('');
|
||||
const [exportSize, setExportSize] = useState('');
|
||||
|
@ -146,8 +147,8 @@ export default function ExportModal(props: Props) {
|
|||
}, [props.show]);
|
||||
|
||||
useEffect(() => {
|
||||
setExportSize(props.usage);
|
||||
}, [props.usage]);
|
||||
setExportSize(convertBytesToHumanReadable(userDetails?.usage));
|
||||
}, [userDetails]);
|
||||
|
||||
// =============
|
||||
// STATE UPDATERS
|
||||
|
@ -179,11 +180,7 @@ export default function ExportModal(props: Props) {
|
|||
const preExportRun = async () => {
|
||||
const exportFolder = getData(LS_KEYS.EXPORT)?.folder;
|
||||
if (!exportFolder) {
|
||||
const folderSelected = await selectExportDirectory();
|
||||
if (!folderSelected) {
|
||||
// no-op as select folder aborted
|
||||
return;
|
||||
}
|
||||
await selectExportDirectory();
|
||||
}
|
||||
updateExportStage(ExportStage.INPROGRESS);
|
||||
await sleep(100);
|
||||
|
@ -193,10 +190,32 @@ export default function ExportModal(props: Props) {
|
|||
updateExportStage(ExportStage.FINISHED);
|
||||
await sleep(100);
|
||||
updateExportTime(Date.now());
|
||||
syncExportStatsWithReport();
|
||||
syncExportStatsWithRecord();
|
||||
}
|
||||
};
|
||||
|
||||
const selectExportDirectory = async () => {
|
||||
const newFolder = await exportService.selectExportDirectory();
|
||||
if (newFolder) {
|
||||
updateExportFolder(newFolder);
|
||||
} else {
|
||||
throw Error(CustomError.REQUEST_CANCELLED);
|
||||
}
|
||||
};
|
||||
|
||||
const syncExportStatsWithRecord = async () => {
|
||||
const exportRecord = await exportService.getExportRecord();
|
||||
const failed = exportRecord?.failedFiles?.length ?? 0;
|
||||
const success = exportRecord?.exportedFiles?.length ?? 0;
|
||||
setExportStats({ failed, success });
|
||||
};
|
||||
|
||||
// =============
|
||||
// UI functions
|
||||
// =============
|
||||
|
||||
const startExport = async () => {
|
||||
try {
|
||||
await preExportRun();
|
||||
updateExportProgress({ current: 0, total: 0 });
|
||||
const exportResult = await exportService.exportFiles(
|
||||
|
@ -204,20 +223,38 @@ export default function ExportModal(props: Props) {
|
|||
ExportType.NEW
|
||||
);
|
||||
await postExportRun(exportResult);
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.REQUEST_CANCELLED) {
|
||||
logError(e, 'startExport failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const stopExport = async () => {
|
||||
try {
|
||||
exportService.stopRunningExport();
|
||||
postExportRun();
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.REQUEST_CANCELLED) {
|
||||
logError(e, 'stopExport failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const pauseExport = () => {
|
||||
try {
|
||||
updateExportStage(ExportStage.PAUSED);
|
||||
exportService.pauseRunningExport();
|
||||
postExportRun({ paused: true });
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.REQUEST_CANCELLED) {
|
||||
logError(e, 'pauseExport failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resumeExport = async () => {
|
||||
try {
|
||||
const exportRecord = await exportService.getExportRecord();
|
||||
await preExportRun();
|
||||
|
||||
|
@ -235,9 +272,15 @@ export default function ExportModal(props: Props) {
|
|||
);
|
||||
|
||||
await postExportRun(exportResult);
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.REQUEST_CANCELLED) {
|
||||
logError(e, 'resumeExport failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const retryFailedExport = async () => {
|
||||
try {
|
||||
await preExportRun();
|
||||
updateExportProgress({ current: 0, total: exportStats.failed });
|
||||
|
||||
|
@ -246,45 +289,22 @@ export default function ExportModal(props: Props) {
|
|||
ExportType.RETRY_FAILED
|
||||
);
|
||||
await postExportRun(exportResult);
|
||||
};
|
||||
|
||||
const syncExportStatsWithReport = async () => {
|
||||
const exportRecord = await exportService.getExportRecord();
|
||||
const failed = exportRecord?.failedFiles?.length ?? 0;
|
||||
const success = exportRecord?.exportedFiles?.length ?? 0;
|
||||
setExportStats({ failed, success });
|
||||
};
|
||||
|
||||
const selectExportDirectory = async () => {
|
||||
const newFolder = await exportService.selectExportDirectory();
|
||||
if (newFolder) {
|
||||
updateExportFolder(newFolder);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.REQUEST_CANCELLED) {
|
||||
logError(e, 'retryFailedExport failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ExportDynamicState = () => {
|
||||
const ExportDynamicContent = () => {
|
||||
switch (exportStage) {
|
||||
case ExportStage.INIT:
|
||||
return (
|
||||
<ExportInit
|
||||
{...props}
|
||||
exportFolder={exportFolder}
|
||||
exportSize={exportSize}
|
||||
updateExportFolder={updateExportFolder}
|
||||
startExport={startExport}
|
||||
selectExportDirectory={selectExportDirectory}
|
||||
/>
|
||||
);
|
||||
return <ExportInit startExport={startExport} />;
|
||||
|
||||
case ExportStage.INPROGRESS:
|
||||
case ExportStage.PAUSED:
|
||||
return (
|
||||
<ExportInProgress
|
||||
{...props}
|
||||
exportFolder={exportFolder}
|
||||
exportSize={exportSize}
|
||||
exportStage={exportStage}
|
||||
exportProgress={exportProgress}
|
||||
resumeExport={resumeExport}
|
||||
|
@ -295,10 +315,7 @@ export default function ExportModal(props: Props) {
|
|||
case ExportStage.FINISHED:
|
||||
return (
|
||||
<ExportFinished
|
||||
{...props}
|
||||
exportFolder={exportFolder}
|
||||
exportSize={exportSize}
|
||||
updateExportFolder={updateExportFolder}
|
||||
onHide={props.onHide}
|
||||
lastExportTime={lastExportTime}
|
||||
exportStats={exportStats}
|
||||
exportFiles={startExport}
|
||||
|
@ -312,53 +329,90 @@ export default function ExportModal(props: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<DialogBox
|
||||
open={props.show}
|
||||
onClose={props.onHide}
|
||||
attributes={{
|
||||
title: constants.EXPORT_DATA,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
borderBottom: '1px solid #444',
|
||||
marginBottom: '20px',
|
||||
padding: '0 5%',
|
||||
width: '450px',
|
||||
}}>
|
||||
<Row>
|
||||
<Label width="40%">{constants.DESTINATION}</Label>
|
||||
<Value width="60%">
|
||||
<Dialog open={props.show} onClose={props.onHide} maxWidth="xs">
|
||||
<DialogTitleWithCloseButton onClose={props.onHide}>
|
||||
{constants.EXPORT_DATA}
|
||||
</DialogTitleWithCloseButton>
|
||||
<DialogContent>
|
||||
<Stack spacing={2}>
|
||||
<ExportDirectory
|
||||
exportFolder={exportFolder}
|
||||
selectExportDirectory={selectExportDirectory}
|
||||
exportStage={exportStage}
|
||||
/>
|
||||
<ExportSize exportSize={exportSize} />
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<Divider />
|
||||
<ExportDynamicContent />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function ExportDirectory({ exportFolder, selectExportDirectory, exportStage }) {
|
||||
return (
|
||||
<FlexWrapper>
|
||||
<Label width="30%">{constants.DESTINATION}</Label>
|
||||
<Value width="70%">
|
||||
{!exportFolder ? (
|
||||
<Button
|
||||
variant={'outline-success'}
|
||||
size={'sm'}
|
||||
onClick={selectExportDirectory}>
|
||||
<Button color={'accent'} onClick={selectExportDirectory}>
|
||||
{constants.SELECT_FOLDER}
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Tooltip title={exportFolder}>
|
||||
<ExportFolderPathContainer>
|
||||
{exportFolder}
|
||||
</ExportFolderPathContainer>
|
||||
</Tooltip>
|
||||
{(exportStage === ExportStage.FINISHED ||
|
||||
exportStage === ExportStage.INIT) && (
|
||||
<FolderIconWrapper
|
||||
onClick={selectExportDirectory}>
|
||||
<FolderIcon />
|
||||
</FolderIconWrapper>
|
||||
<ExportDirectoryOption
|
||||
selectExportDirectory={selectExportDirectory}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Value>
|
||||
</Row>
|
||||
<Row>
|
||||
<Label width="40%">{constants.TOTAL_EXPORT_SIZE} </Label>
|
||||
<Value width="60%">
|
||||
{exportSize ? `${exportSize}` : <InProgressIcon />}
|
||||
</Value>
|
||||
</Row>
|
||||
</div>
|
||||
<ExportDynamicState />
|
||||
</DialogBox>
|
||||
</FlexWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
function ExportSize({ exportSize }) {
|
||||
return (
|
||||
<FlexWrapper>
|
||||
<Label width="30%">{constants.EXPORT_SIZE} </Label>
|
||||
<Value width="70%">
|
||||
{exportSize ? `${exportSize}` : <EnteSpinner />}
|
||||
</Value>
|
||||
</FlexWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
function ExportDirectoryOption({ selectExportDirectory }) {
|
||||
const handleClick = () => {
|
||||
try {
|
||||
selectExportDirectory();
|
||||
} catch (e) {
|
||||
if (e.message !== CustomError.REQUEST_CANCELLED) {
|
||||
logError(e, 'startExport failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<OverflowMenu
|
||||
triggerButtonProps={{
|
||||
sx: {
|
||||
ml: 1,
|
||||
},
|
||||
}}
|
||||
ariaControls={'export-option'}
|
||||
triggerButtonIcon={<MoreHoriz />}>
|
||||
<OverflowMenuOption
|
||||
onClick={handleClick}
|
||||
startIcon={<FolderIcon />}>
|
||||
{constants.CHANGE_FOLDER}
|
||||
</OverflowMenuOption>
|
||||
</OverflowMenu>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import React from 'react';
|
||||
import { styled } from '@mui/material';
|
||||
const HeartUI = styled('button')<{
|
||||
isClick: boolean;
|
||||
size: number;
|
||||
}>`
|
||||
width: ${(props) => props.size}px;
|
||||
height: ${(props) => props.size}px;
|
||||
float: right;
|
||||
background: url('/fav-button.png') no-repeat;
|
||||
cursor: pointer;
|
||||
background-size: cover;
|
||||
border: none;
|
||||
${({ isClick, size }) =>
|
||||
isClick &&
|
||||
`background-position: -${
|
||||
28 * size
|
||||
}px;transition: background 1s steps(28);`}
|
||||
`;
|
||||
|
||||
export default function FavButton({ isClick, onClick, size }) {
|
||||
return <HeartUI isClick={isClick} onClick={onClick} size={size} />;
|
||||
}
|
|
@ -17,12 +17,11 @@ const ShowHidePassword = ({
|
|||
}: Iprops) => (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
color="primary"
|
||||
color="secondary"
|
||||
aria-label="toggle password visibility"
|
||||
onClick={handleClickShowPassword}
|
||||
onMouseDown={handleMouseDownPassword}
|
||||
edge="end"
|
||||
sx={{ color: 'stroke.secondary' }}>
|
||||
edge="end">
|
||||
{showPassword ? <VisibilityOffIcon /> : <VisibilityIcon />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
|
|
|
@ -38,8 +38,7 @@ const Overlay = styled('div')`
|
|||
`;
|
||||
|
||||
type Props = React.PropsWithChildren<{
|
||||
getRootProps: any;
|
||||
getInputProps: any;
|
||||
getDragAndDropRootProps: any;
|
||||
}>;
|
||||
|
||||
export default function FullScreenDropZone(props: Props) {
|
||||
|
@ -79,12 +78,9 @@ export default function FullScreenDropZone(props: Props) {
|
|||
|
||||
return (
|
||||
<DropDiv
|
||||
{...props.getRootProps({
|
||||
{...props.getDragAndDropRootProps({
|
||||
onDragEnter,
|
||||
})}>
|
||||
{!appContext.watchFolderView ? (
|
||||
<input {...props.getInputProps()} />
|
||||
) : null}
|
||||
{isDragActive && (
|
||||
<Overlay onDrop={onDragLeave} onDragLeave={onDragLeave}>
|
||||
<CloseButtonWrapper onClick={onDragLeave}>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import constants from 'utils/strings/constants';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getOtt } from 'services/userService';
|
||||
import { sendOtt } from 'services/userService';
|
||||
import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
|
||||
import { PAGES } from 'constants/pages';
|
||||
import FormPaperTitle from './Form/FormPaper/Title';
|
||||
import FormPaperFooter from './Form/FormPaper/Footer';
|
||||
import LinkButton from './pages/gallery/LinkButton';
|
||||
import SingleInputForm, { SingleInputFormProps } from './SingleInputForm';
|
||||
import { Input } from '@mui/material';
|
||||
|
||||
interface LoginProps {
|
||||
signUp: () => void;
|
||||
|
@ -32,7 +33,7 @@ export default function Login(props: LoginProps) {
|
|||
setFieldError
|
||||
) => {
|
||||
try {
|
||||
await getOtt(email);
|
||||
await sendOtt(email);
|
||||
setData(LS_KEYS.USER, { email });
|
||||
router.push(PAGES.VERIFY);
|
||||
} catch (e) {
|
||||
|
@ -48,6 +49,8 @@ export default function Login(props: LoginProps) {
|
|||
fieldType="email"
|
||||
placeholder={constants.ENTER_EMAIL}
|
||||
buttonText={constants.LOGIN}
|
||||
autoComplete="username"
|
||||
hiddenPostInput={<Input hidden type="password" value="" />}
|
||||
/>
|
||||
|
||||
<FormPaperFooter>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { styled } from '@mui/material';
|
||||
export default styled('img')`
|
||||
height: 25px;
|
||||
vertical-align: bottom;
|
||||
padding-right: 15px;
|
||||
border-right: 2px solid #aaa;
|
||||
margin-right: 15px;
|
||||
`;
|
|
@ -1,14 +1,19 @@
|
|||
import { Button, DialogContent, Typography } from '@mui/material';
|
||||
import VerticallyCentered from 'components/Container';
|
||||
import DialogBoxBase from 'components/DialogBox/base';
|
||||
import DialogTitleWithCloseButton from 'components/DialogBox/titleWithCloseButton';
|
||||
import { Box, Button, Dialog, DialogContent, Typography } from '@mui/material';
|
||||
import VerticallyCentered, { FlexWrapper } from 'components/Container';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import billingService from 'services/billingService';
|
||||
import { getFamilyPlanAdmin } from 'utils/billing';
|
||||
import { preloadImage } from 'utils/common';
|
||||
import constants from 'utils/strings/constants';
|
||||
import DialogTitleWithCloseButton from './DialogBox/TitleWithCloseButton';
|
||||
|
||||
export function MemberSubscriptionManage({ open, userDetails, onClose }) {
|
||||
const { setDialogMessage } = useContext(AppContext);
|
||||
const { setDialogMessage, isMobile } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
preloadImage('/images/family-plan');
|
||||
}, []);
|
||||
|
||||
async function onLeaveFamilyClick() {
|
||||
try {
|
||||
|
@ -26,7 +31,7 @@ export function MemberSubscriptionManage({ open, userDetails, onClose }) {
|
|||
title: `${constants.LEAVE_FAMILY_PLAN}`,
|
||||
content: constants.LEAVE_FAMILY_CONFIRM,
|
||||
proceed: {
|
||||
text: constants.LEAVE_FAMILY_PLAN,
|
||||
text: constants.LEAVE,
|
||||
action: onLeaveFamilyClick,
|
||||
variant: 'danger',
|
||||
},
|
||||
|
@ -40,26 +45,38 @@ export function MemberSubscriptionManage({ open, userDetails, onClose }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<DialogBoxBase open={open} onClose={onClose} maxWidth="xs">
|
||||
<Dialog
|
||||
fullWidth
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="xs"
|
||||
fullScreen={isMobile}>
|
||||
<DialogTitleWithCloseButton onClose={onClose}>
|
||||
<Typography variant="h3">{constants.SUBSCRIPTION}</Typography>
|
||||
<Typography variant="h3" fontWeight={'bold'}>
|
||||
{constants.SUBSCRIPTION}
|
||||
</Typography>
|
||||
<Typography color={'text.secondary'}>
|
||||
{constants.FAMILY_PLAN}
|
||||
</Typography>
|
||||
</DialogTitleWithCloseButton>
|
||||
<DialogContent>
|
||||
<VerticallyCentered>
|
||||
<Box mb={4}>
|
||||
<Typography color="text.secondary">
|
||||
{constants.FAMILY_SUBSCRIPTION_INFO}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{getFamilyPlanAdmin(userDetails.familyData)?.email}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<img
|
||||
height="267px"
|
||||
width="256px"
|
||||
src="/images/family_plan_leave@3x.png"
|
||||
height={256}
|
||||
src="/images/family-plan/1x.png"
|
||||
srcSet="/images/family-plan/2x.png 2x,
|
||||
/images/family-plan/3x.png 3x"
|
||||
/>
|
||||
<FlexWrapper px={2}>
|
||||
<Button
|
||||
size="large"
|
||||
variant="outlined"
|
||||
|
@ -67,8 +84,9 @@ export function MemberSubscriptionManage({ open, userDetails, onClose }) {
|
|||
onClick={confirmLeaveFamily}>
|
||||
{constants.LEAVE_FAMILY_PLAN}
|
||||
</Button>
|
||||
</FlexWrapper>
|
||||
</VerticallyCentered>
|
||||
</DialogContent>
|
||||
</DialogBoxBase>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ export default function Notification({ open, onClose, attributes }: Iprops) {
|
|||
sx={{
|
||||
textAlign: 'left',
|
||||
width: '320px',
|
||||
padding: '12px 16px',
|
||||
padding: (theme) => theme.spacing(1.5, 2),
|
||||
}}>
|
||||
<Stack
|
||||
flex={'1'}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from 'react';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import { IconButton, styled } from '@mui/material';
|
||||
import { IconButton, PaperProps, styled } from '@mui/material';
|
||||
import { OverflowMenuContext } from 'contexts/overflowMenu';
|
||||
|
||||
export interface Iprops {
|
||||
|
@ -8,6 +8,7 @@ export interface Iprops {
|
|||
triggerButtonProps?: any;
|
||||
children?: React.ReactNode;
|
||||
ariaControls: string;
|
||||
menuPaperProps?: Partial<PaperProps>;
|
||||
}
|
||||
|
||||
const StyledMenu = styled(Menu)`
|
||||
|
@ -27,6 +28,7 @@ export default function OverflowMenu({
|
|||
ariaControls,
|
||||
triggerButtonIcon,
|
||||
triggerButtonProps,
|
||||
menuPaperProps,
|
||||
}: Iprops) {
|
||||
const [sortByEl, setSortByEl] = useState(null);
|
||||
const handleClose = () => setSortByEl(null);
|
||||
|
@ -49,6 +51,7 @@ export default function OverflowMenu({
|
|||
disablePadding: true,
|
||||
'aria-labelledby': ariaControls,
|
||||
}}
|
||||
PaperProps={menuPaperProps}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
|
|