Skip to content

Commit

Permalink
Everything in the browser
Browse files Browse the repository at this point in the history
  • Loading branch information
eyeseast committed Apr 11, 2024
1 parent 223b36b commit 17fe3cf
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 81 deletions.
134 changes: 124 additions & 10 deletions src/lib/components/forms/DocumentUpload.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,109 @@
<script lang="ts">
import type { Project } from "$lib/api/types";
<script context="module" lang="ts">
import { error, type ActionResult } from "@sveltejs/kit";
/**
* Collect form data into documents and do three-step upload.
* Exported here for testing and reuse.
*/
export async function upload(
form: FormData,
csrf_token: string,
fetch = globalThis.fetch,
): Promise<ActionResult> {
// one per file
const files = Array.from(form.getAll("uploads")) as File[];
const titles = form.getAll("title") as string[];
const filenames = form.getAll("filename") as string[];
// one per batch
const access = form.get("access") as Access;
// value is a JSON string
const ocr_engine: OCREngine = unwrap(form.get("ocr_engine") as string);
const force_ocr = Boolean(form.get("force_ocr"));
const revision_control = Boolean(form.get("revision_control"));
const projects = unwrap(form.get("projects") as string, []);
const language = unwrap(form.get("language") as string, DEFAULT_LANGUAGE);
// put things together
const docs: DocumentUpload[] = titles.map((title, i) => {
return {
title,
access,
language,
projects: projects.map((p: Project) => p.id),
revision_control,
};
});
let created: Document[];
try {
created = await documents.create(docs, csrf_token, fetch);
} catch (err) {
return {
type: "error",
status: 400,
error: err,
};
}
// upload
const uploads = created.map((d, i) => ({
id: d.id,
presigned_url: new URL(d.presigned_url),
file: files[i],
}));
// todo: handle retries and errors
const upload_responses = await documents.upload(uploads, fetch);
console.log(upload_responses.map((r) => r.status));
// process
const process_response = await documents.process(
created.map((d) => ({
id: d.id,
force_ocr,
ocr_engine: ocr_engine.value,
})),
csrf_token,
fetch,
);
// todo: i18n
if (process_response.ok) {
return {
type: "success",
status: 201,
data: {
success: true,
message: `Uploaded ${created.length} documents`,
},
};
}
return {
type: "error",
status: process_response.status,
error: await process_response.text(),
};
}
</script>

<script lang="ts">
import type {
Access,
Document,
DocumentUpload,
OCREngine,
Project,
} from "$lib/api/types";
import { applyAction } from "$app/forms";
import { filesize } from "filesize";
import { afterUpdate } from "svelte";
import { _ } from "svelte-i18n";
import { File16, File24, Upload16, XCircleFill24 } from "svelte-octicons";
import { enhance } from "$app/forms";
import { page } from "$app/stores";
import Button from "../common/Button.svelte";
Expand All @@ -19,13 +117,13 @@
import Dropzone from "../inputs/Dropzone.svelte";
import FileInput from "../inputs/File.svelte";
import Language from "../inputs/Language.svelte";
import Select from "../inputs/Select.svelte";
import Select, { unwrap } from "../inputs/Select.svelte";
import Switch from "../inputs/Switch.svelte";
import Text from "../inputs/Text.svelte";
import { DOCUMENT_TYPES } from "@/config/config.js";
import * as documents from "$lib/api/documents";
import { DEFAULT_LANGUAGE, DOCUMENT_TYPES } from "@/config/config.js";
import { isSupported } from "@/lib/utils/validateFiles";
import { afterUpdate } from "svelte";
let files: File[] = [];
let projects: Project[] = [];
Expand All @@ -49,8 +147,13 @@
let ocrEngine = ocrEngineOptions[0];
$: csrf_token = $page.data.csrf_token;
$: projects = $page.data.projects.results;
$: total = files.reduce((t, file) => {
return t + file.size;
}, 0);
function addFiles(filesToAdd: FileList) {
files = files.concat(Array.from(filesToAdd).filter(isSupported));
}
Expand All @@ -71,9 +174,20 @@
return name.replace(/_/g, " ").replace(/([A-Z])/g, " $1");
}
$: total = files.reduce((t, file) => {
return t + file.size;
}, 0);
// handle uploads client side instead of going through the server
async function onSubmit(e: SubmitEvent) {
const form = e.target as HTMLFormElement;
const fd = new FormData(form);
const result = await upload(fd, csrf_token);
// send data up
applyAction(result);
if (result.type === "success") {
form.reset();
}
}
afterUpdate(() => {
const dt = new DataTransfer();
Expand All @@ -87,10 +201,10 @@
</script>

<form
use:enhance
method="post"
enctype="multipart/form-data"
action="/app/upload/"
on:submit|preventDefault={onSubmit}
>
<Flex gap={1} align="stretch" wrap>
<div class="files">
Expand Down
76 changes: 8 additions & 68 deletions src/routes/app/upload/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,79 +10,19 @@ import type {
import { CSRF_COOKIE_NAME, DEFAULT_LANGUAGE } from "@/config/config.js";
import * as documents from "$lib/api/documents";
import { unwrap } from "$lib/components/inputs/Select.svelte";
import { upload } from "$lib/components/forms/DocumentUpload.svelte";

export function load({ cookies }) {
const csrf_token = cookies.get(CSRF_COOKIE_NAME);

return { csrf_token };
}

export const actions = {
default: async ({ request, cookies, fetch }) => {
const csrf_token = cookies.get(CSRF_COOKIE_NAME);
const form = await request.formData();

// one per file
const files = Array.from(form.getAll("uploads")) as File[];
const titles = form.getAll("title") as string[];
const filenames = form.getAll("filename") as string[];

// one per batch
const access = form.get("access") as Access;

// value is a JSON string
const ocr_engine: OCREngine = unwrap(form.get("ocr_engine") as string);
const force_ocr = Boolean(form.get("force_ocr"));
const revision_control = Boolean(form.get("revision_control"));
const projects = unwrap(form.get("projects") as string, []);
const language = unwrap(form.get("language") as string, DEFAULT_LANGUAGE);

// put things together
const docs: DocumentUpload[] = titles.map((title, i) => {
return {
title,
access,
language,
projects: projects.map((p: Project) => p.id),
revision_control,
};
});

let created: Document[];
try {
created = await documents.create(docs, csrf_token, fetch);
} catch (err) {
return {
success: false,
error: err,
};
}

// upload
const uploads = created.map((d, i) => ({
id: d.id,
presigned_url: new URL(d.presigned_url),
file: files[i],
}));

// todo: handle retries and errors
const upload_responses = await documents.upload(uploads, fetch);

console.log(upload_responses.map((r) => r.status));

// process
const process_response = await documents.process(
created.map((d) => ({
id: d.id,
force_ocr,
ocr_engine: ocr_engine.value,
})),
csrf_token,
fetch,
);

// todo: i18n
const message = process_response.ok
? `Uploaded ${created.length} documents`
: process_response.statusText;

return {
success: true,
message,
};
return upload(form, csrf_token, fetch);
},
} satisfies Actions;
11 changes: 8 additions & 3 deletions src/routes/app/upload/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script lang="ts">
import type { ActionData } from "./$types";
import Flex from "$lib/components/common/Flex.svelte";
import DocumentUpload from "$lib/components/forms/DocumentUpload.svelte";
export let form;
export let form: ActionData;
</script>

<svelte:head>
Expand All @@ -13,9 +14,13 @@
<Flex direction="column">
<h1>Upload documents</h1>

{#if form?.success}
{#if form?.type === "success"}
<p>
{form.message}
{form.data?.message}
</p>
{:else if form?.type === "error"}
<p class="error">
{form?.error}
</p>
{:else}
<p>
Expand Down

0 comments on commit 17fe3cf

Please sign in to comment.