From 15bfdac718e2282b592862f2942ee425945ca7a3 Mon Sep 17 00:00:00 2001 From: knguyenrise8 <159168836+knguyenrise8@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:15:45 -0600 Subject: [PATCH] fix(RV-396): fix UX flows for annotations and extraction (#411) --- .../src/components/ExtractUploadFile.scss | 5 - frontend/src/components/ExtractUploadFile.tsx | 30 ++-- frontend/src/components/FileInput.scss | 33 ++++ frontend/src/components/FileInput.tsx | 17 +- .../src/components/FileInput/file-input.tsx | 1 + frontend/src/components/FilePreview.scss | 8 + frontend/src/components/FilePreview.tsx | 46 +----- frontend/src/components/Header.scss | 18 +++ frontend/src/components/Header.tsx | 17 +- frontend/src/components/ImageAnnotator.tsx | 15 +- frontend/src/components/PageNumber.scss | 25 +++ frontend/src/components/PageNumber.tsx | 25 +++ frontend/src/components/ReviewTable.scss | 3 + frontend/src/components/ReviewTable.tsx | 4 +- frontend/src/components/Toolbar.scss | 58 +++++++ frontend/src/components/Toolbar.tsx | 43 +++++ frontend/src/components/UploadFile.scss | 35 ++++ frontend/src/components/UploadFile.tsx | 46 ++++-- .../src/components/tests/FilePreview.test.tsx | 135 ---------------- frontend/src/components/tests/Header.test.tsx | 8 - .../src/components/tests/PageNumber.test.tsx | 61 +++++++ .../src/components/tests/Toolbar.test.tsx | 56 +++++++ .../src/components/tests/UploadFile.test.tsx | 19 +-- frontend/src/pages/AnnotateTemplate.scss | 1 + frontend/src/pages/AnnotateTemplate.tsx | 12 +- frontend/src/pages/ExtractProcess.tsx | 11 +- frontend/src/pages/ExtractUpload.tsx | 8 +- frontend/src/pages/SaveTemplate.tsx | 4 +- frontend/src/pages/UploadTemplate.tsx | 4 +- .../UploadTemplate.test.tsx.snap | 152 ++++++++---------- 30 files changed, 539 insertions(+), 361 deletions(-) create mode 100644 frontend/src/components/FileInput.scss create mode 100644 frontend/src/components/FilePreview.scss create mode 100644 frontend/src/components/Header.scss create mode 100644 frontend/src/components/PageNumber.scss create mode 100644 frontend/src/components/PageNumber.tsx create mode 100644 frontend/src/components/ReviewTable.scss create mode 100644 frontend/src/components/Toolbar.scss create mode 100644 frontend/src/components/Toolbar.tsx create mode 100644 frontend/src/components/UploadFile.scss delete mode 100644 frontend/src/components/tests/FilePreview.test.tsx create mode 100644 frontend/src/components/tests/PageNumber.test.tsx create mode 100644 frontend/src/components/tests/Toolbar.test.tsx diff --git a/frontend/src/components/ExtractUploadFile.scss b/frontend/src/components/ExtractUploadFile.scss index fdaedea2..a39806ef 100644 --- a/frontend/src/components/ExtractUploadFile.scss +++ b/frontend/src/components/ExtractUploadFile.scss @@ -8,10 +8,6 @@ margin-top: 16px; } -.extract-file-input { - border: 1px dashed #005ea2; -} - .usa-file-input__target { border: none; } @@ -34,7 +30,6 @@ p { .dashed-container { width: 100%; - height: 50%; border: 1px dashed #A9AEB1; } diff --git a/frontend/src/components/ExtractUploadFile.tsx b/frontend/src/components/ExtractUploadFile.tsx index be6b6dce..0c7f25bb 100644 --- a/frontend/src/components/ExtractUploadFile.tsx +++ b/frontend/src/components/ExtractUploadFile.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, useEffect, useId, useState } from "react"; +import React, { ChangeEvent, useId, useState } from "react"; import { Button, Select } from "@trussworks/react-uswds"; import { useFiles } from "../contexts/FilesContext"; import { useNavigate } from "react-router-dom"; @@ -24,20 +24,20 @@ interface IFilesObj { files: File[]; } -interface Template { - name: string; - description: string; - pages: { - image: string; - fieldNames: string[]; - }[]; -} +// interface Template { +// name: string; +// description: string; +// pages: { +// image: string; +// fieldNames: string[]; +// }[]; +// } export const ExtractUploadFile: React.FC = ({ onUploadComplete, }) => { const id = useId(); - const { addFile, clearFiles, files, setSelectedTemplates, selectedTemplates } = useFiles(); + const { addFile, files, setSelectedTemplates, selectedTemplates , clearTemplates} = useFiles(); const navigate = useNavigate(); // const [templates, setTemplates] = useState([]); const templates = useQuery( @@ -104,6 +104,12 @@ export const ExtractUploadFile: React.FC = ({ const handleSelect = (templateName: string, fileName: string, index: number) => { setSelectedTemplates({ templateName, fileName }, index); } + + const onCancel = () => { + navigate('/'); + clearTemplates(); + } + return (
@@ -217,14 +223,14 @@ export const ExtractUploadFile: React.FC = ({ type="button" outline className="margin-right-1" - onClick={() => navigate('/')} + onClick={onCancel} > Cancel Import

{title}

- - + + {!isUpload && }
diff --git a/frontend/src/components/ImageAnnotator.tsx b/frontend/src/components/ImageAnnotator.tsx index b9cf6177..ec1d14fc 100644 --- a/frontend/src/components/ImageAnnotator.tsx +++ b/frontend/src/components/ImageAnnotator.tsx @@ -1,4 +1,3 @@ -import { Button } from '@trussworks/react-uswds'; import { FC, useEffect } from 'react'; import { ImageAnnotator, Shape } from 'react-image-label'; import { useAnnotationContext } from '../contexts/AnnotationContext'; @@ -11,14 +10,11 @@ interface MultiImageAnnotatorProps { } export const MultiImageAnnotator: FC = ({ images, initialShapes = [[]] }) => { - const {selectedField, setHandles, annotator, shapes, setShapes, index, setIndex, setDrawnFields, drawnFields, setSelectedField} = useAnnotationContext(); + const {selectedField, setHandles, annotator, shapes, setShapes, index, setDrawnFields, drawnFields, setSelectedField} = useAnnotationContext(); - const handleImageChange = (index: number) => { - setIndex(index); - }; const handleShapeAddition = (shape: Shape) => { const fields = [...LABELS.patientInformation.items, ...LABELS.organizationInformation.items]; @@ -46,14 +42,7 @@ export const MultiImageAnnotator: FC = ({ images, init getShapes(); }, []) return ( -
-
- {images.map((_, index) => ( - - ))} -
+
void; + onNext: () => void; +} + +const PageNumber: React.FC = ({ currentPage, totalPages, onPrevious, onNext }) => { + return ( +
+ Page + + + of {totalPages} + +
+ ); +}; + +export default PageNumber; \ No newline at end of file diff --git a/frontend/src/components/ReviewTable.scss b/frontend/src/components/ReviewTable.scss new file mode 100644 index 00000000..50a37251 --- /dev/null +++ b/frontend/src/components/ReviewTable.scss @@ -0,0 +1,3 @@ +.table-container-half { + background-color: white; +} \ No newline at end of file diff --git a/frontend/src/components/ReviewTable.tsx b/frontend/src/components/ReviewTable.tsx index 030c4ced..8977653c 100644 --- a/frontend/src/components/ReviewTable.tsx +++ b/frontend/src/components/ReviewTable.tsx @@ -9,6 +9,8 @@ import aiIconUrl from "../assets/ai_icon.svg"; import ErrorIcon from "../assets/error_icon.svg"; import { useNavigate } from "react-router-dom" +import './ReviewTable.scss'; + interface ReviewTableProps { isSingle: boolean; index: number; @@ -60,7 +62,7 @@ const ReviewTable = ({ isSingle, hasErrors, overallConfidence, confidenceVal, er
-
+

Extracted Data

diff --git a/frontend/src/components/Toolbar.scss b/frontend/src/components/Toolbar.scss new file mode 100644 index 00000000..9a2ca4f7 --- /dev/null +++ b/frontend/src/components/Toolbar.scss @@ -0,0 +1,58 @@ +.toolbar { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + border-bottom: 1px solid #ccc; + background-color: white; + min-height: 72px; + + &__text { + margin: 0 8px; + color: #555; + } + + &__button { + margin: 0 4px; + padding: 4px 8px; + cursor: pointer; + border: 1px solid #ccc; + background: #fff; + border-radius: 4px; + } + + &__input { + width: 30px; + text-align: center; + border: 1px solid #ccc; + border-radius: 4px; + margin: 0 4px; + } + + &__vector-button { + margin: 0 12px; + padding: 4px 12px; + background: #8a2be2; // Purple color + color: #fff; + border: none; + border-radius: 12px; + cursor: pointer; + } + + &__profile-icon { + margin-left: auto; + width: 24px; + height: 24px; + background-color: #4caf50; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + } + + &__profile-initial { + color: #fff; + font-weight: bold; + } + } + \ No newline at end of file diff --git a/frontend/src/components/Toolbar.tsx b/frontend/src/components/Toolbar.tsx new file mode 100644 index 00000000..9c747cf9 --- /dev/null +++ b/frontend/src/components/Toolbar.tsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; +import PageNumber from './PageNumber'; + +import './Toolbar.scss'; + +interface ToolbarProps { + initialPage?: number; + totalPages: number; + onPageChange?: (page: number) => void; +} + +const Toolbar: React.FC = ({ initialPage = 1, totalPages, onPageChange }) => { + const [currentPage, setCurrentPage] = useState(initialPage); + + const handlePrevious = () => { + if (currentPage > 1) { + setCurrentPage(currentPage - 1); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + onPageChange && onPageChange(currentPage - 1); + } + }; + + const handleNext = () => { + if (currentPage < totalPages) { + setCurrentPage(currentPage + 1); + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + onPageChange && onPageChange(currentPage + 1); + } + }; + + return ( +
+ +
+ ); +}; + +export default Toolbar; \ No newline at end of file diff --git a/frontend/src/components/UploadFile.scss b/frontend/src/components/UploadFile.scss new file mode 100644 index 00000000..f0880118 --- /dev/null +++ b/frontend/src/components/UploadFile.scss @@ -0,0 +1,35 @@ +.upload-file-header { + margin-top: 62px; +} + +.upload-file-header-content-container { + width: 70%; + text-align: left; +} + +.upload-file-instructions { + color: #71767A; +} + +.dashed-container { + width: '70%'; + border: '1px dashed #005ea2'; + margin-bottom: 62px; +} + +.dashed-container-preupload { + height: 200px; +} + +.dashed-container-upload { + height: 160px; +} + +.upload-back-button { + height: 44px; + background-color: white; +} + +.upload-annotate-button { + height: 44px; +} \ No newline at end of file diff --git a/frontend/src/components/UploadFile.tsx b/frontend/src/components/UploadFile.tsx index 1d8c86cb..5ed05261 100644 --- a/frontend/src/components/UploadFile.tsx +++ b/frontend/src/components/UploadFile.tsx @@ -1,17 +1,20 @@ -import { Icon } from "@trussworks/react-uswds" -import { ChangeEvent, useId } from "react" +import { Button } from "@trussworks/react-uswds" +import { ChangeEvent, useEffect, useId } from "react" import { FileInput } from "./FileInput" import { useFiles } from "../contexts/FilesContext" import { useNavigate } from "react-router-dom" +import './UploadFile.scss'; + interface IFilesObj { files: File[] } export const Uploadfile = () => { const id = useId() - const {addFile} = useFiles() + const { addFile, files, clearFiles } = useFiles() const navigate = useNavigate() + async function handleChange(event: ChangeEvent) { if (event.target.files && event.target?.files?.length > 0) { @@ -23,22 +26,37 @@ export const Uploadfile = () => { } localStorage.setItem('files', JSON.stringify(filesObj)) addFile(event.target?.files[0]) - navigate('/new-template/annotate') } } + const onBack = () => { + navigate('/new-template') + clearFiles(); + } + + const onSubmit = () => { + navigate('/new-template/annotate') + } + + useEffect(() => { + return () => clearFiles(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + return (
-
-

Upload new form to segment

-

Upload new image or PDF and save as a template

-
-
- -
-

Drag and drop file here

-

or

- +
+

Upload new file to annotate

+

You can upload new lab files (PDF s or images) to annotate and save as a template. Annotation is the process of marking and labeling specific areas of an image to identify key elements or features.

+

Select one file

+
+
+ +
+
+
+ +
diff --git a/frontend/src/components/tests/FilePreview.test.tsx b/frontend/src/components/tests/FilePreview.test.tsx deleted file mode 100644 index aff1c4d9..00000000 --- a/frontend/src/components/tests/FilePreview.test.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { fireEvent, render, waitFor } from "@testing-library/react"; - -import { FilePreview } from "../FilePreview"; - -export const SPACER_GIF = - "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; - -// Test files -export const TEST_TEXT_FILE = new File(["Test File Contents"], "testFile.txt", { - type: "text/plain", -}); - -export const TEST_PDF_FILE = new File(["Test PDF File"], "testFile.pdf", { - type: "application/pdf", -}); - -export const TEST_DOC_FILE = new File(["Test doc File"], "testFile.doc", { - type: "application/msword", -}); - -export const TEST_XLS_FILE = new File(["Test xls File"], "testFile.xls", { - type: "application/vnd.ms-excel", -}); - -export const TEST_VIDEO_FILE = new File(["Test video File"], "testFile.mp4", { - type: "video/mp4", -}); - -export const TEST_PNG_FILE = new File(["Test PNG Image"], "testFile.png", { - type: "image/png", -}); - -describe("FilePreview component", () => { - const testProps = { - imageId: "testImageId_12345", - file: TEST_TEXT_FILE, - }; - - it("renders without errors", async () => { - const { getByTestId } = await waitFor(() => - render() - ); - expect(getByTestId("file-input-preview")).toBeInTheDocument(); - expect(getByTestId("file-input-preview")).toHaveClass( - "usa-file-input__preview" - ); - expect(getByTestId("file-input-preview")).toHaveAttribute( - "aria-hidden", - "true" - ); - expect(getByTestId("file-input-preview")).toHaveTextContent( - testProps.file.name - ); - }); - - it("renders a preview image", async () => { - const { getByTestId } = await waitFor(() => - render() - ); - const imageEl = getByTestId("file-input-preview-image"); - expect(imageEl).toBeInstanceOf(HTMLImageElement); - expect(imageEl).toHaveAttribute("id", testProps.imageId); - expect(imageEl).toHaveClass("usa-file-input__preview-image"); - }); - - describe("while the file is loading", () => { - it("renders a loading image", async () => { - const spy = vi.spyOn(window.FileReader.prototype, "readAsDataURL"); - let fr: FileReader | undefined, blob: Blob | undefined; - // Prevent loadend event from being dispatched until we're ready. - // Grab the blob and FileReader instance when called. - spy.mockImplementationOnce(function (this: FileReader, _blob) { - blob = _blob; - // eslint-disable-next-line @typescript-eslint/no-this-alias - fr = this; - }); - const { getByTestId } = await waitFor(() => - render() - ); - const imageEl = getByTestId("file-input-preview-image"); - - expect(imageEl).toHaveClass("is-loading"); - expect(imageEl).toHaveAttribute("src", SPACER_GIF); - - // Call real `readAsDataURL` with blob to dispatch loadend - expect(fr).toBeDefined(); - expect(blob).toBeDefined(); - fr!.readAsDataURL(blob!); - - await waitFor(() => expect(imageEl).not.toHaveClass("is-loading")); - expect(imageEl).not.toHaveAttribute("src", SPACER_GIF); - }); - }); - - describe("when the file is done loading", () => { - it("renders the file preview image and removes the loading class", async () => { - const { getByTestId } = await waitFor(() => - render() - ); - - const expectedSrc = "data:text/plain;base64,VGVzdCBGaWxlIENvbnRlbnRz"; - - const imageEl = getByTestId("file-input-preview-image"); - await waitFor(() => expect(imageEl).not.toHaveClass("is-loading")); - expect(imageEl).toHaveAttribute("src", expectedSrc); - }); - - describe.each([ - { type: "pdf", exts: ["pdf"] }, - { type: "word", exts: ["doc", "pages"] }, - { type: "video", exts: ["mp4", "mov"] }, - { type: "excel", exts: ["xls", "numbers"] }, - { type: "generic", exts: ["dat"] }, - ])("for a $type file", ({ type, exts }) => { - describe.each(exts)("using extension: %s", (ext) => { - it(`shows the ${type} ${ - type !== "generic" ? "generic" : "" - } preview`, async () => { - const testFile = new File([], `testFile.${ext}`); - const { getByTestId } = await waitFor(() => - render() - ); - - const imageEl = getByTestId("file-input-preview-image"); - await waitFor(() => expect(imageEl).not.toHaveClass("is-loading")); - fireEvent.error(imageEl); - await waitFor(() => - expect(imageEl).toHaveAttribute("src", SPACER_GIF) - ); - expect(imageEl).toHaveClass(`usa-file-input__preview-image--${type}`); - }); - }); - }); - }); -}); diff --git a/frontend/src/components/tests/Header.test.tsx b/frontend/src/components/tests/Header.test.tsx index a4a7b6e4..3e67d303 100644 --- a/frontend/src/components/tests/Header.test.tsx +++ b/frontend/src/components/tests/Header.test.tsx @@ -11,7 +11,6 @@ describe('UploadHeader component', () => { // Check for the main header element const header = screen.getByRole('banner'); expect(header).toBeInTheDocument(); - expect(header).toHaveStyle({ height: '50px', padding: '8px' }); // Check for the Close button with the icon const closeButton = screen.getByTestId('close-button'); @@ -24,17 +23,10 @@ describe('UploadHeader component', () => { // Check for the Back button const backButton = screen.getByRole('button', { name: /back/i }); expect(backButton).toBeInTheDocument(); - expect(backButton).toHaveStyle({ - height: '40px', - color: '#adadad', - }); // Check for the Submit button const submitButton = screen.getByRole('button', { name: /submit/i }); expect(submitButton).toBeInTheDocument(); - expect(submitButton).toHaveStyle({ - height: '40px', - }); }); it('renders all buttons with correct text', () => { diff --git a/frontend/src/components/tests/PageNumber.test.tsx b/frontend/src/components/tests/PageNumber.test.tsx new file mode 100644 index 00000000..b90a178b --- /dev/null +++ b/frontend/src/components/tests/PageNumber.test.tsx @@ -0,0 +1,61 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { describe, it, expect, vi } from 'vitest'; +import PageNumber from '../PageNumber'; + +describe('PageNumber component', () => { + const onPrevious = vi.fn(); + const onNext = vi.fn(); + + const renderComponent = (currentPage: number, totalPages: number) => { + render( + + ); + }; + + it('renders the component with initial props', () => { + renderComponent(1, 5); + + expect(screen.getByTestId('page-number')).toBeInTheDocument(); + expect(screen.getByTestId('page-number-text')).toHaveTextContent('Page'); + expect(screen.getByTestId('page-number-input')).toHaveValue('1'); + expect(screen.getByTestId('page-number-total')).toHaveTextContent('of 5'); + }); + + it('calls onPrevious when the previous button is clicked', () => { + renderComponent(2, 5); + + const previousButton = screen.getByTestId('page-number-previous'); + fireEvent.click(previousButton); + + expect(onPrevious).toHaveBeenCalled(); + }); + + it('calls onNext when the next button is clicked', () => { + renderComponent(1, 5); + + const nextButton = screen.getByTestId('page-number-next'); + fireEvent.click(nextButton); + + expect(onNext).toHaveBeenCalled(); + }); + + it('disables the previous button on the first page', () => { + renderComponent(1, 5); + + const previousButton = screen.getByTestId('page-number-previous'); + expect(previousButton).toBeDisabled(); + }); + + it('disables the next button on the last page', () => { + renderComponent(5, 5); + + const nextButton = screen.getByTestId('page-number-next'); + expect(nextButton).toBeDisabled(); + }); +}); \ No newline at end of file diff --git a/frontend/src/components/tests/Toolbar.test.tsx b/frontend/src/components/tests/Toolbar.test.tsx new file mode 100644 index 00000000..7d272526 --- /dev/null +++ b/frontend/src/components/tests/Toolbar.test.tsx @@ -0,0 +1,56 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { describe, it, expect } from 'vitest'; +import Toolbar from '../Toolbar'; + +describe('Toolbar component', () => { + const renderComponent = (initialPage: number, totalPages: number) => { + render( + + ); + }; + + it('renders the component with initial props', () => { + renderComponent(1, 5); + + expect(screen.getByTestId('toolbar')).toBeInTheDocument(); + expect(screen.getByTestId('page-number')).toBeInTheDocument(); + expect(screen.getByTestId('page-number-input')).toHaveValue('1'); + expect(screen.getByTestId('page-number-total')).toHaveTextContent('of 5'); + }); + + it('calls handlePrevious when the previous button is clicked', () => { + renderComponent(2, 5); + + const previousButton = screen.getByTestId('page-number-previous'); + fireEvent.click(previousButton); + + expect(screen.getByTestId('page-number-input')).toHaveValue('1'); + }); + + it('calls handleNext when the next button is clicked', () => { + renderComponent(1, 5); + + const nextButton = screen.getByTestId('page-number-next'); + fireEvent.click(nextButton); + + expect(screen.getByTestId('page-number-input')).toHaveValue('2'); + }); + + it('disables the previous button on the first page', () => { + renderComponent(1, 5); + + const previousButton = screen.getByTestId('page-number-previous'); + expect(previousButton).toBeDisabled(); + }); + + it('disables the next button on the last page', () => { + renderComponent(5, 5); + + const nextButton = screen.getByTestId('page-number-next'); + expect(nextButton).toBeDisabled(); + }); +}); \ No newline at end of file diff --git a/frontend/src/components/tests/UploadFile.test.tsx b/frontend/src/components/tests/UploadFile.test.tsx index 9e9ac311..5fddbbe8 100644 --- a/frontend/src/components/tests/UploadFile.test.tsx +++ b/frontend/src/components/tests/UploadFile.test.tsx @@ -13,14 +13,8 @@ describe('Uploadfile component', () => { render(, {wrapper: Wrapper}); // Check for the main heading - const mainHeading = screen.getByRole('heading', { name: /upload new form to segment/i }); + const mainHeading = screen.getByRole('heading', { name: /Upload new file to annotate/i }); expect(mainHeading).toBeInTheDocument(); - expect(mainHeading).toHaveStyle({ margin: '0' }); - - // Check for the subheading - const subHeading = screen.getByRole('heading', { name: /upload new image or pdf and save as a template/i }); - expect(subHeading).toBeInTheDocument(); - expect(subHeading).toHaveStyle({ margin: '0' }); }); it('renders the upload area with correct structure', () => { @@ -29,20 +23,11 @@ describe('Uploadfile component', () => { // Check for the dashed border container const dashedContainer = screen.getByTestId('dashed-container'); expect(dashedContainer).toBeInTheDocument(); - expect(dashedContainer).toHaveStyle({ - width: '70%', - height: '40%', - border: '1px dashed #005ea2', - }); - // Check for the icon - const uploadIcon = screen.getByTestId('upload-icon'); - expect(uploadIcon).toBeInTheDocument(); // Check for the "Drag and drop file here" text - const dragDropText = screen.getByRole('heading', { name: /drag and drop file here/i }); + const dragDropText = screen.getByText(/drag files here or/i); expect(dragDropText).toBeInTheDocument(); - expect(dragDropText).toHaveStyle({ fontWeight: 'bold' }); // Check for the FileInput const fileInput = screen.getByTestId('file-input-input'); diff --git a/frontend/src/pages/AnnotateTemplate.scss b/frontend/src/pages/AnnotateTemplate.scss index 5372cfc7..f6522d07 100644 --- a/frontend/src/pages/AnnotateTemplate.scss +++ b/frontend/src/pages/AnnotateTemplate.scss @@ -28,6 +28,7 @@ li:hover { border-right: 1px solid #EAEAEA; border-left: 1px solid #EAEAEA; border-bottom: 1px solid #EAEAEA; + background-color: white; } diff --git a/frontend/src/pages/AnnotateTemplate.tsx b/frontend/src/pages/AnnotateTemplate.tsx index f2d788da..cb80b22a 100644 --- a/frontend/src/pages/AnnotateTemplate.tsx +++ b/frontend/src/pages/AnnotateTemplate.tsx @@ -13,6 +13,7 @@ import { Icon } from "@trussworks/react-uswds"; import { useEffect, useState } from "react"; import { useAnnotationContext } from "../contexts/AnnotationContext.tsx"; import CheckIcon from '../assets/check.svg'; +import Toolbar from "../components/Toolbar.tsx"; import "./AnnotateTemplate.scss"; @@ -173,9 +174,9 @@ const AnnotateTemplate: React.FC = () => { console.error("Error taking screenshot", err); } }; - + return ( -
+
navigate("/new-template/upload")} @@ -186,8 +187,8 @@ const AnnotateTemplate: React.FC = () => {
-
-
+
+

Segment and label

Segmenting means selecting and highlighting a specific part of an image. After segmenting, link it to the correct label that describes what the segment represents. @@ -198,8 +199,9 @@ const AnnotateTemplate: React.FC = () => {

+ {Array.isArray(images) && images.length > 0 && setIndex(page - 1)} />} {Array.isArray(images) && images.length > 0 ? ( img.image)} categories={[]} /> ) : ( diff --git a/frontend/src/pages/ExtractProcess.tsx b/frontend/src/pages/ExtractProcess.tsx index f8d5547d..c1633941 100644 --- a/frontend/src/pages/ExtractProcess.tsx +++ b/frontend/src/pages/ExtractProcess.tsx @@ -67,16 +67,16 @@ const ExtractProcess = () => { if(response.name in arrResults) { Object.keys(response.resp as ImageToTextResponse).forEach(key => { arrResults[response.name][key] = { - text: response.resp[key][0], - confidence: response.resp[key][1], + text: response.resp ? response.resp[key][0] : '', + confidence: response.resp ? response.resp[key][1] : 0, }; }); } else { arrResults[response.name] = {} Object.keys(response.resp as ImageToTextResponse).forEach(key => { arrResults[response.name][key] = { - text: response.resp[key][0], - confidence: response.resp[key][1], + text: response.resp ? response.resp[key][0] : '', + confidence: response.resp ? response.resp[key][1] : 0, }; }); } @@ -105,6 +105,7 @@ const ExtractProcess = () => { setIsLoading(false); navigate("/extract/upload"); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [navigate]) useEffect(() => { @@ -126,7 +127,7 @@ const ExtractProcess = () => { return ( -
+
navigate("extract/upload")} onSubmit={handleSubmit} diff --git a/frontend/src/pages/ExtractUpload.tsx b/frontend/src/pages/ExtractUpload.tsx index 59c0f490..9a350d25 100644 --- a/frontend/src/pages/ExtractUpload.tsx +++ b/frontend/src/pages/ExtractUpload.tsx @@ -12,14 +12,14 @@ import "./ExtractUpload.scss"; const ExtractUpload = () => { const navigate = useNavigate(); - const { files } = useFiles(); + const { files, clearFiles } = useFiles(); const [currentIndex, setCurrentIndex] = useState(1); const [isUploadComplete, setIsUploadComplete] = useState(false); const handleUploadComplete = (isComplete: boolean) => { setIsUploadComplete(isComplete); }; - + useEffect(() => { if (files.length > 0) { setCurrentIndex(1); @@ -33,8 +33,8 @@ const ExtractUpload = () => { return (
navigate("/")} - onExit={() => navigate("/")} + onBack={() => {navigate("/"); clearFiles();}} + onExit={() => {navigate("/"); clearFiles();}} onSubmit={() => navigate("/extract/process")} isUploadComplete={isUploadComplete} /> diff --git a/frontend/src/pages/SaveTemplate.tsx b/frontend/src/pages/SaveTemplate.tsx index 53f078d3..9f146353 100644 --- a/frontend/src/pages/SaveTemplate.tsx +++ b/frontend/src/pages/SaveTemplate.tsx @@ -5,7 +5,7 @@ import { Stepper } from "../components/Stepper"; import { AnnotateStep } from "../utils/constants"; import { useNavigate } from "react-router-dom"; import { useAnnotationContext } from "../contexts/AnnotationContext"; -import { useFiles, FileType, Page } from "../contexts/FilesContext"; +import { FileType, Page } from "../contexts/FilesContext"; import hexRgb from "hex-rgb"; import { ImageData } from "./AnnotateTemplate"; import { makeScreenshots } from "../utils/functions"; @@ -15,7 +15,6 @@ import { makeScreenshots } from "../utils/functions"; export const SaveTemplate = () => { const navigate = useNavigate(); const { fields, setDescription, setName, name, description, shapes, setShapes, setFields, setDrawnFields, setSelectedField } = useAnnotationContext() - const { addFile } = useFiles(); const handleSubmit = async () => { const images: ImageData[] = localStorage.getItem('images') ? JSON.parse(localStorage.getItem('images') as string) : []; @@ -59,7 +58,6 @@ export const SaveTemplate = () => { console.error("Invalid information found in templates, it will be overwritten") } localStorage.setItem('templates', JSON.stringify([...existingTemplates, tempFile])) - addFile(tempFile) } setShapes([]); diff --git a/frontend/src/pages/UploadTemplate.tsx b/frontend/src/pages/UploadTemplate.tsx index 1622a045..63639ca0 100644 --- a/frontend/src/pages/UploadTemplate.tsx +++ b/frontend/src/pages/UploadTemplate.tsx @@ -4,12 +4,14 @@ import { UploadHeader } from "../components/Header" import { Stepper } from "../components/Stepper" import { Uploadfile } from "../components/UploadFile" import { AnnotateStep } from "../utils/constants" +import { useFiles } from "../contexts/FilesContext" export const UploadTemplate = () => { const navigate = useNavigate(); + const { files } = useFiles(); return (
- {}} onSubmit={() => navigate('/new-template/annotate')} /> + {navigate('/')}} onSubmit={() => navigate('/new-template/annotate')} isUpload={files.length === 0} />
diff --git a/frontend/src/pages/__snapshots__/UploadTemplate.test.tsx.snap b/frontend/src/pages/__snapshots__/UploadTemplate.test.tsx.snap index acd2c50a..ae36bf1f 100644 --- a/frontend/src/pages/__snapshots__/UploadTemplate.test.tsx.snap +++ b/frontend/src/pages/__snapshots__/UploadTemplate.test.tsx.snap @@ -6,22 +6,18 @@ exports[`UploadTemplate component > matches the snapshot 1`] = ` style="display: flex; flex-direction: column; justify-content: start; width: 100%; height: 100%;" >
@@ -163,91 +150,86 @@ exports[`UploadTemplate component > matches the snapshot 1`] = ` class="display-flex flex-column flex-align-center flex-justify-start height-full width-full padding-2 bg-primary-lighter" >

- Upload new form to segment + Upload new file to annotate

-

- Upload new image or PDF and save as a template -

-
-
- + You can upload new lab files (PDF s or images) to annotate and save as a template. Annotation is the process of marking and labeling specific areas of an image to identify key elements or features. +

+

- - + Select one file +

-

- Drag and drop file here -

-

- or -

+
+
-
-
+
+ + +