Compare commits

...

No commits in common. "master" and "old" have entirely different histories.
master ... old

75 changed files with 4914 additions and 27372 deletions

View File

@ -1,13 +1,8 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.{md,svelte,ts,json,rst}]
indent_size = 2

View File

@ -1,14 +0,0 @@
module.exports = {
"root": true,
"env": {
"browser": true,
"node": true,
},
"extends": ["@nuxtjs/eslint-config-typescript", "plugin:nuxt/recommended", "prettier"],
"plugins": [],
"rules": {
"quote-props": ["error", "always"],
"no-unused-vars": ["off"],
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
},
};

97
.gitignore vendored
View File

@ -1,92 +1,7 @@
test.db*
node_modules
*/package-lock.json
!/package-lock.json
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp
/build
/.svelte-kit
/package

1
.ignore Normal file
View File

@ -0,0 +1 @@
/package-lock.json

View File

@ -1,97 +0,0 @@
###
# Place your Prettier ignore content here
###
# .gitignore content is duplicated here due to https://github.com/prettier/prettier/issues/8506
package-lock.json
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"useTabs": false,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
}

View File

@ -1,7 +0,0 @@
{
useTabs: false,
tabWidth: 2,
singleQuote: false,
trailingComma: "all",
printWidth: 100,
}

10
LICENSE.md Normal file
View File

@ -0,0 +1,10 @@
This project comes in two parts:
- Lesson material, which lives in `material`, licensed with [CC BY-SA 4.0][2].
- Source code, which is everything else in this repository, licensed with [GPL 3.0][1].
The full legal text of the license can be found in a file called LICENSE within
the respective directories.
[1]: https://www.gnu.org/licenses/quick-guide-gplv3.html
[2]: https://creativecommons.org/licenses/by-sa/4.0/

View File

@ -1,68 +1,22 @@
# eduproj
education project
===
## Build Setup
The education project is just my personal ideal education system. Here are its
goals:
```bash
# install dependencies
$ npm install
- **Learning is measured through mastery, which is measured through tests.**
Mastery is similar to what Anki uses and is trained with spaced repetition.
Notably, doing well on a test once doesn't indicate complete mastery and
failing a test doesn't indicate complete unmastery. Skipping a question
lowers mastery a bit less than getting it wrong.
# serve with hot reload at localhost:3000
$ npm run dev
- **Learning by doing.** Many different types of activity formats that should
all contribute to mastery of the given concepts. In addition, mastery of
certain concepts should also backpropagate to the concepts it depends on. The
planned list of activity types are:
* Classic multiple-choice problems
* Short-answer problems (for math)
* Write a short program (+ linting)
* Write a bigger project
# build for production and launch server
$ npm run build
$ npm run start
# generate static project
$ npm run generate
```
For detailed explanation on how things work, check out the [documentation](https://nuxtjs.org).
## Special Directories
You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality.
### `assets`
The assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/assets).
### `components`
The components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/components).
### `layouts`
Layouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts).
### `pages`
This directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/get-started/routing).
### `plugins`
The plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins).
### `static`
This directory contains your static files. Each file inside this directory is mapped to `/`.
Example: `/static/robots.txt` is mapped as `/robots.txt`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/static).
### `store`
This directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/store).

View File

@ -1,3 +0,0 @@
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient();

View File

@ -1,7 +0,0 @@
import { Router } from "express";
const router = Router();
router.post("/claim", async (req, res) => {});
export default router;

View File

@ -1,20 +0,0 @@
import express, { json as jsonBodyParser } from "express";
import userRouter from "./user";
import gradeRouter from "./grade";
const app = express();
app.use(jsonBodyParser({ "strict": true }));
app.get("/hello", (_, res) => {
res.send("hellosu there");
});
// routes
app.use("/grade", gradeRouter);
app.use("/user", userRouter);
export default {
"path": "/api",
"handler": app,
};

View File

@ -1,27 +0,0 @@
import { Router } from "express";
import { hash as bcryptHash } from "bcrypt";
import { prisma } from "./db";
const router = Router();
router.post("/user/register", async (req, res) => {
const body = req.body;
const email = body.email;
const password = body.password;
const passwordHash = await bcryptHash(password, 10);
const newUser = await prisma.user.create({
"data": {
email,
passwordHash,
},
});
res.send({
"id": newUser.id,
});
});
export default router;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,39 +0,0 @@
<template>
<div class="column">
<div class="card">
<header class="card-header">
<p class="card-header-title has-text-grey">
{{ title }}
</p>
</header>
<div class="card-content">
<div class="content has-text-centered">
<b-icon :icon="icon" size="is-large" type="is-primary" />
</div>
</div>
<footer class="card-footer">
<div class="card-footer-item">
<span>
<slot />
</span>
</div>
</footer>
</div>
</div>
</template>
<script>
export default {
"name": "BuefyCard",
"props": {
"title": {
"type": String,
"required": true,
},
"icon": {
"type": String,
"required": true,
},
},
};
</script>

1
jails/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/result

14
jails/default.nix Normal file
View File

@ -0,0 +1,14 @@
{ pkgs ? import <nixpkgs> {} }:
let
ocamlStudentModule = pkgs.callPackage ./ocamlStudentModule.nix {};
in
pkgs.stdenv.mkDerivation {
name = "edujails";
src = ./.;
installPhase = ''
mkdir -p $out/bin
cp ${ocamlStudentModule} $out/bin/ocamlStudentModule
'';
}

View File

@ -0,0 +1,34 @@
{ writeTextFile, nsjail }:
let
inner = writeTextFile {
name = "ocamlStudentModuleInner";
executable = true;
text = ''
#!/bin/sh
INTERFACE_FILE=$1
STUDENT_FILE=$2
DRIVER_FILE=$3
ocamlc -o student.cmi $INTERFACE_FILE
ocamlc -o student.cmo $STUDENT_FILE
ocaml student.cmo $DRIVER_FILE
'';
};
in
writeTextFile {
name = "ocamlStudentModule";
executable = true;
text = ''
JAIL=$(mktemp -d)
mkdir -p $JAIL/bin
cp ${inner} $JAIL/bin/ocamlStudentModule
${nsjail}/bin/nsjail \
-Mo \
--chroot $JAIL \
-R /bin/sh \
-R /bin/ls \
/bin/ls
# /bin/ocamlStudentModule
'';
}

View File

@ -1,58 +0,0 @@
<template>
<div>
<b-navbar class="is-dark" wrapper-class="container">
<template #brand>
<b-navbar-item tag="router-link" :to="{ path: '/' }">
<img src="~assets/buefy.png" alt="Buefy" height="28" />
</b-navbar-item>
</template>
<template #start>
<b-navbar-item v-for="(item, key) of items" :key="key" :to="item.to">
<b-icon :icon="item.icon" /> {{ item.title }}
</b-navbar-item>
</template>
<template #end>
<b-navbar-item tag="div">
<div class="buttons">
<a class="button is-primary">
<strong>Sign up</strong>
</a>
<a class="button is-light"> Log in </a>
</div>
</b-navbar-item>
</template>
</b-navbar>
<section class="main-content columns">
<aside class="column is-2 section">
<p class="menu-label is-hidden-touch">General</p>
</aside>
<div class="container column is-10">
<Nuxt />
</div>
</section>
</div>
</template>
<script>
export default {
"name": "DefaultLayout",
data() {
return {
"items": [
{
"title": "Home",
"icon": "home",
"to": { "name": "index" },
},
{
"title": "Inspire",
"icon": "lightbulb",
"to": { "name": "inspire" },
},
],
};
},
};
</script>

View File

View File

@ -1,3 +0,0 @@
/**
* Mastery algorithm
*/

4
material/README.md Normal file
View File

@ -0,0 +1,4 @@
material
===
Exercises are decoupled from the actual material and can appear wherever they want

7
material/fp-course.yml Normal file
View File

@ -0,0 +1,7 @@
title: Functional Programming Basics
type: listing
content: |
This listing contains some of the basics of functional programming.
- [Data Types](page://fp-datatypes)
- [Functions](page://fp-function)

16
material/fp-datatypes.yml Normal file
View File

@ -0,0 +1,16 @@
title: Data Types
type: concept
summary: |
content: |
exercises:
- name: whatType
style: multipleChoice
description: |
Which of the following can be described as a _function_?
props:
choices:
- foo: bar

46
material/fp-function.yml Normal file
View File

@ -0,0 +1,46 @@
title: Functions
type: concept
summary: |
Functions describe a process of turning *inputs* into *outputs*.
content: |
In a purely mathematical setting, functions typically have one input and one
output, but in functional programming, we can usually get around this either
by using [tuples][1] or by [currying][2].
[1]: page://fp-tuples
[2]: page://fp-currying
exercises:
- name: doubleIt
style: gradedProgram
description: |
Write a function called `doubleIt` that takes an integer and doubles it.
satisfiesConcept:
- fp-function
graders:
ocaml:
style: studentModule
props:
interface: |
val doubleIt : int -> int
driver: |
open List
let () = List.iter
(fun x -> assert ((doubleIt x) = (x * 2)))
(List.init 100 (fun x -> x + 1));
- name: whichIsFunction
style: multipleChoice
description: |
Which of the following can be described as a _function_?
concepts:
- fp-function
props:
choices:
- foo: bar

View File

@ -1,2 +0,0 @@
---
---

View File

@ -1,2 +0,0 @@
---
---

View File

@ -1,9 +0,0 @@
---
---
# Introduction to Calculus
Calculus is a study of how things change at an infinitesimally small level.
Although only a theoretical fantasy, the study of calculus can help us model
real-life processes of change, even though the level of granularity is
different.

2
materialdb/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/test.db
/dist

5
materialdb/README.md Normal file
View File

@ -0,0 +1,5 @@
compile-database
===
This is a script that converts the files from the `material` directory into an
sqlite database with some indexing to allow fast search during run-time.

3
materialdb/db/index.ts Normal file
View File

@ -0,0 +1,3 @@
import { init, Page, Exercise, Grader } from "./page";
export { init, Page, Exercise, Grader };

73
materialdb/db/page.ts Normal file
View File

@ -0,0 +1,73 @@
import { PrimaryKey, ForeignKey, HasMany, Sequelize, Column, Table, Model, DataType } from "sequelize-typescript";
@Table
export class Page extends Model {
@PrimaryKey
@Column(DataType.STRING)
public slug: string;
@Column(DataType.STRING)
public title: string;
@Column(DataType.STRING)
public type: string;
@Column(DataType.STRING)
public content: string;
}
@Table
export class Exercise extends Model {
@PrimaryKey
@Column(DataType.STRING)
public name: string;
@Column(DataType.STRING)
public description: string;
@Column(DataType.STRING)
public style: string;
@Column(DataType.JSON)
public props: any;
@HasMany(() => Grader)
public graders: Grader[];
}
@Table
export class ExerciseSatisfiesConcept extends Model {
@ForeignKey(() => Exercise)
@Column(DataType.STRING)
public exercise_name: string;
@ForeignKey(() => Page)
@Column(DataType.STRING)
public concept_slug: string;
}
@Table
export class Grader extends Model {
@PrimaryKey
@ForeignKey(() => Exercise)
@Column(DataType.STRING)
public exercise_name: string;
@PrimaryKey
@Column(DataType.STRING)
public language: string;
@Column(DataType.STRING)
public style: string;
@Column(DataType.JSON)
public props: any;
}
export async function init(path: string): Promise<Sequelize> {
let sequelize = new Sequelize(`sqlite:${path}`, {
models: [Page, Exercise, Grader],
});
await sequelize.sync({ force: true });
return sequelize;
}

89
materialdb/index.ts Normal file
View File

@ -0,0 +1,89 @@
import { readdir, readFile } from "fs/promises";
import { join } from "path";
import * as yaml from "js-yaml";
import { plainToClass } from "class-transformer";
import { validate } from "class-validator";
import { init, Page, Exercise, Grader } from "./db";
import { Page as PageConfig, Exercise as ExerciseConfig, Grader as GraderConfig } from "./page";
// TODO: configure this thru cmdline or something later
let materials_dir = "../material";
let db_file = "test.db";
let db;
let SLUG_RE = /([A-Za-z\-\_]+)/;
async function loadPageIntoDb(name: string): Promise<void> {
let slug_match = name.match(SLUG_RE);
let slug = slug_match[0];
// if this slug has already been loaded into the database, don't do anything
let hasPage = await Page.count({ where: { slug }}) > 0;
if (hasPage) { return; }
let path = join(materials_dir, name);
let rawData = await readFile(path, { encoding: "utf8" });
let parsedData = yaml.load(rawData);
let page_cfg = plainToClass(PageConfig, parsedData);
await validate(page_cfg);
// save page
let page = new Page({
slug,
title: page_cfg.title,
type: page_cfg.type,
content: page_cfg.content,
});
await page.save();
// save exercises
async function loadExerciseIntoDb(ex_cfg: ExerciseConfig): Promise<void> {
let exercise = new Exercise({
page_slug: slug,
name: ex_cfg.name,
style: ex_cfg.style,
props: ex_cfg.props,
description: ex_cfg.description,
});
await exercise.save();
async function loadConceptsIntoDb(concept: string): Promise<void> {
};
async function loadGraderIntoDb([language, grader_cfg]: [string, GraderConfig]): Promise<void> {
let grader = new Grader({
page_slug: slug,
exercise_name: ex_cfg.name,
language,
style: grader_cfg.style,
props: grader_cfg.props,
});
await grader.save();
}
let graders = ex_cfg.graders;
if (graders != null) {
await Promise.all(Object.entries(graders).map(loadGraderIntoDb));
}
}
let exercises = page_cfg.exercises;
if (exercises != null) {
await Promise.all(exercises.map(loadExerciseIntoDb));
}
}
async function main() {
db = await init(db_file);
// TODO: streaming version of readdir when the folder gets big
let names = await readdir(materials_dir);
await Promise.all(names
.filter(name => name.toLowerCase().endsWith(".yml"))
.map(loadPageIntoDb)
);
}
main();

24
materialdb/package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "materialdb",
"types": "dist/index.d.ts",
"scripts": {
"start": "ts-node index.ts",
"gen": "tsc --declaration"
},
"devDependencies": {
"@types/js-yaml": "^4.0.3",
"@types/node": "^16.7.4",
"@types/validator": "^13.6.3",
"ts-node": "^10.2.1"
},
"dependencies": {
"typescript": "^4.4.2",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.1",
"js-yaml": "^4.1.0",
"reflect-metadata": "^0.1.13",
"sequelize": "^6.6.5",
"sequelize-typescript": "^2.1.0",
"sqlite3": "^4.2.0"
}
}

22
materialdb/page.ts Normal file
View File

@ -0,0 +1,22 @@
export class Page {
public title: string;
public type: string;
public summary?: string;
public content: string;
public exercises?: Exercise[];
};
export class Exercise {
public name: string;
public style: string;
public description: string;
public props: any;
public graders?: Grader[];
};
export class Grader {
public language: string;
public style: string;
public props: any;
};

10
materialdb/tsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"declaration": true,
"outDir": "dist"
}
}

47
notes.txt Normal file
View File

@ -0,0 +1,47 @@
concepts:
- learning targets
- can relate to other concepts in the following ways:
- concept A "depends" on concept B; explaining concept A requires some
information from concept B
- concept A "optdepends" on concept B
- concept A "satisfies" concept B; mastery of concept A implies mastery of
concept B
topics:
- groups of concepts
- can nest infinitely
"reviews" are randomly constructed sets of activities
each user has a mastery level for each concept
references:
- super memo algorithm used by anki: https://en.wikipedia.org/wiki/SuperMemo#Description_of_SM-2_algorithm
---
ocaml should have a runner studentModule, which just puts the student code into
a file called student.ml
the material file defines a student.mli, as well as a driver.ml, then they all
get called using:
```
ocamlc -c student.mli # produces student.cmi
ocamlc -c student.ml # produces student.cmo
ocaml student.cmo driver.ml
```
probably should have like $OCAMLCFLAGS in there to be able to customize each
step as well
---
what are some good classes to start out with?
- functional programming
- ctfs?
- possibly a logic class for math
- proof class
- ML???? look into running it
- can we do reading????????????

View File

@ -1,50 +0,0 @@
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
"head": {
"title": "eduproj",
"htmlAttrs": {
"lang": "en",
},
"meta": [
{ "charset": "utf-8" },
{ "name": "viewport", "content": "width=device-width, initial-scale=1" },
{ "hid": "description", "name": "description", "content": "" },
{ "name": "format-detection", "content": "telephone=no" },
],
"link": [{ "rel": "icon", "type": "image/x-icon", "href": "/favicon.ico" }],
},
// Global CSS: https://go.nuxtjs.dev/config-css
"css": [],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
"plugins": [],
// Auto import components: https://go.nuxtjs.dev/config-components
"components": true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
"buildModules": [
// https://go.nuxtjs.dev/typescript
"@nuxt/typescript-build",
],
// Modules: https://go.nuxtjs.dev/config-modules
"modules": [
// https://go.nuxtjs.dev/buefy
"nuxt-buefy",
// https://go.nuxtjs.dev/axios
"@nuxtjs/axios",
],
// Axios module configuration: https://go.nuxtjs.dev/config-axios
"axios": {
// Workaround to avoid enforcing hard-coded localhost:3000: https://github.com/nuxt-community/axios-module/issues/308
"baseURL": "/",
},
// Build Configuration: https://go.nuxtjs.dev/config-build
"build": {},
"serverMiddleware": [{ "path": "/api", "handler": "~/api" }],
};

30337
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +1,11 @@
{
"name": "eduproj",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint:js": "eslint --ext \".js,.ts,.vue\" --ignore-path .gitignore .",
"lint:prettier": "prettier --check .",
"lint": "npm run lint:js && npm run lint:prettier",
"lintfix": "prettier --write --list-different . && npm run lint:js -- --fix",
"migrate": "prisma migrate dev"
},
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
"@prisma/client": "^3.7.0",
"bcrypt": "^5.0.1",
"core-js": "^3.19.3",
"express": "^4.17.2",
"nuxt": "^2.15.8",
"nuxt-buefy": "^0.4.13",
"vue": "^2.6.14",
"vue-class-component": "^7.2.6",
"vue-server-renderer": "^2.6.14",
"vue-template-compiler": "^2.6.14",
"webpack": "^4.46.0"
"compiledb": "cd materialdb && npm start",
"webdev": "cd web && npm run dev"
},
"devDependencies": {
"@babel/eslint-parser": "^7.16.3",
"@nuxt/types": "^2.15.8",
"@nuxt/typescript-build": "^2.1.0",
"@nuxtjs/eslint-config-typescript": "^8.0.0",
"@nuxtjs/eslint-module": "^3.0.2",
"@types/bcrypt": "^5.0.0",
"eslint": "^8.4.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-nuxt": "^3.1.0",
"eslint-plugin-vue": "^8.2.0",
"prettier": "^2.5.1",
"prisma": "^3.7.0"
"materialdb": "file:materialdb",
"web": "file:web"
}
}

View File

@ -1,35 +0,0 @@
<template>
<section class="section">
<div>
<Manim> </Manim>
</div>
<div class="columns is-mobile">
<card title="Free" icon="github">
Open source on <a href="https://github.com/buefy/buefy"> GitHub </a>
</card>
<card title="Responsive" icon="cellphone-link">
<b class="has-text-grey"> Every </b> component is responsive
</card>
<card title="Modern" icon="alert-decagram">
Built with <a href="https://vuejs.org/"> Vue.js </a> and
<a href="http://bulma.io/"> Bulma </a>
</card>
<card title="Lightweight" icon="arrange-bring-to-front"> No other internal dependency </card>
</div>
</section>
</template>
<script>
import Card from "~/components/Card";
export default {
"name": "IndexPage",
"components": {
Card,
},
};
</script>

View File

@ -1,67 +0,0 @@
-- CreateTable
CREATE TABLE "User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"name" TEXT,
"passwordHash" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "ApiKey" (
"key" TEXT NOT NULL PRIMARY KEY,
"expires" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"ownerId" INTEGER NOT NULL,
CONSTRAINT "ApiKey_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Page" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT
);
-- CreateTable
CREATE TABLE "Concept" (
"name" TEXT NOT NULL PRIMARY KEY
);
-- CreateTable
CREATE TABLE "Exercise" (
"name" TEXT NOT NULL PRIMARY KEY,
"isGroup" BOOLEAN NOT NULL,
"parentName" TEXT,
"graderConfig" TEXT NOT NULL,
"graderType" TEXT NOT NULL,
CONSTRAINT "Exercise_parentName_fkey" FOREIGN KEY ("parentName") REFERENCES "Exercise" ("name") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "ExerciseInstance" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"exerciseName" TEXT NOT NULL,
"userId" INTEGER NOT NULL,
"timeSubmitted" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"timeClaimed" DATETIME NOT NULL,
"timeoutSeconds" INTEGER NOT NULL,
"timeCompleted" DATETIME,
"needsGrading" BOOLEAN NOT NULL,
"gradingProgress" TEXT NOT NULL,
"gradingStatus" TEXT NOT NULL,
"gradingVerdict" TEXT NOT NULL,
"gradingFeedback" TEXT NOT NULL,
CONSTRAINT "ExerciseInstance_exerciseName_fkey" FOREIGN KEY ("exerciseName") REFERENCES "Exercise" ("name") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "ExerciseInstance_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "ExerciseConceptSat" (
"exerciseName" TEXT NOT NULL,
"conceptName" TEXT NOT NULL,
"satisfiesFactor" REAL NOT NULL,
PRIMARY KEY ("exerciseName", "conceptName"),
CONSTRAINT "ExerciseConceptSat_exerciseName_fkey" FOREIGN KEY ("exerciseName") REFERENCES "Exercise" ("name") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "ExerciseConceptSat_conceptName_fkey" FOREIGN KEY ("conceptName") REFERENCES "Concept" ("name") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

View File

@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"

View File

@ -1,92 +0,0 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:./test.db"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
passwordHash String
solvedExercises ExerciseInstance[]
apiKeys ApiKey[]
}
model ApiKey {
key String @id @default(uuid())
expires DateTime @default(now())
scopes String // JSON array of scopes
owner User @relation(fields: [ownerId], references: [id])
ownerId Int
}
model Page {
id Int @id @default(autoincrement())
}
model Concept {
name String @id
exercises ExerciseConceptSat[]
}
model Exercise {
name String @id
concepts ExerciseConceptSat[]
instances ExerciseInstance[]
isGroup Boolean
children Exercise[] @relation("Lineage")
parent Exercise? @relation("Lineage", fields: [parentName], references: [name])
parentName String?
// https://github.com/prisma/prisma/issues/3786
graderConfig String?
}
model ExerciseInstance {
id Int @id @default(autoincrement())
exercise Exercise @relation(fields: [exerciseName], references: [name])
exerciseName String
user User @relation(fields: [userId], references: [id])
userId Int
job Job? @relation(fields: [jobId], references: [id])
jobId Int?
}
model Job {
id Int @id @default(autoincrement())
timeSubmitted DateTime @default(now())
timeClaimed DateTime
timeoutSeconds Int
timeCompleted DateTime?
gradingProgress String // JSON progress information
gradingStatus String
gradingVerdict String
gradingFeedback String // JSON
ExerciseInstance ExerciseInstance[]
}
model ExerciseConceptSat {
exercise Exercise @relation(fields: [exerciseName], references: [name])
exerciseName String
concept Concept @relation(fields: [conceptName], references: [name])
conceptName String
satisfiesFactor Float
@@id([exerciseName, conceptName])
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -1,10 +0,0 @@
# STORE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Vuex Store files.
Vuex Store option is implemented in the Nuxt.js framework.
Creating a file in this directory automatically activates the option in the framework.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).

View File

@ -1,21 +0,0 @@
{
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"moduleResolution": "Node",
"lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
"esModuleInterop": true,
"allowJs": true,
"sourceMap": true,
"strict": true,
"noEmit": true,
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"@/*": ["./*"]
},
"types": ["@nuxt/types", "@nuxtjs/axios", "@types/node"]
},
"exclude": ["node_modules", ".nuxt", "dist"]
}

20
web/.eslintrc.cjs Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2019
},
env: {
browser: true,
es2017: true,
node: true
}
};

4
web/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/build
/.svelte-kit
/package
/test.db

43
web/package.json Normal file
View File

@ -0,0 +1,43 @@
{
"name": "web",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"preview": "svelte-kit preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/kit": "next",
"@types/node": "^16.7.5",
"@types/sequelize": "^4.28.10",
"@types/validator": "^13.6.3",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-svelte3": "^3.2.0",
"node-sass": "^6.0.1",
"prettier": "~2.2.1",
"prettier-plugin-svelte": "^2.2.0",
"sqlite3": "^5.0.2",
"svelte": "^3.34.0",
"svelte-check": "^2.0.0",
"svelte-preprocess": "^4.0.0",
"tslib": "^2.0.0",
"typescript": "^4.0.0"
},
"type": "module",
"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",
"ts-pattern": "^3.2.5"
}
}

18
web/src/app.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Heebo:wght@300;400;700&display=swap" integrity="sha384-TIFtbbKQ3b73InoKF2MDgqFhPYGLQ1h2cnNtGUFVvvd/eRw94RvudHY4y+MMmbw4" crossorigin="anonymous">
%svelte.head%
<style>html, body { margin: 0; paddding: 0; }</style>
</head>
<body>
<div id="svelte">%svelte.body%</div>
</body>
</html>

2
web/src/global.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
/// <reference types="@sveltejs/kit" />
/// <reference types="materialdb" />

9
web/src/hooks/index.ts Normal file
View File

@ -0,0 +1,9 @@
import { checkLogin } from "$lib/auth";
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);
}

View File

@ -0,0 +1,20 @@
<script lang="ts">
import { onMount } from "svelte";
import Exercise from "$lib/exercise/Exercise.svelte";
let state = "loading";
let exercise = null;
onMount(async () => {
let resp = await fetch("/api/recommendExercise");
let body = await resp.json();
exercise = body.exercise;
state = "finished";
});
</script>
{#if state == "loading"}
loading...
{:else}
<Exercise exercise={exercise} />
{/if}

19
web/src/lib/auth.ts Normal file
View File

@ -0,0 +1,19 @@
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 user_id?: number,
public username?: string,
) {}
}
export function checkLogin(req: Request): LoginStatus {
console.log("req", req);
return new LoginStatus(false);
}

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>

1
web/src/lib/consts.ts Normal file
View File

@ -0,0 +1 @@

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

28
web/src/lib/db/index.ts Normal file
View File

@ -0,0 +1,28 @@
import { Sequelize } from "sequelize-typescript";
import { User } from "./user";
import { ExerciseInstance } from "./exercise";
import { Page, Exercise, Grader } from "materialdb/db";
async function loadMaterialDb() {
// TODO: ensure this database is read-only?
// possibly could just chmod -w it at the start but that's kinda hacky
let sequelize = new Sequelize(`sqlite:../materialdb/test.db`, {
models: [Page, Exercise, Grader],
});
await sequelize.sync();
return sequelize;
}
async function init(): Promise<Sequelize> {
let sequelize = new Sequelize(`sqlite:test.db`, {
models: [User, ExerciseInstance],
});
await sequelize.sync({ force: true });
return sequelize;
}
export let dbPromise = init();
export let materialDb = loadMaterialDb();
export { User, ExerciseInstance };

16
web/src/lib/db/user.ts Normal file
View File

@ -0,0 +1,16 @@
import { DataType, Unique, Column, Table, Model } from "sequelize-typescript";
@Table
export class User extends Model {
@Column(DataType.STRING)
public email: string;
@Unique
@Column(DataType.STRING)
public normalizedEmail: string;
}
@Table
export class UserMastery extends Model {
}

View File

@ -0,0 +1,22 @@
<script lang="ts">
import MultipleChoice from "./MultipleChoice/Component.svelte";
import GradedProgram from "./GradedProgram/Component.svelte";
import { ExerciseStyle, ExerciseInfo } from "$lib/exercise";
export let exercise: ExerciseInfo;
</script>
<details open>
<summary>Exercise Spec</summary>
<pre>{JSON.stringify(exercise, null, 2)}</pre>
</details>
{#if !exercise}
loading
{: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}

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

@ -0,0 +1,142 @@
<script lang="ts">
export let props;
export let question = {
description: `
what is 1 + 1?
`,
concepts: [ "addition" ],
choices: [
{ text: "1", correct: false },
{ text: "2", correct: true },
{ text: "3", correct: false },
],
};
// State related variables
let state = "ask";
let currentChoice: number = -1;
let wasCorrect = false;
let choose = (index: number) => {
currentChoice = index;
};
let answer = async (sure: boolean) => {
state = "submitting";
let resp = await fetch("/api/submit", {
method: "POST",
body: JSON.stringify({
}),
});
state = "answer";
wasCorrect = question.choices[currentChoice].correct;
};
let clearChoice = () => {
currentChoice = -1;
};
let goBack = () => {
state = "ask";
currentChoice = -1;
};
</script>
<div class="quiz-box">
{#if state == "ask" }
<small>Q:</small>
{:else}
<small>A:</small>
{/if}
<p>{ question.description }</p>
<ul class="choices">
{#each props.choices as choice, index }
<li>
<input type="radio"
name="choice"
class="box"
id={"choice" + index}
checked={currentChoice == index}
disabled={state != "ask"}
on:change={() => choose(index)} />
<label for={"choice" + index}>{ choice.text }</label>
</li>
{/each}
</ul>
{#if state == "ask" }
<div class="answer-buttons">
{#if currentChoice < 0}
<button>Skip</button>
{:else}
<button on:click={() => clearChoice()}>Clear</button>
{/if}
<button on:click={() => answer(false)} disabled={currentChoice < 0}>Not sure</button>
<button on:click={() => answer(true)} disabled={currentChoice < 0}>I'm sure</button>
</div>
{:else}
{#if wasCorrect }
<p>you Got it!</p>
{:else}
<p>you Failed</p>
{/if}
<div> <button on:click={() => goBack()}>Go back</button> </div>
{/if}
</div>
<style lang="scss" scoped>
.quiz-box {
border: 1px solid gray;
border-radius: 8px;
padding: 8px;
.choices {
display: flex;
flex-direction: row;
justify-content: space-between;
padding-left: 0;
li {
list-style: none;
flex-grow: 1;
margin: 12px;
width: 100%;
input.box {
display: none;
}
label {
border-radius: 8px;
cursor: pointer;
display: block;
width: 100%;
padding: 6px;
box-sizing: border-box;
text-align: center;
}
input.box:not(:checked) + label {
border: 2px solid #ccc;
}
input.box:checked + label {
border: 2px solid red;
background-color: lighten(red, 45%);
font-weight: bold;
}
}
}
.answer-buttons {
display: flex;
justify-content: flex-end;
button {
margin: 6px;
}
}
}
</style>

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

@ -0,0 +1,174 @@
<div id="app">
<div class="content-wrap">
<div class="container">
<header class="header">
<div class="brand"><a href="/">Edu</a></div>
<nav class="header-nav">
<ul class="list list-reset">
<li><a href="/about">About</a></li>
<li><a href="/learn">Learn</a></li>
<li><a href="/browse">Browse</a></li>
<li><a href="/contribute">Contribute</a></li>
</ul>
</nav>
</header>
<slot></slot>
</div>
<footer class="footer">
<div class="container">
<div class="footer-blocks">
<div class="footer-block">
<div class="footer-block-title">Eduproj</div>
<ul class="list-reset">
<li><a href="https://git.mzhang.io/michael/eduproj" target="_blank">Source Code</a></li>
</ul>
</div>
<div class="footer-block">
<div class="footer-block-title">For Students</div>
<ul class="list-reset">
<li><a href="/about">Edu</a></li>
<li><a href="/about">Edu</a></li>
<li><a href="/about">Edu</a></li>
<li><a href="/about">Edu</a></li>
</ul>
</div>
<div class="footer-block">
<div class="footer-block-title">About Us</div>
<ul class="list-reset">
<li><a href="/about">About Me</a></li>
<li><a href="/about">About Me</a></li>
<li><a href="/about">About Me</a></li>
<li><a href="/about">About Me</a></li>
</ul>
</div>
</div>
</div>
</footer>
</div>
</div>
<style lang="scss" scoped>
$footer-height: 180px;
#app {
font-family: Heebo, sans-serif;
position: relative;
min-height: 100vh;
.content-wrap {
border-top: 10px solid #c00;
padding-bottom: $footer-height;
> .container {
padding-bottom: 36px;
}
}
}
.container {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
@media (min-width: 768px) {
.container {
width: 750px;
}
}
@media (min-width: 992px) {
.container {
width: 970px;
}
}
@media (min-width: 1200px) {
.container {
width: 1170px;
}
}
.list-reset {
list-style: none;
padding: 0;
justify-content: flex-end;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
height: 80px;
.brand {
margin-right: 32px;
a {
color: black;
font-weight: bold;
text-decoration: none;
font-size: 1.5em;
}
}
.header-nav {
flex-grow: 1;
.list {
display: flex;
align-items: center;
flex-grow: 1;
white-space: nowrap;
margin-bottom: 0;
li {
margin: 0;
box-sizing: inherit;
a {
display: block;
text-decoration: none;
color: black;
padding: 0 32px;
}
}
}
}
}
.footer {
background-color: #eee;
position: absolute;
bottom: 0;
width: 100%;
height: $footer-height;
padding-top: 24px;
.footer-blocks {
display: flex;
flex-wrap: wrap;
margin-right: -24px;
margin-left: -24px;
margin-top: -12px;
.footer-block {
flex-grow: 1;
flex-basis: 160px;
box-sizing: content-box;
padding: 12px 24px;
.footer-block-title {
text-transform: uppercase;
font-weight: bold;
line-height: 22px;
}
a {
color: lighten(black, 20%);
text-decoration: none;
}
}
}
}
</style>

View File

@ -0,0 +1,4 @@
<h2>How does it work?</h2>
<ul>
</ul>

View File

@ -0,0 +1,29 @@
// recommend a NEW activity (not a review) to do for the user based on their
// current mastery levels
//
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;
let loginStatus: LoginStatus = req.locals.loginStatus;
console.log("login Status:", loginStatus);
let exercise = await Exercise.findOne({
where: {
// TODO: join against ExerciseSatisfiesConcept
},
order: db.random(),
});
console.log("picked", exercise);
let instance = await createInstance(loginStatus, exercise);
console.log("instance", instance);
return {
body: { exercise: instance },
};
}

View File

@ -0,0 +1,9 @@
import { User } from "$lib/db";
export async function post(req) {
// get the activity from the database
return {
body: { count: await User.count() },
};
}

View File

@ -0,0 +1,11 @@
<script lang="ts">
import MasteryDemo from "$lib/MasteryDemo.svelte";
</script>
<h1>Learn by mastery</h1>
<MasteryDemo />
<p>
yadda yadda yadda what's unique about this mastery based learning appraoch
</p>

BIN
web/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

15
web/svelte.config.js Normal file
View File

@ -0,0 +1,15 @@
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess(),
kit: {
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte'
}
};
export default config;

33
web/tsconfig.json Normal file
View File

@ -0,0 +1,33 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020", "DOM"],
"target": "es2020",
/**
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
to enforce using \`import type\` instead of \`import\` for Types.
*/
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"resolveJsonModule": true,
/**
To have warnings/errors of the Svelte compiler at the correct position,
enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"baseUrl": ".",
"allowJs": true,
"checkJs": true,
"paths": {
"$lib": ["src/lib"],
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
}