Browse Source

Less glitchy task expansion

mj-deploy
Standa Lukeš 4 years ago
parent
commit
debe92d440
  1. 2
      frontend/public/grafik.html
  2. 16
      frontend/src/App.svelte
  3. 12
      frontend/src/Editor.svelte
  4. 10
      frontend/src/Graph.svelte
  5. 23
      frontend/src/TaskDisplay.svelte
  6. 61
      frontend/src/TaskPanel.svelte
  7. 1
      frontend/src/helpers.ts
  8. 40
      frontend/src/ksp-task-grabber.ts

2
frontend/public/grafik.html

@ -14,6 +14,6 @@
</head> </head>
<body style="padding: 0"> <body style="padding: 0">
<div id="svelte-root"></div> <div id="svelte-root" style="position: relative;"></div>
</body> </body>
</html> </html>

16
frontend/src/App.svelte

@ -5,15 +5,12 @@
import TasksLoader from "./TasksLoader.svelte"; import TasksLoader from "./TasksLoader.svelte";
import TaskPanel from "./TaskPanel.svelte"; import TaskPanel from "./TaskPanel.svelte";
import Editor from "./Editor.svelte"; import Editor from "./Editor.svelte";
import GraphEdge from "./GraphEdge.svelte";
import type { detach } from "svelte/internal";
const tasksPromise: Promise<TasksFile> = loadTasks(); const tasksPromise: Promise<TasksFile> = loadTasks();
let selectedTask: string | null = null; let taskPanel: TaskPanel
let finalSelect: boolean = false;
function clickTask(e: CustomEvent<TaskDescriptor>) {
finalSelect = true;
}
// react to hash changes // react to hash changes
let hash = window.location.hash.substr(1); let hash = window.location.hash.substr(1);
@ -51,9 +48,12 @@
</TasksLoader> </TasksLoader>
{:else} {:else}
<TasksLoader promise={tasksPromise} let:data={t}> <TasksLoader promise={tasksPromise} let:data={t}>
<TaskPanel bind:finalSelect {selectedTask} /> <TaskPanel bind:this={taskPanel} />
<div style="height: 100%"> <div style="height: 100%">
<Graph tasks={t} bind:selectedTask on:selectTask={clickTask} /> <Graph tasks={t}
on:selectTask={e => taskPanel.select(e.detail)}
on:preSelectTask={e => taskPanel.preSelect(e.detail)}
on:unPreSelectTask={e => taskPanel.unPreselect(e.detail)} />
</div> </div>
</TasksLoader> </TasksLoader>
{/if} {/if}

12
frontend/src/Editor.svelte

@ -1,8 +1,10 @@
<script type="ts"> <script type="ts">
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import { nonNull } from "./helpers";
import { grabAssignment } from "./ksp-task-grabber"; import { grabAssignment } from "./ksp-task-grabber";
import type { TaskDescriptor, TasksFile } from "./task-loader"; import type { TaskDescriptor, TasksFile } from "./task-loader";
import { saveTasks, createTaskMap, getCategories } from "./task-loader"; import { saveTasks, createTaskMap, getCategories } from "./task-loader";
import TaskDisplay from "./TaskDisplay.svelte";
export let tasks: TasksFile; export let tasks: TasksFile;
@ -145,19 +147,13 @@
<div class="taskDetails"> <div class="taskDetails">
{#if currentTask != null} {#if currentTask != null}
<h3>{currentTask}</h3> <h3>{currentTask}</h3>
<span>{taskMap.get(currentTask).comment}</span> <span>{nonNull(taskMap.get(currentTask)).comment}</span>
<ul> <ul>
{#each getCategories(tasks, currentTask) as cat} {#each getCategories(tasks, currentTask) as cat}
<li>{cat}</li> <li>{cat}</li>
{/each} {/each}
</ul> </ul>
<div> <TaskDisplay task={currentTask} />
{#await grabAssignment(currentTask)}
Loading...
{:then text}
{@html text}
{/await}
</div>
{:else} {:else}
<h3>Nothing selected...</h3> <h3>Nothing selected...</h3>
{/if} {/if}

10
frontend/src/Graph.svelte

@ -7,7 +7,7 @@
import type { TasksFile, TaskDescriptor } from "./task-loader"; import type { TasksFile, TaskDescriptor } from "./task-loader";
export let tasks: TasksFile; export let tasks: TasksFile;
export let selectedTask: null | string = null; let hoveredTask: null | string = null;
export let repulsionForce: number = -600; export let repulsionForce: number = -600;
// Svelte automatically fills these with a reference // Svelte automatically fills these with a reference
@ -22,7 +22,6 @@
const eventDispatcher = createEventDispatcher(); const eventDispatcher = createEventDispatcher();
const nodeClick = (task: TaskDescriptor) => (e: CustomEvent<MouseEvent>) => { const nodeClick = (task: TaskDescriptor) => (e: CustomEvent<MouseEvent>) => {
selectedTask = task.id;
eventDispatcher("selectTask", task); eventDispatcher("selectTask", task);
}; };
@ -30,9 +29,12 @@
hovering: CustomEvent<boolean> hovering: CustomEvent<boolean>
) => { ) => {
if (hovering.detail) { if (hovering.detail) {
selectedTask = task.id; hoveredTask = task.id;
eventDispatcher("preSelectTask", task);
} else { } else {
if (selectedTask == task.id) selectedTask = null; if (hoveredTask == task.id)
hoveredTask = null;
eventDispatcher("unPreSelectTask", task);
} }
}; };

23
frontend/src/TaskDisplay.svelte

@ -0,0 +1,23 @@
<script type="ts">
import { grabAssignment } from "./ksp-task-grabber";
import { nonNull } from './helpers'
export let task: string | null | undefined
</script>
<style>
div {
text-align: justify;
}
</style>
<div>
{#if task != null}
{#await grabAssignment(nonNull(task))}
Načítám úlohu
{:then task}
{@html task.titleHtml}
<hr>
{@html task.description}
<div class="clearfloat" />
{/await}
{/if}
</div>

61
frontend/src/TaskPanel.svelte

@ -1,42 +1,67 @@
<script lang="ts"> <script lang="ts">
import { grabAssignment } from "./ksp-task-grabber"; import { grabAssignment } from "./ksp-task-grabber";
import type { TasksFile, TaskDescriptor } from "./task-loader"; import type { TasksFile, TaskDescriptor } from "./task-loader";
import TaskDisplay from "./TaskDisplay.svelte";
// export let tasks: TasksFile; // export let tasks: TasksFile;
export let selectedTask: string | null = null let selectedTask: TaskDescriptor | null = null
export let finalSelect: boolean = false
let mouse: boolean = false
let height: string; let heightClass: "collapsed" | "full" | "preview" = "collapsed"
$: height = selectedTask == null && !mouse ? "0" :
finalSelect ? "100%" :
"100px"
let taskPromise: Promise<string | null> let taskPromise: Promise<string | null>
export function preSelect(task: TaskDescriptor) {
if (heightClass != "full") {
selectedTask = task
heightClass = "preview"
}
}
export function unPreselect(task: TaskDescriptor) {
setTimeout(() => {
if (selectedTask && task.id == selectedTask.id && heightClass != "full") {
heightClass = "collapsed"
}
}, 10);
}
export function select(task: TaskDescriptor) {
selectedTask = task
heightClass = "full"
}
$: { $: {
if (selectedTask != null) if (selectedTask != null)
taskPromise = grabAssignment(selectedTask) taskPromise = grabAssignment(selectedTask.id)
} }
</script> </script>
<style> <style>
.panel { .panel {
position: absolute; position: absolute;
width: 100%; width: calc(100%-200px);
background-color: #222; background-color: #222;
overflow: hidden; overflow: hidden;
padding: 0 100px 0 100px;
}
.panel.collapsed:not(:hover) {
display: none;
}
.panel.preview, .panel:not(.full):hover {
height: 100px;
}
.panel.full {
min-height: 100%;
} }
.closeButton { display: none }
.panel.full .closeButton { display: inherit }
</style> </style>
<div class="panel" style="height: {height}" on:mouseover={() => mouse = false} on:mouseout={() => mouse = false}> <div class="panel {heightClass}"
{#if selectedTask != null} on:click={() => selectedTask && select(selectedTask)}>
{#await taskPromise} <TaskDisplay task={selectedTask?.id} />
Načítám úložku {selectedTask} ;) <button type=button class="closeButton" on:click|stopPropagation={() => heightClass = "collapsed"}>
{:then task}
{@html task}
{/await}
<button type=button on:click={() => finalSelect = false}>
Zavřít Zavřít
</button> </button>
{/if}
</div> </div>

1
frontend/src/helpers.ts

@ -0,0 +1 @@
export function nonNull<T>(a: T | null | undefined): T { return a! }

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

@ -1,8 +1,9 @@
export type TaskAssignmentData = { export type TaskAssignmentData = {
id: string, id: string,
name: string, name: string,
points: number, points: number | null,
description: HTMLElement description: string,
titleHtml: string
} }
type TaskLocation = { type TaskLocation = {
@ -31,7 +32,7 @@ function getLocation(id: string, solution: boolean): TaskLocation | null {
} }
} }
function parseTask(startElementId: string, html: string): string { function parseTask(startElementId: string, html: string): TaskAssignmentData {
const parser = new DOMParser() const parser = new DOMParser()
const doc = parser.parseFromString(html, "text/html") const doc = parser.parseFromString(html, "text/html")
@ -42,6 +43,13 @@ function parseTask(startElementId: string, html: string): string {
let e = titleElement let e = titleElement
const titleMatch = /(\d-Z?\d+-\d+) (.*?)( \((\d+) bod.*\))?/.exec(e.innerText.trim())
if (!titleMatch) {
var [_, id, name, _, points] = ["", startElementId, "Neznámé jméno úlohy", "", ""]
} else {
var [_, id, name, _, points] = titleMatch
}
while (e.nextElementSibling && while (e.nextElementSibling &&
e.nextElementSibling?.tagName.toLowerCase() == "hr") e.nextElementSibling?.tagName.toLowerCase() == "hr")
e = e.nextElementSibling as HTMLElement e = e.nextElementSibling as HTMLElement
@ -60,7 +68,13 @@ function parseTask(startElementId: string, html: string): string {
let r = "" let r = ""
for (const e of elements) for (const e of elements)
r += e.outerHTML + "\n" r += e.outerHTML + "\n"
return r return {
description: r,
id: id.trim(),
name: name.trim(),
points: points ? +points : null,
titleHtml: titleElement.outerHTML
}
} }
async function loadTask({ url, startElement }: TaskLocation) { async function loadTask({ url, startElement }: TaskLocation) {
@ -72,14 +86,24 @@ async function loadTask({ url, startElement }: TaskLocation) {
return parseTask(startElement, rText) return parseTask(startElement, rText)
} }
export async function grabAssignment(id: string): Promise<string> { function virtualTask(id: string): TaskAssignmentData {
return {
id,
description: "úloha je virtuální a neexistuje",
name: id,
points: 0,
titleHtml: "<h3>Virtuální úloha</h3>"
}
}
export async function grabAssignment(id: string): Promise<TaskAssignmentData> {
const l = getLocation(id, false) const l = getLocation(id, false)
if (!l) return "úloha je virtuální a neexistuje" if (!l) return virtualTask(id)
return await loadTask(l) return await loadTask(l)
} }
export async function grabSolution(id: string) { export async function grabSolution(id: string): Promise<TaskAssignmentData> {
const l = getLocation(id, true) const l = getLocation(id, true)
if (!l) return "úloha je virtuální a neexistuje" if (!l) return virtualTask(id)
return await loadTask(l) return await loadTask(l)
} }