Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

329 use the lightbox to show document image #330

Merged
merged 2 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"react-select": "^5.8.0",
"recharts": "2",
"swr": "^2.2.5",
"web-vitals": "^3.5.2"
"web-vitals": "^3.5.2",
"yet-another-react-lightbox": "^3.18.0"
},
"scripts": {
"start": "react-scripts start",
Expand Down Expand Up @@ -72,7 +73,6 @@
]
},
"devDependencies": {
"typescript": "^5.4.5",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
Expand All @@ -87,7 +87,8 @@
"i18next-parser": "^8.13.0",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"source-map-explorer": "^2.5.3"
"source-map-explorer": "^2.5.3",
"typescript": "^5.4.5"
},
"husky": {
"hooks": {
Expand Down
14 changes: 14 additions & 0 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions frontend/src/components/ImageLightBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { serverBaseUrl } from '../index';
import { Buffer } from 'buffer';
import React from 'react';
import Lightbox from 'yet-another-react-lightbox';

interface Props {
src?: string;
onChange: (src: string | undefined) => void;
}
export function ImageLightBox(props: Props) {
return (
<Lightbox
slides={[
{
src: props.src ? `${serverBaseUrl}/api/documents/${Buffer.from(props.src).toString('base64')}` : '',
},
]}
open={props.src !== undefined}
controller={{ closeOnPullDown: false, closeOnBackdropClick: true }}
close={() => props.onChange(undefined)}
render={{
buttonPrev: () => null,
buttonNext: () => null,
}}
/>
);
}
28 changes: 7 additions & 21 deletions frontend/src/components/documentLines/AccountDocumentLine.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Box, Card, Text } from '@mantine/core';
import { openContextModal } from '@mantine/modals';
import { Buffer } from 'buffer';
import { serverBaseUrl } from '../../index';
import { Document } from '../../rest-model';
import { createStyles } from '@mantine/emotion';
import { isDocumentAnImage } from '../../utils/documents';

const useStyles = createStyles((theme, _, u) => ({
imgBox: {
Expand Down Expand Up @@ -60,31 +60,17 @@ const useStyles = createStyles((theme, _, u) => ({
},
}));

export interface Props extends Document {}

export const EXTENSIONS_SUPPORT_PREVIEW = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'];
export interface Props extends Document {
onClick: (path: string) => void;
}

export default function AccountDocumentLine(props: Props) {
const { classes } = useStyles();

const extension = (props.extension ?? '').toLowerCase();
const canPreview = EXTENSIONS_SUPPORT_PREVIEW.includes(extension);
const openPreviewModal = () => {
if (canPreview) {
openContextModal({
modal: 'documentPreviewModal',
title: props.filename,
size: 'lg',
centered: true,
innerProps: {
filename: props.filename,
path: props.path,
},
});
}
};
const canPreview = isDocumentAnImage(props.path);

return (
<Card shadow="sm" p="xs" radius="sm" withBorder onClick={openPreviewModal}>
<Card shadow="sm" p="xs" radius="sm" withBorder onClick={isDocumentAnImage(props.path) ? () => props.onClick(props.path) : undefined}>
<Card.Section className={classes.imgBox}>
{canPreview ? (
<img
Expand Down
30 changes: 10 additions & 20 deletions frontend/src/components/journalPreview/DocumentPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Box } from '@mantine/core';
import { openContextModal } from '@mantine/modals';
import { Buffer } from 'buffer';
import { serverBaseUrl } from '../..';
import { createStyles } from '@mantine/emotion';
import { isDocumentAnImage } from '../../utils/documents';

export const EXTENSIONS_SUPPORT_PREVIEW = ['png', 'jpg', 'jpeg', 'gif'];
const useStyles = createStyles((theme, _, u) => ({
imgBox: {
overflow: 'hidden',
Expand Down Expand Up @@ -64,30 +63,21 @@ const useStyles = createStyles((theme, _, u) => ({
interface Props {
uri: string;
filename: string;
onClick: (path: string) => void;
}

export default function DocumentPreview({ filename }: Props) {
export default function DocumentPreview(props: Props) {
const { classes } = useStyles();
const extension = filename.split('.').pop()?.toLocaleLowerCase() || '';
const simpleFilename = filename.split('/').pop() || '';
const canPreview = EXTENSIONS_SUPPORT_PREVIEW.includes(extension);
const canPreview = isDocumentAnImage(props.filename);

const openDocumentModal = () => {
openContextModal({
modal: 'documentPreviewModal',
title: simpleFilename,
size: 'lg',
centered: true,
innerProps: {
filename: simpleFilename,
path: filename,
},
});
};
return (
<Box className={classes.imgBox} onClick={openDocumentModal}>
<Box className={classes.imgBox} onClick={canPreview ? () => props.onClick(props.filename) : undefined}>
{canPreview ? (
<img className={classes.img} alt={filename} src={canPreview ? `${serverBaseUrl}/api/documents/${Buffer.from(filename).toString('base64')}` : ''} />
<img
className={classes.img}
alt={props.filename}
src={canPreview ? `${serverBaseUrl}/api/documents/${Buffer.from(props.filename).toString('base64')}` : ''}
/>
) : (
<Box className={classes.empty}>This document cannot be previewed</Box>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import Section from '../Section';
import DocumentPreview from './DocumentPreview';
import AccountDocumentUpload from '../AccountDocumentUpload';
import { createStyles } from '@mantine/emotion';
import { ImageLightBox } from '../ImageLightBox';
import { useState } from 'react';

const useStyles = createStyles((theme, _, u) => ({
amount: {
Expand All @@ -25,6 +27,8 @@ interface Props {
}

export default function TransactionPreview(props: Props) {
const [lightboxSrc, setLightboxSrc] = useState<string | undefined>(undefined);

const { classes } = useStyles();
return (
<div>
Expand Down Expand Up @@ -137,12 +141,13 @@ export default function TransactionPreview(props: Props) {
</Section>
)}
<Box mx={1} my={4}>
<ImageLightBox src={lightboxSrc} onChange={setLightboxSrc} />
<Section title={`${props.data.metas.filter((meta) => meta.key === 'document').length} Documents`}>
<SimpleGrid cols={{ base: 1, md: 2, lg: 4 }} spacing={{ base: 'sm', md: 'md', sm: 'sm', xs: 'sm' }}>
{props.data.metas
.filter((meta) => meta.key === 'document')
.map((meta, idx) => (
<DocumentPreview key={idx} uri={meta.value} filename={meta.value} />
<DocumentPreview onClick={() => setLightboxSrc(meta.value)} key={idx} uri={meta.value} filename={meta.value} />
))}
<AccountDocumentUpload url={`/api/transactions/${props.data.id}/documents`} />
</SimpleGrid>
Expand Down
11 changes: 0 additions & 11 deletions frontend/src/components/modals/DocumentPreviewModal.tsx

This file was deleted.

2 changes: 0 additions & 2 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import { DocumentPreviewModal } from './components/modals/DocumentPreviewModal';
import { TransactionPreviewModal } from './components/modals/TransactionPreviewModal';
import './i18n';
import { store } from './states';
Expand Down Expand Up @@ -47,7 +46,6 @@ root.render(
<MantineEmotionProvider>
<ModalsProvider
modals={{
documentPreviewModal: DocumentPreviewModal,
transactionPreviewModal: TransactionPreviewModal,
transactionEditModal: TransactionEditModal,
}}
Expand Down
24 changes: 8 additions & 16 deletions frontend/src/pages/Documents.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Container, Group, SegmentedControl, SimpleGrid, Table, Title } from '@mantine/core';
import { useDocumentTitle, useLocalStorage } from '@mantine/hooks';
import { openContextModal } from '@mantine/modals';
import { format } from 'date-fns';
import useSWR from 'swr';
import AccountDocumentLine from '../components/documentLines/AccountDocumentLine';
Expand All @@ -11,30 +10,23 @@ import { groupBy, reverse, sortBy } from 'lodash-es';
import { TextBadge } from '../components/basic/TextBadge';
import { useNavigate } from 'react-router';
import { useAppSelector } from '../states';
import { useState } from 'react';
import 'yet-another-react-lightbox/styles.css';
import { ImageLightBox } from '../components/ImageLightBox';
import { isDocumentAnImage } from '../utils/documents';

export default function Documents() {
let navigate = useNavigate();
const [layout, setLayout] = useLocalStorage({ key: `document-list-layout`, defaultValue: 'Grid' });
const { data: documents, error } = useSWR<Document[]>('/api/documents', fetcher);
const [lightboxSrc, setLightboxSrc] = useState<string | undefined>(undefined);

const ledgerTitle = useAppSelector((state) => state.basic.title ?? 'Zhang Accounting');

useDocumentTitle(`Documents - ${ledgerTitle}`);

if (error) return <div>failed to load</div>;
if (!documents) return <div>loading...</div>;
const openDocumentPreviewModal = (filename: string, path: string) => {
openContextModal({
modal: 'documentPreviewModal',
title: filename,
size: 'lg',
centered: true,
innerProps: {
filename: filename,
path: path,
},
});
};

const groupedDocuments = reverse(
sortBy(
Expand All @@ -48,7 +40,7 @@ export default function Documents() {
<Heading title={`${documents.length} Documents`}></Heading>
<SegmentedControl value={layout} onChange={setLayout} data={['Grid', 'Table']} />
</Group>

<ImageLightBox src={lightboxSrc} onChange={setLightboxSrc} />
{layout === 'Grid' ? (
<>
{groupedDocuments.map((targetMonthDocuments, idx) => (
Expand All @@ -58,7 +50,7 @@ export default function Documents() {
</Title>
<SimpleGrid key={`grid=${idx}`} cols={{ base: 1, sm: 2, md: 4 }} spacing={{ base: 'ms', md: 'md', lg: 'lg' }}>
{targetMonthDocuments.map((document, idx) => (
<AccountDocumentLine key={idx} {...document} />
<AccountDocumentLine onClick={setLightboxSrc} key={idx} {...document} />
))}
</SimpleGrid>
</>
Expand All @@ -77,7 +69,7 @@ export default function Documents() {
<tbody>
{documents.map((document, idx) => (
<Table.Tr>
<Table.Td onClick={() => openDocumentPreviewModal(document.filename, document.path)}>
<Table.Td onClick={isDocumentAnImage(document.path) ? () => setLightboxSrc(document.path) : undefined}>
<div>{document.filename}</div>
</Table.Td>
<Table.Td>
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/pages/SingleAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { useAppSelector } from '../states';
import { useDocumentTitle } from '@mantine/hooks';
import { createStyles } from '@mantine/emotion';
import { AccountBalanceHistoryGraph } from '../components/AccountBalanceHistoryGraph';
import { useState } from 'react';
import { ImageLightBox } from '../components/ImageLightBox';

const useStyles = createStyles((theme, _) => ({
calculatedAmount: {
Expand All @@ -30,6 +32,8 @@ function SingleAccount() {
let { accountName } = useParams();
const { classes } = useStyles();

const [lightboxSrc, setLightboxSrc] = useState<string | undefined>(undefined);

const { data: account, error } = useSWR<AccountInfo>(`/api/accounts/${accountName}`, fetcher);
const { data: account_balance_data, error: account_balance_error } = useSWR<AccountBalanceHistory>(`/api/accounts/${accountName}/balances`, fetcher);

Expand Down Expand Up @@ -127,10 +131,11 @@ function SingleAccount() {
skeleton={<div>loading</div>}
render={(data: Document[]) => (
<>
<ImageLightBox src={lightboxSrc} onChange={setLightboxSrc} />
<SimpleGrid cols={{ base: 4, md: 3, sm: 2, xs: 1 }} spacing={{ base: 'sm', md: 'md', sm: 'sm' }}>
<AccountDocumentUpload url={`/api/accounts/${accountName}/documents`} />
{data.map((document, idx) => (
<DocumentPreview key={idx} uri={document.path} filename={document.path} />
<DocumentPreview onClick={(path) => setLightboxSrc(path)} key={idx} uri={document.path} filename={document.path} />
))}
</SimpleGrid>
</>
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/utils/documents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const EXTENSIONS_SUPPORT_PREVIEW = ['png', 'jpg', 'jpeg', 'gif'];

export function isDocumentAnImage(path: string): boolean {
const extension = path.split('.').pop()?.toLocaleLowerCase() || '';
return EXTENSIONS_SUPPORT_PREVIEW.includes(extension);
}
Loading