Display task status in TaskDisplay
This commit is contained in:
		
							parent
							
								
									b20594d6b9
								
							
						
					
					
						commit
						af57e530f8
					
				
					 6 changed files with 88 additions and 20 deletions
				
			
		|  | @ -6,6 +6,7 @@ | ||||||
|   import type { TasksFile, TaskDescriptor } from "./task-loader"; |   import type { TasksFile, TaskDescriptor } from "./task-loader"; | ||||||
|   import { createNodesAndEdges } from "./graph-types"; |   import { createNodesAndEdges } from "./graph-types"; | ||||||
|   import { taskForce } from "./task-force"; |   import { taskForce } from "./task-force"; | ||||||
|  |   import { taskStatuses } from './task-status-cache' | ||||||
|   import { grabTaskStates, isLoggedIn } from "./ksp-task-grabber"; |   import { grabTaskStates, isLoggedIn } from "./ksp-task-grabber"; | ||||||
|   import type { TaskStatus } from "./ksp-task-grabber" |   import type { TaskStatus } from "./ksp-task-grabber" | ||||||
| 
 | 
 | ||||||
|  | @ -20,7 +21,6 @@ | ||||||
|   let clientHeight: number; |   let clientHeight: number; | ||||||
|   let clientWidth: number; |   let clientWidth: number; | ||||||
|   let svgElement: SVGElement; |   let svgElement: SVGElement; | ||||||
|   let taskStatuses = new Map<string, TaskStatus>(); |  | ||||||
| 
 | 
 | ||||||
|   // this prevents svelte from updating nodes and edges |   // this prevents svelte from updating nodes and edges | ||||||
|   // when we update nodes and edges |   // when we update nodes and edges | ||||||
|  | @ -94,17 +94,6 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   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 |   // start simulation and center view on create | ||||||
|   onMount(() => { |   onMount(() => { | ||||||
|     // set center of the SVG at (0,0) |     // set center of the SVG at (0,0) | ||||||
|  | @ -155,7 +144,7 @@ | ||||||
|             on:click={nodeClick(task.task)} |             on:click={nodeClick(task.task)} | ||||||
|             on:hoveringChange={nodeHover(task.task)} |             on:hoveringChange={nodeHover(task.task)} | ||||||
|             on:positionChange={() => { tasks = tasks; }} |             on:positionChange={() => { tasks = tasks; }} | ||||||
|             status={taskStatuses.get(task.id)} |             status={$taskStatuses.get(task.id)} | ||||||
|             draggingEnabled={nodeDraggingEnabled} |             draggingEnabled={nodeDraggingEnabled} | ||||||
|             on:dblclick={nodeDoubleClick(task.task)} /> |             on:dblclick={nodeDoubleClick(task.task)} /> | ||||||
|         {/each} |         {/each} | ||||||
|  |  | ||||||
|  | @ -83,11 +83,23 @@ | ||||||
|     fill: #69b3a2 |     fill: #69b3a2 | ||||||
|   } |   } | ||||||
|   .submitted ellipse { |   .submitted ellipse { | ||||||
|     fill: red |     fill: red; /* TODO */ | ||||||
|  |   } | ||||||
|  |   .solved ellipse { | ||||||
|  |     fill: green; /* TODO */ | ||||||
|   } |   } | ||||||
| </style> | </style> | ||||||
| 
 | 
 | ||||||
| <g on:mouseenter={enter} on:mouseleave={leave} on:click={click} on:mousedown={dragStart} on:mouseup={dragStop} on:mousemove={drag} class={status && status.submitted ? "submitted" : ""} on:dblclick={dblclick}> | <g on:mouseenter={enter} | ||||||
|  |    on:mouseleave={leave} | ||||||
|  |    on:click={click} | ||||||
|  |    on:mousedown={dragStart} | ||||||
|  |    on:mouseup={dragStop} | ||||||
|  |    on:mousemove={drag} | ||||||
|  |    on:dblclick={dblclick} | ||||||
|  |    class={status == null ? "" : | ||||||
|  |           status.solved ? "solved" : | ||||||
|  |           status.submitted ? "submitted" : ""}> | ||||||
|   <ellipse rx={ellipse_rx} ry={20} {cx} {cy} /> |   <ellipse rx={ellipse_rx} ry={20} {cx} {cy} /> | ||||||
|   <text |   <text | ||||||
|     bind:this={text_element} |     bind:this={text_element} | ||||||
|  |  | ||||||
|  | @ -1,7 +1,9 @@ | ||||||
| <script type="ts"> | <script type="ts"> | ||||||
|     import { grabAssignment, grabSolution } from "./ksp-task-grabber"; |     import { grabAssignment, grabSolution } from "./ksp-task-grabber"; | ||||||
|  |     import type { TaskStatus } from "./ksp-task-grabber"; | ||||||
|     import { nonNull } from './helpers' |     import { nonNull } from './helpers' | ||||||
| import App from "./App.svelte"; | import App from "./App.svelte"; | ||||||
|  | import { taskStatuses } from "./task-status-cache"; | ||||||
|     export let taskId: string | null | undefined |     export let taskId: string | null | undefined | ||||||
| 
 | 
 | ||||||
|     export let showSolution: boolean = false |     export let showSolution: boolean = false | ||||||
|  | @ -9,11 +11,26 @@ import App from "./App.svelte"; | ||||||
|         taskId |         taskId | ||||||
|         showSolution = false |         showSolution = false | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     let status: TaskStatus | undefined | ||||||
|  |     $: if (taskId) status = $taskStatuses.get(taskId) | ||||||
| </script> | </script> | ||||||
| <style> | <style> | ||||||
|     div { |     div { | ||||||
|         text-align: justify; |         text-align: justify; | ||||||
|     } |     } | ||||||
|  |     .header { | ||||||
|  |         display: flex; | ||||||
|  |         width: 100%; | ||||||
|  |         flex-direction: row; | ||||||
|  |     } | ||||||
|  |     .header div { | ||||||
|  |         flex-grow: 1; | ||||||
|  |     } | ||||||
|  |     .header .status { | ||||||
|  |         text-align: right; | ||||||
|  |         font-style: italic; | ||||||
|  |     } | ||||||
| </style> | </style> | ||||||
| 
 | 
 | ||||||
| <div> | <div> | ||||||
|  | @ -21,7 +38,22 @@ import App from "./App.svelte"; | ||||||
|     {#await grabAssignment(nonNull(taskId))} |     {#await grabAssignment(nonNull(taskId))} | ||||||
|         Načítám úlohu |         Načítám úlohu | ||||||
|     {:then task} |     {:then task} | ||||||
|         {@html task.titleHtml} |         <div class="header"> | ||||||
|  |             <div class="title"><h3>{task.name}</h3></div> | ||||||
|  | 
 | ||||||
|  |             <div class="status"> | ||||||
|  |                 <p> | ||||||
|  |                     {task.id} | {task.points} bodů | ||||||
|  |                     {#if status && status.submitted} | ||||||
|  |                         {#if nonNull(status).solved} | ||||||
|  |                         | Vyřešeno 🥳 | ||||||
|  |                         {:else} | ||||||
|  |                         | odevzdáno za {nonNull(status).points} bod{ "ů yyy"[nonNull(status).points] ?? "ů" } | ||||||
|  |                         {/if} | ||||||
|  |                     {/if} | ||||||
|  |                 </p> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|         {@html task.description} |         {@html task.description} | ||||||
|         <div class="clearfloat" /> |         <div class="clearfloat" /> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import type { TasksFile } from "./task-loader"; |   import type { TasksFile } from "./task-loader"; | ||||||
|  |   import { refresh } from './task-status-cache' | ||||||
| 
 | 
 | ||||||
|   export let promise: Promise<TasksFile>; |   export let promise: Promise<TasksFile>; | ||||||
| 
 | 
 | ||||||
|  | @ -8,6 +9,7 @@ | ||||||
|   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) => { | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ export type TaskStatus = { | ||||||
|     id: string |     id: string | ||||||
|     name: string |     name: string | ||||||
|     submitted: boolean |     submitted: boolean | ||||||
|  |     solved: boolean | ||||||
|     points: number |     points: number | ||||||
|     maxPoints: number |     maxPoints: number | ||||||
|     type: string |     type: string | ||||||
|  | @ -80,15 +81,17 @@ function parseTask(startElementId: string, doc: HTMLDocument): TaskAssignmentDat | ||||||
| 
 | 
 | ||||||
|     let e = titleElement |     let e = titleElement | ||||||
| 
 | 
 | ||||||
|     const titleMatch = /(\d-Z?\d+-\d+) (.*?)( \((\d+) bod.*\))?/.exec(e.innerText.trim()) |     const titleMatch = /^(\d+-Z?\d+-\d+) (.*?)( \((\d+) bod.*\))?$/.exec(e.innerText.trim()) | ||||||
|     if (!titleMatch) { |     if (!titleMatch) { | ||||||
|         var [_, id, name, __, points] = ["", startElementId, "Neznámé jméno úlohy", "", ""] |         var [_, id, name, __, points] = ["", startElementId, "Neznámé jméno úlohy", "", ""] | ||||||
|     } else { |     } else { | ||||||
|         var [_, id, name, __, points] = titleMatch |         var [_, id, name, __, points] = titleMatch | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     e = e.nextElementSibling as HTMLElement | ||||||
|  | 
 | ||||||
|     while (e.nextElementSibling && |     while (e.nextElementSibling && | ||||||
|            e.nextElementSibling?.tagName.toLowerCase() == "hr") |            e.tagName.toLowerCase() == "hr") | ||||||
|         e = e.nextElementSibling as HTMLElement |         e = e.nextElementSibling as HTMLElement | ||||||
| 
 | 
 | ||||||
|     while (!e.classList.contains("story") && |     while (!e.classList.contains("story") && | ||||||
|  | @ -124,11 +127,12 @@ function parseTaskStatuses(doc: HTMLDocument): TaskStatus[] { | ||||||
|         const type = r.cells[1].innerText.trim() |         const type = r.cells[1].innerText.trim() | ||||||
|         const name = r.cells[2].innerText.trim() |         const name = r.cells[2].innerText.trim() | ||||||
|         const pointsStr = r.cells[4].innerText.trim() |         const pointsStr = r.cells[4].innerText.trim() | ||||||
|         const pointsMatch = /(–|\d+) *\/ *(\d+)/.exec(pointsStr) |         const pointsMatch = /((–|\.|\d)+) *\/ *(\d+)/.exec(pointsStr) | ||||||
|         if (!pointsMatch) throw new Error() |         if (!pointsMatch) throw new Error() | ||||||
|         const points = +pointsMatch[1] |         const points = +pointsMatch[1] | ||||||
|         const maxPoints = +pointsMatch[2] |         const maxPoints = +pointsMatch[2] | ||||||
|         return { id, name, submitted, type, points, maxPoints } |         const solved = r.classList.contains("zs-submitted") | ||||||
|  |         return { id, name, submitted, type, points, maxPoints, solved } | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										29
									
								
								frontend/src/task-status-cache.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								frontend/src/task-status-cache.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | ||||||
|  | import { grabTaskStates, isLoggedIn} from "./ksp-task-grabber" | ||||||
|  | import type { TaskStatus } from "./ksp-task-grabber" | ||||||
|  | import { readable } from 'svelte/store'; | ||||||
|  | 
 | ||||||
|  | let writeFn: (value: Map<string, TaskStatus>) => void = null!; | ||||||
|  | let lastVal = new Map<string, TaskStatus>() | ||||||
|  | if (isLoggedIn()) { | ||||||
|  |     const cachedTaskStatuses = localStorage.getItem("taskStatuses-cache") | ||||||
|  |     if (cachedTaskStatuses) { | ||||||
|  |         lastVal = new Map(JSON.parse(cachedTaskStatuses)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | export const taskStatuses = readable(lastVal, write => { | ||||||
|  |     writeFn = v => { lastVal = v; write(v); } | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | console.log(isLoggedIn()) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export function refresh(ids: string[]) { | ||||||
|  |     if (!isLoggedIn()) return; | ||||||
|  | 
 | ||||||
|  |     grabTaskStates(ids).then(t => { | ||||||
|  |         const tt = Array.from(t.entries()) | ||||||
|  |         writeFn(new Map(Array.from(lastVal.entries()).concat(tt))) | ||||||
|  |         localStorage.setItem("taskStatuses-cache", JSON.stringify(tt)) | ||||||
|  |     }) | ||||||
|  | } | ||||||
		Reference in a new issue