This commit is contained in:
Michael Zhang 2021-09-01 08:32:43 -05:00
parent b3917dc756
commit 8c213b0714
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
16 changed files with 162 additions and 25 deletions

View file

@ -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"
} }
} }

View file

@ -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);
} }

View file

@ -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"}

View file

@ -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 {

View 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>

View 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;
}

View file

@ -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 };

View file

@ -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}

View file

@ -0,0 +1,9 @@
<script lang="ts">
import Editor from "$lib/components/Editor.svelte";
export let props;
</script>
<code>{props}</code>
<Editor />

View 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;

View file

@ -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"

View file

@ -0,0 +1,7 @@
export class Props {
}
export class MaskedInfo {
}

View 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;
}

View 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 };

View file

@ -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 {};
}

View file

@ -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 },
}; };
} }