Skip to content

Commit

Permalink
Image submissions
Browse files Browse the repository at this point in the history
  • Loading branch information
Piturnah committed Jan 27, 2024
1 parent 91f21b9 commit d954ecb
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 35 deletions.
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "client",
"version": "0.0.1",
"version": "0.0.2",
"private": true,
"scripts": {
"dev": "vite dev",
Expand Down
1 change: 1 addition & 0 deletions client/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
<a href="/review" class="btn btn-white">Review</a>
<a href="/add" class="btn btn-white">Add problems</a>
</Box>
<p class="text-xs mt-1">Watson client v{PKG.version}</p>
</div>
<div class="bg-coal border border-dust p-6 mr-10 h-full">
<h1 class="text-lg font-bold mb-5">Contributions Leaderboard</h1>
Expand Down
71 changes: 65 additions & 6 deletions client/src/routes/add/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { axios, exampleProblem } from "$lib";
import TexBox from "$lib/TexBox.svelte";
import Box from "$lib/Box.svelte";
import { PUBLIC_WATSON_MEDIA_BASE } from "$env/static/public";
let { body, soln } = exampleProblem;
let source = "";
let author = "";
Expand Down Expand Up @@ -35,7 +36,8 @@
errors = [];
if (selectedModule < -1) errors = ["Please select a module", ...errors];
if (selectedTopic < -1) errors = ["Please select a topic", ...errors];
if (body.replaceAll(/\s+/g, "") === "") errors = ["Please enter a problem", ...errors];
if (problemImg === null && body.replaceAll(/\s+/g, "") === "")
errors = ["Please enter a problem", ...errors];
if (errors.length > 0) return;
waiting = true;
Expand All @@ -45,13 +47,17 @@
author,
body,
soln: soln.replaceAll(/\s+/g, "") === "" ? null : soln,
soln_img: solnImg,
solnlink: solnLink,
img_path: problemImg,
module: selectedModule === -1 ? newModule : selectedModule,
topic: selectedTopic === -1 ? newTopic : selectedTopic,
})
.then(() => {
body = "";
soln = "";
problemImg = null;
solnImg = null;
if (selectedModule === -1) selectedModule = -2;
if (selectedTopic === -1) selectedTopic = -2;
waiting = false;
Expand All @@ -61,6 +67,39 @@
waiting = false;
});
}
// Srcs for images relative to the media base URL.
let problemImg: string | null = null;
let solnImg: string | null = null;
type ImgSrc = "problem" | "solution";
const uploadClipboardImg = (imgData, src: ImgSrc) => {
if (imgData !== undefined && imgData.type.startsWith("image/")) {
axios
.post("/upload", { file: imgData }, { headers: { "content-type": "multipart/form-data" } })
.then((res) => {
if (src === "problem") {
problemImg = res.data;
} else {
solnImg = res.data;
}
})
.catch((e) => console.log(e));
}
};
const onloadProblem = (el) => {
el.addEventListener("paste", (ev) => {
uploadClipboardImg(ev.clipboardData.files[0], "problem");
});
};
const onloadSolution = (el) => {
el.addEventListener("paste", (ev) => {
uploadClipboardImg(ev.clipboardData.files[0], "solution");
});
};
</script>

<form class="flex flex-col items-center p-12">
Expand Down Expand Up @@ -100,14 +139,28 @@
class="p-2 w-full bg-midnight text-white"
/>
<div>
<label class="block text-white">Problem statement</label>
<textarea bind:value={body} class="h-36 w-full p-1 font-mono bg-midnight text-white" />
<label class="block text-white">
Problem statement
<span class="text-xs">(you may also paste an image)</span>
</label>
<textarea
use:onloadProblem
bind:value={body}
class="h-36 w-full p-1 font-mono bg-midnight text-white"
/>
</div>
<div>
<label class="block text-white">Solution</label>
<textarea bind:value={soln} class="h-56 w-full p-1 font-mono bg-midnight text-white" />
<label class="block text-white">
Solution
<span class="text-xs">(you may also paste an image)</span>
</label>
<textarea
use:onloadSolution
bind:value={soln}
class="h-56 w-full p-1 font-mono bg-midnight text-white"
/>
</div>
{#if soln === ""}
{#if soln === "" && solnImg === null}
<div>
<label class="block text-white"
>Where can the solution be found? (e.g. URL or textbook)</label
Expand All @@ -122,9 +175,15 @@
</div>
</div>
<Box>
{#if problemImg !== null}
<img class="rounded-md" src={`${PUBLIC_WATSON_MEDIA_BASE}/${problemImg}`} />
{/if}
{#if body !== ""}
<TexBox content={body} />
{/if}
{#if solnImg !== null}
<img class="rounded-md" src={`${PUBLIC_WATSON_MEDIA_BASE}/${solnImg}`} />
{/if}
{#if soln !== ""}
<TexBox content={soln} />
{/if}
Expand Down
1 change: 0 additions & 1 deletion client/src/routes/login/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import Box from "$lib/Box.svelte";
import { axios } from "$lib";
import { goto } from "$app/navigation";
import { PUBLIC_WATSON_CLIENT_ID } from "$env/static/public";
const urlParams = new URLSearchParams(window.location.search);
const redirect = urlParams.get("to");
Expand Down
49 changes: 44 additions & 5 deletions client/src/routes/review/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
import { get } from "svelte/store";
import { selected_topic_ids } from "$lib/stores";
import { onMount } from "svelte";
import { PUBLIC_WATSON_MEDIA_BASE } from "$env/static/public";
let showSoln = false;
let problem: { id: number; body: string; solnlink: string } = { id: -1, body: "", solnlink: "" };
let soln = "";
let problem: { id: number; body: string | null; solnlink: string; img_path: string | null } = {
id: -1,
body: "",
solnlink: "",
};
let soln: string | null = null;
let soln_img: string | null = null;
let noProblem = false;
function getProblem() {
Expand All @@ -21,6 +27,7 @@
else {
problem = data.problem;
soln = data.solution;
soln_img = data.solution_img;
submitSoln = "";
}
})
Expand All @@ -40,22 +47,47 @@
function handleSolnSubmit() {
if (problem.id === -1) return;
axios
.post("/solutions", { problem_id: problem.id, body: submitSoln })
.post("/solutions", { problem_id: problem.id, body: submitSoln, img_path: solnImg })
.then(() => {
soln = submitSoln;
submitSoln = "";
})
.catch((err) => console.warn(err));
}
let solnImg: string | null = null;
const onloadSolution = (el) => {
el.addEventListener("paste", (ev) => {
const imgData = ev.clipboardData.files[0];
if (imgData !== undefined && imgData.type.startsWith("image/")) {
axios
.post(
"/upload",
{ file: imgData },
{ headers: { "content-type": "multipart/form-data" } },
)
.then((res) => (solnImg = res.data))
.catch((e) => console.log(e));
}
});
};
</script>

<div class="grid grid-cols-1 p-12 w-[959px] m-auto gap-6">
{#if !noProblem}
<Box>
<TexBox content={`${problem.id}. ` + problem.body} />
{#if problem.img_path !== null}
<img class="rounded-md" src={`${PUBLIC_WATSON_MEDIA_BASE}/${problem.img_path}`} />
{/if}
{#if problem.body !== null}
<TexBox content={`${problem.id}. ` + problem.body} />
{/if}
</Box>
{#if showSoln}
<Box>
{#if soln_img !== null}
<img class="rounded-md" src={`${PUBLIC_WATSON_MEDIA_BASE}/${problem.soln_img}`} />
{/if}
{#if soln !== null}
<TexBox content={soln} />
{:else}
Expand All @@ -68,14 +100,21 @@
>
</p>
{/if}
<p>If you know the answer, you can submit the solution below:</p>
<p>
If you know the answer, you can submit the solution below:
<span class="text-xs">(you may also paste an image)</span>
</p>
<textarea
use:onloadSolution
bind:value={submitSoln}
class="h-56 w-full p-1 font-mono bg-midnight text-white"
/>
{#if submitSoln !== ""}
<TexBox content={submitSoln} />
{/if}
{#if solnImg !== null}
<img class="rounded-md" src={`${PUBLIC_WATSON_MEDIA_BASE}/${solnImg}`} />
{/if}
{/if}
</Box>
<div class="flex flex-row-reverse w-full gap-4">
Expand Down
9 changes: 9 additions & 0 deletions client/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
import { fileURLToPath } from "url";
import { readFileSync } from "fs";

const file = fileURLToPath(new URL("package.json", import.meta.url));
const json = readFileSync(file, "utf8");
const pkg = JSON.parse(json);

export default defineConfig({
plugins: [sveltekit()],
define: {
PKG: pkg,
},
});
27 changes: 26 additions & 1 deletion server/Cargo.lock

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

4 changes: 2 additions & 2 deletions server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "watson-server"
version = "0.1.0"
version = "0.1.1"
edition = "2021"

[profile.release]
Expand All @@ -13,7 +13,7 @@ dotenv = ["dep:dotenvy"]

[dependencies]
argon2 = "0.5.3"
axum = "0.7.2"
axum = { version = "0.7.2", features = ["multipart"] }
chrono = { version = "0.4.31", features = ["serde"] }
diesel = { version = "2.1.0", features = ["postgres", "chrono", "uuid"] }
diesel_migrations = "2.1.0"
Expand Down
5 changes: 5 additions & 0 deletions server/migrations/2024-01-26-171232_accept_images/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE problems ALTER COLUMN body SET NOT NULL;
ALTER TABLE solutions ALTER COLUMN body SET NOT NULL;

ALTER TABLE problems DROP COLUMN img_path;
ALTER TABLE solutions DROP COLUMN img_path;
5 changes: 5 additions & 0 deletions server/migrations/2024-01-26-171232_accept_images/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ALTER TABLE problems ALTER COLUMN body DROP NOT NULL;
ALTER TABLE solutions ALTER COLUMN body DROP NOT NULL;

ALTER TABLE problems ADD COLUMN img_path VARCHAR;
ALTER TABLE solutions ADD COLUMN img_path VARCHAR;
2 changes: 1 addition & 1 deletion server/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ pub async fn register(
))
.execute(&mut conn)
.map_err(internal_error)?;
todo!()
Ok(())
}

#[derive(Deserialize)]
Expand Down
Loading

0 comments on commit d954ecb

Please sign in to comment.