diff --git a/next.config.js b/next.config.js index 4a46dcfac4..601f9534d1 100644 --- a/next.config.js +++ b/next.config.js @@ -28,12 +28,6 @@ module.exports = (phase) => '@mui/system', '@mui/icons-material', ], - compiler: { - styledComponents: { - ssr: true, - displayName: true, - }, - }, env: { SENTRY_RELEASE: GIT_SHA, NEXT_PUBLIC_IS_TEST_APP: process.env.IS_TEST_RELEASE, diff --git a/package.json b/package.json index 7ecf044254..1c0f6b84e6 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,12 @@ }, "dependencies": { "@date-io/date-fns": "^2.14.0", + "@emotion/cache": "^11.10.5", + "@emotion/react": "^11.10.6", + "@emotion/server": "^11.10.0", + "@emotion/styled": "^11.10.6", "@mui/icons-material": "^5.6.2", "@mui/material": "^5.6.2", - "@mui/styled-engine": "npm:@mui/styled-engine-sc@latest", - "@mui/styled-engine-sc": "^5.6.1", "@mui/x-date-pickers": "^5.0.0-alpha.6", "@sentry/nextjs": "^6.7.1", "@stripe/stripe-js": "^1.13.2", @@ -97,7 +99,6 @@ "@types/react-select": "^4.0.15", "@types/react-window": "^1.8.2", "@types/react-window-infinite-loader": "^1.0.3", - "@types/styled-components": "^5.1.25", "@types/wicg-file-system-access": "^2020.9.5", "@types/yup": "^0.29.7", "@types/zxcvbn": "^4.4.1", @@ -112,8 +113,5 @@ }, "standard": { "parser": "babel-eslint" - }, - "resolutions": { - "@mui/styled-engine": "npm:@mui/styled-engine-sc@latest" } -} +} \ No newline at end of file diff --git a/src/components/Collections/styledComponents.ts b/src/components/Collections/styledComponents.ts index 3a45be2487..d80425615f 100644 --- a/src/components/Collections/styledComponents.ts +++ b/src/components/Collections/styledComponents.ts @@ -1,7 +1,7 @@ import { Box } from '@mui/material'; import { styled } from '@mui/material'; import { Overlay } from 'components/Container'; -import { SpecialPadding } from 'styles/SpecialPadding'; +import { IMAGE_CONTAINER_MAX_WIDTH, MIN_COLUMNS } from 'constants/gallery'; export const CollectionListWrapper = styled(Box)` position: relative; overflow: hidden; @@ -10,7 +10,10 @@ export const CollectionListWrapper = styled(Box)` `; export const CollectionListBarWrapper = styled(Box)` - ${SpecialPadding} + padding: 0 24px; + @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { + padding: 0 4px; + } margin-bottom: 16px; border-bottom: 1px solid ${({ theme }) => theme.palette.divider}; `; diff --git a/src/components/EnteDrawer.tsx b/src/components/EnteDrawer.tsx index 5c860e9d69..beacae98a0 100644 --- a/src/components/EnteDrawer.tsx +++ b/src/components/EnteDrawer.tsx @@ -1,5 +1,4 @@ -import { Drawer } from '@mui/material'; -import styled from 'styled-components'; +import { Drawer, styled } from '@mui/material'; export const EnteDrawer = styled(Drawer)(({ theme }) => ({ '& .MuiPaper-root': { diff --git a/src/components/MachineLearning/ImageViews.tsx b/src/components/MachineLearning/ImageViews.tsx index f8c7bdf991..1b059359de 100644 --- a/src/components/MachineLearning/ImageViews.tsx +++ b/src/components/MachineLearning/ImageViews.tsx @@ -1,19 +1,18 @@ import React, { useState, useEffect } from 'react'; -import styled from 'styled-components'; +import { styled } from '@mui/material'; + import { imageBitmapToBlob } from 'utils/image'; import { logError } from 'utils/sentry'; import { getBlobFromCache } from 'utils/storage/cache'; -export const Image = styled.img``; - -export const FaceCropsRow = styled.div` +export const FaceCropsRow = styled('div')` & > img { width: 256px; height: 256px; } `; -export const FaceImagesRow = styled.div` +export const FaceImagesRow = styled('div')` & > img { width: 112px; height: 112px; @@ -92,9 +91,5 @@ export function ImageBlobView(props: { blob: Blob }) { } }, [props.blob]); - return ( - <> - - - ); + return ; } diff --git a/src/components/MachineLearning/MlDebug-disabled.tsx b/src/components/MachineLearning/MlDebug-disabled.tsx index cba276875f..86405b3d23 100644 --- a/src/components/MachineLearning/MlDebug-disabled.tsx +++ b/src/components/MachineLearning/MlDebug-disabled.tsx @@ -24,7 +24,8 @@ export {}; // import mlIDbStorage from 'utils/storage/mlIDbStorage'; // import { getFaceCropBlobFromStorage } from 'utils/machineLearning/faceCrop'; // import { PeopleList } from './PeopleList'; -// import styled from 'styled-components'; +// import { styled } from '@mui/material'; + // import { RawNodeDatum } from 'react-d3-tree/lib/types/common'; // import { DebugInfo, mstToBinaryTree } from 'hdbscan'; // import { toD3Tree } from 'utils/machineLearning/clustering'; @@ -70,7 +71,7 @@ export {}; // ); // } -// const D3ImageContainer = styled.div` +// const D3ImageContainer = styled('div')` // & > img { // width: 100%; // height: 100%; diff --git a/src/components/MachineLearning/PeopleList.tsx b/src/components/MachineLearning/PeopleList.tsx index fec3c365d9..d28a75b621 100644 --- a/src/components/MachineLearning/PeopleList.tsx +++ b/src/components/MachineLearning/PeopleList.tsx @@ -5,7 +5,7 @@ import { getPeopleList, getUnidentifiedFaces, } from 'utils/machineLearning'; -import styled from 'styled-components'; +import { styled } from '@mui/material'; import { EnteFile } from 'types/file'; import { ImageCacheView } from './ImageViews'; import { CACHES } from 'constants/cache'; @@ -14,7 +14,7 @@ import { addLogLine } from 'utils/logging'; import { logError } from 'utils/sentry'; import { t } from 'i18next'; -const FaceChipContainer = styled.div` +const FaceChipContainer = styled('div')` display: flex; flex-wrap: wrap; justify-content: center; @@ -24,7 +24,7 @@ const FaceChipContainer = styled.div` overflow: auto; `; -const FaceChip = styled.div<{ clickable?: boolean }>` +const FaceChip = styled('div')<{ clickable?: boolean }>` width: 112px; height: 112px; margin: 5px; diff --git a/src/components/Navbar/base.tsx b/src/components/Navbar/base.tsx index 1c67b12fe1..d4df73a5be 100644 --- a/src/components/Navbar/base.tsx +++ b/src/components/Navbar/base.tsx @@ -1,6 +1,6 @@ import { FlexWrapper } from 'components/Container'; import { styled } from '@mui/material'; -import { SpecialPadding } from 'styles/SpecialPadding'; +import { IMAGE_CONTAINER_MAX_WIDTH, MIN_COLUMNS } from 'constants/gallery'; const NavbarBase = styled(FlexWrapper)` min-height: 64px; position: sticky; @@ -10,7 +10,10 @@ const NavbarBase = styled(FlexWrapper)` border-bottom: 1px solid ${({ theme }) => theme.palette.divider}; background-color: ${({ theme }) => theme.palette.background.default}; margin-bottom: 16px; - ${SpecialPadding} + padding: 0 24px; + @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { + padding: 0 4px; + } `; export default NavbarBase; diff --git a/src/components/PhotoList.tsx b/src/components/PhotoList.tsx index e86f664844..3c293fe7a9 100644 --- a/src/components/PhotoList.tsx +++ b/src/components/PhotoList.tsx @@ -19,7 +19,6 @@ import { DeduplicateContext } from 'pages/deduplicate'; import { FlexWrapper } from './Container'; import { Typography } from '@mui/material'; import { GalleryContext } from 'pages/gallery'; -import { SpecialPadding } from 'styles/SpecialPadding'; import { formatDate } from 'utils/time/format'; import { Trans } from 'react-i18next'; import { t } from 'i18next'; @@ -111,7 +110,10 @@ const ListContainer = styled(Box)<{ grid-column-gap: ${GAP_BTW_TILES}px; width: 100%; color: #fff; - ${SpecialPadding} + padding: 0 24px; + @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { + padding: 0 4px; + } `; const ListItemContainer = styled(FlexWrapper)<{ span: number }>` diff --git a/src/components/Search/SearchBar/styledComponents.tsx b/src/components/Search/SearchBar/styledComponents.tsx index 07f183ffbb..5723671d4d 100644 --- a/src/components/Search/SearchBar/styledComponents.tsx +++ b/src/components/Search/SearchBar/styledComponents.tsx @@ -4,10 +4,13 @@ import { FluidContainer, } from 'components/Container'; import { css, styled } from '@mui/material'; -import { SpecialPadding } from 'styles/SpecialPadding'; +import { IMAGE_CONTAINER_MAX_WIDTH, MIN_COLUMNS } from 'constants/gallery'; export const SearchBarWrapper = styled(FlexWrapper)` - ${SpecialPadding} + padding: 0 24px; + @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { + padding: 0 4px; + } `; export const SearchMobileBox = styled(FluidContainer)` diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index a9d68dcdc6..7f549b4bad 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -25,8 +25,6 @@ import { styled, ThemeProvider } from '@mui/material/styles'; import darkThemeOptions from 'themes/darkThemeOptions'; import lightThemeOptions from 'themes/lightThemeOptions'; import { CssBaseline, useMediaQuery } from '@mui/material'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import * as types from 'styled-components/cssprop'; // need to css prop on styled component import { SetDialogBoxAttributes, DialogBoxAttributes } from 'types/dialogBox'; import { getFamilyPortalRedirectURL, @@ -56,6 +54,9 @@ import { SetTheme } from 'types/theme'; import { useLocalState } from 'hooks/useLocalState'; import { THEME_COLOR } from 'constants/theme'; import { setupI18n } from 'i18n'; +import createEmotionCache from 'themes/createEmotionCache'; +import { CacheProvider, EmotionCache } from '@emotion/react'; +import { AppProps } from 'next/app'; export const MessageContainer = styled('div')` background-color: #111; @@ -115,7 +116,19 @@ const redirectMap = new Map([ const APP_TITLE = 'ente Photos'; -export default function App({ Component, err }) { +// Client-side cache, shared for the whole session of the user in the browser. +const clientSideEmotionCache = createEmotionCache(); + +export interface EnteAppProps extends AppProps { + emotionCache?: EmotionCache; +} + +export default function App(props) { + const { + Component, + emotionCache = clientSideEmotionCache, + pageProps, + } = props; const router = useRouter(); const [isI18nReady, setIsI18nReady] = useState(false); const [loading, setLoading] = useState(false); @@ -323,7 +336,7 @@ export default function App({ Component, err }) { }); return ( - <> + {isI18nReady ? t('TITLE') : APP_TITLE} ) : ( - + )} - + ); } diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 21fc9bdd82..83925b400a 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -1,66 +1,110 @@ import React from 'react'; -import Document, { Html, Head, Main, NextScript } from 'next/document'; -import { ServerStyleSheet } from 'styled-components'; +import Document, { + Html, + Head, + Main, + NextScript, + DocumentProps, + DocumentContext, +} from 'next/document'; -export default class MyDocument extends Document { - static async getInitialProps(ctx) { - const sheet = new ServerStyleSheet(); - const originalRenderPage = ctx.renderPage; +import createEmotionServer from '@emotion/server/create-instance'; +import { AppType } from 'next/app'; +import createEmotionCache from 'themes/createEmotionCache'; +import { EnteAppProps } from './_app'; - try { - ctx.renderPage = () => - originalRenderPage({ - enhanceApp: (App) => (props) => - sheet.collectStyles(), - }); - - const initialProps = await Document.getInitialProps(ctx); - return { - ...initialProps, - styles: ( - <> - {initialProps.styles} - {sheet.getStyleElement()} - - ), - }; - } finally { - sheet.seal(); - } - } +interface EnteDocumentProps extends DocumentProps { + emotionStyleTags: JSX.Element[]; +} - render() { - return ( - - - - - - - - - - - - -
- - - - ); - } +export default function EnteDocument({ emotionStyleTags }: EnteDocumentProps) { + return ( + + + + + + + + + + + {emotionStyleTags} + + +
+ + + + ); } + +// `getInitialProps` belongs to `_document` (instead of `_app`), +// it's compatible with static-site generation (SSG). +EnteDocument.getInitialProps = async (ctx: DocumentContext) => { + // Resolution order + // + // On the server: + // 1. app.getInitialProps + // 2. page.getInitialProps + // 3. document.getInitialProps + // 4. app.render + // 5. page.render + // 6. document.render + // + // On the server with error: + // 1. document.getInitialProps + // 2. app.render + // 3. page.render + // 4. document.render + // + // On the client + // 1. app.getInitialProps + // 2. page.getInitialProps + // 3. app.render + // 4. page.render + + const originalRenderPage = ctx.renderPage; + + // You can consider sharing the same Emotion cache between all the SSR requests to speed up performance. + // However, be aware that it can have global side effects. + const cache = createEmotionCache(); + // eslint-disable-next-line @typescript-eslint/unbound-method + const { extractCriticalToChunks } = createEmotionServer(cache); + + ctx.renderPage = () => + originalRenderPage({ + enhanceApp: ( + App: React.ComponentType< + React.ComponentProps & EnteAppProps + > + ) => + function EnhanceApp(props) { + return ; + }, + }); + + const initialProps = await Document.getInitialProps(ctx); + // This is important. It prevents Emotion to render invalid HTML. + // See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153 + const emotionStyles = extractCriticalToChunks(initialProps.html); + const emotionStyleTags = emotionStyles.styles.map((style) => ( +