parent
0e28646b62
commit
30cdba8fd6
|
|
@ -25,6 +25,8 @@ RUN mkdir built && \
|
||||||
|
|
||||||
FROM node:16.14.2-alpine
|
FROM node:16.14.2-alpine
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
COPY --from=client-builder --chown=node:node /usr/src/app/picture-gallery-client/built /usr/src/app/picture-gallery-client/
|
COPY --from=client-builder --chown=node:node /usr/src/app/picture-gallery-client/built /usr/src/app/picture-gallery-client/
|
||||||
COPY --from=server-builder --chown=node:node /usr/src/app/picture-gallery-server/built /usr/src/app/picture-gallery-server/
|
COPY --from=server-builder --chown=node:node /usr/src/app/picture-gallery-server/built /usr/src/app/picture-gallery-server/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
import React from "react";
|
||||||
|
import { FolderPreview } from "./models";
|
||||||
|
import { useColumns } from "../util/responsive";
|
||||||
|
import {
|
||||||
|
Chip,
|
||||||
|
ImageList,
|
||||||
|
ImageListItem,
|
||||||
|
ImageListItemBar,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
export const FolderGallery = ({ folders }: { folders: FolderPreview[] }) => {
|
||||||
|
const columns = useColumns();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ImageList cols={columns} gap={8}>
|
||||||
|
{folders.map((folder) => (
|
||||||
|
<ImageListItem key={folder.fullPath}>
|
||||||
|
{/* Link and image styling taken from https://github.com/mui/material-ui/issues/22597 */}
|
||||||
|
<Link
|
||||||
|
to={folder.fullPath}
|
||||||
|
style={{ display: "block", height: "100%" }}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={folder.imagePreviewSrc}
|
||||||
|
alt={folder.name}
|
||||||
|
loading="lazy"
|
||||||
|
style={{ objectFit: "cover", width: "100%", height: "100%" }}
|
||||||
|
/>
|
||||||
|
<ImageListItemBar
|
||||||
|
title={folder.name}
|
||||||
|
actionIcon={
|
||||||
|
<Chip
|
||||||
|
label={folder.numberOfFiles}
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
color: "white",
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.24);",
|
||||||
|
marginRight: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</ImageListItem>
|
||||||
|
))}
|
||||||
|
</ImageList>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -9,21 +9,15 @@ import Thumbnails from "yet-another-react-lightbox/plugins/thumbnails";
|
||||||
import "yet-another-react-lightbox/plugins/thumbnails.css";
|
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";
|
||||||
import { ImageList, ImageListItem } from "@mui/material";
|
import { ImageList, ImageListItem } from "@mui/material";
|
||||||
|
import { useColumns } from "../util/responsive";
|
||||||
|
|
||||||
function ImageGallery({ images }: { images: ImageWithThumbnail[] }) {
|
export const ImageGallery = ({ images }: { images: ImageWithThumbnail[] }) => {
|
||||||
const [index, setIndex] = useState(-1);
|
const [index, setIndex] = useState(-1);
|
||||||
|
const columns = useColumns();
|
||||||
if (images.length === 0) {
|
|
||||||
return (
|
|
||||||
<p>
|
|
||||||
No images available. You may want to add images in your root directory.
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ImageList variant="masonry" cols={3} gap={8}>
|
<ImageList variant="masonry" cols={columns} gap={8}>
|
||||||
{images.map((item, index) => (
|
{images.map((item, index) => (
|
||||||
<ImageListItem key={item.thumbnail}>
|
<ImageListItem key={item.thumbnail}>
|
||||||
<img
|
<img
|
||||||
|
|
@ -50,6 +44,4 @@ function ImageGallery({ images }: { images: ImageWithThumbnail[] }) {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ImageGallery;
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,13 @@ export interface Folders {
|
||||||
children: Folders[];
|
children: Folders[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FolderPreview {
|
||||||
|
name: string;
|
||||||
|
fullPath: string;
|
||||||
|
numberOfFiles: number;
|
||||||
|
imagePreviewSrc: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ImageWithThumbnail extends Slide {
|
export interface ImageWithThumbnail extends Slide {
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,18 @@ import React, { useEffect, useState } from "react";
|
||||||
import CssBaseline from "@mui/material/CssBaseline";
|
import CssBaseline from "@mui/material/CssBaseline";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Folders, ImageWithThumbnail } from "./ImageGallery/models";
|
import {
|
||||||
|
FolderPreview,
|
||||||
|
Folders,
|
||||||
|
ImageWithThumbnail,
|
||||||
|
} from "./ImageGallery/models";
|
||||||
import { ImageGalleryAppBar } from "./ImageGallery/ImageGalleryAppBar";
|
import { ImageGalleryAppBar } from "./ImageGallery/ImageGalleryAppBar";
|
||||||
import { ImageGalleryDrawer } from "./ImageGallery/ImageGalleryDrawer";
|
import { ImageGalleryDrawer } from "./ImageGallery/ImageGalleryDrawer";
|
||||||
import ImageGallery from "./ImageGallery/ImageGallery";
|
import { ImageGallery } from "./ImageGallery/ImageGallery";
|
||||||
import { Spinner } from "./ImageGallery/Spinner";
|
import { Spinner } from "./ImageGallery/Spinner";
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
|
import { Chip, Divider } from "@mui/material";
|
||||||
|
import { FolderGallery } from "./ImageGallery/FolderGallery";
|
||||||
|
|
||||||
const drawerWidth = 240;
|
const drawerWidth = 240;
|
||||||
export const smallScreenMediaQuery = `(min-width:${drawerWidth * 3}px)`;
|
export const smallScreenMediaQuery = `(min-width:${drawerWidth * 3}px)`;
|
||||||
|
|
@ -19,7 +25,9 @@ function ImageGalleryLayout() {
|
||||||
const [images, setImages] = useState<ImageWithThumbnail[]>([]);
|
const [images, setImages] = useState<ImageWithThumbnail[]>([]);
|
||||||
|
|
||||||
const [folders, setFolders] = useState<Folders | undefined>(undefined);
|
const [folders, setFolders] = useState<Folders | undefined>(undefined);
|
||||||
|
const [foldersPreview, setFoldersPreview] = useState<
|
||||||
|
FolderPreview[] | undefined
|
||||||
|
>([]);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|
@ -28,6 +36,7 @@ function ImageGalleryLayout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setFoldersPreview(undefined);
|
||||||
setImages([]);
|
setImages([]);
|
||||||
setError(false);
|
setError(false);
|
||||||
setImagesLoaded(false);
|
setImagesLoaded(false);
|
||||||
|
|
@ -49,6 +58,15 @@ function ImageGalleryLayout() {
|
||||||
setImagesLoaded(true);
|
setImagesLoaded(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
fetch(`/folderspreview${location.pathname}`, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
setFoldersPreview(data);
|
||||||
|
});
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -85,7 +103,32 @@ function ImageGalleryLayout() {
|
||||||
/>
|
/>
|
||||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
{imagesLoaded ? <ImageGallery images={images} /> : <Spinner />}
|
{!imagesLoaded || !foldersPreview ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{foldersPreview.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Divider>
|
||||||
|
<Chip label="Folders" size="small" />
|
||||||
|
</Divider>
|
||||||
|
<FolderGallery folders={foldersPreview} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{images.length > 0 && foldersPreview.length > 0 && (
|
||||||
|
<Divider>
|
||||||
|
<Chip label="Images" size="small" />
|
||||||
|
</Divider>
|
||||||
|
)}
|
||||||
|
{images.length > 0 && <ImageGallery images={images} />}
|
||||||
|
{images.length == 0 && foldersPreview.length == 0 && (
|
||||||
|
<p>
|
||||||
|
No images available. You may want to add images in your root
|
||||||
|
directory.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
|
|
||||||
|
const breakpoints = Object.freeze([1200, 600, 300, 0]);
|
||||||
|
|
||||||
|
// TODO: never can be one column at the moment
|
||||||
|
export function useColumns(): number {
|
||||||
|
const values = [5, 4, 3, 2];
|
||||||
|
const index = breakpoints
|
||||||
|
.map((b) => useMediaQuery(`(min-width:${b}px)`))
|
||||||
|
.findIndex((a) => a);
|
||||||
|
return Math.max(values[Math.max(index, 0)], 1);
|
||||||
|
}
|
||||||
|
|
@ -31,6 +31,11 @@ export default defineConfig(() => {
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
},
|
},
|
||||||
|
"/folderspreview": {
|
||||||
|
target: "http://localhost:3001",
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
"/staticImages": {
|
"/staticImages": {
|
||||||
target: "http://localhost:3001",
|
target: "http://localhost:3001",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import express from "express";
|
||||||
|
import path from "path";
|
||||||
|
import fs, { Dirent } from "fs";
|
||||||
|
import { thumbnailPath, thumbnailPublicPath } from "../paths";
|
||||||
|
|
||||||
|
export const getRequestedPath = (req: express.Request): string =>
|
||||||
|
req.params[1] === undefined || req.params[1] === "/" ? "" : req.params[1];
|
||||||
|
|
||||||
|
export const readThumbnails = (requestedPath: string): string[] => {
|
||||||
|
const requestedThumbnailPath = path.posix.join(
|
||||||
|
thumbnailPublicPath,
|
||||||
|
requestedPath,
|
||||||
|
);
|
||||||
|
return fs.existsSync(requestedThumbnailPath)
|
||||||
|
? fs.readdirSync(requestedThumbnailPath)
|
||||||
|
: [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSrc = (requestedPath: string, f: Dirent): string =>
|
||||||
|
path.posix.join("/staticImages", requestedPath, f.name);
|
||||||
|
|
||||||
|
export const getThumbnail = (
|
||||||
|
thumbnailExists: boolean,
|
||||||
|
requestedPath: string,
|
||||||
|
f: Dirent,
|
||||||
|
): string =>
|
||||||
|
thumbnailExists
|
||||||
|
? path.posix.join("/staticImages", thumbnailPath, requestedPath, f.name)
|
||||||
|
: getSrc(requestedPath, f);
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
import fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import express from "express";
|
||||||
|
import { a, FolderPreview, Folders } from "../models";
|
||||||
|
import { publicPath, thumbnailPath, thumbnailPublicPath } from "../paths";
|
||||||
|
import { securityValidation } from "./securityChecks";
|
||||||
|
import { getRequestedPath, getThumbnail } from "./common";
|
||||||
|
import { consoleLogger } from "../logging";
|
||||||
|
|
||||||
|
export const walk = async (dirPath: string): Promise<Folders> => {
|
||||||
|
const dirEnts = fs.readdirSync(path.posix.join(publicPath, dirPath), {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const numberOfFiles = dirEnts.filter((f) => f.isFile()).length;
|
||||||
|
|
||||||
|
const children = await Promise.all(
|
||||||
|
dirEnts
|
||||||
|
.filter((d) => d.isDirectory())
|
||||||
|
.filter((d) => !d.name.includes(thumbnailPath.substring(1)))
|
||||||
|
.map((d) => walk(path.posix.join(dirPath, d.name))),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: path.basename(dirPath) || "Home",
|
||||||
|
fullPath: dirPath,
|
||||||
|
numberOfFiles,
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFirstImageInFolder = (dirPath: string): string | undefined => {
|
||||||
|
const dirs = [dirPath];
|
||||||
|
while (dirs.length > 0) {
|
||||||
|
const curPath = dirs.shift();
|
||||||
|
const dirContent = fs.readdirSync(path.posix.join(publicPath, curPath), {
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
for (let i = 0; i < dirContent.length; i += 1) {
|
||||||
|
const content = dirContent[i];
|
||||||
|
const filePath = path.posix.join(curPath, content.name);
|
||||||
|
if (content.isFile()) {
|
||||||
|
return getThumbnail(
|
||||||
|
fs.existsSync(path.posix.join(thumbnailPublicPath, filePath)),
|
||||||
|
curPath,
|
||||||
|
content,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (content.isDirectory()) {
|
||||||
|
dirs.push(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNumberOfFiles = (dirPath: string): number =>
|
||||||
|
fs
|
||||||
|
.readdirSync(path.posix.join(publicPath, dirPath), {
|
||||||
|
withFileTypes: true,
|
||||||
|
})
|
||||||
|
.filter((d) => d.isFile()).length;
|
||||||
|
|
||||||
|
export const getFolderPreview = (
|
||||||
|
req: express.Request,
|
||||||
|
res: express.Response,
|
||||||
|
) => {
|
||||||
|
const requestedPath = getRequestedPath(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
securityValidation(requestedPath);
|
||||||
|
|
||||||
|
const dirents = fs
|
||||||
|
.readdirSync(path.posix.join(publicPath, requestedPath), {
|
||||||
|
withFileTypes: true,
|
||||||
|
})
|
||||||
|
.filter((d) => d.isDirectory())
|
||||||
|
.filter((d) => !d.name.includes(thumbnailPath.substring(1)));
|
||||||
|
|
||||||
|
res.json(
|
||||||
|
dirents.map((dir) => {
|
||||||
|
const fullPath = path.posix.join(requestedPath, dir.name);
|
||||||
|
return a<FolderPreview>({
|
||||||
|
name: dir.name,
|
||||||
|
fullPath,
|
||||||
|
numberOfFiles: getNumberOfFiles(fullPath),
|
||||||
|
imagePreviewSrc: getFirstImageInFolder(fullPath),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
consoleLogger.warn(`Error when trying to access ${req.path}: ${e}`);
|
||||||
|
res.status(400).json({ message: `Path ${req.path} not accessible.` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -3,48 +3,29 @@ import express from "express";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import natsort from "natsort";
|
import natsort from "natsort";
|
||||||
import { publicPath, thumbnailPath, thumbnailPublicPath } from "../paths";
|
import { publicPath } from "../paths";
|
||||||
import { a, Folder, Image } from "../models";
|
import { a, Folder, Image } from "../models";
|
||||||
import { createThumbnailAsyncForImage } from "../thumbnails";
|
import { createThumbnailAsyncForImage } from "../thumbnails";
|
||||||
import { consoleLogger } from "../logging";
|
import { consoleLogger } from "../logging";
|
||||||
import { securityValidation } from "./securityChecks";
|
import { securityValidation } from "./securityChecks";
|
||||||
|
import {
|
||||||
|
getRequestedPath,
|
||||||
|
getSrc,
|
||||||
|
getThumbnail,
|
||||||
|
readThumbnails,
|
||||||
|
} from "./common";
|
||||||
|
|
||||||
const notEmpty = <TValue>(
|
const notEmpty = <TValue>(
|
||||||
value: TValue | void | null | undefined
|
value: TValue | void | null | undefined,
|
||||||
): value is TValue => {
|
): value is TValue => {
|
||||||
return value !== null && value !== undefined;
|
return value !== null && value !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRequestedPath = (req: express.Request): string =>
|
|
||||||
req.params[1] === undefined || req.params[1] === "/" ? "" : req.params[1];
|
|
||||||
|
|
||||||
const readThumbnails = (requestedPath: string): string[] => {
|
|
||||||
const requestedThumbnailPath = path.posix.join(
|
|
||||||
thumbnailPublicPath,
|
|
||||||
requestedPath
|
|
||||||
);
|
|
||||||
return fs.existsSync(requestedThumbnailPath)
|
|
||||||
? fs.readdirSync(requestedThumbnailPath)
|
|
||||||
: [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSrc = (requestedPath: string, f: Dirent): string =>
|
|
||||||
path.posix.join("/staticImages", requestedPath, f.name);
|
|
||||||
|
|
||||||
const getThumbnail = (
|
|
||||||
thumbnailExists: boolean,
|
|
||||||
requestedPath: string,
|
|
||||||
f: Dirent
|
|
||||||
): string =>
|
|
||||||
thumbnailExists
|
|
||||||
? path.posix.join("/staticImages", thumbnailPath, requestedPath, f.name)
|
|
||||||
: getSrc(requestedPath, f);
|
|
||||||
|
|
||||||
const toImage = (
|
const toImage = (
|
||||||
metadata: sharp.Metadata,
|
metadata: sharp.Metadata,
|
||||||
thumbnailExists: boolean,
|
thumbnailExists: boolean,
|
||||||
requestedPath: string,
|
requestedPath: string,
|
||||||
f: Dirent
|
f: Dirent,
|
||||||
): Image => {
|
): Image => {
|
||||||
const widthAndHeightSwap = metadata.orientation > 4; // see https://exiftool.org/TagNames/EXIF.html
|
const widthAndHeightSwap = metadata.orientation > 4; // see https://exiftool.org/TagNames/EXIF.html
|
||||||
return a<Image>({
|
return a<Image>({
|
||||||
|
|
@ -58,7 +39,7 @@ const toImage = (
|
||||||
const getImagesToBeLoaded = (
|
const getImagesToBeLoaded = (
|
||||||
dirents: Dirent[],
|
dirents: Dirent[],
|
||||||
thumbnails: string[],
|
thumbnails: string[],
|
||||||
requestedPath: string
|
requestedPath: string,
|
||||||
): Promise<Image | void>[] =>
|
): Promise<Image | void>[] =>
|
||||||
dirents
|
dirents
|
||||||
.filter((f) => f.isFile())
|
.filter((f) => f.isFile())
|
||||||
|
|
@ -73,22 +54,22 @@ const getImagesToBeLoaded = (
|
||||||
return sharp(path.posix.join(publicPath, requestedPath, f.name))
|
return sharp(path.posix.join(publicPath, requestedPath, f.name))
|
||||||
.metadata()
|
.metadata()
|
||||||
.then((metadata) =>
|
.then((metadata) =>
|
||||||
toImage(metadata, thumbnailExists, requestedPath, f)
|
toImage(metadata, thumbnailExists, requestedPath, f),
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
consoleLogger.error(
|
consoleLogger.error(
|
||||||
`Reading metadata from ${path.posix.join(
|
`Reading metadata from ${path.posix.join(
|
||||||
publicPath,
|
publicPath,
|
||||||
requestedPath,
|
requestedPath,
|
||||||
f.name
|
f.name,
|
||||||
)} produced the following error: ${err.message}`
|
)} produced the following error: ${err.message}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getImages = async (
|
export const getImages = async (
|
||||||
req: express.Request,
|
req: express.Request,
|
||||||
res: express.Response
|
res: express.Response,
|
||||||
) => {
|
) => {
|
||||||
const requestedPath = getRequestedPath(req);
|
const requestedPath = getRequestedPath(req);
|
||||||
|
|
||||||
|
|
@ -103,7 +84,7 @@ export const getImages = async (
|
||||||
const imagesToBeLoaded = getImagesToBeLoaded(
|
const imagesToBeLoaded = getImagesToBeLoaded(
|
||||||
dirents,
|
dirents,
|
||||||
thumbnails,
|
thumbnails,
|
||||||
requestedPath
|
requestedPath,
|
||||||
);
|
);
|
||||||
const images = (await Promise.all(imagesToBeLoaded)).filter(notEmpty);
|
const images = (await Promise.all(imagesToBeLoaded)).filter(notEmpty);
|
||||||
res.json(a<Folder>({ images }));
|
res.json(a<Folder>({ images }));
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import fs from "fs";
|
|
||||||
import * as path from "path";
|
|
||||||
import { Folders } from "./models";
|
|
||||||
import { publicPath, thumbnailPath } from "./paths";
|
|
||||||
|
|
||||||
export const walk = async (dirPath: string): Promise<Folders> => {
|
|
||||||
const dirEnts = fs.readdirSync(path.posix.join(publicPath, dirPath), {
|
|
||||||
withFileTypes: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const numberOfFiles = dirEnts.filter((f) => f.isFile()).length;
|
|
||||||
|
|
||||||
const children = await Promise.all(
|
|
||||||
dirEnts
|
|
||||||
.filter((d) => d.isDirectory())
|
|
||||||
.filter((d) => !d.name.includes(thumbnailPath.substring(1)))
|
|
||||||
.map((d) => walk(path.posix.join(dirPath, d.name))),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: path.basename(dirPath) || "Home",
|
|
||||||
fullPath: dirPath,
|
|
||||||
numberOfFiles,
|
|
||||||
children,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -16,6 +16,13 @@ export interface Folders {
|
||||||
children: Folders[];
|
children: Folders[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FolderPreview {
|
||||||
|
name: string;
|
||||||
|
fullPath: string;
|
||||||
|
numberOfFiles: number;
|
||||||
|
imagePreviewSrc: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export const a = <T>(v: T): T => {
|
export const a = <T>(v: T): T => {
|
||||||
return v;
|
return v;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { getImages } from "../controller/images";
|
import { getImages } from "../controller/images";
|
||||||
import { walk } from "../fsExtension";
|
import { getFolderPreview, walk } from "../controller/directories";
|
||||||
|
|
||||||
export const routerApi = express.Router();
|
export const routerApi = express.Router();
|
||||||
|
|
||||||
|
|
@ -9,3 +9,5 @@ routerApi.get(`/images(/*)?`, getImages);
|
||||||
routerApi.get("/directories", async (req, res) => {
|
routerApi.get("/directories", async (req, res) => {
|
||||||
res.json(await walk(""));
|
res.json(await walk(""));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
routerApi.get("/folderspreview(/*)?", getFolderPreview);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue