Skip to content

Commit

Permalink
Replace local storage with zustand for new template flow (#420)
Browse files Browse the repository at this point in the history
  • Loading branch information
schreiaj authored Nov 25, 2024
1 parent 2ff6aa4 commit 3f8cc8e
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 58 deletions.
37 changes: 33 additions & 4 deletions frontend/package-lock.json

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

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-image-label": "^1.3.4",
"react-router-dom": "^6.26.0"
"react-router-dom": "^6.26.0",
"zustand": "^5.0.1"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/components/ImageAnnotator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FC, useEffect } from 'react';
import { ImageAnnotator, Shape } from 'react-image-label';
import { useAnnotationContext } from '../contexts/AnnotationContext';
import { LABELS } from '../constants/labels';
import {useCreateTemplateStore} from "../types/templates.ts";

interface MultiImageAnnotatorProps {
images: string[];
Expand All @@ -11,7 +12,8 @@ interface MultiImageAnnotatorProps {

export const MultiImageAnnotator: FC<MultiImageAnnotatorProps> = ({ images, initialShapes = [[]] }) => {
const {selectedField, setHandles, annotator, shapes, setShapes, index, setDrawnFields, drawnFields, setSelectedField} = useAnnotationContext();

const storeShapes = useCreateTemplateStore((state) => state.setShapes);
const getStoredShapes = useCreateTemplateStore((state) => state.shapes);



Expand All @@ -24,7 +26,8 @@ export const MultiImageAnnotator: FC<MultiImageAnnotatorProps> = ({ images, init
// for field?.color.slice(0,7) to remove the alpha channel from the hexcode
updatedShapes[index] = [...(updatedShapes[index] || []), {...shape, field: selectedField?.name as string, color: field?.color, id: tempFieldsSet.size}];
setShapes(updatedShapes);
localStorage.setItem('shapes', JSON.stringify(updatedShapes));
setShapes(updatedShapes);
storeShapes(updatedShapes);
annotator?.updateCategories(shape.id, [], `${field?.color}4D`);
setDrawnFields(tempFieldsSet);
setSelectedField(null);
Expand All @@ -35,9 +38,8 @@ export const MultiImageAnnotator: FC<MultiImageAnnotatorProps> = ({ images, init
annotator?.edit(shape.id);
};
useEffect(() => {
const getShapes = async () => {
const localStorageShapes = await JSON.parse(localStorage.getItem('shapes') || '[]') as unknown as Array<Array<Shape>> || [];
setShapes(localStorageShapes.length > 0 ? localStorageShapes : initialShapes);
const getShapes = () => {
setShapes(getStoredShapes);
}
getShapes();
}, [])
Expand Down
15 changes: 10 additions & 5 deletions frontend/src/pages/AnnotateTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import CheckIcon from '../assets/check.svg';
import Toolbar from "../components/Toolbar.tsx";

import "./AnnotateTemplate.scss";
import {useMutation, useQuery} from "@tanstack/react-query";
import {TemplateAPI, useCreateTemplateStore} from "../types/templates.ts";

interface AccordionItemProps {
title: React.ReactNode | string;
Expand Down Expand Up @@ -48,6 +50,9 @@ export interface ImageData {

const AnnotateTemplate: React.FC = () => {
const [images, setImages] = useState<ImageData[]>([]);
const storeImages = useCreateTemplateStore((state) => state.setBaseImages);
const baseImages = useCreateTemplateStore((state) => state.baseImages);

const [localIds, setLocalIds] = useState<Map<string, string>>(new Map());
const navigate = useNavigate();
const { files } = useFiles();
Expand Down Expand Up @@ -94,24 +99,24 @@ const AnnotateTemplate: React.FC = () => {
};

convertPdfToImages(pdfFile).then((imgs) => {
// TODO: This is redundant, we should just store the images in the store
setImages(imgs);
localStorage.setItem('images', JSON.stringify(imgs));
storeImages(imgs);
});
}, [files, pdfFile]);
useEffect(() => {
const getImage = async () => {
const localImages = await JSON.parse(
localStorage.getItem('images') || "[]"
);
const localImages = baseImages;
if (localImages && localImages.length > 0) {
setImages(localImages.images);
setImages(localImages);
}
};

const handleUnmount = () => {
setDrawnFields(new Set());
setSelectedField(null);
}
// TODO: Ask Kevin what this is for, it's not clear
getImage();
return () => handleUnmount();
}, [setDrawnFields, setSelectedField]);
Expand Down
82 changes: 47 additions & 35 deletions frontend/src/pages/SaveTemplate.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,49 @@
import { Label, TextInput } from "@trussworks/react-uswds";
import { Divider } from "../components/Divider";
import { UploadHeader } from "../components/Header";
import { Stepper } from "../components/Stepper";
import { AnnotateStep } from "../utils/constants";
import { useNavigate } from "react-router-dom";
import { useAnnotationContext } from "../contexts/AnnotationContext";
import { FileType, Page } from "../contexts/FilesContext";
import {Label, TextInput} from "@trussworks/react-uswds";
import {Divider} from "../components/Divider";
import {UploadHeader} from "../components/Header";
import {Stepper} from "../components/Stepper";
import {AnnotateStep} from "../utils/constants";
import {useNavigate} from "react-router-dom";
import {useAnnotationContext} from "../contexts/AnnotationContext";
import {FileType, Page} from "../contexts/FilesContext";
import hexRgb from "hex-rgb";
import { ImageData } from "./AnnotateTemplate";
import { makeScreenshots } from "../utils/functions";

import {makeScreenshots} from "../utils/functions";
import {useCreateTemplateStore} from "../types/templates.ts";


export const SaveTemplate = () => {
const navigate = useNavigate();
const { fields, setDescription, setName, name, description, shapes, setShapes, setFields, setDrawnFields, setSelectedField } = useAnnotationContext()

const {
fields,
setDescription,
setName,
name,
description,
shapes,
setShapes,
setFields,
setDrawnFields,
setSelectedField
} = useAnnotationContext()
const baseImages = useCreateTemplateStore((state) => state.baseImages);
const storeTemplateImages = useCreateTemplateStore((state) => state.setTemplateImages);
const reset = useCreateTemplateStore((state) => state.reset);

const handleSubmit = async () => {
const images: ImageData[] = localStorage.getItem('images') ? JSON.parse(localStorage.getItem('images') as string) : [];
// const images: ImageData[] = localStorage.getItem('images') ? JSON.parse(localStorage.getItem('images') as string) : [];
const images = baseImages;
let pages: Page[] = [];
const tempFields = fields.filter(field => field.size > 0);

const screenshots = await makeScreenshots()

const screenshots = await makeScreenshots(images, shapes);
storeTemplateImages(screenshots);

if (images.length > 0) {
pages = tempFields.map((_, index) => {
const shape = shapes[index]
return {
fieldNames: shape.map(s => {
const { red, green, blue } = hexRgb(s.color as string);
const {red, green, blue} = hexRgb(s.color as string);
return {
type: 'text',
color: `${red},${green},${blue}`,
Expand All @@ -44,34 +59,31 @@ export const SaveTemplate = () => {
name,
description,
pages: pages

}

let existingTemplates = []
try {
const data = localStorage.getItem('templates');
if (data) {
existingTemplates = JSON.parse(data);
}

} catch {
console.error("Invalid information found in templates, it will be overwritten")
}
localStorage.setItem('templates', JSON.stringify([...existingTemplates, tempFile]))
let existingTemplates = []
try {
const data = localStorage.getItem('templates');
if (data) {
existingTemplates = JSON.parse(data);
}

} catch {
console.error("Invalid information found in templates, it will be overwritten")
}
// TODO: Need to persist this to the backend
localStorage.setItem('templates', JSON.stringify([...existingTemplates, tempFile]))
}

setShapes([]);
setFields([new Set(), new Set()]);
setDrawnFields(new Set());
setSelectedField(null);
localStorage.setItem('shapes', '');
localStorage.setItem('images', '');
localStorage.setItem('screenshots', '');
localStorage.setItem('images', '');
localStorage.setItem('files', '');
reset();
navigate("/")
}
}

return (
<div className="display-flex flex-column flex-justify-start width-full height-full padding-1 padding-top-2"
data-testid="save-template-page">
Expand Down
25 changes: 21 additions & 4 deletions frontend/src/types/templates.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {Organization, Page, User} from "./models.ts";
import {create} from "zustand";
import {Shape} from "react-image-label";

export interface Template {
id: string;
Expand All @@ -7,15 +9,16 @@ export interface Template {
labName: string
pages: Page[];
status: TemplateStatus;
createdBy: User;
createdBy: User | null;
updatedBy: User | undefined;
createdAt: Date;
createdAt: Date | null;
updatedAt: Date | undefined;
organization: Organization;
organization: Organization | null;
}

type TemplateStatus = "Completed" | "In Progress" | "Deprecated"


const MIDDLEWARE_URL = import.meta.env.MIDDLEWARE_API_URL || 'http://localhost:8081';
export const TemplateAPI = {
getTemplates: async (): Promise<Template[]> => {
Expand All @@ -25,5 +28,19 @@ export const TemplateAPI = {
}
const jsonResponse = await response.json();
return jsonResponse._embedded.templates;
}
},
}

// This is the store for the new template, basically we can treat this as we were treating local storage
export const useCreateTemplateStore = create((set) => ({
baseImages: [],
shapes: [],
templateImages: [],
reset: () => {
set({baseImages: [], shapes: [], templateImages: []})
},
setTemplateImages: (images: string[]) => set({templateImages: images}),
setShapes: (shapes: Shape[]) => set({shapes: shapes}),
setBaseImages: (images: ImageData[]) => set({baseImages: images}),

}));
10 changes: 6 additions & 4 deletions frontend/src/utils/functions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { CustomShape } from "../contexts/AnnotationContext";
import { ImageData } from "../pages/AnnotateTemplate";
import {useCreateTemplateStore} from "../types/templates.ts";

export const makeScreenshots = async () => {
const images: ImageData[] = JSON.parse(localStorage.getItem('images') || '[]') as ImageData[];
const shapes: CustomShape[][] = JSON.parse(localStorage.getItem('shapes') || '[]') as CustomShape[][];
export const makeScreenshots = async (images, shapes) => {
// const images: ImageData[] = JSON.parse(localStorage.getItem('images') || '[]') as ImageData[];
// const shapes: CustomShape[][] = JSON.parse(localStorage.getItem('shapes') || '[]') as CustomShape[][];
const screenshots: string[] = [];

for (let i = 0; i < images.length; i++) {
try {
const screenshot = await createScreenshot(images[i], shapes[i] ?? []);
Expand All @@ -15,7 +17,7 @@ export const makeScreenshots = async () => {
}

// Final log of all screenshots
localStorage.setItem('screenshots', JSON.stringify(screenshots));
// localStorage.setItem('screenshots', JSON.stringify(screenshots));
return screenshots;
};

Expand Down

0 comments on commit 3f8cc8e

Please sign in to comment.