diff --git a/package.json b/package.json index f6bf406..4f24985 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,9 @@ "packageManager": "pnpm@9.5.0", "dependencies": { "@types/d3": "^7.4.3", + "@types/uuid": "^10.0.0", "d3": "^7.9.0", - "dotenv": "^16.4.5" + "dotenv": "^16.4.5", + "uuid": "^10.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 933e620..f903ee1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,12 +11,18 @@ importers: '@types/d3': specifier: ^7.4.3 version: 7.4.3 + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 d3: specifier: ^7.9.0 version: 7.9.0 dotenv: specifier: ^16.4.5 version: 16.4.5 + uuid: + specifier: ^10.0.0 + version: 10.0.0 devDependencies: '@prisma/client': specifier: ^5.14.0 @@ -608,6 +614,9 @@ packages: '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@typescript-eslint/eslint-plugin@6.21.0': resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -1849,6 +1858,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -2434,6 +2447,8 @@ snapshots: '@types/semver@7.5.8': {} + '@types/uuid@10.0.0': {} + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.10.0 @@ -3724,6 +3739,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@10.0.0: {} + v8-compile-cache-lib@3.0.1: {} vite-node@1.6.0(@types/node@20.12.12)(sass@1.77.2): diff --git a/prisma/migrations/20240710131522_add_lectures/migration.sql b/prisma/migrations/20240710131522_add_lectures/migration.sql new file mode 100644 index 0000000..a2b7190 --- /dev/null +++ b/prisma/migrations/20240710131522_add_lectures/migration.sql @@ -0,0 +1,53 @@ +/* + Warnings: + + - A unique constraint covering the columns `[code]` on the table `Course` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropForeignKey +ALTER TABLE "Subject" DROP CONSTRAINT "Subject_domainId_fkey"; + +-- AlterTable +ALTER TABLE "Domain" ALTER COLUMN "name" DROP NOT NULL, +ALTER COLUMN "style" DROP NOT NULL; + +-- AlterTable +ALTER TABLE "Subject" ALTER COLUMN "name" DROP NOT NULL, +ALTER COLUMN "style" DROP NOT NULL, +ALTER COLUMN "domainId" DROP NOT NULL; + +-- CreateTable +CREATE TABLE "Lecture" ( + "id" SERIAL NOT NULL, + "graphId" INTEGER NOT NULL, + "name" TEXT, + + CONSTRAINT "Lecture_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_LectureToSubject" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "_LectureToSubject_AB_unique" ON "_LectureToSubject"("A", "B"); + +-- CreateIndex +CREATE INDEX "_LectureToSubject_B_index" ON "_LectureToSubject"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "Course_code_key" ON "Course"("code"); + +-- AddForeignKey +ALTER TABLE "Subject" ADD CONSTRAINT "Subject_domainId_fkey" FOREIGN KEY ("domainId") REFERENCES "Domain"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Lecture" ADD CONSTRAINT "Lecture_graphId_fkey" FOREIGN KEY ("graphId") REFERENCES "Graph"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_LectureToSubject" ADD CONSTRAINT "_LectureToSubject_A_fkey" FOREIGN KEY ("A") REFERENCES "Lecture"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_LectureToSubject" ADD CONSTRAINT "_LectureToSubject_B_fkey" FOREIGN KEY ("B") REFERENCES "Subject"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 276e469..ecff9a1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -109,7 +109,6 @@ model Lecture { id Int @id @default(autoincrement()) graph Graph @relation(fields: [graphId], references: [id]) graphId Int - index Int - name String + name String? subjects Subject[] } diff --git a/src/lib/assets/info-icon.svg b/src/lib/assets/info-icon.svg new file mode 100644 index 0000000..874471b --- /dev/null +++ b/src/lib/assets/info-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/lib/components/Dropdown.svelte b/src/lib/components/Dropdown.svelte index a7028b1..8f11b4b 100644 --- a/src/lib/components/Dropdown.svelte +++ b/src/lib/components/Dropdown.svelte @@ -1,11 +1,8 @@ @@ -83,16 +72,16 @@ {/if} diff --git a/src/lib/components/GraphInterface.svelte b/src/lib/components/GraphSVG.svelte similarity index 89% rename from src/lib/components/GraphInterface.svelte rename to src/lib/components/GraphSVG.svelte index cdd946d..1f902b9 100644 --- a/src/lib/components/GraphInterface.svelte +++ b/src/lib/components/GraphSVG.svelte @@ -2,9 +2,9 @@ @@ -48,7 +48,7 @@ on:click={() => graphSVG.view = View.lectures} > Lectures - + diff --git a/src/lib/components/Infobox.svelte b/src/lib/components/Infobox.svelte new file mode 100644 index 0000000..fc65ae2 --- /dev/null +++ b/src/lib/components/Infobox.svelte @@ -0,0 +1,53 @@ + + + + + + +
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/src/lib/components/Validation.svelte b/src/lib/components/Validation.svelte index 52555d3..998423f 100644 --- a/src/lib/components/Validation.svelte +++ b/src/lib/components/Validation.svelte @@ -2,7 +2,7 @@ + + + + + + + + + + + +
+ + Settings + + + +

Autolayout

+

You are about to activate autolayout. This will irreversibly alter the layout of your graph.

+ + + When you activate autolayout, 'unlocked' nodes with a dashed outline will repel other nodes, and be attracted by their relations. Drag or click nodes to 'lock' them in place. + + +
+ +
+
+ + +
+ +
+ + + + + + + + diff --git a/src/lib/styles/DomainSettings.svelte b/src/lib/styles/DomainSettings.svelte new file mode 100644 index 0000000..8923dbb --- /dev/null +++ b/src/lib/styles/DomainSettings.svelte @@ -0,0 +1,307 @@ + + + + + + + + +
+ + +
+

Domains

+ go to relations + +
+ + + +
+ + + {#if $graph.domains.some(domain => domainMatchesQuery(domain_query, domain))} + + +
+ + +
+ Name + { + domain_style_sort = undefined + domain_name_sort = !domain_name_sort + $graph.sort(SortOption.domains | SortOption.name, domain_name_sort) + update() + }} + /> +
+ + +
+ Style + { + domain_name_sort = undefined + domain_style_sort = !domain_style_sort + $graph.sort(SortOption.domains | SortOption.style, domain_style_sort) + update() + }} + /> +
+
+ + {:else} + + +
No domains found
+ + {/if} + + + {#each $graph.domains as domain} + {#if domainMatchesQuery(domain_query, domain)} +
+ + {domain.index + 1} + { await domain.delete(); update() }} /> + + + +
+ {/if} + {/each} +
+ + +
+ + +
+

Relations

+ go to domains + +
+ + + +
+ + + {#if $graph.domain_relations.some(relation => relationMatchesQuery(relation_query, relation))} + + +
+ + +
+ From + { + relation_child_sort = undefined + relation_parent_sort = !relation_parent_sort + $graph.sort(SortOption.relations | SortOption.domains | SortOption.parent, relation_parent_sort) + update() + }} + /> +
+ + +
+ To + { + relation_parent_sort = undefined + relation_child_sort = !relation_child_sort + $graph.sort(SortOption.relations | SortOption.domains | SortOption.child, relation_child_sort) + update() + }} + /> +
+
+ + {:else} + + +
No relations found
+ + {/if} + + + {#each $graph.domain_relations as relation} + {#if relationMatchesQuery(relation_query, relation)} +
+ + {relation.index + 1} + { relation.delete(); update() }} /> + + + + +
+ {/if} + {/each} +
+ + + + + + diff --git a/src/lib/styles/GraphSVG.svelte b/src/lib/styles/GraphSVG.svelte new file mode 100644 index 0000000..d781e1d --- /dev/null +++ b/src/lib/styles/GraphSVG.svelte @@ -0,0 +1,156 @@ + + + + + + + +
+
+ + + + + + + +
+ + + +
+ + +
+
+ + + + + + \ No newline at end of file diff --git a/src/params/number.ts b/src/params/number.ts new file mode 100644 index 0000000..4efdac1 --- /dev/null +++ b/src/params/number.ts @@ -0,0 +1,3 @@ +export function match(param) { + return !isNaN(Number(param)) +} diff --git a/src/routes/[course]/[link]/+page.server.ts b/src/routes/[course]/[link]/+page.server.ts new file mode 100644 index 0000000..2f9adcb --- /dev/null +++ b/src/routes/[course]/[link]/+page.server.ts @@ -0,0 +1,19 @@ + +// External imports +import prisma from '$lib/server/prisma' + +// Internal imports +import { GraphHelper } from '$lib/server/helpers' + +// Load +export const load = async ({ params }) => { + const graphId = 1 // TODO replace with actual graph id, once we have a way to get it + + const graph = await GraphHelper.toDTO( + (await prisma.graph.findUnique({ + where: { id: graphId } + }))! + ) + + return { graph } +} diff --git a/src/routes/[course]/[link]/+page.svelte b/src/routes/[course]/[link]/+page.svelte index cd98bb9..50196d1 100644 --- a/src/routes/[course]/[link]/+page.svelte +++ b/src/routes/[course]/[link]/+page.svelte @@ -1,17 +1,8 @@ @@ -19,4 +10,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/routes/[course]/[link]/+page.ts b/src/routes/[course]/[link]/+page.ts index 84f8d2c..d7f4a11 100644 --- a/src/routes/[course]/[link]/+page.ts +++ b/src/routes/[course]/[link]/+page.ts @@ -1,9 +1,12 @@ -import { Graph } from '$scripts/entities' +// Svelte imports import type { PageLoad } from './$types' -export const load: PageLoad = ({ params }) => { - return { - graph: Graph.revive({"id":1,"name":"New Graph","domains":[{"id":1,"x":0,"y":0,"style":"prosperous-red","name":"Domain 1","parents":[],"children":[2,3]},{"id":2,"x":0,"y":0,"style":"energizing-orange","name":"Domain 2","parents":[1],"children":[3]},{"id":3,"x":0,"y":0,"style":"sunny-yellow","name":"Domain 3","parents":[1,2],"children":[]}],"subjects":[{"id":1,"x":0,"y":0,"domain":1,"name":"Subject 1","parents":[],"children":[2,3]},{"id":2,"x":0,"y":0,"domain":1,"name":"Subject 2","parents":[1],"children":[3]},{"id":3,"x":0,"y":0,"domain":2,"name":"Subject 3","parents":[1,2],"children":[4,5]},{"id":4,"x":0,"y":0,"domain":3,"name":"Subject 4","parents":[3],"children":[]},{"id":5,"x":0,"y":0,"domain":3,"name":"Subject 5","parents":[3],"children":[]}],"lectures":[{"id":1,"name":"Lecture 1","subjects":[2,3]}]}) - } +// Internal imports +import { Graph } from '$scripts/entities' +import { graph } from '$stores' + +// load +export const load: PageLoad = ({ data }) => { + graph.set(Graph.revive(data.graph)) } \ No newline at end of file diff --git a/src/routes/api/domain/[id=number]/+server.ts b/src/routes/api/domain/[id=number]/+server.ts new file mode 100644 index 0000000..9d74097 --- /dev/null +++ b/src/routes/api/domain/[id=number]/+server.ts @@ -0,0 +1,7 @@ +import { DomainHelper } from '$lib/server/helpers'; + +export async function DELETE({ params }) { + const id = Number(params.id); + await DomainHelper.remove(id); + return new Response(null, { status: 200 }); +} diff --git a/src/routes/api/graph/[id=number]/+server.ts b/src/routes/api/graph/[id=number]/+server.ts new file mode 100644 index 0000000..0d3382b --- /dev/null +++ b/src/routes/api/graph/[id=number]/+server.ts @@ -0,0 +1,29 @@ +import { json, error } from '@sveltejs/kit'; + +import { GraphHelper } from '$lib/server/helpers'; +import { getGraphById } from '$lib/server/controllers/GraphController'; +import { type SerializedGraph } from '$scripts/entities'; + + +/** + * Fetches a graph by its ID and returns it as a DTO + */ +export async function GET({ params }): Promise { + const id = Number(params.id); + const graph = await getGraphById(id); + return json(graph); +} + + +/** + * Updates a graph from a DTO + */ +export async function PUT({ request, params }): Promise { + const id = Number(params.id); + const dto: SerializedGraph = await request.json(); + + if (id !== dto.id) error(400, 'ID mismatch'); + + await GraphHelper.updateFromDTO(dto); + return new Response(null, { status: 201 }); +} diff --git a/src/routes/api/lecture/[id=number]/+server.ts b/src/routes/api/lecture/[id=number]/+server.ts new file mode 100644 index 0000000..100e3b9 --- /dev/null +++ b/src/routes/api/lecture/[id=number]/+server.ts @@ -0,0 +1,7 @@ +import { LectureHelper } from '$lib/server/helpers'; + +export async function DELETE({ params }) { + const id = Number(params.id); + await LectureHelper.remove(id); + return new Response(null, { status: 200 }); +} diff --git a/src/routes/api/subject/[id=number]/+server.ts b/src/routes/api/subject/[id=number]/+server.ts new file mode 100644 index 0000000..afdec83 --- /dev/null +++ b/src/routes/api/subject/[id=number]/+server.ts @@ -0,0 +1,7 @@ +import { SubjectHelper } from '$lib/server/helpers'; + +export async function DELETE({ params }) { + const id = Number(params.id); + await SubjectHelper.remove(id); + return new Response(null, { status: 200 }); +} diff --git a/src/routes/app/course/[course]/graph/[graph]/layout/+page.server.ts b/src/routes/app/course/[course]/graph/[graph]/layout/+page.server.ts new file mode 100644 index 0000000..42f2947 --- /dev/null +++ b/src/routes/app/course/[course]/graph/[graph]/layout/+page.server.ts @@ -0,0 +1,26 @@ + +// External imports +import prisma from '$lib/server/prisma' + +// Internal imports +import { CourseHelper, GraphHelper } from '$lib/server/helpers' + +// Load +export const load = async ({ params }) => { + const courseCode = params.course + const graphId = Number(params.graph) + + const course = await CourseHelper.toDTO( + (await prisma.course.findUnique({ + where: { code: courseCode } + }))! + ) + + const graph = await GraphHelper.toDTO( + (await prisma.graph.findUnique({ + where: { id: graphId } + }))! + ) + + return { course, graph } +} diff --git a/src/routes/app/course/[course]/graph/[graph]/layout/+page.svelte b/src/routes/app/course/[course]/graph/[graph]/layout/+page.svelte index d6a31ba..fea6c8c 100644 --- a/src/routes/app/course/[course]/graph/[graph]/layout/+page.svelte +++ b/src/routes/app/course/[course]/graph/[graph]/layout/+page.svelte @@ -1,25 +1,23 @@ @@ -28,38 +26,52 @@ - - - + + +
- Settings - + + Settings + + + +

Autolayout

+

You are about to activate autolayout. This will irreversibly alter the layout of your graph.

+ + + When you activate autolayout, 'unlocked' nodes with a dashed outline will repel other nodes, and be attracted by their relations. Drag or click nodes to 'lock' them in place. + + +
+ +
+
- +
@@ -73,6 +85,11 @@ @use "$styles/variables.sass" as * @use "$styles/palette.sass" as * + .button-row + display: flex + justify-content: flex-end + margin-top: $card-thin-padding + .editor height: 800px diff --git a/src/routes/app/course/[course]/graph/[graph]/layout/+page.ts b/src/routes/app/course/[course]/graph/[graph]/layout/+page.ts index 2bcd915..cbb040b 100644 --- a/src/routes/app/course/[course]/graph/[graph]/layout/+page.ts +++ b/src/routes/app/course/[course]/graph/[graph]/layout/+page.ts @@ -1,115 +1,10 @@ -import { Course, Graph } from '$scripts/entities' -import type { PageLoad } from './$types' +// Internal imports +import { Course, Graph } from '$lib/scripts/entities' +import { course, graph } from '$stores' -export const load: PageLoad = ({ params }) => { - return { - course: Course.revive({code: 'CSE1200', name: 'Calculus', users: [{name: 'Bram Kreulen', permissions: 'admin'}]}), - graph: Graph.revive({ - id: 1, - name: 'Calculus Graph', - domains: [ - { - id: 1, - x: 0, - y: 0, - style: 'prosperous-red', - name: 'Domain A', - parents: [], - children: [2, 3] - }, - { - id: 2, - x: 0, - y: 0, - style: 'electric-green', - name: 'Domain B', - parents: [1], - children: [3] - }, - { - id: 3, - x: 0, - y: 0, - style: 'mysterious-blue', - name: 'Domain C', - parents: [1, 2], - children: [] - } - ], - subjects: [ - { - id: 1, - x: 0, - y: 0, - domain: 1, - name: 'Subject 1', - parents: [], - children: [2, 3, 5] - }, - { - id: 2, - x: 0, - y: 0, - domain: 1, - name: 'Subject 2', - parents: [1], - children: [] - }, - { - id: 3, - x: 0, - y: 0, - domain: 2, - name: 'Subject 3', - parents: [1], - children: [4, 5] - }, - { - id: 4, - x: 0, - y: 0, - domain: 2, - name: 'Subject 4', - parents: [3], - children: [] - }, - { - id: 5, - x: 0, - y: 0, - domain: 3, - name: 'Subject 5', - parents: [3, 1], - children: [6] - }, - { - id: 6, - x: 0, - y: 0, - domain: 3, - name: 'Subject 6', - parents: [5], - children: [] - } - ], - lectures: [ - { - id: 1, - name: 'Lecture 1', - subjects: [1, 2] - }, - { - id: 2, - name: 'Lecture 2', - subjects: [3, 4] - }, - { - id: 3, - name: 'Lecture 3', - subjects: [5, 6] - } - ] - }) - } +// Load +export const load = ({ data }) => { + course.set(Course.revive(data.course)) + graph.set(Graph.revive(data.graph)) } \ No newline at end of file diff --git a/src/routes/app/course/[course]/graph/[graph]/settings/+page.server.ts b/src/routes/app/course/[course]/graph/[graph]/settings/+page.server.ts index e126757..c49bb96 100644 --- a/src/routes/app/course/[course]/graph/[graph]/settings/+page.server.ts +++ b/src/routes/app/course/[course]/graph/[graph]/settings/+page.server.ts @@ -1,46 +1,61 @@ -import { fail } from '@sveltejs/kit'; -import prisma from '$lib/server/prisma'; -import { CourseHelper, GraphHelper, DomainHelper, SubjectHelper } from '$lib/server/helpers'; +// Svelte imports +import { fail } from '@sveltejs/kit' +// External imports +import prisma from '$lib/server/prisma' +// Internal imports +import { CourseHelper, GraphHelper, DomainHelper, SubjectHelper, LectureHelper } from '$lib/server/helpers' + +// Actions export const actions = { - newDomain: async ({ params, request }) => { - const data = await request.formData(); - const graphId = Number(data.get('graph')); + newDomain: async ({ params, request }): Promise => { + const data = await request.formData() + const graphId = Number(data.get('graph')) - if (!graphId) return fail(400, { graphId, missing: true }); + if (!graphId) return fail(400, { graphId, missing: true }) - await DomainHelper.create(graphId); + return await DomainHelper.create(graphId) }, - newSubject: async ({ params, request }) => { - const data = await request.formData(); - const graphId = Number(data.get('graph')); + newSubject: async ({ params, request }): Promise => { + const data = await request.formData() + const graphId = Number(data.get('graph')) - if (!graphId) return fail(400, { graphId, missing: true }); + if (!graphId) return fail(400, { graphId, missing: true }) + + return await SubjectHelper.create(graphId) + }, - await SubjectHelper.create(graphId); - } -}; + newLecture: async ({ params, request }): Promise => { + const data = await request.formData() + const graphId = Number(data.get('graph')) + + if (!graphId) return fail(400, { graphId, missing: true }) + + return await LectureHelper.create(graphId) + } +} +// Load export const load = async ({ params }) => { - const courseCode = params.course; - const graphId = Number(params.graph); + const courseCode = params.course + const graphId = Number(params.graph) const course = await CourseHelper.toDTO( (await prisma.course.findUnique({ where: { code: courseCode } }))! - ); + ) const graph = await GraphHelper.toDTO( (await prisma.graph.findUnique({ where: { id: graphId } }))! - ); + ) - return { course, graph }; -}; + return { course, graph } +} diff --git a/src/routes/app/course/[course]/graph/[graph]/settings/+page.svelte b/src/routes/app/course/[course]/graph/[graph]/settings/+page.svelte index a55cbf4..8d929a3 100644 --- a/src/routes/app/course/[course]/graph/[graph]/settings/+page.svelte +++ b/src/routes/app/course/[course]/graph/[graph]/settings/+page.svelte @@ -1,34 +1,45 @@ @@ -85,46 +69,48 @@ href: '/app/dashboard' }, { - name: `${course.code} ${course.name}`, - href: `/app/course/${course.code}/overview` + name: `${$course.code} ${$course.name}`, + href: `/app/course/${$course.code}/overview` }, { - name: graph.name, - href: `/app/course/${course.code}/graph/${graph.id}/overview` + name: $graph.name, + href: `/app/course/${$course.code}/graph/${$graph.id}/overview` }, { name: 'Settings', - href: `/app/course/${course.code}/graph/${graph.id}/settings` + href: `/app/course/${$course.code}/graph/${$graph.id}/settings` } ]} > - + +
- Edit layout -
- - - -
{#if active_tab === 0} - + {:else if active_tab === 1} - + {:else if active_tab === 2} - + {:else if active_tab === 3} - + {/if}
@@ -183,7 +169,7 @@ &:first-child border-left: none !important - + .dynamic-border border-bottom: 1px solid $gray flex: 1 diff --git a/src/routes/app/course/[course]/graph/[graph]/settings/+page.ts b/src/routes/app/course/[course]/graph/[graph]/settings/+page.ts new file mode 100644 index 0000000..cbb040b --- /dev/null +++ b/src/routes/app/course/[course]/graph/[graph]/settings/+page.ts @@ -0,0 +1,10 @@ + +// Internal imports +import { Course, Graph } from '$lib/scripts/entities' +import { course, graph } from '$stores' + +// Load +export const load = ({ data }) => { + course.set(Course.revive(data.course)) + graph.set(Graph.revive(data.graph)) +} \ No newline at end of file diff --git a/src/routes/app/course/[course]/graph/[graph]/settings/DomainSettings.svelte b/src/routes/app/course/[course]/graph/[graph]/settings/DomainSettings.svelte index 684c76f..8923dbb 100644 --- a/src/routes/app/course/[course]/graph/[graph]/settings/DomainSettings.svelte +++ b/src/routes/app/course/[course]/graph/[graph]/settings/DomainSettings.svelte @@ -2,8 +2,9 @@ @@ -119,7 +121,7 @@
- {#if graph.domains.some(domain => domainMatchesQuery(domain_query, domain))} + {#if $graph.domains.some(domain => domainMatchesQuery(domain_query, domain))}
@@ -132,7 +134,7 @@ on:click={() => { domain_style_sort = undefined domain_name_sort = !domain_name_sort - alphabetize(graph.domains, domain => domain.name, domain_name_sort) + $graph.sort(SortOption.domains | SortOption.name, domain_name_sort) update() }} /> @@ -146,7 +148,7 @@ on:click={() => { domain_name_sort = undefined domain_style_sort = !domain_style_sort - alphabetize(graph.domains, domain => domain.style ? styles[domain.style].display_name : '', domain_style_sort) + $graph.sort(SortOption.domains | SortOption.style, domain_style_sort) update() }} /> @@ -161,14 +163,14 @@ {/if} - {#each graph.domains as domain} + {#each $graph.domains as domain} {#if domainMatchesQuery(domain_query, domain)} -
+
{domain.index + 1} - { domain.delete(); update() }} /> - - + { await domain.delete(); update() }} /> + +
{/if} @@ -192,7 +194,7 @@
- {#if graph.domain_relations.some(relation => relationMatchesQuery(relation_query, relation))} + {#if $graph.domain_relations.some(relation => relationMatchesQuery(relation_query, relation))}
@@ -205,7 +207,7 @@ on:click={() => { relation_child_sort = undefined relation_parent_sort = !relation_parent_sort - alphabetize(graph.domain_relations, relation => relation.parent?.name || '', relation_parent_sort) + $graph.sort(SortOption.relations | SortOption.domains | SortOption.parent, relation_parent_sort) update() }} /> @@ -219,7 +221,7 @@ on:click={() => { relation_parent_sort = undefined relation_child_sort = !relation_child_sort - alphabetize(graph.domain_relations, relation => relation.child?.name || '', relation_child_sort) + $graph.sort(SortOption.relations | SortOption.domains | SortOption.child, relation_child_sort) update() }} /> @@ -234,15 +236,15 @@ {/if} - {#each graph.domain_relations as relation} + {#each $graph.domain_relations as relation} {#if relationMatchesQuery(relation_query, relation)} -
+
{relation.index + 1} { relation.delete(); update() }} /> - + - +
{/if} diff --git a/src/routes/app/course/[course]/graph/[graph]/settings/GeneralSettings.svelte b/src/routes/app/course/[course]/graph/[graph]/settings/GeneralSettings.svelte index 26fa380..455e0ac 100644 --- a/src/routes/app/course/[course]/graph/[graph]/settings/GeneralSettings.svelte +++ b/src/routes/app/course/[course]/graph/[graph]/settings/GeneralSettings.svelte @@ -2,22 +2,16 @@ @@ -52,33 +76,31 @@

Lectures

-
- -
- {#if graph.lectures.some(lecture => lectureMatchesQuery(query, lecture))} + {#if $graph.lectures.some(lecture => lectureMatchesQuery(query, lecture))} - {#each graph.lectures as lecture} + {#each $graph.lectures as lecture} {#if lectureMatchesQuery(query, lecture)} -
+
{lecture.index + 1} - { lecture.delete(); update() }} /> - + { await lecture.delete(); update() }} /> +
{#each lecture.lecture_subjects as lecture_subject, n} {n + 1} { lecture_subject.delete(); update() }} /> - + {/each}
diff --git a/src/routes/app/course/[course]/graph/[graph]/settings/SubjectSettings.svelte b/src/routes/app/course/[course]/graph/[graph]/settings/SubjectSettings.svelte index d10a474..4018c63 100644 --- a/src/routes/app/course/[course]/graph/[graph]/settings/SubjectSettings.svelte +++ b/src/routes/app/course/[course]/graph/[graph]/settings/SubjectSettings.svelte @@ -3,7 +3,8 @@ @@ -118,7 +121,7 @@
- {#if graph.subjects.some(subject => subjectMatchesQuery(subject_query, subject))} + {#if $graph.subjects.some(subject => subjectMatchesQuery(subject_query, subject))}
@@ -131,7 +134,7 @@ on:click={() => { subject_domain_sort = undefined subject_name_sort = !subject_name_sort - alphabetize(graph.subjects, subject => subject.name, subject_name_sort) + $graph.sort(SortOption.subjects | SortOption.name, subject_name_sort) update() }} /> @@ -145,7 +148,7 @@ on:click={() => { subject_name_sort = undefined subject_domain_sort = !subject_domain_sort - alphabetize(graph.subjects, subject => subject.domain?.name || '', subject_domain_sort) + $graph.sort(SortOption.subjects | SortOption.domain, subject_domain_sort) update() }} /> @@ -160,14 +163,14 @@ {/if} - {#each graph.subjects as subject} + {#each $graph.subjects as subject} {#if subjectMatchesQuery(subject_query, subject)} -
+
{subject.index + 1} - { subject.delete(); update() }} /> - - + { await subject.delete(); update() }} /> + +
{/if} @@ -191,7 +194,7 @@
- {#if graph.subject_relations.some(relation => relationMatchesQuery(relation_query, relation))} + {#if $graph.subject_relations.some(relation => relationMatchesQuery(relation_query, relation))}
@@ -204,7 +207,7 @@ on:click={() => { relation_child_sort = undefined relation_parent_sort = !relation_parent_sort - alphabetize(graph.subject_relations, relation => relation.parent?.name || '', relation_parent_sort) + $graph.sort(SortOption.relations | SortOption.subjects | SortOption.parent, relation_parent_sort) update() }} /> @@ -218,7 +221,7 @@ on:click={() => { relation_parent_sort = undefined relation_child_sort = !relation_child_sort - alphabetize(graph.subject_relations, relation => relation.child?.name || '', relation_child_sort) + $graph.sort(SortOption.relations | SortOption.subjects | SortOption.child, relation_child_sort) update() }} /> @@ -233,15 +236,15 @@ {/if} - {#each graph.subject_relations as relation} + {#each $graph.subject_relations as relation} {#if relationMatchesQuery(relation_query, relation)} -
+
{relation.index + 1} { relation.delete(); update() }} /> - + - +
{/if} diff --git a/src/routes/app/course/[course]/overview/+page.svelte b/src/routes/app/course/[course]/overview/+page.svelte index 573c649..e17b20b 100644 --- a/src/routes/app/course/[course]/overview/+page.svelte +++ b/src/routes/app/course/[course]/overview/+page.svelte @@ -4,7 +4,7 @@ import { enhance } from '$app/forms' // Internal imports - import { ValidationData, Severity, DropdownOption } from '$scripts/entities' + import { ValidationData, Severity } from '$scripts/entities' // Components import Layout from '$layouts/DefaultLayout.svelte' @@ -32,13 +32,15 @@ // Helpers - class GraphInput { + class GraphModal { name: string = '' - get options(): DropdownOption[] { - return graphs.map(graph => { - return { name: graph.name, value: graph.id, validation: ValidationData.success() } - }) + show() { + graph_modal?.show() + } + + hide() { + graph_modal?.hide() } validate(): ValidationData { @@ -59,10 +61,28 @@ } } - class Link { + class LinkModal { name: string = '' graph?: number + get graph_options() { + return graphs.map(graph => { + return { + name: graph.name, + value: graph.id, + validation: ValidationData.success() + } + }) + } + + show() { + link_modal?.show() + } + + hide() { + link_modal?.hide() + } + validate(): ValidationData { const result = new ValidationData() @@ -93,8 +113,8 @@ $: course = Course.revive(data.course); $: graphs = data.graphs.map(graph => Graph.revive(graph)); - const graphInput = new GraphInput() - const link = new Link() + const graph = new GraphModal() + const link = new LinkModal() let graph_modal: Modal let link_modal: Modal @@ -121,11 +141,11 @@ ]} > - - @@ -139,13 +159,13 @@

Create Graph

Add a new graph to this course. Graphs are visual representations of the course content. They are intended to help students understand the course structure. -
+ - +
- - + +
@@ -154,12 +174,12 @@

Create Link

Add a new link to this course. This will link to a graph in this course, and can be provided to students, or embedded into course material. -
+ - +