exercise
This commit is contained in:
parent
b3917dc756
commit
8c213b0714
16 changed files with 162 additions and 25 deletions
|
@ -34,8 +34,10 @@
|
|||
"dependencies": {
|
||||
"@types/materialdb": "file:../materialdb",
|
||||
"materialdb": "file:../materialdb",
|
||||
"monaco-editor": "^0.27.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"sequelize": "^6.6.5",
|
||||
"sequelize-typescript": "^2.1.0"
|
||||
"sequelize-typescript": "^2.1.0",
|
||||
"ts-pattern": "^3.2.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,5 +4,6 @@ import { dbPromise } from "$lib/db";
|
|||
export async function handle({ request, resolve }) {
|
||||
request.locals.db = await dbPromise;
|
||||
request.locals.loginStatus = checkLogin(request);
|
||||
console.log("DB", request.locals.db);
|
||||
return resolve(request);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
<script lang="ts">
|
||||
import MultipleChoice from "$lib/exercise/MultipleChoice.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import Exercise from "$lib/exercise/Exercise.svelte";
|
||||
|
||||
let state = "loading";
|
||||
let exercise = null;
|
||||
|
||||
(async function() {
|
||||
onMount(async () => {
|
||||
let resp = await fetch("/api/recommendExercise");
|
||||
let body = await resp.json();
|
||||
exercise = body.exercise;
|
||||
state = "finished";
|
||||
})();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if state == "loading"}
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
import { User } from "$lib/db";
|
||||
import { match, select } from "ts-pattern";
|
||||
|
||||
type _LoginStatus =
|
||||
| { type: "loggedIn"; }
|
||||
| { type: "notLoggedIn"; }
|
||||
|
||||
export class LoginStatus {
|
||||
constructor(public isLoggedIn: boolean, public username?: string) {}
|
||||
constructor(
|
||||
public isLoggedIn: boolean,
|
||||
public user_id?: number,
|
||||
public username?: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
export function checkLogin(req: Request): LoginStatus {
|
||||
|
|
23
web/src/lib/components/Editor.svelte
Normal file
23
web/src/lib/components/Editor.svelte
Normal file
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
|
||||
|
||||
export let language = "ocaml";
|
||||
let editorElement;
|
||||
|
||||
onMount(() => {
|
||||
let editor = monaco.editor.create(editorElement, {
|
||||
language,
|
||||
});
|
||||
editor.layout();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={editorElement} class="editor" />
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editor {
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
}
|
||||
</style>
|
14
web/src/lib/db/exercise.ts
Normal file
14
web/src/lib/db/exercise.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { DataType, Unique, Column, Table, Model } from "sequelize-typescript";
|
||||
|
||||
/// An instance of an exercise, created to be solved by a specific user.
|
||||
@Table
|
||||
export class ExerciseInstance extends Model {
|
||||
@Column(DataType.STRING)
|
||||
public user_id: string;
|
||||
|
||||
@Column(DataType.STRING)
|
||||
public exercise_name: string;
|
||||
|
||||
@Column(DataType.JSON)
|
||||
public instance_props: any;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { Sequelize } from "sequelize-typescript";
|
||||
import { User } from "./user";
|
||||
import { ExerciseInstance } from "./exercise";
|
||||
|
||||
import { Page, Exercise, Grader } from "materialdb/db";
|
||||
|
||||
|
@ -15,7 +16,7 @@ async function loadMaterialDb() {
|
|||
|
||||
async function init(): Promise<Sequelize> {
|
||||
let sequelize = new Sequelize(`sqlite:test.db`, {
|
||||
models: [User],
|
||||
models: [User, ExerciseInstance],
|
||||
});
|
||||
await sequelize.sync({ force: true });
|
||||
return sequelize;
|
||||
|
@ -24,4 +25,4 @@ async function init(): Promise<Sequelize> {
|
|||
export let dbPromise = init();
|
||||
export let materialDb = loadMaterialDb();
|
||||
|
||||
export { User };
|
||||
export { User, ExerciseInstance };
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
<script lang="ts">
|
||||
import MultipleChoice from "./MultipleChoice.svelte";
|
||||
import MultipleChoice from "./MultipleChoice/Component.svelte";
|
||||
import GradedProgram from "./GradedProgram/Component.svelte";
|
||||
import { ExerciseStyle, ExerciseInfo } from "$lib/exercise";
|
||||
|
||||
export let exercise;
|
||||
export let exercise: ExerciseInfo;
|
||||
</script>
|
||||
|
||||
{JSON.stringify(exercise)}
|
||||
{exercise.style}
|
||||
<details open>
|
||||
<summary>Exercise Spec</summary>
|
||||
<pre>{JSON.stringify(exercise, null, 2)}</pre>
|
||||
</details>
|
||||
|
||||
{#if !exercise}
|
||||
loading
|
||||
{:else if exercise.style == "multipleChoice"}
|
||||
<MultipleChoice />
|
||||
{:else if exercise.style == ExerciseStyle.MultipleChoice}
|
||||
<MultipleChoice props={exercise.props} />
|
||||
{:else if exercise.style == ExerciseStyle.GradedProgram}
|
||||
<GradedProgram props={exercise.props} />
|
||||
{:else}
|
||||
Sorry, no support for <code>{exercise.style}</code> type of questions yet!
|
||||
{/if}
|
||||
|
|
9
web/src/lib/exercise/GradedProgram/Component.svelte
Normal file
9
web/src/lib/exercise/GradedProgram/Component.svelte
Normal file
|
@ -0,0 +1,9 @@
|
|||
<script lang="ts">
|
||||
import Editor from "$lib/components/Editor.svelte";
|
||||
|
||||
export let props;
|
||||
</script>
|
||||
|
||||
<code>{props}</code>
|
||||
|
||||
<Editor />
|
9
web/src/lib/exercise/GradedProgram/index.ts
Normal file
9
web/src/lib/exercise/GradedProgram/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { ExerciseStyle, IExercise } from "..";
|
||||
import Component from "./Component.svelte";
|
||||
|
||||
let spec: IExercise = {
|
||||
style: ExerciseStyle.GradedProgram,
|
||||
component: Component,
|
||||
};
|
||||
|
||||
export default spec;
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
export let props;
|
||||
export let question = {
|
||||
|
||||
description: `
|
||||
|
@ -51,7 +52,7 @@
|
|||
|
||||
<p>{ question.description }</p>
|
||||
<ul class="choices">
|
||||
{#each question.choices as choice, index }
|
||||
{#each props.choices as choice, index }
|
||||
<li>
|
||||
<input type="radio"
|
||||
name="choice"
|
7
web/src/lib/exercise/MultipleChoice/index.ts
Normal file
7
web/src/lib/exercise/MultipleChoice/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export class Props {
|
||||
|
||||
}
|
||||
|
||||
export class MaskedInfo {
|
||||
|
||||
}
|
36
web/src/lib/exercise/createInstance.ts
Normal file
36
web/src/lib/exercise/createInstance.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
// mask the full exercise object to only the part that the client needs to see
|
||||
// in order to present it to the user (so the user can't just peek into network
|
||||
// transactions to see what the correct answer is)
|
||||
|
||||
import type { Exercise } from "materialdb/db";
|
||||
import { ExerciseInstance } from "$lib/db";
|
||||
import type { LoginStatus } from "$lib/auth";
|
||||
import { ExerciseStyle } from ".";
|
||||
|
||||
export class ExerciseInfo {
|
||||
public style: string;
|
||||
public props: any;
|
||||
}
|
||||
|
||||
export async function createInstance(loginStatus: LoginStatus, exercise: Exercise): Promise<ExerciseInfo> {
|
||||
let props;
|
||||
switch (exercise.style) {
|
||||
case ExerciseStyle.GradedProgram:
|
||||
break;
|
||||
case ExerciseStyle.MultipleChoice:
|
||||
break;
|
||||
}
|
||||
|
||||
let instance = new ExerciseInstance({
|
||||
user_id: loginStatus.user_id,
|
||||
exercise_name: exercise.name,
|
||||
props,
|
||||
});
|
||||
instance.save();
|
||||
|
||||
let info = new ExerciseInfo();
|
||||
info.style = exercise.style;
|
||||
info.props = props;
|
||||
console.log("info", info);
|
||||
return info;
|
||||
}
|
20
web/src/lib/exercise/index.ts
Normal file
20
web/src/lib/exercise/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { createInstance, ExerciseInfo } from "./createInstance";
|
||||
import type { SvelteComponentDev } from "svelte";
|
||||
|
||||
export enum ExerciseStyle {
|
||||
GradedProgram = "gradedProgram",
|
||||
MultipleChoice = "multipleChoice",
|
||||
}
|
||||
|
||||
export interface IExercise {
|
||||
style: ExerciseStyle;
|
||||
component: SvelteComponentDev;
|
||||
}
|
||||
|
||||
export interface IExerciseProps {
|
||||
}
|
||||
|
||||
export interface IExerciseMaskedInfo {
|
||||
}
|
||||
|
||||
export { createInstance, ExerciseInfo };
|
|
@ -1,9 +0,0 @@
|
|||
// mask the full exercise object to only the part that the client needs to see
|
||||
// in order to present it to the user (so the user can't just peek into network
|
||||
// transactions to see what the correct answer is)
|
||||
|
||||
import type { Exercise } from "materialdb/db";
|
||||
|
||||
export async function maskExercise(exercise: Exercise): Promise<any> {
|
||||
return {};
|
||||
}
|
|
@ -4,10 +4,13 @@
|
|||
|
||||
import { Exercise } from "materialdb/db";
|
||||
import type { Sequelize } from "sequelize-typescript";
|
||||
import type { LoginStatus } from "$lib/auth";
|
||||
import { createInstance } from "$lib/exercise";
|
||||
|
||||
export async function get(req) {
|
||||
let db: Sequelize = req.locals.db;
|
||||
console.log("login Status:", req.locals.loginStatus);
|
||||
let loginStatus: LoginStatus = req.locals.loginStatus;
|
||||
console.log("login Status:", loginStatus);
|
||||
|
||||
let exercise = await Exercise.findOne({
|
||||
where: {
|
||||
|
@ -17,7 +20,10 @@ export async function get(req) {
|
|||
});
|
||||
console.log("picked", exercise);
|
||||
|
||||
let instance = await createInstance(loginStatus, exercise);
|
||||
console.log("instance", instance);
|
||||
|
||||
return {
|
||||
body: { exercise },
|
||||
body: { exercise: instance },
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue