1
Fork 0

Update UI deps + replace create-react-app with vite

This commit is contained in:
viktorstrate 2022-07-08 00:33:35 +02:00
parent a5d152f0c0
commit fdcf0cfd05
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
28 changed files with 14413 additions and 22269 deletions

View File

@ -1,7 +0,0 @@
module.exports = {
style: {
postcss: {
plugins: [require('tailwindcss'), require('autoprefixer')],
},
},
}

22
ui/index.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/photoview-logo.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/logo192.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="manifest" href="/manifest.json" />
<meta name="apple-mobile-web-app-title" content="Photoview" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="white" />
</head>
<body>
<noscript>You need to enable JavaScript to run Photoview.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

36324
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,78 +8,70 @@
}, },
"license": "GPL-3.0", "license": "GPL-3.0",
"description": "UI app for Photoview", "description": "UI app for Photoview",
"dependencies": {
"@apollo/client": "^3.5.8",
"@babel/preset-typescript": "^7.16.7",
"@craco/craco": "^6.4.3",
"@headlessui/react": "^1.4.3",
"@react-aria/focus": "^3.5.0",
"@rollup/plugin-babel": "^5.3.0",
"@types/geojson": "^7946.0.8",
"@types/jest": "^27.4.0",
"@types/mapbox-gl": "^2.6.0",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"@types/react-helmet": "^6.1.5",
"@types/react-router-dom": "^5.3.3",
"@types/styled-components": "^5.1.21",
"@types/url-join": "^4.0.1",
"autoprefixer": "^9.8.6",
"babel-plugin-graphql-tag": "^3.3.0",
"blurhash": "^1.1.4",
"classnames": "^2.3.1",
"connect-history-api-fallback": "^1.6.0",
"copy-to-clipboard": "^3.3.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest-dom": "^4.0.1",
"fs-extra": "^10.0.0",
"i18next": "^21.6.10",
"mapbox-gl": "^2.6.1",
"postcss": "^7.0.36",
"prettier": "^2.5.1",
"prop-types": "^15.8.1",
"react": "^17.0.2",
"react-blurhash": "^0.1.3",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-hook-form": "^7.25.3",
"react-i18next": "^11.15.3",
"react-router-dom": "^6.2.1",
"react-router-prop-types": "^1.0.5",
"react-scripts": "^4.0.3",
"react-spring": "^8.0.27",
"react-test-renderer": "^17.0.2",
"styled-components": "^5.3.3",
"subscriptions-transport-ws": "^0.11.0",
"tailwind-override": "^0.6.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",
"typescript": "^4.5.5",
"url-join": "^4.0.1"
},
"scripts": { "scripts": {
"start": "BROWSER=none PORT=1234 craco start", "start": "vite",
"build": "craco build", "build": "vite build",
"test": "npm run lint && npm run jest -- --watchAll=false", "test": "npm run lint && npm run jest -- --watchAll=false",
"test:ci": "npm run lint && npm run jest:ci", "test:ci": "npm run lint && npm run jest:ci",
"lint": "npm run lint:types & npm run lint:eslint", "lint": "eslint ./src --max-warnings 0 --cache --config .eslintrc.js",
"lint:eslint": "eslint ./src --max-warnings 0 --cache --config .eslintrc.js",
"lint:types": "tsc --noemit",
"jest": "craco test --setupFilesAfterEnv ./testing/setupTests.ts", "jest": "craco test --setupFilesAfterEnv ./testing/setupTests.ts",
"jest:ci": "CI=true craco test --setupFilesAfterEnv ./testing/setupTests.ts --verbose --ci --coverage", "jest:ci": "CI=true craco test --setupFilesAfterEnv ./testing/setupTests.ts --verbose --ci --coverage",
"genSchemaTypes": "apollo client:codegen --target=typescript --globalTypesFile=src/__generated__/globalTypes.ts && prettier --write */**/__generated__/*.ts", "genSchemaTypes": "apollo client:codegen --target=typescript --globalTypesFile=src/__generated__/globalTypes.ts && prettier --write */**/__generated__/*.ts",
"extractTranslations": "i18next -c i18next-parser.config.js", "extractTranslations": "i18next -c i18next-parser.config.js",
"prepare": "(cd .. && npx husky install)" "prepare": "(cd .. && npx husky install)"
}, },
"dependencies": {
"@apollo/client": "^3.6.9",
"@babel/preset-typescript": "^7.18.6",
"@headlessui/react": "^1.6.6",
"@types/geojson": "^7946.0.8",
"@types/jest": "^28.1.4",
"@types/mapbox-gl": "^2.7.3",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/react-helmet": "^6.1.5",
"@types/react-router-dom": "^5.3.3",
"@types/styled-components": "^5.1.25",
"@types/url-join": "^4.0.1",
"@vitejs/plugin-react": "^1.3.2",
"autoprefixer": "^10.4.7",
"blurhash": "^1.1.5",
"classnames": "^2.3.1",
"connect-history-api-fallback": "^2.0.0",
"copy-to-clipboard": "^3.3.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest-dom": "^4.0.2",
"i18next": "^21.8.13",
"mapbox-gl": "^2.9.1",
"postcss": "^8.4.14",
"prettier": "^2.7.1",
"react": "^18.2.0",
"react-blurhash": "^0.1.3",
"react-dom": "^18.2.0",
"react-helmet": "^6.1.0",
"react-hook-form": "^7.33.1",
"react-i18next": "^11.18.0",
"react-router-dom": "^6.3.0",
"react-scripts": "^5.0.1",
"react-test-renderer": "^18.2.0",
"styled-components": "^5.3.5",
"subscriptions-transport-ws": "^0.11.0",
"tailwind-override": "^0.6.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",
"typescript": "^4.7.4",
"url-join": "^5.0.0",
"vite": "^2.9.13",
"vite-plugin-svgr": "^2.2.0"
},
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^5.16.1", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.2", "@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^14.2.1",
"apollo": "2.33.9", "apollo": "2.34.0",
"apollo-language-server": "1.26.7", "apollo-language-server": "1.26.9",
"husky": "^7.0.4", "husky": "^8.0.1",
"i18next-parser": "^5.4.0", "i18next-parser": "^6.5.0",
"lint-staged": "^12.3.2", "lint-staged": "^13.0.3"
"tsc-files": "1.1.2"
}, },
"overrides": { "overrides": {
"graphql": "^15.0.0" "graphql": "^15.0.0"
@ -93,8 +85,7 @@
}, },
"lint-staged": { "lint-staged": {
"*.{ts,tsx,js,json,css,md,graphql}": "prettier --write", "*.{ts,tsx,js,json,css,md,graphql}": "prettier --write",
"*.{js,ts,tsx}": "eslint --cache --fix --max-warnings 0", "*.{js,ts,tsx}": "eslint --cache --fix --max-warnings 0"
"*.{ts,tsx}": "tsc-files --noEmit"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [

6
ui/postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,42 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/photoview-logo.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<meta name="apple-mobile-web-app-title" content="Photoview" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="white" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
</head>
<body>
<noscript>You need to enable JavaScript to run Photoview.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -100,7 +100,7 @@ function AlbumPage() {
}) })
const toggleFavorites = useCallback( const toggleFavorites = useCallback(
onlyFavorites => { (onlyFavorites: boolean) => {
if ( if (
(refetchNeededAll && !onlyFavorites) || (refetchNeededAll && !onlyFavorites) ||
(refetchNeededFavorites && onlyFavorites) (refetchNeededFavorites && onlyFavorites)

View File

@ -29,7 +29,7 @@ const LogoHeader = () => {
<div className="flex justify-center flex-col mb-14 mt-20"> <div className="flex justify-center flex-col mb-14 mt-20">
<img <img
className="h-24" className="h-24"
src={process.env.PUBLIC_URL + '/photoview-logo.svg'} src={import.meta.env.BASE_URL + 'photoview-logo.svg'}
alt="photoview logo" alt="photoview logo"
/> />
<h1 className="text-3xl text-center mt-4"> <h1 className="text-3xl text-center mt-4">

View File

@ -40,7 +40,7 @@ const ChangePasswordModal = ({
title={t('settings.users.password_reset.title', 'Change password')} title={t('settings.users.password_reset.title', 'Change password')}
description={ description={
<Trans t={t} i18nKey="settings.users.password_reset.description"> <Trans t={t} i18nKey="settings.users.password_reset.description">
Change password for <b>{{ username: user.username }}</b> Change password for <b>{user.username}</b>
</Trans> </Trans>
} }
actions={[ actions={[

View File

@ -7,10 +7,12 @@ import {
SectionTitle, SectionTitle,
} from './SettingsPage' } from './SettingsPage'
const VERSION = process.env.REACT_APP_BUILD_VERSION ?? 'undefined' const VERSION = import.meta.env.REACT_APP_BUILD_VERSION ?? 'undefined'
const BUILD_DATE = process.env.REACT_APP_BUILD_DATE ?? 'undefined' const BUILD_DATE = import.meta.env.REACT_APP_BUILD_DATE ?? 'undefined'
const COMMIT_SHA = process.env.REACT_APP_BUILD_COMMIT_SHA as string | undefined const COMMIT_SHA = import.meta.env.REACT_APP_BUILD_COMMIT_SHA as
| string
| undefined
let commitLink: ReactElement let commitLink: ReactElement
if (COMMIT_SHA) { if (COMMIT_SHA) {

View File

@ -35,7 +35,7 @@ const PasswordProtectedShare = ({
'share_page.wrong_password', 'share_page.wrong_password',
'Wrong password, please try again.' 'Wrong password, please try again.'
) )
} else if (errors.password?.type === 'required') { } else if (errors.password) {
errorMessage = t( errorMessage = t(
'share_page.protected_share.password_required_error', 'share_page.protected_share.password_required_error',
'Password is required' 'Password is required'

View File

@ -17,8 +17,8 @@ import { MessageState } from './components/messages/Messages'
import { Message } from './components/messages/SubscriptionsHook' import { Message } from './components/messages/SubscriptionsHook'
import { NotificationType } from './__generated__/globalTypes' import { NotificationType } from './__generated__/globalTypes'
export const API_ENDPOINT = process.env.REACT_APP_API_ENDPOINT export const API_ENDPOINT = import.meta.env.REACT_APP_API_ENDPOINT
? (process.env.REACT_APP_API_ENDPOINT as string) ? (import.meta.env.REACT_APP_API_ENDPOINT as string)
: urlJoin(location.origin, '/api') : urlJoin(location.origin, '/api')
export const GRAPHQL_ENDPOINT = urlJoin(API_ENDPOINT, '/graphql') export const GRAPHQL_ENDPOINT = urlJoin(API_ENDPOINT, '/graphql')

View File

@ -18,7 +18,7 @@ const Header = () => {
<h1 className="mr-4 lg:mr-8 flex-shrink-0 flex items-center"> <h1 className="mr-4 lg:mr-8 flex-shrink-0 flex items-center">
<img <img
className="h-12 lg:h-10" className="h-12 lg:h-10"
src={process.env.PUBLIC_URL + '/photoview-logo.svg'} src={import.meta.env.BASE_URL + 'photoview-logo.svg'}
alt="logo" alt="logo"
/> />
<span className="hidden lg:block ml-2 text-2xl font-light"> <span className="hidden lg:block ml-2 text-2xl font-light">

View File

@ -1,5 +1,4 @@
import { gql } from '@apollo/client' import { gql } from '@apollo/client'
import PropTypes from 'prop-types'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import Header from '../header/Header' import Header from '../header/Header'
@ -50,9 +49,4 @@ const Layout = ({ children, title, ...otherProps }: LayoutProps) => {
) )
} }
Layout.propTypes = {
children: PropTypes.any.isRequired,
title: PropTypes.string,
}
export default Layout export default Layout

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { animated, useTransition } from 'react-spring'
import styled from 'styled-components' import styled from 'styled-components'
import { authToken } from '../../helpers/authentication' import { authToken } from '../../helpers/authentication'
import MessageProgress from './MessageProgress' import MessageProgress from './MessageProgress'
@ -83,29 +82,30 @@ const Messages = () => {
} }
} }
const transitions = useTransition(messages.slice().reverse(), x => x.key, { // const transitions = useTransition(messages.slice().reverse(), x => x.key, {
from: { // from: {
opacity: 0, // opacity: 0,
height: '0px', // height: '0px',
}, // },
enter: { // enter: {
opacity: 1, // opacity: 1,
height: `100px`, // height: `100px`,
}, // },
leave: { opacity: 0, height: '0px' }, // leave: { opacity: 0, height: '0px' },
// })
const messageElems = messages.map(msg => {
const Elem = getMessageElement(msg)
return (
<div key={msg.key}>
<Elem />
</div>
)
}) })
return ( return (
<Container> <Container>
{transitions.map(({ item, props: style, key }) => { {messageElems}
const MessageElement = getMessageElement(item)
return (
<animated.div key={key} style={style}>
<MessageElement />
</animated.div>
)
})}
{authToken() && ( {authToken() && (
<SubscriptionsHook messages={messages} setMessages={setMessages} /> <SubscriptionsHook messages={messages} setMessages={setMessages} />
)} )}

View File

@ -1,5 +1,4 @@
import { notificationSubscription } from './__generated__/notificationSubscription' import { notificationSubscription } from './__generated__/notificationSubscription'
import PropTypes from 'prop-types'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useSubscription, gql } from '@apollo/client' import { useSubscription, gql } from '@apollo/client'
import { authToken } from '../../helpers/authentication' import { authToken } from '../../helpers/authentication'
@ -122,9 +121,4 @@ const SubscriptionsHook = ({
return null return null
} }
SubscriptionsHook.propTypes = {
messages: PropTypes.array.isRequired,
setMessages: PropTypes.func.isRequired,
}
export default SubscriptionsHook export default SubscriptionsHook

View File

@ -2,7 +2,7 @@ import React from 'react'
import PresentNavigationOverlay from './PresentNavigationOverlay' import PresentNavigationOverlay from './PresentNavigationOverlay'
import { fireEvent, render, screen, act } from '@testing-library/react' import { fireEvent, render, screen, act } from '@testing-library/react'
jest.useFakeTimers('modern') jest.useFakeTimers()
describe('PresentNavigationOverlay component', () => { describe('PresentNavigationOverlay component', () => {
test('simple render', () => { test('simple render', () => {

View File

@ -67,7 +67,7 @@ const PresentView = ({
}) })
return ( return (
<StyledContainer {...className}> <StyledContainer className={className}>
<PreventScroll /> <PreventScroll />
<PresentNavigationOverlay <PresentNavigationOverlay
dispatchMedia={dispatchMedia} dispatchMedia={dispatchMedia}

View File

@ -1,5 +1,4 @@
import React, { useRef, useEffect, useReducer } from 'react' import React, { useRef, useEffect, useReducer } from 'react'
import PropTypes from 'prop-types'
import { useQuery, gql } from '@apollo/client' import { useQuery, gql } from '@apollo/client'
import TimelineGroupDate from './TimelineGroupDate' import TimelineGroupDate from './TimelineGroupDate'
import PresentView from '../photoGallery/presentView/PresentView' import PresentView from '../photoGallery/presentView/PresentView'
@ -197,9 +196,4 @@ const TimelineGallery = () => {
) )
} }
TimelineGallery.propTypes = {
favorites: PropTypes.bool,
setFavorites: PropTypes.func,
}
export default TimelineGallery export default TimelineGallery

View File

@ -1,7 +1,7 @@
import 'regenerator-runtime/runtime' import 'regenerator-runtime/runtime'
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import { createRoot } from 'react-dom/client'
import App from './App' import App from './App'
import client from './apolloClient' import client from './apolloClient'
import { ApolloProvider } from '@apollo/client' import { ApolloProvider } from '@apollo/client'
@ -26,6 +26,7 @@ const Main = () => (
</ApolloProvider> </ApolloProvider>
) )
ReactDOM.render(<Main />, document.getElementById('root')) const root = createRoot(document.getElementById('root')!)
root.render(<Main />)
serviceWorkerRegistration.register() serviceWorkerRegistration.register()

View File

@ -21,7 +21,7 @@ export function setupLocalization(): void {
}, },
react: { react: {
useSuspense: process.env.NODE_ENV == 'production', useSuspense: import.meta.env.PROD,
}, },
}) })
} }

View File

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View File

@ -55,7 +55,7 @@ registerRoute(
// Return true to signal that we want to use the handler. // Return true to signal that we want to use the handler.
return true return true
}, },
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') createHandlerBoundToURL(import.meta.env.BASE_URL + 'index.html')
) )
// An example runtime caching route for requests that aren't handled by the // An example runtime caching route for requests that aren't handled by the

View File

@ -26,9 +26,9 @@ type Config = {
} }
export function register(config?: Config) { export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (import.meta.env.PROD && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href) const publicUrl = new URL(import.meta.env.BASE_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin // Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to // from what our page is served on. This might happen if a CDN is used to
@ -37,7 +37,7 @@ export function register(config?: Config) {
} }
window.addEventListener('load', () => { window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js` const swUrl = `${import.meta.env.BASE_URL}service-worker.js`
if (isLocalhost) { if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not. // This is running on localhost. Let's check if a service worker still exists or not.

1
ui/src/vite.env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -1,5 +1,6 @@
module.exports = { module.exports = {
mode: 'jit', mode: 'jit',
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
purge: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], purge: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
darkMode: 'class', darkMode: 'class',
theme: { theme: {

View File

@ -1,21 +1,20 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"], "useDefineForClassFields": true,
"allowJs": true, "lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": false,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true, "module": "ESNext",
"module": "esnext", "moduleResolution": "Node",
"moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx"
}, },
"include": ["src"], "include": ["src", "vite.config.ts"]
"exclude": ["./node_modules", "./build"]
} }

11
ui/vite.config.js Normal file
View File

@ -0,0 +1,11 @@
import { defineConfig } from 'vite'
import svgr from 'vite-plugin-svgr'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react(), svgr()],
envPrefix: ['VITE_', 'REACT_APP_'],
server: {
port: 1234,
},
})