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>
|
</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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
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">
|
<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
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 = {
|
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)
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue