Browse Source

Use x-summary endpoint for points

This endpoint includes points from the old tasks in series - resolves #44
master
Standa Lukeš 4 years ago
parent
commit
e2b6bdc319
  1. 2
      frontend/src/Editor.svelte
  2. 5
      frontend/src/Odevzdavatko.svelte
  3. 3
      frontend/src/TasksLoader.svelte
  4. 27
      frontend/src/ksp-submit-api.ts
  5. 46
      frontend/src/ksp-task-grabber.ts
  6. 11
      frontend/src/task-status-cache.ts

2
frontend/src/Editor.svelte

@ -191,7 +191,7 @@
return; return;
} }
const y = prompt("Který ročník (číslo 26...X)"); const y = prompt("Který ročník (číslo 26...X)");
await refreshTaskStatuses([`${y}-Z1-1`]); await refreshTaskStatuses();
const newTasks = Array.from($taskStatuses.values()).filter( const newTasks = Array.from($taskStatuses.values()).filter(
(t) => (t) =>
t.id.startsWith(y + "-") && t.id.startsWith(y + "-") &&

5
frontend/src/Odevzdavatko.svelte

@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import { isLoggedIn, parseTaskId } from "./ksp-task-grabber"; import { isLoggedIn, parseTaskId } from "./ksp-task-grabber";
import type { TaskStatus } from "./ksp-task-grabber"; import type { TaskSubmitStatus, SubtaskSubmitStatus, TaskStatus } from './ksp-submit-api'
import type { TaskSubmitStatus, SubtaskSubmitStatus } from './ksp-submit-api'
import * as api from './ksp-submit-api' import * as api from './ksp-submit-api'
import { taskStatuses, refresh as refreshTaskStatus } from './task-status-cache' import { taskStatuses, refresh as refreshTaskStatus } from './task-status-cache'
import * as s from 'svelte' import * as s from 'svelte'
@ -164,7 +163,7 @@
async function upload(file: File) { async function upload(file: File) {
const x = await api.submit(id, uploadSubtaskId!, file) const x = await api.submit(id, uploadSubtaskId!, file)
refreshTaskStatus([id]) refreshTaskStatus()
const subtasks = [...task.subtasks] const subtasks = [...task.subtasks]
subtasks[subtasks.findIndex(s => s.id == x.id)] = x subtasks[subtasks.findIndex(s => s.id == x.id)] = x
task = { ...task, subtasks } task = { ...task, subtasks }

3
frontend/src/TasksLoader.svelte

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { TasksFile } from "./tasks"; import type { TasksFile } from "./tasks";
import { refresh } from './task-status-cache'
export let promise: Promise<TasksFile>; export let promise: Promise<TasksFile>;
@ -9,14 +8,12 @@
let err: any | null = null; let err: any | null = null;
promise.then( promise.then(
(d) => { (d) => {
refresh(d.tasks.map(t => t.id))
data = d; data = d;
}, },
(e) => { (e) => {
err = e; err = e;
} }
) )
</script> </script>
{#if data == null && err == null} {#if data == null && err == null}

27
frontend/src/ksp-submit-api.ts

@ -1,4 +1,4 @@
import { fetchHtml } from "./ksp-task-grabber"; import { fetchHtml, isLoggedIn } from "./ksp-task-grabber";
let apitoken : string | null = null let apitoken : string | null = null
@ -85,3 +85,28 @@ export async function submit(id: string, subtask: string, uploadedData: string |
] ]
}) })
} }
export type TaskStatus = {
id: string
name: string
submitted: boolean
solved: boolean
points: number
maxPoints: number
}
export async function grabTaskSummary(): Promise<Map<string, TaskStatus>> {
if (!isLoggedIn()) throw new Error()
const results = await requestJson(`/api/tasks/x-summary`) as { tasks: TaskSubmitStatus[] }
function mapId(id: string) {
if (id.startsWith("cviciste/"))
return id.substr("cviciste/".length)
else
return id
}
return new Map<string, TaskStatus>(
results.tasks.map(r => [mapId(r.id), { id: mapId(r.id), maxPoints: r.max_points, name: r.name, points: r.points, solved: r.points > r.max_points - 0.001, submitted: r.points > 0 }])
)
}

46
frontend/src/ksp-task-grabber.ts

@ -14,16 +14,6 @@ type TaskLocation = {
startElement: string startElement: string
} }
export type TaskStatus = {
id: string
name: string
submitted: boolean
solved: boolean
points: number
maxPoints: number
type: string
}
function fixAllLinks(e: any) { function fixAllLinks(e: any) {
if (typeof e.src == "string") { if (typeof e.src == "string") {
e.src = e.src e.src = e.src
@ -150,27 +140,6 @@ function parseTask(startElementId: string, doc: HTMLDocument): TaskAssignmentDat
} }
} }
function parseTaskStatuses(doc: HTMLDocument): TaskStatus[] {
const rows = Array.from(doc.querySelectorAll("table.zs-tasklist tr")).slice(1) as HTMLTableRowElement[]
return rows.map(r => {
const submitted = !r.classList.contains("zs-unsubmitted")
const id = r.cells[0].textContent!.trim()
const type = r.cells[1].textContent!.trim()
const name = r.cells[2].textContent!.trim()
const pointsStr = r.cells[4].textContent!.trim()
const pointsMatch = /((–|\.|\d)+) *\/ *(\d+)/.exec(pointsStr)
if (!pointsMatch) throw new Error()
let points = +pointsMatch[1]
// points was a dash, means 0
if (isNaN(points)) {
points = 0
}
const maxPoints = +pointsMatch[3]
const solved = r.classList.contains("zs-submitted")
return { id, name, submitted, type, points, maxPoints, solved }
})
}
export async function fetchHtml(url: string) { export async function fetchHtml(url: string) {
const r = await fetch(url, { headers: { "Accept": "text/html,application/xhtml+xml" } }) const r = await fetch(url, { headers: { "Accept": "text/html,application/xhtml+xml" } })
if (r.status >= 400) { if (r.status >= 400) {
@ -196,21 +165,6 @@ export function isLoggedIn(): boolean {
return !!document.head.querySelector("meta[name=x-ksp-uid]") return !!document.head.querySelector("meta[name=x-ksp-uid]")
} }
export async function grabTaskStates(kspIds: string[]): Promise<Map<string, TaskStatus>> {
if (!isLoggedIn()) throw new Error()
const ids = new Set<string>(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<string, TaskStatus>(
([] as TaskStatus[]).concat(...results)
.map(r => [r.id, r])
)
}
export async function grabAssignment(id: string): Promise<TaskAssignmentData> { export async function grabAssignment(id: string): Promise<TaskAssignmentData> {
return await loadTask(getLocation(id, false)) return await loadTask(getLocation(id, false))
} }

11
frontend/src/task-status-cache.ts

@ -1,5 +1,6 @@
import { grabTaskStates, isLoggedIn} from "./ksp-task-grabber" import { isLoggedIn } from "./ksp-task-grabber"
import type { TaskStatus } from "./ksp-task-grabber" import { grabTaskSummary } from './ksp-submit-api'
import type { TaskStatus } from "./ksp-submit-api"
import { readable } from 'svelte/store'; import { readable } from 'svelte/store';
let writeFn: (value: Map<string, TaskStatus>) => void = null!; let writeFn: (value: Map<string, TaskStatus>) => void = null!;
@ -14,12 +15,14 @@ export const taskStatuses = readable(lastVal, write => {
writeFn = v => { lastVal = v; write(v); } writeFn = v => { lastVal = v; write(v); }
}) })
export function refresh(ids: string[]) { export function refresh() {
if (!isLoggedIn()) return; if (!isLoggedIn()) return;
return grabTaskStates(ids).then(t => { return grabTaskSummary().then(t => {
const tt = Array.from(t.entries()) const tt = Array.from(t.entries())
writeFn(new Map(Array.from(lastVal.entries()).concat(tt))) writeFn(new Map(Array.from(lastVal.entries()).concat(tt)))
localStorage.setItem("taskStatuses-cache", JSON.stringify(tt)) localStorage.setItem("taskStatuses-cache", JSON.stringify(tt))
}) })
} }
refresh()