editor: first version

This commit is contained in:
Vašek Šraier 2020-09-28 00:30:34 +02:00
parent 040d73492a
commit 2006216a99
6 changed files with 219 additions and 46 deletions

View file

@ -13,7 +13,7 @@
<script type="module" src='/build/bundle.js'></script> <script type="module" src='/build/bundle.js'></script>
</head> </head>
<body> <body style="padding: 0">
<div id="svelte-root"></div> <div id="svelte-root"></div>
</body> </body>
</html> </html>

View file

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
import GraphNode from "./GraphNode.svelte";
import { loadTasks } from "./task-loader"; import { loadTasks } from "./task-loader";
import type { TasksFile, TaskDescriptor } from "./task-loader"; import type { TasksFile, TaskDescriptor } from "./task-loader";
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";
const tasksPromise: Promise<TasksFile> = loadTasks(); const tasksPromise: Promise<TasksFile> = loadTasks();
@ -15,12 +15,13 @@
finalSelect = true finalSelect = true
} }
const hash = window.location.hash.substr(1);
</script> </script>
<style> <style>
main { main {
text-align: center; text-align: center;
padding: 1em;
max-width: 240px; max-width: 240px;
margin: 0 auto; margin: 0 auto;
} }
@ -40,13 +41,14 @@
</style> </style>
<main> <main>
<!-- {#if hash == "editor"}
<svg height=200 width=200> <TasksLoader promise={tasksPromise} let:data={t}>
<GraphNode task="null" /> <Editor tasks={t} />
</svg> </TasksLoader>
--> {:else}
<TasksLoader promise={tasksPromise} let:data={t}> <TasksLoader promise={tasksPromise} let:data={t}>
<TaskPanel bind:finalSelect={finalSelect} selectedTask={selectedTask} /> <TaskPanel bind:finalSelect={finalSelect} selectedTask={selectedTask} />
<Graph tasks={t} bind:selectedTask={selectedTask} on:selectTask={clickTask} /> <Graph tasks={t} bind:selectedTask={selectedTask} on:selectTask={clickTask} />
</TasksLoader> </TasksLoader>
{/if}
</main> </main>

143
frontend/src/Editor.svelte Normal file
View 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>

View file

@ -6,48 +6,35 @@
import { createLinksFromTaskMap } from "./task-loader"; import { createLinksFromTaskMap } from "./task-loader";
import type { TasksFile, TaskDescriptor } from "./task-loader"; import type { TasksFile, TaskDescriptor } from "./task-loader";
const eventDispatcher = createEventDispatcher() const eventDispatcher = createEventDispatcher();
export let tasks: TasksFile; export let tasks: TasksFile;
let nodes = tasks.tasks; export let selectedTask: null | string = null;
let edges = createLinksFromTaskMap(tasks);
$: nodes = tasks.tasks;
$: edges = createLinksFromTaskMap(tasks);
export let selectedTask: null | string = null
const nodeClick = (task: TaskDescriptor) => (e: CustomEvent<MouseEvent>) => { const nodeClick = (task: TaskDescriptor) => (e: CustomEvent<MouseEvent>) => {
selectedTask = task.id selectedTask = task.id;
eventDispatcher("selectTask", task) eventDispatcher("selectTask", task);
} };
const nodeHover = (task: TaskDescriptor) => (hovering: CustomEvent<boolean>) => { const nodeHover = (task: TaskDescriptor) => (
hovering: CustomEvent<boolean>
) => {
if (hovering.detail) { if (hovering.detail) {
selectedTask = task.id selectedTask = task.id;
} else { } else {
if (selectedTask == task.id) if (selectedTask == task.id) selectedTask = null;
selectedTask = null
} }
} };
// Svelte automatically fills this with a reference // Svelte automatically fills this with a reference
let container: HTMLElement; let container: HTMLElement;
onMount(async () => { function runSimulation() {
// 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 + ")");
// Let's list the force we wanna apply on the network // 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 .forceSimulation(nodes) // Force algorithm is applied to data.nodes
.force( .force(
"link", "link",
@ -69,12 +56,41 @@
edges = edges; edges = edges;
nodes = nodes; 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> </script>
<style> <style>
div { div {
height: 100vh; height: 100%;
width: 100%; width: 100%;
} }
</style> </style>
@ -83,10 +99,13 @@
<svg> <svg>
<g> <g>
{#each edges as edge} {#each edges as edge}
<GraphEdge {edge} /> <GraphEdge {edge} />
{/each} {/each}
{#each nodes as task} {#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} {/each}
</g> </g>
</svg> </svg>

View file

@ -72,7 +72,7 @@ async function loadTask({ url, startElement }: TaskLocation) {
return parseTask(startElement, rText) return parseTask(startElement, rText)
} }
export async function grabAssignment(id: string) { export async function grabAssignment(id: string): Promise<string> {
const l = getLocation(id, false) const l = getLocation(id, false)
if (!l) return "úloha je virtuální a neexistuje" if (!l) return "úloha je virtuální a neexistuje"
return await loadTask(l) return await loadTask(l)

View file

@ -2,7 +2,7 @@ import type { SimulationNodeDatum, SimulationLinkDatum } from "d3";
export type TaskDescriptor = { export type TaskDescriptor = {
id: string id: string
requires: [] requires: string[]
comment?: string comment?: string
} & SimulationNodeDatum } & SimulationNodeDatum
@ -49,3 +49,12 @@ export function createLinksFromTaskMap(tasks: TasksFile): SimulationLinkDatum<Ta
return links; 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;
}