Less glitchy task expansion
This commit is contained in:
parent
ddfca887c3
commit
debe92d440
8 changed files with 118 additions and 47 deletions
|
@ -14,6 +14,6 @@
|
|||
</head>
|
||||
|
||||
<body style="padding: 0">
|
||||
<div id="svelte-root"></div>
|
||||
<div id="svelte-root" style="position: relative;"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,15 +5,12 @@
|
|||
import TasksLoader from "./TasksLoader.svelte";
|
||||
import TaskPanel from "./TaskPanel.svelte";
|
||||
import Editor from "./Editor.svelte";
|
||||
import GraphEdge from "./GraphEdge.svelte";
|
||||
import type { detach } from "svelte/internal";
|
||||
|
||||
const tasksPromise: Promise<TasksFile> = loadTasks();
|
||||
|
||||
let selectedTask: string | null = null;
|
||||
let finalSelect: boolean = false;
|
||||
|
||||
function clickTask(e: CustomEvent<TaskDescriptor>) {
|
||||
finalSelect = true;
|
||||
}
|
||||
let taskPanel: TaskPanel
|
||||
|
||||
// react to hash changes
|
||||
let hash = window.location.hash.substr(1);
|
||||
|
@ -51,9 +48,12 @@
|
|||
</TasksLoader>
|
||||
{:else}
|
||||
<TasksLoader promise={tasksPromise} let:data={t}>
|
||||
<TaskPanel bind:finalSelect {selectedTask} />
|
||||
<TaskPanel bind:this={taskPanel} />
|
||||
<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>
|
||||
</TasksLoader>
|
||||
{/if}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
<script type="ts">
|
||||
import Graph from "./Graph.svelte";
|
||||
import { nonNull } from "./helpers";
|
||||
import { grabAssignment } from "./ksp-task-grabber";
|
||||
import type { TaskDescriptor, TasksFile } from "./task-loader";
|
||||
import { saveTasks, createTaskMap, getCategories } from "./task-loader";
|
||||
import TaskDisplay from "./TaskDisplay.svelte";
|
||||
|
||||
export let tasks: TasksFile;
|
||||
|
||||
|
@ -145,19 +147,13 @@
|
|||
<div class="taskDetails">
|
||||
{#if currentTask != null}
|
||||
<h3>{currentTask}</h3>
|
||||
<span>{taskMap.get(currentTask).comment}</span>
|
||||
<span>{nonNull(taskMap.get(currentTask)).comment}</span>
|
||||
<ul>
|
||||
{#each getCategories(tasks, currentTask) as cat}
|
||||
<li>{cat}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div>
|
||||
{#await grabAssignment(currentTask)}
|
||||
Loading...
|
||||
{:then text}
|
||||
{@html text}
|
||||
{/await}
|
||||
</div>
|
||||
<TaskDisplay task={currentTask} />
|
||||
{:else}
|
||||
<h3>Nothing selected...</h3>
|
||||
{/if}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import type { TasksFile, TaskDescriptor } from "./task-loader";
|
||||
|
||||
export let tasks: TasksFile;
|
||||
export let selectedTask: null | string = null;
|
||||
let hoveredTask: null | string = null;
|
||||
export let repulsionForce: number = -600;
|
||||
|
||||
// Svelte automatically fills these with a reference
|
||||
|
@ -22,7 +22,6 @@
|
|||
const eventDispatcher = createEventDispatcher();
|
||||
|
||||
const nodeClick = (task: TaskDescriptor) => (e: CustomEvent<MouseEvent>) => {
|
||||
selectedTask = task.id;
|
||||
eventDispatcher("selectTask", task);
|
||||
};
|
||||
|
||||
|
@ -30,9 +29,12 @@
|
|||
hovering: CustomEvent<boolean>
|
||||
) => {
|
||||
if (hovering.detail) {
|
||||
selectedTask = task.id;
|
||||
hoveredTask = task.id;
|
||||
eventDispatcher("preSelectTask", task);
|
||||
} else {
|
||||
if (selectedTask == task.id) selectedTask = null;
|
||||
if (hoveredTask == task.id)
|
||||
hoveredTask = null;
|
||||
eventDispatcher("unPreSelectTask", task);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
23
frontend/src/TaskDisplay.svelte
Normal file
23
frontend/src/TaskDisplay.svelte
Normal file
|
@ -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>
|
|
@ -1,42 +1,67 @@
|
|||
<script lang="ts">
|
||||
import { grabAssignment } from "./ksp-task-grabber";
|
||||
import type { TasksFile, TaskDescriptor } from "./task-loader";
|
||||
import TaskDisplay from "./TaskDisplay.svelte";
|
||||
|
||||
// export let tasks: TasksFile;
|
||||
export let selectedTask: string | null = null
|
||||
export let finalSelect: boolean = false
|
||||
let mouse: boolean = false
|
||||
let selectedTask: TaskDescriptor | null = null
|
||||
|
||||
let height: string;
|
||||
$: height = selectedTask == null && !mouse ? "0" :
|
||||
finalSelect ? "100%" :
|
||||
"100px"
|
||||
let heightClass: "collapsed" | "full" | "preview" = "collapsed"
|
||||
|
||||
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)
|
||||
taskPromise = grabAssignment(selectedTask)
|
||||
taskPromise = grabAssignment(selectedTask.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.panel {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
width: calc(100%-200px);
|
||||
background-color: #222;
|
||||
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>
|
||||
|
||||
<div class="panel" style="height: {height}" on:mouseover={() => mouse = false} on:mouseout={() => mouse = false}>
|
||||
{#if selectedTask != null}
|
||||
{#await taskPromise}
|
||||
Načítám úložku {selectedTask} ;)
|
||||
{:then task}
|
||||
{@html task}
|
||||
{/await}
|
||||
<button type=button on:click={() => finalSelect = false}>
|
||||
<div class="panel {heightClass}"
|
||||
on:click={() => selectedTask && select(selectedTask)}>
|
||||
<TaskDisplay task={selectedTask?.id} />
|
||||
<button type=button class="closeButton" on:click|stopPropagation={() => heightClass = "collapsed"}>
|
||||
Zavřít
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
1
frontend/src/helpers.ts
Normal file
1
frontend/src/helpers.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export function nonNull<T>(a: T | null | undefined): T { return a! }
|
|
@ -1,8 +1,9 @@
|
|||
export type TaskAssignmentData = {
|
||||
id: string,
|
||||
name: string,
|
||||
points: number,
|
||||
description: HTMLElement
|
||||
points: number | null,
|
||||
description: string,
|
||||
titleHtml: string
|
||||
}
|
||||
|
||||
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 doc = parser.parseFromString(html, "text/html")
|
||||
|
||||
|
@ -42,6 +43,13 @@ function parseTask(startElementId: string, html: string): string {
|
|||
|
||||
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 &&
|
||||
e.nextElementSibling?.tagName.toLowerCase() == "hr")
|
||||
e = e.nextElementSibling as HTMLElement
|
||||
|
@ -60,7 +68,13 @@ function parseTask(startElementId: string, html: string): string {
|
|||
let r = ""
|
||||
for (const e of elements)
|
||||
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) {
|
||||
|
@ -72,14 +86,24 @@ async function loadTask({ url, startElement }: TaskLocation) {
|
|||
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)
|
||||
if (!l) return "úloha je virtuální a neexistuje"
|
||||
if (!l) return virtualTask(id)
|
||||
return await loadTask(l)
|
||||
}
|
||||
|
||||
export async function grabSolution(id: string) {
|
||||
export async function grabSolution(id: string): Promise<TaskAssignmentData> {
|
||||
const l = getLocation(id, true)
|
||||
if (!l) return "úloha je virtuální a neexistuje"
|
||||
if (!l) return virtualTask(id)
|
||||
return await loadTask(l)
|
||||
}
|
||||
|
|
Reference in a new issue