diff --git a/ui/src/Layout.tsx b/ui/src/Layout.tsx deleted file mode 100644 index 4ca3f167..00000000 --- a/ui/src/Layout.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React, { ReactChild } from 'react' -import PropTypes from 'prop-types' -import styled from 'styled-components' -import { NavLink } from 'react-router-dom' -import { Icon } from 'semantic-ui-react' -import Sidebar from './components/sidebar/Sidebar' -import { useQuery, gql } from '@apollo/client' -import { Authorized } from './components/routes/AuthorizedRoute' -import { Helmet } from 'react-helmet' -import Header from './components/header/Header' -import { authToken } from './helpers/authentication' -import { useTranslation } from 'react-i18next' - -export const ADMIN_QUERY = gql` - query adminQuery { - myUser { - admin - } - } -` - -export const MAPBOX_QUERY = gql` - query mapboxEnabledQuery { - mapboxToken - } -` - -const Container = styled.div` - height: 100%; - display: flex; - overflow: hidden; - position: relative; -` - -const SideMenuContainer = styled.div` - height: 100%; - width: 80px; - left: 0; - padding-top: 70px; - - @media (max-width: 1000px) { - width: 100%; - height: 80px; - position: fixed; - background: white; - z-index: 10; - padding-top: 0; - display: flex; - bottom: 0; - box-shadow: 0 0 2px rgba(0, 0, 0, 0.3); - } -` - -const Content = styled.div` - margin-top: 70px; - padding: 10px 12px 0; - width: 100%; - overflow-y: scroll; -` - -const SideButtonLink = styled(NavLink)` - text-align: center; - padding-top: 8px; - padding-left: 2px; - display: block; - width: 60px; - height: 60px; - margin: 10px; - margin-bottom: 24px; - - font-size: 28px; - - color: #888; - - transition: transform 200ms, box-shadow 200ms; - - :hover { - transform: scale(1.02); - } -` - -type SideButtonProps = { - children: ReactChild | ReactChild[] - to: string - exact: boolean -} - -const SideButton = ({ - children, - to, - exact, - ...otherProps -}: SideButtonProps) => { - return ( - - {children} - - ) -} - -const SideButtonLabel = styled.div` - font-size: 16px; -` - -export const SideMenu = () => { - const { t } = useTranslation() - - const mapboxQuery = authToken() ? useQuery(MAPBOX_QUERY) : null - - const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken - - return ( - - - - {t('sidemenu.photos', 'Photos')} - - - - {t('sidemenu.albums', 'Albums')} - - {mapboxEnabled ? ( - - - {t('sidemenu.places', 'Places')} - - ) : null} - - - {t('sidemenu.people', 'People')} - - - - {t('sidemenu.settings', 'Settings')} - - - ) -} - -type LayoutProps = { - children: React.ReactNode - title: string -} - -const Layout = ({ children, title, ...otherProps }: LayoutProps) => { - return ( - - - {title ? `${title} - Photoview` : `Photoview`} - - - - - - - {children} -
-
-
-
- - ) -} - -Layout.propTypes = { - children: PropTypes.any.isRequired, - title: PropTypes.string, -} - -export default Layout diff --git a/ui/src/Pages/AlbumPage/AlbumPage.tsx b/ui/src/Pages/AlbumPage/AlbumPage.tsx index a2ca8735..b7b141c5 100644 --- a/ui/src/Pages/AlbumPage/AlbumPage.tsx +++ b/ui/src/Pages/AlbumPage/AlbumPage.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useEffect } from 'react' import { useQuery, gql } from '@apollo/client' import AlbumGallery from '../../components/albumGallery/AlbumGallery' -import Layout from '../../Layout' +import Layout from '../../components/layout/Layout' import useURLParameters from '../../hooks/useURLParameters' import useScrollPagination from '../../hooks/useScrollPagination' import PaginateLoader from '../../components/PaginateLoader' @@ -96,15 +96,13 @@ function AlbumPage({ match }: AlbumPageProps) { }, }) - const { - containerElem, - finished: finishedLoadingMore, - } = useScrollPagination({ - loading, - fetchMore, - data, - getItems: data => data.album.media, - }) + const { containerElem, finished: finishedLoadingMore } = + useScrollPagination({ + loading, + fetchMore, + data, + getItems: data => data.album.media, + }) const toggleFavorites = useCallback( onlyFavorites => { diff --git a/ui/src/Pages/AllAlbumsPage/AlbumsPage.tsx b/ui/src/Pages/AllAlbumsPage/AlbumsPage.tsx index ae805b11..581804c2 100644 --- a/ui/src/Pages/AllAlbumsPage/AlbumsPage.tsx +++ b/ui/src/Pages/AllAlbumsPage/AlbumsPage.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react' import AlbumBoxes from '../../components/albumGallery/AlbumBoxes' -import Layout from '../../Layout' +import Layout from '../../components/layout/Layout' import { useQuery, gql } from '@apollo/client' import LazyLoad from '../../helpers/LazyLoad' import { useTranslation } from 'react-i18next' diff --git a/ui/src/Pages/LoginPage/LoginPage.tsx b/ui/src/Pages/LoginPage/LoginPage.tsx index a492f72e..ce677e83 100644 --- a/ui/src/Pages/LoginPage/LoginPage.tsx +++ b/ui/src/Pages/LoginPage/LoginPage.tsx @@ -8,7 +8,7 @@ import logoPath from '../../assets/photoview-logo.svg' import { useTranslation } from 'react-i18next' import { Helmet } from 'react-helmet' import { Redirect } from 'react-router' -import { Submit, TextField } from '../../primitives/form/Input' +import { TextField } from '../../primitives/form/Input' import MessageBox from '../../primitives/form/MessageBox' import { CheckInitialSetup } from './__generated__/CheckInitialSetup' import { Authorize, AuthorizeVariables } from './__generated__/Authorize' @@ -27,7 +27,7 @@ const LogoHeader = () => { const { t } = useTranslation() return ( -
+
photoview logo

{t('login_page.welcome', 'Welcome to Photoview')} @@ -71,7 +71,7 @@ const LoginForm = () => { return (
diff --git a/ui/src/Pages/PeoplePage/PeoplePage.tsx b/ui/src/Pages/PeoplePage/PeoplePage.tsx index 0698b651..a1f6d0db 100644 --- a/ui/src/Pages/PeoplePage/PeoplePage.tsx +++ b/ui/src/Pages/PeoplePage/PeoplePage.tsx @@ -1,6 +1,6 @@ import React, { createRef, useEffect, useState } from 'react' import { gql, useMutation, useQuery } from '@apollo/client' -import Layout from '../../Layout' +import Layout from '../../components/layout/Layout' import styled from 'styled-components' import { Link } from 'react-router-dom' import SingleFaceGroup from './SingleFaceGroup/SingleFaceGroup' diff --git a/ui/src/Pages/PhotosPage/PhotosPage.tsx b/ui/src/Pages/PhotosPage/PhotosPage.tsx index 6da5e955..82e5e82f 100644 --- a/ui/src/Pages/PhotosPage/PhotosPage.tsx +++ b/ui/src/Pages/PhotosPage/PhotosPage.tsx @@ -1,5 +1,5 @@ import React from 'react' -import Layout from '../../Layout' +import Layout from '../../components/layout/Layout' import TimelineGallery from '../../components/timelineGallery/TimelineGallery' import { useTranslation } from 'react-i18next' diff --git a/ui/src/Pages/PlacesPage/PlacesPage.tsx b/ui/src/Pages/PlacesPage/PlacesPage.tsx index 8830c292..a4d083d6 100644 --- a/ui/src/Pages/PlacesPage/PlacesPage.tsx +++ b/ui/src/Pages/PlacesPage/PlacesPage.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useReducer, useRef, useState } from 'react' import { Helmet } from 'react-helmet' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import Layout from '../../Layout' +import Layout from '../../components/layout/Layout' import { makeUpdateMarkers } from './mapboxHelperFunctions' import MapPresentMarker from './MapPresentMarker' import { urlPresentModeSetupHook } from '../../components/photoGallery/photoGalleryReducer' diff --git a/ui/src/Pages/SettingsPage/SettingsPage.tsx b/ui/src/Pages/SettingsPage/SettingsPage.tsx index afaac4d7..e48cd99b 100644 --- a/ui/src/Pages/SettingsPage/SettingsPage.tsx +++ b/ui/src/Pages/SettingsPage/SettingsPage.tsx @@ -4,7 +4,7 @@ import { Button } from 'semantic-ui-react' import styled from 'styled-components' import { useIsAdmin } from '../../components/routes/AuthorizedRoute' -import Layout from '../../Layout' +import Layout from '../../components/layout/Layout' import ScannerSection from './ScannerSection' import UserPreferences from './UserPreferences' diff --git a/ui/src/components/header/Header.tsx b/ui/src/components/header/Header.tsx index c22959a0..5c86b3ae 100644 --- a/ui/src/components/header/Header.tsx +++ b/ui/src/components/header/Header.tsx @@ -1,57 +1,17 @@ import React from 'react' -import styled from 'styled-components' import SearchBar from './Searchbar' import logoPath from '../../assets/photoview-logo.svg' import { authToken } from '../../helpers/authentication' -const Container = styled.div` - height: 60px; - width: 100%; - display: inline-flex; - position: fixed; - background: white; - top: 0; - /* border-bottom: 1px solid rgba(0, 0, 0, 0.1); */ - box-shadow: 0 0 2px rgba(0, 0, 0, 0.3); -` - -const Title = styled.h1` - font-size: 36px; - font-weight: 400; - padding: 2px 12px; - flex-grow: 1; - min-width: 245px; - - @media (max-width: 400px) { - min-width: auto; - - & span { - display: none; - } - } -` - -const Logo = styled.img` - width: 42px; - height: 42px; - display: inline-block; - vertical-align: middle; - margin-right: 8px; -` - -const LogoText = styled.span` - vertical-align: middle; -` - const Header = () => ( - - - <Logo src={logoPath} alt="logo" /> - <LogoText>Photoview</LogoText> - +
+

+ logo + Photoview +

{authToken() ? : null} - +
) export default Header diff --git a/ui/src/components/header/Searchbar.tsx b/ui/src/components/header/Searchbar.tsx index 4d3d2271..e043cc4d 100644 --- a/ui/src/components/header/Searchbar.tsx +++ b/ui/src/components/header/Searchbar.tsx @@ -123,14 +123,15 @@ const SearchBar = () => { } return ( - - + {results} - +

) } diff --git a/ui/src/Layout.test.js b/ui/src/components/layout/Layout.test.js similarity index 100% rename from ui/src/Layout.test.js rename to ui/src/components/layout/Layout.test.js diff --git a/ui/src/components/layout/Layout.tsx b/ui/src/components/layout/Layout.tsx new file mode 100644 index 00000000..13c1d8a1 --- /dev/null +++ b/ui/src/components/layout/Layout.tsx @@ -0,0 +1,230 @@ +import React, { ReactChild } from 'react' +import PropTypes from 'prop-types' +import styled from 'styled-components' +import { NavLink } from 'react-router-dom' +import { Icon } from 'semantic-ui-react' +import { Sidebar, SidebarProvider } from '../sidebar/Sidebar' +import { useQuery, gql } from '@apollo/client' +import { Authorized } from '../routes/AuthorizedRoute' +import { Helmet } from 'react-helmet' +import Header from '../header/Header' +import { authToken } from '../../helpers/authentication' +import { useTranslation } from 'react-i18next' + +export const ADMIN_QUERY = gql` + query adminQuery { + myUser { + admin + } + } +` + +export const MAPBOX_QUERY = gql` + query mapboxEnabledQuery { + mapboxToken + } +` + +const SideMenuContainer = styled.div` + height: 100%; + width: 80px; + left: 0; + padding-top: 70px; + + @media (max-width: 1000px) { + width: 100%; + height: 80px; + position: fixed; + background: white; + z-index: 10; + padding-top: 0; + display: flex; + bottom: 0; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.3); + } +` + +const Content = styled.div` + margin-top: 70px; + padding: 10px 12px 0; + width: 100%; + overflow-y: scroll; +` + +const SideButtonLink = styled(NavLink)` + text-align: center; + padding-top: 8px; + padding-left: 2px; + display: block; + width: 60px; + height: 60px; + margin: 10px; + margin-bottom: 24px; + + font-size: 28px; + + color: #888; + + transition: transform 200ms, box-shadow 200ms; + + :hover { + transform: scale(1.02); + } +` + +type SideButtonProps = { + to: string + exact: boolean + label: string + gradient: string + icon?: React.ReactChild +} + +const SideButton = ({ to, exact, label, gradient, icon }: SideButtonProps) => { + return ( + +
  • + {label} +
    {icon}
    +
  • +
    + ) +} + +export const SideMenu = () => { + const { t } = useTranslation() + + const mapboxQuery = authToken() ? useQuery(MAPBOX_QUERY) : null + + const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken + + return ( +
      + + + + + } + /> + + + + + } + /> + {mapboxEnabled ? ( + + + + + } + /> + ) : null} + + + + + } + /> + + + + + } + /> +
    + ) +} + +type LayoutProps = { + children: React.ReactNode + title: string +} + +const Layout = ({ children, title, ...otherProps }: LayoutProps) => { + return ( + + + {title ? `${title} - Photoview` : `Photoview`} + +
    +
    +
    + + + +
    + {children} +
    +
    +
    + {/* */} +
    +
    + ) +} + +Layout.propTypes = { + children: PropTypes.any.isRequired, + title: PropTypes.string, +} + +export default Layout diff --git a/ui/src/components/layout/icons/mountain.svg b/ui/src/components/layout/icons/mountain.svg new file mode 100644 index 00000000..239dbe14 --- /dev/null +++ b/ui/src/components/layout/icons/mountain.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/src/components/routes/AuthorizedRoute.tsx b/ui/src/components/routes/AuthorizedRoute.tsx index 6c0c6a41..854517d9 100644 --- a/ui/src/components/routes/AuthorizedRoute.tsx +++ b/ui/src/components/routes/AuthorizedRoute.tsx @@ -3,7 +3,7 @@ import PropTypes, { ReactComponentLike } from 'prop-types' import { Route, Redirect, RouteProps } from 'react-router-dom' import { useLazyQuery } from '@apollo/client' import { authToken } from '../../helpers/authentication' -import { ADMIN_QUERY } from '../../Layout' +import { ADMIN_QUERY } from '../layout/Layout' export const useIsAdmin = (enabled = true) => { const [fetchAdminQuery, { data, called }] = useLazyQuery(ADMIN_QUERY) diff --git a/ui/src/components/routes/Routes.tsx b/ui/src/components/routes/Routes.tsx index 6bf93eb8..f5d1fe8f 100644 --- a/ui/src/components/routes/Routes.tsx +++ b/ui/src/components/routes/Routes.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Route, Switch, Redirect } from 'react-router-dom' import { Loader } from 'semantic-ui-react' -import Layout from '../../Layout' +import Layout from '../layout/Layout' import { clearTokenCookie } from '../../helpers/authentication' import { useTranslation } from 'react-i18next' diff --git a/ui/src/components/sidebar/Sidebar.tsx b/ui/src/components/sidebar/Sidebar.tsx index 10b1114b..34b33739 100644 --- a/ui/src/components/sidebar/Sidebar.tsx +++ b/ui/src/components/sidebar/Sidebar.tsx @@ -1,5 +1,4 @@ -import React, { createContext, useState } from 'react' -import PropTypes from 'prop-types' +import React, { createContext, useContext, useState } from 'react' import styled from 'styled-components' import { Icon } from 'semantic-ui-react' @@ -56,11 +55,11 @@ export const SidebarContext = createContext({ }) SidebarContext.displayName = 'SidebarContext' -type SidebarProps = { - children: React.ReactElement +type SidebarProviderProps = { + children: React.ReactChild | React.ReactChild[] } -const Sidebar = ({ children }: SidebarProps) => { +export const SidebarProvider = ({ children }: SidebarProviderProps) => { const [state, setState] = useState<{ content: React.ReactNode | null }>({ content: null, }) @@ -74,26 +73,23 @@ const Sidebar = ({ children }: SidebarProps) => { value={{ updateSidebar: update, content: state.content }} > {children} - - {value => ( - - {value.content} - setState({ content: null })} - /> -
    -
    - )} -
    ) } -Sidebar.propTypes = { - children: PropTypes.element, -} +export const Sidebar = () => { + const { updateSidebar, content } = useContext(SidebarContext) -export default Sidebar + return ( + + {content} + updateSidebar(null)} + /> +
    +
    + ) +} diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js index 36a447de..b39dd5cd 100644 --- a/ui/tailwind.config.js +++ b/ui/tailwind.config.js @@ -3,7 +3,11 @@ module.exports = { purge: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], darkMode: false, // or 'media' or 'class' theme: { - extend: {}, + extend: { + boxShadow: { + separator: '0 0 4px 0 rgba(0, 0, 0, 0.1)', + }, + }, }, variants: { extend: {},