migrate to vite + update dependencies

This commit is contained in:
Stefan Forstenlechner 2024-08-12 12:27:16 +02:00
parent 5b3f77c794
commit 210cd74155
19 changed files with 5030 additions and 23894 deletions

View File

@ -1,31 +1,31 @@
# Simple Picture Gallery
## Getting Started
### Docker
```shell
docker build . -t simple-picture-gallery
docker run -p 3005:3001 -v /mnt/data/pictures:/usr/src/app/public --name my-picture-gallery simple-picture-gallery
```
### Customization
Create an environment file `.env`:
```properties
REACT_APP_TITLE=My Gallery
REACT_APP_APPBAR_COLOR=#F8AB2D
```
Other properties:
```properties
REACT_APP_FAVICON_HREF=<URL to your favicon>
```
And run docker with `--env-file .env`
```shell
docker run -p 3005:3001 -v C:/DATA/temp/bla:/usr/src/app/public --env-file .env --name my-picture-gallery simple-picture-gallery
# Simple Picture Gallery
## Getting Started
### Docker
```shell
docker build . -t simple-picture-gallery
docker run -p 3005:3001 -v /mnt/data/pictures:/usr/src/app/public --name my-picture-gallery simple-picture-gallery
```
### Customization
Create an environment file `.env`:
```properties
VITE_TITLE=My Gallery
VITE_APPBAR_COLOR=#F8AB2D
```
Other properties:
```properties
VITE_FAVICON_HREF=<URL to your favicon>
```
And run docker with `--env-file .env`
```shell
docker run -p 3005:3001 -v C:/DATA/temp/bla:/usr/src/app/public --env-file .env --name my-picture-gallery simple-picture-gallery
```

View File

@ -1,3 +1,3 @@
# Set user specific variables
REACT_APP_TITLE=Simple Picture Gallery
REACT_APP_APPBAR_COLOR=#1976D2
VITE_TITLE=Simple Picture Gallery
VITE_APPBAR_COLOR=#1976D2

View File

@ -1,8 +1,7 @@
{
"env": {
"browser": true,
"es2021": true,
"jest/globals": true
"es2021": true
},
"extends": [
"plugin:react/recommended",
@ -19,8 +18,7 @@
},
"plugins": [
"react",
"@typescript-eslint",
"jest"
"@typescript-eslint"
],
"ignorePatterns": [
"**/*.css"
@ -55,6 +53,9 @@
"import/resolver": {
"typescript": {
}
},
"react": {
"version": "detect"
}
}
}

View File

@ -2,28 +2,19 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" id="favicon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="icon" id="favicon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Simple Picture Gallery"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="apple-touch-icon" href="/logo192.png" />
<!--
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" />
<!--
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`.
-->
<link rel="manifest" href="/manifest.json" />
<title id="appTitle">Simple Picture Gallery</title>
</head>
<body>
@ -39,6 +30,7 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script src='%PUBLIC_URL%/env.js'></script>
<script src='/env.js'></script>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -3,48 +3,60 @@
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:3001",
"dependencies": {
"@emotion/react": "11.9.0",
"@emotion/styled": "11.8.1",
"@mui/icons-material": "5.6.0",
"@mui/lab": "5.0.0-alpha.76",
"@mui/material": "5.6.0",
"@types/jest": "27.4.1",
"@types/node": "16.11.26",
"@types/react": "18.0.0",
"@types/react-dom": "18.0.0",
"eslint": "8.21.0",
"eslint-config-prettier": "8.5.0",
"eslint-import-resolver-typescript": "3.4.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jsx-a11y": "6.6.1",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.30.1",
"eslint-plugin-react-hooks": "4.4.0",
"eslint-plugin-jest": "26.5.3",
"react": "18.0.0",
"react-dom": "18.0.0",
"react-photo-album": "1.12.0",
"react-router-dom": "6.3.0",
"react-scripts": "5.0.0",
"react-inject-env": "2.1.0",
"typescript": "4.6.3",
"web-vitals": "2.1.4",
"yet-another-react-lightbox": "1.14.0"
},
"type": "module",
"scripts": {
"format": "prettier --write \"**/*.+(ts|tsx)\"",
"format:check": "prettier --check \"**/*.+(ts|tsx)\"",
"lint": "eslint src/**",
"lint:fix": "eslint --fix src/**",
"client:build": "react-scripts build && npm run set-environment",
"client:eject": "react-scripts eject",
"client:build": "vite build && npm run set-environment",
"client:run": "npm run client:build && npm run client:start",
"client:start": "react-scripts start",
"client:test": "react-scripts test",
"client:start": "vite",
"client:test": "TODO",
"set-environment": "npx react-inject-env set",
"test": "jest"
},
"dependencies": {
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@mui/icons-material": "^5.16.7",
"@mui/x-tree-view": "^7.12.1",
"@mui/material": "^5.16.7",
"@types/jest": "^29.5.12",
"@types/node": "^22.2.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"eslint": "8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-photo-album": "^3.0.1",
"react-router-dom": "^6.26.0",
"react-inject-env": "^2.1.0",
"typescript": "^5.5.4",
"web-vitals": "^4.2.3",
"yet-another-react-lightbox": "^3.21.3"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"@typescript-eslint/eslint-plugin": "8.0.1",
"@typescript-eslint/parser": "8.0.1",
"@vitejs/plugin-react":"^4.3.1",
"prettier": "^3.3.3",
"ts-jest": "^29.2.4",
"ts-node": "^10.9.2",
"vite": "^5.4.0",
"vite-plugin-eslint": "^1.8.1",
"vite-tsconfig-paths": "^5.0.1"
},
"eslintConfig": {
"extends": [
"react-app",
@ -62,15 +74,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.3.0",
"@testing-library/user-event": "14.4.2",
"@typescript-eslint/eslint-plugin": "5.32.0",
"@typescript-eslint/parser": "5.32.0",
"prettier": "2.7.1",
"ts-jest": "27.1.5",
"ts-node": "10.7.0"
}
}

View File

@ -1,22 +0,0 @@
import { PhotoProps } from "react-photo-album";
import { ImageWithThumbnail } from "./models";
import React from "react";
export const Image = <T extends ImageWithThumbnail>({
imageProps: { alt, style, src: _useSrcAndThumbnailFromPhoto, ...rest },
photo,
}: PhotoProps<T>): JSX.Element => {
return (
<>
<img
alt={alt}
style={{
...style,
}}
{...rest}
src={photo.thumbnail}
loading={"lazy"}
/>
</>
);
};

View File

@ -1,15 +1,14 @@
import PhotoAlbum from "react-photo-album";
import React, { useState } from "react";
import { Image } from "./Image";
import { ImageWithThumbnail } from "./models";
import { Lightbox } from "yet-another-react-lightbox";
import "yet-another-react-lightbox/styles.css";
import { Fullscreen } from "yet-another-react-lightbox/plugins/fullscreen";
import { Slideshow } from "yet-another-react-lightbox/plugins/slideshow";
import { Thumbnails } from "yet-another-react-lightbox/plugins/thumbnails";
import Fullscreen from "yet-another-react-lightbox/plugins/fullscreen";
import Slideshow from "yet-another-react-lightbox/plugins/slideshow";
import Thumbnails from "yet-another-react-lightbox/plugins/thumbnails";
import "yet-another-react-lightbox/plugins/thumbnails.css";
import { Zoom } from "yet-another-react-lightbox/plugins/zoom";
import Zoom from "yet-another-react-lightbox/plugins/zoom";
function ImageGallery({ images }: { images: ImageWithThumbnail[] }) {
const [index, setIndex] = useState(-1);
@ -30,8 +29,16 @@ function ImageGallery({ images }: { images: ImageWithThumbnail[] }) {
<PhotoAlbum
layout="masonry"
photos={images}
renderPhoto={Image}
onClick={(event, photo, index) => setIndex(index)}
componentsProps={{
image: {
loading: "lazy",
},
}}
onClick={(props) => {
// TODO: what is eslint complaining?
// eslint-disable-next-line react/prop-types
setIndex(props.index);
}}
/>
<Lightbox
slides={images}

View File

@ -1,7 +1,6 @@
import Toolbar from "@mui/material/Toolbar";
import Typography from "@mui/material/Typography";
import React from "react";
import env from "../env";
import AppBar from "@mui/material/AppBar";
import useMediaQuery from "@mui/material/useMediaQuery";
import { IconButton } from "@mui/material";
@ -21,7 +20,9 @@ export const ImageGalleryAppBar = ({
<AppBar
position="fixed"
sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
style={{ backgroundColor: env.REACT_APP_APPBAR_COLOR ?? "#1976D2" }}
style={{
backgroundColor: import.meta.env.VITE_APPBAR_COLOR ?? "#1976D2",
}}
>
<Toolbar>
{smallScreen && (
@ -36,7 +37,7 @@ export const ImageGalleryAppBar = ({
</IconButton>
)}
<Typography variant="h6" noWrap component="div">
{env.REACT_APP_TITLE ?? "Simple Picture Gallery"}
{import.meta.env.VITE_TITLE ?? "Simple Picture Gallery"}
</Typography>
</Toolbar>
</AppBar>

View File

@ -1,7 +1,7 @@
import Drawer from "@mui/material/Drawer";
import FolderIcon from "@mui/icons-material/Folder";
import FolderOpenIcon from "@mui/icons-material/FolderOpen";
import { TreeItem, TreeView } from "@mui/lab";
import { SimpleTreeView, TreeItem } from "@mui/x-tree-view";
import { useLocation, useNavigate } from "react-router-dom";
import React, { useState } from "react";
import { Folders } from "./models";
@ -13,7 +13,7 @@ import { getDefaultExpanded } from "./PathToExpaned";
function generateTreeViewChildren(
folders: Folders[],
navigateAndToggleExpand: (_path: string, _navigationAllowed: boolean) => void
navigateAndToggleExpand: (_path: string, _navigationAllowed: boolean) => void,
) {
return (
<>
@ -25,7 +25,7 @@ function generateTreeViewChildren(
return (
<TreeItem
key={f.fullPath}
nodeId={f.fullPath}
itemId={f.fullPath}
label={label}
onClick={() =>
navigateAndToggleExpand(f.fullPath, containsImages)
@ -36,7 +36,7 @@ function generateTreeViewChildren(
return (
<TreeItem
key={f.fullPath}
nodeId={f.fullPath}
itemId={f.fullPath}
label={label}
onClick={() => navigateAndToggleExpand(f.fullPath, containsImages)}
>
@ -51,21 +51,21 @@ function generateTreeViewChildren(
const GenerateTreeView = ({ root }: { root: Folders }) => {
const location = useLocation();
const navigate = useNavigate();
const [expanded, setExpanded] = useState<string[]>(
getDefaultExpanded(location.pathname)
const [expandedItems, setExpandedItems] = useState<string[]>(
getDefaultExpanded(location.pathname),
);
const toggleExpanded = (path: string) => {
if (expanded.includes(path)) {
setExpanded(expanded.filter((p) => p !== path));
if (expandedItems.includes(path)) {
setExpandedItems(expandedItems.filter((p) => p !== path));
} else {
setExpanded([path, ...expanded]);
setExpandedItems([path, ...expandedItems]);
}
};
const navigateAndToggleExpand = (
path: string,
navigationAllowed: boolean
navigationAllowed: boolean,
) => {
if (!navigationAllowed || location.pathname === path) {
toggleExpanded(path);
@ -76,15 +76,14 @@ const GenerateTreeView = ({ root }: { root: Folders }) => {
};
return (
<TreeView
<SimpleTreeView
disableSelection
defaultCollapseIcon={<FolderOpenIcon />}
defaultExpandIcon={<FolderIcon />}
expanded={expanded}
slots={{ collapseIcon: FolderOpenIcon, expandIcon: FolderIcon }}
expandedItems={expandedItems}
>
<TreeItem
key={root.fullPath}
nodeId={root.fullPath}
itemId={root.fullPath}
label={`${root.name} - (${root.numberOfFiles})`}
onClick={() => navigate(root.fullPath)}
/>
@ -94,7 +93,7 @@ const GenerateTreeView = ({ root }: { root: Folders }) => {
// eslint-disable-next-line react/jsx-no-useless-fragment
<></>
)}
</TreeView>
</SimpleTreeView>
);
};

View File

@ -1,6 +1,4 @@
import { Pathname } from "history";
export const getDefaultExpanded = (pathname: Pathname): string[] => {
export const getDefaultExpanded = (pathname: string): string[] => {
const pathParts = [];
let curPathname = pathname.startsWith("/") ? pathname.slice(1) : pathname;
while (curPathname.endsWith("/")) {

View File

@ -1,11 +1,10 @@
import env from "../env";
import { CircularProgress } from "@mui/material";
import React from "react";
export const Spinner = (): JSX.Element => {
return (
<CircularProgress
style={{ color: env.REACT_APP_APPBAR_COLOR ?? "#1976D2" }}
style={{ color: import.meta.env.VITE_APPBAR_COLOR ?? "#1976D2" }}
size={100}
/>
);

View File

@ -20,6 +20,6 @@ describe("getDefaultExpanded", () => {
"should allow valid path: %s",
({ pathname, expanded }) => {
expect(getDefaultExpanded(pathname).sort()).toEqual(expanded.sort());
}
},
);
});

View File

@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import Box from "@mui/material/Box";
import CssBaseline from "@mui/material/CssBaseline";
import Box from "@mui/material/Box";
import { useLocation, useNavigate } from "react-router-dom";
import { Folders, ImageWithThumbnail } from "./ImageGallery/models";
import { ImageGalleryAppBar } from "./ImageGallery/ImageGalleryAppBar";

View File

@ -1,18 +1,3 @@
declare global {
// eslint-disable-next-line no-unused-vars
interface Window {
env: any;
}
}
type EnvType = {
REACT_APP_TITLE: string;
REACT_APP_APPBAR_COLOR: string;
REACT_APP_FAVICON_HREF: string | undefined;
};
const env: EnvType = { ...process.env, ...window.env };
function getTitleElement() {
return document.getElementById("appTitle")!;
}
@ -22,13 +7,11 @@ function getFaviconElement() {
}
export const setGalleryTitleAndFavicon = () => {
if (env.REACT_APP_FAVICON_HREF !== undefined) {
if (import.meta.env.VITE_FAVICON_HREF !== undefined) {
const favicon = getFaviconElement();
favicon.href = env.REACT_APP_FAVICON_HREF;
favicon.href = import.meta.env.VITE_FAVICON_HREF;
}
const title = getTitleElement();
title.textContent = env.REACT_APP_TITLE;
title.textContent = import.meta.env.VITE_TITLE;
};
export default env;

View File

@ -9,14 +9,14 @@ import { setGalleryTitleAndFavicon } from "./env";
setGalleryTitleAndFavicon();
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
document.getElementById("root") as HTMLElement,
);
root.render(
<React.StrictMode>
<BrowserRouter>
<ImageGalleryLayout />
</BrowserRouter>
</React.StrictMode>
</React.StrictMode>,
);
// If you want to start measuring performance in your app, pass a function

View File

@ -0,0 +1,12 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_TITLE: string;
readonly VITE_APPBAR_COLOR: string;
readonly VITE_FAVICON_HREF: string | undefined;
}
// eslint-disable-next-line no-unused-vars
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@ -0,0 +1,33 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import eslint from "vite-plugin-eslint";
import viteTsconfigPaths from "vite-tsconfig-paths";
export default defineConfig(() => {
return {
build: {
outDir: "build",
sourcemap: true,
},
plugins: [react(), eslint(), viteTsconfigPaths()],
server: {
proxy: {
"/images": {
target: "http://localhost:3001",
changeOrigin: true,
secure: false,
},
"/directories": {
target: "http://localhost:3001",
changeOrigin: true,
secure: false,
},
"/staticImages": {
target: "http://localhost:3001",
changeOrigin: true,
secure: false,
},
},
},
};
});

View File

@ -15,7 +15,7 @@ const withCaching = {
setHeaders(res, _) {
res.setHeader(
"Expires",
new Date(Date.now() + 2592000000 * 30).toUTCString()
new Date(Date.now() + 2592000000 * 30).toUTCString(),
);
},
};
@ -26,9 +26,7 @@ app.use(express.static("../picture-gallery-client/build"));
app.use(expressLogger);
const imagesPath = "/images";
app.get(`${imagesPath}(/*)?`, getImages);
app.get(`/images(/*)?`, getImages);
app.get("/directories", async (req, res) => {
res.json(await walk(""));
@ -37,7 +35,7 @@ app.get("/directories", async (req, res) => {
// All other GET requests not handled before will return our React app
app.get("*", (req, res) => {
res.sendFile(
path.resolve(__dirname, "../../picture-gallery-client/build/index.html")
path.resolve(__dirname, "../../picture-gallery-client/build/index.html"),
);
});