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

Add a form to allow panorama uploads to Pano #90

Draft
wants to merge 35 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
75fccf1
first wack
WillNilges Oct 5, 2024
f795754
Aha I think I got it
WillNilges Oct 5, 2024
9b2b576
Well I got a 200 but it's not working
WillNilges Oct 5, 2024
ceb0bd4
IDK how to use computers
WillNilges Oct 5, 2024
260624b
Works for one file
WillNilges Oct 5, 2024
39c50a9
dropzone maybe
WillNilges Oct 5, 2024
99264cb
wait hold on
WillNilges Oct 5, 2024
c05610a
thumbs are too big
WillNilges Oct 5, 2024
53b7fd3
Rawdogging the styling works tho, still get errors
WillNilges Oct 5, 2024
933e31e
Now, to combine them.
WillNilges Oct 5, 2024
7f72f01
Getting somewhere
WillNilges Oct 5, 2024
05f444c
Now that's what I call a high pass filter
WillNilges Oct 5, 2024
bc770c9
Previews are so back plus jsx lol
WillNilges Oct 5, 2024
393dc78
Checkpoint
WillNilges Oct 5, 2024
4f7bfe9
styling and code cleanup
WillNilges Oct 5, 2024
aa6bee7
Delete old submit handler
WillNilges Oct 5, 2024
670dc20
Frontend is hard
WillNilges Oct 6, 2024
9762523
ok getting there
WillNilges Oct 6, 2024
981921b
Good enough for government work
WillNilges Oct 6, 2024
3302ee0
Checkpoint
WillNilges Oct 6, 2024
f45bf52
No wrap
WillNilges Oct 6, 2024
e0b930f
chom
WillNilges Oct 6, 2024
c6ef17a
Ugh I hate it when I do that
WillNilges Oct 7, 2024
6ce5c73
Jesus that took way too long
WillNilges Oct 7, 2024
eb57ca9
comments
WillNilges Oct 7, 2024
7962fa7
cleanup
WillNilges Oct 7, 2024
9e54346
Ah fuck I broke it
WillNilges Oct 7, 2024
18798ac
thanks chatgpt
WillNilges Oct 7, 2024
9bbf180
Jesus christ I can't believe that worked
WillNilges Oct 7, 2024
3c5d3b6
We're almost there
WillNilges Oct 7, 2024
a27c4e1
Fucking sweet dude we got it working
WillNilges Oct 7, 2024
2d7fe61
Cleanup
WillNilges Oct 7, 2024
a6e30cd
Fix race condition and stub tests
WillNilges Oct 7, 2024
a818349
Throbber
WillNilges Oct 7, 2024
97f1527
Stub test
WillNilges Oct 7, 2024
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
13 changes: 13 additions & 0 deletions app/panorama/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Idea: Have people validate their panoramas with their email?

import PanoramaUploadForm from "@/components/PanoramaUpload/PanoramaUpload";

export default async function PanoramaUpload() {
return (
<>
<main>
<PanoramaUploadForm />
</main>
</>
);
}
1 change: 1 addition & 0 deletions components/Landing/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Landing = () => {
{ text: "Join Form", link: "/join" },
{ text: "NN Assign Form", link: "/nn-assign" },
{ text: "Query Form", link: "/query" },
{ text: "Img Upload", link: "/panorama" },
{
text: "MeshDB Admin",
link: process.env.NEXT_PUBLIC_MESHDB_URL + "/admin/",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.alertTable table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
margin: 10px 0 0 0;
}

.alertTable th {
background: #eeeee;
}

.alertTable td,
th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
122 changes: 122 additions & 0 deletions components/PanoramaDuplicateDialog/PanoramaDuplicateDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import * as React from "react";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import styles from "./PanoramaDuplicateDialog.module.scss";
import { FormValues } from "../PanoramaUpload/PanoramaUpload";

interface PanoramaDuplicateDialogProps {
formSubmission: FormValues;
possibleDuplicates: Array<[string, string]>;
isDialogOpened: boolean;
handleClickUpload: () => void;
handleClickCancel: () => void;
}

// https://mui.com/material-ui/react-dialog/#alerts
export default function PanoramaDuplicateDialog({
formSubmission,
possibleDuplicates,
isDialogOpened,
handleClickUpload,
handleClickCancel,
}: PanoramaDuplicateDialogProps) {
return (
<React.Fragment>
<Dialog
open={isDialogOpened}
onClose={handleClickCancel}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle color="error" id="alert-dialog-title">
{"Possible duplicates detected"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
The following images submitted seem to be duplicates of existing
images for Install #{formSubmission.installNumber}. Would you like
to upload these anyway?
<br />
<div className={styles.alertTable}>
<table>
<tr>
<th>Uploaded</th>
<th>Existing Image</th>
</tr>
{possibleDuplicates.map(([k, v], _) => (
<tr>
<td>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<img
src={URL.createObjectURL(
formSubmission.dropzoneImages.find(
(file: File) => file.name === k,
),
)}
Comment on lines +61 to +65

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix AI 29 days ago

To fix the problem, we need to ensure that any data used in the DOM is properly sanitized or validated. Specifically, we should:

  1. Validate the file objects in formSubmission.dropzoneImages before using them with URL.createObjectURL.
  2. Ensure that URLs in the possibleDuplicates array are sanitized before being used in the DOM.

We can use a library like DOMPurify to sanitize URLs and ensure that they do not contain any malicious content.

Suggested changeset 2
components/PanoramaDuplicateDialog/PanoramaDuplicateDialog.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/components/PanoramaDuplicateDialog/PanoramaDuplicateDialog.tsx b/components/PanoramaDuplicateDialog/PanoramaDuplicateDialog.tsx
--- a/components/PanoramaDuplicateDialog/PanoramaDuplicateDialog.tsx
+++ b/components/PanoramaDuplicateDialog/PanoramaDuplicateDialog.tsx
@@ -1,2 +1,3 @@
 import * as React from "react";
+import DOMPurify from "dompurify";
 import Button from "@mui/material/Button";
@@ -61,5 +62,5 @@
                           src={URL.createObjectURL(
-                            formSubmission.dropzoneImages.find(
+                            DOMPurify.sanitize(formSubmission.dropzoneImages.find(
                               (file: File) => file.name === k,
-                            ),
+                            )),
                           )}
@@ -96,4 +97,4 @@
                         />
-                        <a href={new URL(v).origin + new URL(v).pathname}>
-                          {new URL(v).origin + new URL(v).pathname}
+                        <a href={DOMPurify.sanitize(new URL(v).origin + new URL(v).pathname)}>
+                          {DOMPurify.sanitize(new URL(v).origin + new URL(v).pathname)}
                         </a>
EOF
@@ -1,2 +1,3 @@
import * as React from "react";
import DOMPurify from "dompurify";
import Button from "@mui/material/Button";
@@ -61,5 +62,5 @@
src={URL.createObjectURL(
formSubmission.dropzoneImages.find(
DOMPurify.sanitize(formSubmission.dropzoneImages.find(
(file: File) => file.name === k,
),
)),
)}
@@ -96,4 +97,4 @@
/>
<a href={new URL(v).origin + new URL(v).pathname}>
{new URL(v).origin + new URL(v).pathname}
<a href={DOMPurify.sanitize(new URL(v).origin + new URL(v).pathname)}>
{DOMPurify.sanitize(new URL(v).origin + new URL(v).pathname)}
</a>
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -35,3 +35,4 @@
     "typescript": "5.3.3",
-    "zod": "^3.22.4"
+    "zod": "^3.22.4",
+    "dompurify": "^3.1.7"
   },
EOF
@@ -35,3 +35,4 @@
"typescript": "5.3.3",
"zod": "^3.22.4"
"zod": "^3.22.4",
"dompurify": "^3.1.7"
},
This fix introduces these dependencies
Package Version Security advisories
dompurify (npm) 3.1.7 None
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
style={{
display: "block",
marginLeft: "auto",
marginRight: "auto",
height: "100px",
}}
/>
{
formSubmission.dropzoneImages.find(
(file: File) => file.name === k,
).name
}
</div>
</td>
<td>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<img
src={v}
style={{
display: "block",
marginLeft: "auto",
marginRight: "auto",
height: "100px",
}}
/>
<a href={new URL(v).origin + new URL(v).pathname}>
{new URL(v).origin + new URL(v).pathname}
</a>
</div>
</td>
</tr>
))}
</table>
</div>
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClickCancel}>Cancel</Button>
<Button
color="error"
variant="contained"
onClick={handleClickUpload}
autoFocus
>
Upload
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
);
}
99 changes: 99 additions & 0 deletions components/PanoramaUpload/PanoramaDropzone.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
.container {
display: flex;
flex-direction: row;
font-family: sans-serif;
justify-content: center;

margin: 10px;
padding: 10px;
}

.subContainer {
width: 75%;
}

.container > p {
font-size: 1rem;
}

.container > em {
font-size: 0.8rem;
}

.dropzone {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
flex-grow: 1;
padding: 20px;
border-width: 2px;
border-radius: 5px;
border-color: #bbbbbb;
border-style: dashed;
background-color: #eaeaea;
color: #adadad;
outline: none;
transition: border 0.24s ease-in-out;
}

.dropzone:hover {
border-color: #8bc8ff;
}

.dropzone:focus {
border-color: #2196f3;
}

.dropzone.disabled {
opacity: 0.6;
}

.thumbsContainer {
display: "flex";
flexdirection: "row";
flexwrap: "wrap";
margintop: 16;
backgroundcolor: red;
}

.thumb {
display: "inline-flex";
borderradius: 2;
border: "1px solid #eaeaea";
marginbottom: 8;
marginright: 8;
width: 100;
height: 100;
padding: 4;
boxsizing: "border-box";
}

.thumbInner {
display: "flex";
minwidth: 0;
overflow: "hidden";
}

.img {
display: "block";
width: "auto";
height: "100%";
}

.itemList {
display: flex;
flex-direction: column;
padding: 5px;
}

/*Janky*/
.itemList h4 {
margin: 0px;
padding: 0 0 0 24px;
}

.itemList ul {
margin: 0px;
white-space: nowrap;
}
103 changes: 103 additions & 0 deletions components/PanoramaUpload/PanoramaDropzone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"use client";
import React, { useCallback, useEffect, useState } from "react";
import { useDropzone } from "react-dropzone";
import styles from "./PanoramaDropzone.module.scss";

const thumbsContainer = {
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
marginTop: 16,
};

const thumb = {
display: "inline-flex",
borderRadius: 2,
border: "1px solid #eaeaea",
marginBottom: 8,
marginRight: 8,
width: 100,
height: 100,
padding: 4,
boxSizing: "border-box",
};

const thumbInner = {
display: "flex",
minWidth: 0,
overflow: "hidden",
};

const img = {
display: "block",
width: "auto",
height: "100%",
};

interface PanoramaDropzoneProps {
onFileDrop: (dropzoneImages: File[]) => void;
}

const PanoramaDropzone: React.FC<PanoramaDropzoneProps> = ({ onFileDrop }) => {
const [files, setFiles] = useState<File[]>([]);

const onDrop = useCallback(
(acceptedFiles: File[]) => {
setFiles(acceptedFiles); // Store files locally
onFileDrop(acceptedFiles); // Pass files to main form via callback
},
[onFileDrop],
);

const { getRootProps, getInputProps } = useDropzone({ onDrop });

const filenames = files.map((file) => (
<li key={file.path}>
{file.path} - {file.size} bytes
</li>
));

const thumbs = files.map((file) => (
<div style={thumb} key={file.name}>
<div style={thumbInner}>
<img
src={URL.createObjectURL(file)}
style={img}
// Revoke data uri after image is loaded
onLoad={() => {
URL.revokeObjectURL(file.preview);
}}
name={file.path}
/>
</div>
</div>
));

useEffect(() => {
// Make sure to revoke the data uris to avoid memory leaks, will run on unmount
return () => files.forEach((file) => URL.revokeObjectURL(file.preview));
}, [files]);

return (
<div className={styles.container}>
<div className={styles.subContainer}>
<div {...getRootProps({ className: styles.dropzone })}>
<input {...getInputProps()} />
<p>
Drag and drop panoramas here;
<br />
Or click to open the file dialog
</p>
</div>
<div className={styles.thumbsContainer}>{thumbs}</div>
</div>
{/*XXX (wdn): It would be very cool if I could integrate the file names into the photos.*/}
<div className={styles.itemList}>
<h4>Files</h4>
<ul>{filenames}</ul>
</div>
</div>
);
};

export default PanoramaDropzone;
29 changes: 29 additions & 0 deletions components/PanoramaUpload/PanoramaUpload.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.formBody {
display: flex;
flex-direction: row;
gap: 10px;
justify-content: center;
}

.formBody input[type="number"] {
border: 1px solid #ccc;
padding: 10px;
font-size: 16px;
box-sizing: border-box;
margin-bottom: 10px;
border-radius: 4px;
outline-offset: 4px;
}

.formBody button {
margin-top: 2px; /* Graphic Design Is My Passion */
}

.formBody button:hover {
opacity: 0.8;
}

.formBody button[aria-disabled="true"] {
opacity: 0.5;
cursor: not-allowed;
}
Loading
Loading