diff --git a/devenv.lock b/devenv.lock index d30b322f5f26e2a359af7b0298f110f79068767f..facd3c0c205d1018c388faf6b133c83a1238de82 100644 --- a/devenv.lock +++ b/devenv.lock @@ -114,31 +114,10 @@ "type": "github" } }, - "git-hooks": { - "inputs": { - "flake-compat": "flake-compat", - "gitignore": "gitignore", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1742058297, - "owner": "cachix", - "repo": "git-hooks.nix", - "rev": "59f17850021620cd348ad2e9c0c64f4e6325ce2a", - "type": "github" - }, - "original": { - "owner": "cachix", - "repo": "git-hooks.nix", - "type": "github" - } - }, "gitignore": { "inputs": { "nixpkgs": [ - "git-hooks", + "pre-commit-hooks", "nixpkgs" ] }, @@ -234,17 +213,35 @@ "type": "github" } }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1742058297, + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "59f17850021620cd348ad2e9c0c64f4e6325ce2a", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, "root": { "inputs": { "devenv": "devenv", - "git-hooks": "git-hooks", "mk-shell-bin": "mk-shell-bin", "nix2container": "nix2container", "nixpkgs": "nixpkgs", "nixpkgs-unstable": "nixpkgs-unstable", - "pre-commit-hooks": [ - "git-hooks" - ], + "pre-commit-hooks": "pre-commit-hooks", "surrealdb-git": "surrealdb-git" } }, diff --git a/src/lib/components/AnnotationEditor/AnnotationEditor.svelte b/src/lib/components/AnnotationEditor/AnnotationEditor.svelte index 8096b21da0ab4295e498e01812faecc50098302c..49273af18452ae944454935d4ae5f103e01306f7 100644 --- a/src/lib/components/AnnotationEditor/AnnotationEditor.svelte +++ b/src/lib/components/AnnotationEditor/AnnotationEditor.svelte @@ -1,14 +1,8 @@ <script lang="ts"> - import type { Annotation, SourceSelection } from './types'; + import type { AnnotationEditorProps, SourceSelection } from './types'; import CodeBlock from './CodeBlock.svelte'; - let { - code, - lang - }: { - code: string; - lang: string; - } = $props(); + let { submission, evaluation, onSave = (_) => {} }: AnnotationEditorProps = $props(); let showDebug = $state(true); let sourceSelection: SourceSelection | null = $state(null); @@ -23,42 +17,40 @@ const handleSave = () => { if (!sourceSelection || !comment.trim()) return; - // TODO global annotation kind (ignoring from any current selection which may be active) const date = new Date().toISOString(); + + const annotationData = { scoreDelta, comment, color: annotationColor, createdAt: date }; + if (sourceSelection.type === 'Caret') { - const newAnnotation: Annotation = { + onSave({ kind: 'line', line: sourceSelection.start.line, - scoreDelta, - comment, - createdAt: date - }; + ...annotationData + }); } else if (sourceSelection.type === 'Range') { - const newAnnotation: Annotation = { + onSave({ kind: 'range', selection: sourceSelection, - scoreDelta, - comment, - createdAt: date - }; + ...annotationData + }); } - // TODO { - // const newAnnotation: Annotation = { + // else { + // // TODO global annotation case + // onSave({ // kind: 'global', - // comment, - // scoreDelta, - // createdAt: date - // } + // ...annotationData + // }); + // } comment = ''; scoreDelta = 0; }; + /* // helper to determine if the selection is a point, in which case we want to annotate the whole line it's on function isSelectionPoint(s: SourceSelection): boolean { return s.start.line === s.end.line && s.start.column === s.end.column; } - /* function getSelectionInfo(sel: Selection): SelectionInfo { const info: Record<string, any> = {}; @@ -94,13 +86,18 @@ */ </script> -<CodeBlock {code} {lang} onSelectionChange={handleSelectionChange} /> +<CodeBlock code={submission.code} lang="markdown" onSelectionChange={handleSelectionChange} /> {#if showDebug} <div class="selection-debug"> <h3>Selection Debug</h3> {#if sourceSelection} <hr /> + <div> + <span class="label" + ><button class="btn preset-filled-primary" onclick={handleSave}>Save</button></span + > + </div> <div class="info-row"> <span class="label">Type:</span> <span class="value">{sourceSelection.type}</span> diff --git a/src/lib/components/AnnotationEditor/live.svelte.ts b/src/lib/components/AnnotationEditor/live.svelte.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd231ae8573098fa9fad0b50bf5b57a8392235c6 --- /dev/null +++ b/src/lib/components/AnnotationEditor/live.svelte.ts @@ -0,0 +1,7 @@ +import { getDb } from '$lib/surreal.svelte'; + +const liveEvaluation = async (evalId: string) => { + // const db = await getDb(); + // await db.signin({username: "root", password: "root"}); + // db.live() +}; diff --git a/src/lib/components/AnnotationEditor/types.ts b/src/lib/components/AnnotationEditor/types.ts index 111ed3d5bb1c7da0a4d8063f6c4d3e6e1e1b760e..34b133c68a0ccb7d69bfc20a247f3cf8c5e34350 100644 --- a/src/lib/components/AnnotationEditor/types.ts +++ b/src/lib/components/AnnotationEditor/types.ts @@ -1,10 +1,8 @@ -import type { sourceSelection, SourceSelection } from './schema'; +import type { Evaluation, Submission, SubmissionCreate } from '$lib/surreal/schema'; +import type { Annotation, sourceSelection, SourceSelection } from './schema'; export type { SourcePosition, SourceSelection, Annotation } from './schema.ts'; -/** - * Props for the CodeBlock component - */ export interface CodeBlockProps { code?: string; lang?: string; @@ -24,6 +22,12 @@ export interface CodeBlockProps { showDebug?: boolean; } +export interface AnnotationEditorProps { + submission: Submission; + evaluation?: Evaluation; + onSave?: (annotation: Annotation) => void; +} + // TODO text position for all ranges (startLine, startColumn, endLine, endColumn) export type SelectionInfo = { readonly [K in keyof Selection as Selection[K] extends Function ? never : K]: Selection[K]; diff --git a/src/lib/db/queries.ts b/src/lib/db/queries.ts index 4da06a88de4762c1a6aef58307ba578339829122..b9949853044fd72314cb82e97c3b79770598cf8a 100644 --- a/src/lib/db/queries.ts +++ b/src/lib/db/queries.ts @@ -1,3 +1,4 @@ +import type { Annotation } from '$lib/components/AnnotationEditor/schema'; import { getDb } from '$lib/surreal.svelte'; import type { User } from '$lib/surreal/schema'; import type { RecordId } from 'surrealdb'; @@ -7,6 +8,18 @@ export const getSubmissionsOverview = async (assignment: RecordId) => { const db = await getDb(); }; +// TODO make annotation an edge table, relate author-[annotation]->evaluation/review/whatever +export async function annotateSubmission( + evalRecord: RecordId<'evaluation'>, + annotation: Annotation +) { + const db = await getDb(); + // TEMP + await db.signin({ username: 'root', password: 'root' }); + + const _ = await db.patch(evalRecord, [{ op: 'add', path: '/annotations', value: annotation }]); +} + // export const executeQuery = async <T>( // queryFn: (db: Surreal) => Promise<T>, // config: DbConfig = DEFAULT_CONFIG diff --git a/src/lib/surreal.svelte.ts b/src/lib/surreal.svelte.ts index 6bb508964194e6f55b785f86a641253a8bf7045b..0c5c95824f3b940242a50e167bcad563ba86ebc8 100644 --- a/src/lib/surreal.svelte.ts +++ b/src/lib/surreal.svelte.ts @@ -8,6 +8,7 @@ import { sessionInfoSchema } from './zod'; // } from '$env/static/public'; import { AuthCookie } from './cookies'; import { userSchema, type User } from './surreal/schema'; +import { browser } from '$app/environment'; type DbConfig = { url: string; @@ -61,7 +62,6 @@ export const getDb = async (config: DbConfig = DEFAULT_CONFIG): Promise<Surreal> if (!connectionPromise) { connectionPromise = connectToDatabase(config); } - return connectionPromise; }; diff --git a/src/routes/(app)/submission/[id]/eval/+page.server.ts b/src/routes/(app)/submission/[id]/eval/+page.server.ts index 5df5b43af7eaf0926252b7b482ed43bbf8e092db..e7de3d7041680e0bcbc4c63f17401a7d512b840b 100644 --- a/src/routes/(app)/submission/[id]/eval/+page.server.ts +++ b/src/routes/(app)/submission/[id]/eval/+page.server.ts @@ -1,20 +1,22 @@ import { getDb } from '$lib/surreal.svelte'; import { RecordId, StringRecordId, jsonify } from 'surrealdb'; import type { Actions, PageServerLoad } from './$types'; +import { tick } from 'svelte'; export const load: PageServerLoad = async ({ params }) => { const db = await getDb(); await db.signin({ username: 'root', password: 'root' }); - const submission = await db.query<any[]>( - 'SELECT *, <-evaluation[0] as evaluation from ONLY $submission;', + const [result] = await db.query<any[]>( + 'SELECT *,<-evaluation[0] as evaluation from ONLY $submission;', { submission: new RecordId('submission', params.id) } ); - // console.log(submission?.[0]); + console.log(result); return { - submission: jsonify(submission) + submission: jsonify(result), + evaluation: jsonify(result.evaluation) }; }; diff --git a/src/routes/(app)/submission/[id]/eval/+page.svelte b/src/routes/(app)/submission/[id]/eval/+page.svelte index 821bb79a6719e53dc5d1dde6cd61c7f49d9b816f..57fc0173a71267fea3080f5acceb9b9cccd27900 100644 --- a/src/routes/(app)/submission/[id]/eval/+page.svelte +++ b/src/routes/(app)/submission/[id]/eval/+page.svelte @@ -1,19 +1,21 @@ <script lang="ts"> import { enhance } from '$app/forms'; import AnnotationEditor from '$lib/components/AnnotationEditor/AnnotationEditor.svelte'; + import type { Annotation } from '$lib/components/AnnotationEditor/schema'; + import { annotateSubmission } from '$lib/db/queries'; + import { RecordId, StringRecordId } from 'surrealdb'; + import type { PageProps } from './$types'; ('$lib/components/AnnotationEditor'); - import type { PageData } from './$types'; - const { data } = $props<{ data: PageData }>(); + // TODO live query + let { data }: PageProps = $props(); + $inspect(data); - const submissionData = $derived(data.submission?.[0] || null); - // $inspect(submissionData); + const hasOngoingEvaluation = $derived(data.evaluation !== null); - const evaluation = $derived(submissionData?.evaluation || null); - - const hasOngoingEvaluation = $derived(evaluation !== null); - - const submissionCode = $derived(submissionData?.code || ''); + const onSave = async (annotation: Annotation) => { + await annotateSubmission(new StringRecordId(data.evaluation.id), annotation); + }; function formatJson(obj: any) { return JSON.stringify(obj, null, 2); @@ -23,7 +25,7 @@ <div class="container mx-auto p-4"> <h1 class="mb-6 text-2xl font-bold">Submission Evaluation Debug</h1> - {#if submissionData} + {#if data.submission} <div class="mb-8 grid gap-6"> <div class="rounded-md border bg-gray-50 p-4"> <h2 class="mb-2 text-xl font-semibold">Evaluation Status</h2> @@ -32,29 +34,30 @@ <p>This submission has an ongoing evaluation</p> </div> - {#if evaluation} + {#if data.evaluation} <div class="mt-4"> <h3 class="mb-2 font-medium">Evaluation Details:</h3> <div class="mb-4 rounded-md border bg-white p-4"> <div class="mb-3 flex items-center justify-between"> <h4 class="font-medium">Evaluation</h4> - <span class="text-sm text-gray-500">Tutor: {evaluation.in}</span> - <span class="text-sm text-gray-500">ID: {evaluation.id}</span> + <span class="text-sm text-gray-500">Tutor: {data.evaluation.in}</span> + <span class="text-sm text-gray-500">ID: {data.evaluation.id}</span> </div> <div class="mb-4"> <p class="mb-1"> - Current Score: <span class="font-semibold">{evaluation.score ?? 'Not set'}</span + Current Score: <span class="font-semibold" + >{data.evaluation.score ?? 'Not set'}</span > </p> <p class="mb-1"> - Final: <span class="font-semibold">{evaluation.final ? 'Yes' : 'No'}</span> + Final: <span class="font-semibold">{data.evaluation.final ? 'Yes' : 'No'}</span> </p> </div> </div> <form method="POST" action="?/updateScore" use:enhance class="flex items-end gap-3"> - <input type="hidden" name="evaluationId" value={evaluation.id} /> + <input type="hidden" name="evaluationId" value={data.evaluation.id} /> <div class="flex flex-col"> <label for={`score`} class="mb-1 text-sm font-medium">New Score:</label> <input @@ -65,7 +68,7 @@ max="100" step="0.1" placeholder="Enter score" - value={evaluation.score || ''} + value={data.evaluation.score || ''} class="rounded-md border p-2 text-sm" /> </div> @@ -79,7 +82,7 @@ <div class="p-3"> <form method="POST" action="?/deleteEval" use:enhance class="flex items-end gap-3"> - <input type="hidden" name="evaluationId" value={evaluation.id} /> + <input type="hidden" name="evaluationId" value={data.evaluation.id} /> <button type="submit" class="rounded-md bg-red-600 px-3 py-2 text-sm font-medium text-white hover:bg-red-700" @@ -92,7 +95,7 @@ <details class="mt-2"> <summary class="cursor-pointer text-sm text-gray-600">Show Raw Data</summary> <pre class="mt-2 overflow-auto rounded-md bg-gray-100 p-3 text-sm">{formatJson( - evaluation + data.evaluation )}</pre> </details> </div> @@ -119,10 +122,10 @@ <h2 class="mb-2 text-xl font-semibold">Submission Stage</h2> <div class="rounded-md bg-gray-100 p-3"> <p> - Current stage: <span class="font-semibold">{submissionData.stage || 'Unknown'}</span> + Current stage: <span class="font-semibold">{data.submission.stage || 'Unknown'}</span> </p> <p> - Current score: <span class="font-semibold">{submissionData.score || 'Unknown'}</span> + Current score: <span class="font-semibold">{data.submission.score || 'Unknown'}</span> </p> </div> </div> @@ -131,13 +134,13 @@ <h2 class="mb-2 text-xl font-semibold">Submission Editor</h2> <!--<pre class="h-64 overflow-auto rounded-md bg-gray-100 p-3 text-sm">{submissionCode}</pre>--> <!-- <CodeBlock code={submissionCode} /> --> - <AnnotationEditor code={submissionCode} lang={'python'} /> + <AnnotationEditor submission={data.submission} evaluation={data.evaluation} {onSave} /> </div> <details class="rounded-md border bg-gray-50 p-4"> <summary class="cursor-pointer text-xl font-semibold">Raw Submission Data</summary> <pre class="mt-2 overflow-auto rounded-md bg-gray-100 p-3 text-sm">{formatJson( - submissionData + data.submission )}</pre> </details> </div>