exercise
This commit is contained in:
parent
b3917dc756
commit
8c213b0714
16 changed files with 162 additions and 25 deletions
|
@ -34,8 +34,10 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/materialdb": "file:../materialdb",
|
"@types/materialdb": "file:../materialdb",
|
||||||
"materialdb": "file:../materialdb",
|
"materialdb": "file:../materialdb",
|
||||||
|
"monaco-editor": "^0.27.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"sequelize": "^6.6.5",
|
"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 }) {
|
export async function handle({ request, resolve }) {
|
||||||
request.locals.db = await dbPromise;
|
request.locals.db = await dbPromise;
|
||||||
request.locals.loginStatus = checkLogin(request);
|
request.locals.loginStatus = checkLogin(request);
|
||||||
|
console.log("DB", request.locals.db);
|
||||||
return resolve(request);
|
return resolve(request);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import MultipleChoice from "$lib/exercise/MultipleChoice.svelte";
|
import { onMount } from "svelte";
|
||||||
import Exercise from "$lib/exercise/Exercise.svelte";
|
import Exercise from "$lib/exercise/Exercise.svelte";
|
||||||
|
|
||||||
let state = "loading";
|
let state = "loading";
|
||||||
let exercise = null;
|
let exercise = null;
|
||||||
|
|
||||||
(async function() {
|
onMount(async () => {
|
||||||
let resp = await fetch("/api/recommendExercise");
|
let resp = await fetch("/api/recommendExercise");
|
||||||
let body = await resp.json();
|
let body = await resp.json();
|
||||||
exercise = body.exercise;
|
exercise = body.exercise;
|
||||||
state = "finished";
|
state = "finished";
|
||||||
})();
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if state == "loading"}
|
{#if state == "loading"}
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
import { User } from "$lib/db";
|
import { User } from "$lib/db";
|
||||||
|
import { match, select } from "ts-pattern";
|
||||||
|
|
||||||
|
type _LoginStatus =
|
||||||
|
| { type: "loggedIn"; }
|
||||||
|
| { type: "notLoggedIn"; }
|
||||||
|
|
||||||
export class LoginStatus {
|
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 {
|
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 { Sequelize } from "sequelize-typescript";
|
||||||
import { User } from "./user";
|
import { User } from "./user";
|
||||||
|
import { ExerciseInstance } from "./exercise";
|
||||||
|
|
||||||
import { Page, Exercise, Grader } from "materialdb/db";
|
import { Page, Exercise, Grader } from "materialdb/db";
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ async function loadMaterialDb() {
|
||||||
|
|
||||||
async function init(): Promise<Sequelize> {
|
async function init(): Promise<Sequelize> {
|
||||||
let sequelize = new Sequelize(`sqlite:test.db`, {
|
let sequelize = new Sequelize(`sqlite:test.db`, {
|
||||||
models: [User],
|
models: [User, ExerciseInstance],
|
||||||
});
|
});
|
||||||
await sequelize.sync({ force: true });
|
await sequelize.sync({ force: true });
|
||||||
return sequelize;
|
return sequelize;
|
||||||
|
@ -24,4 +25,4 @@ async function init(): Promise<Sequelize> {
|
||||||
export let dbPromise = init();
|
export let dbPromise = init();
|
||||||
export let materialDb = loadMaterialDb();
|
export let materialDb = loadMaterialDb();
|
||||||
|
|
||||||
export { User };
|
export { User, ExerciseInstance };
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
<script lang="ts">
|
<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>
|
</script>
|
||||||
|
|
||||||
{JSON.stringify(exercise)}
|
<details open>
|
||||||
{exercise.style}
|
<summary>Exercise Spec</summary>
|
||||||
|
<pre>{JSON.stringify(exercise, null, 2)}</pre>
|
||||||
|
</details>
|
||||||
|
|
||||||
{#if !exercise}
|
{#if !exercise}
|
||||||
loading
|
loading
|
||||||
{:else if exercise.style == "multipleChoice"}
|
{:else if exercise.style == ExerciseStyle.MultipleChoice}
|
||||||
<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}
|
{/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">
|
<script lang="ts">
|
||||||
|
export let props;
|
||||||
export let question = {
|
export let question = {
|
||||||
|
|
||||||
description: `
|
description: `
|
||||||
|
@ -51,7 +52,7 @@
|
||||||
|
|
||||||
<p>{ question.description }</p>
|
<p>{ question.description }</p>
|
||||||
<ul class="choices">
|
<ul class="choices">
|
||||||
{#each question.choices as choice, index }
|
{#each props.choices as choice, index }
|
||||||
<li>
|
<li>
|
||||||
<input type="radio"
|
<input type="radio"
|
||||||
name="choice"
|
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 { Exercise } from "materialdb/db";
|
||||||
import type { Sequelize } from "sequelize-typescript";
|
import type { Sequelize } from "sequelize-typescript";
|
||||||
|
import type { LoginStatus } from "$lib/auth";
|
||||||
|
import { createInstance } from "$lib/exercise";
|
||||||
|
|
||||||
export async function get(req) {
|
export async function get(req) {
|
||||||
let db: Sequelize = req.locals.db;
|
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({
|
let exercise = await Exercise.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
@ -17,7 +20,10 @@ export async function get(req) {
|
||||||
});
|
});
|
||||||
console.log("picked", exercise);
|
console.log("picked", exercise);
|
||||||
|
|
||||||
|
let instance = await createInstance(loginStatus, exercise);
|
||||||
|
console.log("instance", instance);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
body: { exercise },
|
body: { exercise: instance },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue