diff --git a/frontend/src/Graph.svelte b/frontend/src/Graph.svelte index 56f1955..5f5924c 100644 --- a/frontend/src/Graph.svelte +++ b/frontend/src/Graph.svelte @@ -6,6 +6,9 @@ import type { TasksFile, TaskDescriptor } from "./task-loader"; import { createNodesAndEdges } from "./graph-types"; import { taskForce } from "./task-force"; + import { grabTaskStates, isLoggedIn } from "./ksp-task-grabber"; + import type { TaskStatus } from "./ksp-task-grabber" +import { json } from "d3"; export let tasks: TasksFile; export let repulsionForce: number = -1000; @@ -18,6 +21,7 @@ let clientHeight: number; let clientWidth: number; let svgElement: SVGElement; + let taskStatuses = new Map(); // this prevents svelte from updating nodes and edges // when we update nodes and edges @@ -87,6 +91,17 @@ } + if (isLoggedIn()) { + const cachedTaskStatuses = localStorage.getItem("taskStatuses-cache") + if (cachedTaskStatuses) { + try { taskStatuses = new Map(JSON.parse(cachedTaskStatuses)) } catch(e) { console.warn(e) } + } + grabTaskStates(tasks.tasks.map(t => t.id)).then(t => { + taskStatuses = t + localStorage.setItem("taskStatuses-cache", JSON.stringify(Array.from(t.entries()))) + }) + } + // start simulation and center view on create onMount(() => { // set center of the SVG at (0,0) @@ -137,6 +152,7 @@ on:click={nodeClick(task.task)} on:hoveringChange={nodeHover(task.task)} on:positionChange={() => { tasks = tasks; }} + status={taskStatuses.get(task.id)} draggingEnabled={nodeDraggingEnabled} /> {/each} diff --git a/frontend/src/GraphNode.svelte b/frontend/src/GraphNode.svelte index da8fa14..074861f 100644 --- a/frontend/src/GraphNode.svelte +++ b/frontend/src/GraphNode.svelte @@ -3,9 +3,11 @@ import { createEventDispatcher, onMount } from "svelte"; import type { TaskId } from "./graph-types"; + import type { TaskStatus } from "./ksp-task-grabber"; export let task: TaskId; export let draggingEnabled: boolean = false; + export let status: TaskStatus | undefined = undefined let hovering: boolean = false; let text_element: SVGTextElement; @@ -74,9 +76,12 @@ ellipse { fill: #69b3a2 } + .submitted ellipse { + fill: red + } - + { + const submitted = !r.classList.contains("zs-unsubmitted") + const id = r.cells[0].innerText.trim() + const type = r.cells[1].innerText.trim() + const name = r.cells[2].innerText.trim() + const pointsStr = r.cells[4].innerText.trim() + const pointsMatch = /(–|\d+) *\/ *(\d+)/.exec(pointsStr) + if (!pointsMatch) throw new Error() + const points = +pointsMatch[1] + const maxPoints = +pointsMatch[2] + return { id, name, submitted, type, points, maxPoints } + }) +} + +async function fetchHtml(url: string) { const r = await fetch(url, { headers: { "Accept": "text/html,application/xhtml+xml" } }) if (r.status >= 400) { - throw Error("Bad request") + throw Error(r.statusText) + } + const html = await r.text() + const parser = new DOMParser() + const doc = parser.parseFromString(html, "text/html") + if (!doc.head.querySelector("base")) { + let baseEl = doc.createElement('base'); + baseEl.setAttribute('href', new URL(url, location.href).href); + doc.head.append(baseEl); } - const rText = await r.text() - return parseTask(startElement, rText, url) + return doc +} + +async function loadTask({ url, startElement }: TaskLocation): Promise { + const html = await fetchHtml(url) + return parseTask(startElement, html) } function virtualTask(id: string): TaskAssignmentData { @@ -130,6 +163,25 @@ function virtualTask(id: string): TaskAssignmentData { } } +export function isLoggedIn(): boolean { + return !!document.querySelector(".auth a[href='/profil/profil.cgi']") +} + +export async function grabTaskStates(kspIds: string[]): Promise> { + if (!isLoggedIn()) throw new Error() + + const ids = new Set(kspIds.map(parseTaskId).filter(t => t != null).map(t => t!.rocnik)) + const results = await Promise.all(Array.from(ids.keys()).map(async (rocnik) => { + const html = await fetchHtml(`/cviciste/?year=${rocnik}`) + return parseTaskStatuses(html) + })) + + return new Map( + ([] as TaskStatus[]).concat(...results) + .map(r => [r.id, r]) + ) +} + export async function grabAssignment(id: string): Promise { const l = getLocation(id, false) if (!l) return virtualTask(id)