editor: first version
This commit is contained in:
parent
040d73492a
commit
2006216a99
6 changed files with 219 additions and 46 deletions
|
@ -13,7 +13,7 @@
|
|||
<script type="module" src='/build/bundle.js'></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body style="padding: 0">
|
||||
<div id="svelte-root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import Graph from "./Graph.svelte";
|
||||
import GraphNode from "./GraphNode.svelte";
|
||||
import { loadTasks } from "./task-loader";
|
||||
import type { TasksFile, TaskDescriptor } from "./task-loader";
|
||||
import TasksLoader from "./TasksLoader.svelte";
|
||||
import TaskPanel from "./TaskPanel.svelte";
|
||||
import Editor from "./Editor.svelte";
|
||||
|
||||
const tasksPromise: Promise<TasksFile> = loadTasks();
|
||||
|
||||
|
@ -15,12 +15,13 @@
|
|||
finalSelect = true
|
||||
}
|
||||
|
||||
const hash = window.location.hash.substr(1);
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
main {
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
max-width: 240px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
@ -40,13 +41,14 @@
|
|||
</style>
|
||||
|
||||
<main>
|
||||
<!--
|
||||
<svg height=200 width=200>
|
||||
<GraphNode task="null" />
|
||||
</svg>
|
||||
-->
|
||||
<TasksLoader promise={tasksPromise} let:data={t}>
|
||||
<TaskPanel bind:finalSelect={finalSelect} selectedTask={selectedTask} />
|
||||
<Graph tasks={t} bind:selectedTask={selectedTask} on:selectTask={clickTask} />
|
||||
</TasksLoader>
|
||||
{#if hash == "editor"}
|
||||
<TasksLoader promise={tasksPromise} let:data={t}>
|
||||
<Editor tasks={t} />
|
||||
</TasksLoader>
|
||||
{:else}
|
||||
<TasksLoader promise={tasksPromise} let:data={t}>
|
||||
<TaskPanel bind:finalSelect={finalSelect} selectedTask={selectedTask} />
|
||||
<Graph tasks={t} bind:selectedTask={selectedTask} on:selectTask={clickTask} />
|
||||
</TasksLoader>
|
||||
{/if}
|
||||
</main>
|
||||
|
|
143
frontend/src/Editor.svelte
Normal file
143
frontend/src/Editor.svelte
Normal file
|
@ -0,0 +1,143 @@
|
|||
<script type="ts">
|
||||
import Graph from "./Graph.svelte";
|
||||
import { grabAssignment } from "./ksp-task-grabber";
|
||||
import type { TaskDescriptor, TasksFile } from "./task-loader";
|
||||
import { createTaskMap, getCategories } from "./task-loader";
|
||||
|
||||
export let tasks: TasksFile;
|
||||
|
||||
let selectedTask: string | null = null;
|
||||
let clickedTask: string | null = null;
|
||||
|
||||
let clicked: string[] = [];
|
||||
|
||||
function clickTask(e: CustomEvent<TaskDescriptor>) {
|
||||
// ukladani seznamu poslednich kliknuti
|
||||
clicked.push(selectedTask);
|
||||
if (clicked.length > 3)
|
||||
clicked = clicked.slice(clicked.length - 3, clicked.length);
|
||||
clicked = clicked;
|
||||
|
||||
// trackovani, kam se naposledy kliknulo
|
||||
if (clickedTask == selectedTask) {
|
||||
clickedTask = null;
|
||||
} else {
|
||||
clickedTask = selectedTask;
|
||||
}
|
||||
}
|
||||
|
||||
$: taskMap = createTaskMap(tasks);
|
||||
$: currentTask = clickedTask != null ? clickedTask : selectedTask;
|
||||
|
||||
function addEdge() {
|
||||
if (clicked.length > 1) {
|
||||
const first = clicked[clicked.length - 1];
|
||||
const second = clicked[clicked.length - 2];
|
||||
|
||||
tasks.tasks.forEach((t) => {
|
||||
if (t.id == first) {
|
||||
t.requires.push(second);
|
||||
t = t;
|
||||
}
|
||||
});
|
||||
tasks = tasks;
|
||||
} else {
|
||||
alert("Nope, prvni musis nekam klikat...");
|
||||
}
|
||||
}
|
||||
|
||||
let hovnoDivnaPromenaKteraJeFaktFuj = true;
|
||||
function toggleDivnaPromena() {
|
||||
hovnoDivnaPromenaKteraJeFaktFuj = ! hovnoDivnaPromenaKteraJeFaktFuj;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.graph {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
background-color: black;
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 40vw;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.left {
|
||||
width: 60vw;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.toolbox {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
height: 50%;
|
||||
background-color: pink;
|
||||
}
|
||||
|
||||
.taskDetails {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
height: 50%;
|
||||
background-color: aqua;
|
||||
}
|
||||
|
||||
.lastClicked {
|
||||
height: 5%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<div class="left">
|
||||
<div class="lastClicked">Last clicked: <b>{clicked.join(' | ')}</b></div>
|
||||
<div class="graph">
|
||||
<Graph {tasks} bind:selectedTask on:selectTask={clickTask} runSimulationWeirdHack={hovnoDivnaPromenaKteraJeFaktFuj} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="toolbox">
|
||||
<div>Toolbox</div>
|
||||
<div>
|
||||
<button on:click={addEdge}>Pridat hranu - posledni vyzaduje predposledni</button>
|
||||
</div>
|
||||
<div>
|
||||
<button on:click={toggleDivnaPromena}>Spustit simulaci</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="taskDetails">
|
||||
{#if currentTask != null}
|
||||
<h3>{currentTask}</h3>
|
||||
<span>{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>
|
||||
{:else}
|
||||
<h3>Nothing selected...</h3>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -6,48 +6,35 @@
|
|||
import { createLinksFromTaskMap } from "./task-loader";
|
||||
import type { TasksFile, TaskDescriptor } from "./task-loader";
|
||||
|
||||
const eventDispatcher = createEventDispatcher()
|
||||
const eventDispatcher = createEventDispatcher();
|
||||
|
||||
export let tasks: TasksFile;
|
||||
let nodes = tasks.tasks;
|
||||
let edges = createLinksFromTaskMap(tasks);
|
||||
export let selectedTask: null | string = null;
|
||||
|
||||
$: nodes = tasks.tasks;
|
||||
$: edges = createLinksFromTaskMap(tasks);
|
||||
|
||||
export let selectedTask: null | string = null
|
||||
const nodeClick = (task: TaskDescriptor) => (e: CustomEvent<MouseEvent>) => {
|
||||
selectedTask = task.id
|
||||
eventDispatcher("selectTask", task)
|
||||
}
|
||||
selectedTask = task.id;
|
||||
eventDispatcher("selectTask", task);
|
||||
};
|
||||
|
||||
const nodeHover = (task: TaskDescriptor) => (hovering: CustomEvent<boolean>) => {
|
||||
const nodeHover = (task: TaskDescriptor) => (
|
||||
hovering: CustomEvent<boolean>
|
||||
) => {
|
||||
if (hovering.detail) {
|
||||
selectedTask = task.id
|
||||
selectedTask = task.id;
|
||||
} else {
|
||||
if (selectedTask == task.id)
|
||||
selectedTask = null
|
||||
if (selectedTask == task.id) selectedTask = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Svelte automatically fills this with a reference
|
||||
let container: HTMLElement;
|
||||
|
||||
onMount(async () => {
|
||||
// set the dimensions and margins of the graph
|
||||
var margin = { top: 10, right: 30, bottom: 30, left: 40 },
|
||||
width = container.clientWidth - margin.left - margin.right,
|
||||
height = container.clientHeight - margin.top - margin.bottom;
|
||||
|
||||
// resize the svg object
|
||||
var svg = d3
|
||||
.select(container)
|
||||
.select("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
.attr("viewBox", [-width / 2, -height / 2, width, height])
|
||||
.select("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
function runSimulation() {
|
||||
// Let's list the force we wanna apply on the network
|
||||
var simulation = d3
|
||||
let simulation = d3
|
||||
.forceSimulation(nodes) // Force algorithm is applied to data.nodes
|
||||
.force(
|
||||
"link",
|
||||
|
@ -69,12 +56,41 @@
|
|||
edges = edges;
|
||||
nodes = nodes;
|
||||
}
|
||||
}
|
||||
|
||||
// run on create
|
||||
onMount(() => {
|
||||
// set the dimensions and margins of the graph
|
||||
var margin = { top: 10, right: 30, bottom: 30, left: 40 },
|
||||
width = container.clientWidth - margin.left - margin.right,
|
||||
height = container.clientHeight - margin.top - margin.bottom;
|
||||
|
||||
// resize the svg object
|
||||
var svg = d3
|
||||
.select(container)
|
||||
.select("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
.attr("viewBox", [-width / 2, -height / 2, width, height])
|
||||
.select("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
runSimulation();
|
||||
});
|
||||
|
||||
// don't forget to vomit 🤮🤢
|
||||
export let runSimulationWeirdHack: boolean = true;
|
||||
$: {
|
||||
runSimulationWeirdHack;
|
||||
runSimulation();
|
||||
}
|
||||
// now it's safe to stop vomitting 🤮
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
div {
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@ -83,10 +99,13 @@
|
|||
<svg>
|
||||
<g>
|
||||
{#each edges as edge}
|
||||
<GraphEdge {edge} />
|
||||
<GraphEdge {edge} />
|
||||
{/each}
|
||||
{#each nodes as task}
|
||||
<GraphNode {task} on:click={nodeClick(task)} on:hoveringChange={nodeHover(task)} />
|
||||
<GraphNode
|
||||
{task}
|
||||
on:click={nodeClick(task)}
|
||||
on:hoveringChange={nodeHover(task)} />
|
||||
{/each}
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
@ -72,7 +72,7 @@ async function loadTask({ url, startElement }: TaskLocation) {
|
|||
return parseTask(startElement, rText)
|
||||
}
|
||||
|
||||
export async function grabAssignment(id: string) {
|
||||
export async function grabAssignment(id: string): Promise<string> {
|
||||
const l = getLocation(id, false)
|
||||
if (!l) return "úloha je virtuální a neexistuje"
|
||||
return await loadTask(l)
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { SimulationNodeDatum, SimulationLinkDatum } from "d3";
|
|||
|
||||
export type TaskDescriptor = {
|
||||
id: string
|
||||
requires: []
|
||||
requires: string[]
|
||||
comment?: string
|
||||
} & SimulationNodeDatum
|
||||
|
||||
|
@ -49,3 +49,12 @@ export function createLinksFromTaskMap(tasks: TasksFile): SimulationLinkDatum<Ta
|
|||
|
||||
return links;
|
||||
}
|
||||
|
||||
export function getCategories(tasks: TasksFile, taskId: string): string[] {
|
||||
let res: string[] = [];
|
||||
for (let [cat, ids] of Object.entries(tasks.clusters)) {
|
||||
if (ids.indexOf(taskId) >= 0) res.push(cat);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
Reference in a new issue