mirror of
https://github.com/poanse/obsidian-taskmap.git
synced 2026-07-22 06:05:58 +00:00
added position calculations
This commit is contained in:
parent
d90f45b637
commit
ce0821ebb6
7 changed files with 477 additions and 68 deletions
355
src/NodePositionsCalculator.ts
Normal file
355
src/NodePositionsCalculator.ts
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
// Assumed types and constants based on usage
|
||||
import type { TaskData, TaskId, Vector2 } from "./types";
|
||||
|
||||
export const NoNodeId = -1 as TaskId;
|
||||
export const RootNodeId = 0 as TaskId;
|
||||
|
||||
// Helper for Vector2 operations since TS doesn't support operator overloading
|
||||
const V2 = {
|
||||
Zero: { x: 0, y: 0 },
|
||||
One: { x: 1, y: 1 },
|
||||
add: (v1: Vector2, v2: Vector2): Vector2 => ({
|
||||
x: v1.x + v2.x,
|
||||
y: v1.y + v2.y,
|
||||
}),
|
||||
sub: (v1: Vector2, v2: Vector2): Vector2 => ({
|
||||
x: v1.x - v2.x,
|
||||
y: v1.y - v2.y,
|
||||
}),
|
||||
mult: (v: Vector2, s: number | Vector2): Vector2 => {
|
||||
if (typeof s === "number") return { x: v.x * s, y: v.y * s };
|
||||
return { x: v.x * s.x, y: v.y * s.y };
|
||||
},
|
||||
};
|
||||
|
||||
export enum AlgorithmEnum {
|
||||
DefaultTree,
|
||||
SingleRow,
|
||||
DoubleRow,
|
||||
}
|
||||
|
||||
export class NodePositionsCalculator {
|
||||
public readonly ParentToChildHorizontalShift = 400;
|
||||
public readonly SiblingDelta = 90;
|
||||
public Algorithm: AlgorithmEnum = AlgorithmEnum.DefaultTree;
|
||||
public subtreeWidthByHalfPriority: Map<number, number> = new Map<
|
||||
number,
|
||||
number
|
||||
>();
|
||||
public xshift = 0.25;
|
||||
|
||||
public RootFramePositions: Map<TaskId, Vector2> = new Map<
|
||||
TaskId,
|
||||
Vector2
|
||||
>();
|
||||
|
||||
public CalculatePositionsInGlobalFrame(
|
||||
tasks: TaskData[],
|
||||
rootPosition: Vector2,
|
||||
): Map<TaskId, Vector2> {
|
||||
const rootFrame = this.CalculatePositionsInRootFrame(tasks);
|
||||
const result: Map<TaskId, Vector2> = new Map<TaskId, Vector2>();
|
||||
|
||||
for (const [key, value] of rootFrame) {
|
||||
result.set(key, V2.add(value, rootPosition));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Позиции относительно рута
|
||||
*/
|
||||
private CalculatePositionsInRootFrame(
|
||||
tasks: TaskData[],
|
||||
): Map<TaskId, Vector2> {
|
||||
const parentFramePositions =
|
||||
this.CalculatePositionsInParentFrame(tasks);
|
||||
const positions: Map<TaskId, Vector2> = new Map<TaskId, Vector2>();
|
||||
positions.set(NoNodeId, V2.Zero);
|
||||
|
||||
tasks.forEach((t) => {
|
||||
const parentPos = positions.get(t.parentId)!;
|
||||
const relativePos = parentFramePositions.get(t.taskId)!;
|
||||
positions.set(t.taskId, V2.add(parentPos, relativePos));
|
||||
});
|
||||
|
||||
this.RootFramePositions = positions;
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Считаем позиции на основе веса поддеревьев и константы alignmentRatio
|
||||
*/
|
||||
private CalculatePositionsInParentFrame(
|
||||
tasks: TaskData[],
|
||||
): Map<TaskId, Vector2> {
|
||||
// Расположение ноды родителя относительно высоты поддерева. 0 - top, 0.5 - center, 1 - bottom
|
||||
const alignmentRatio: Vector2 = { x: 0, y: 0.5 };
|
||||
// Дополнительный сдвиг в виде доли от siblingDelta, чтобы дети сиблингов не сливались в один список
|
||||
const parentDelta: Vector2 = { x: 0, y: 0.15 };
|
||||
|
||||
//// Вспомогательные структуры данных
|
||||
// Сортируем таски, чтобы упросить реализацию DP: depth DESC, parentId ASC/DESC, priority ASC
|
||||
// TODO: мб поддерживать порядок сортировки в DataModel?
|
||||
const sortedTasks = [...tasks].sort((a, b) => {
|
||||
if (b.depth !== a.depth) {
|
||||
return b.depth - a.depth;
|
||||
} else if (a.parentId !== b.parentId) {
|
||||
return a.parentId - b.parentId;
|
||||
} else {
|
||||
return a.priority - b.priority;
|
||||
}
|
||||
});
|
||||
|
||||
const taskById = new Map<TaskId, TaskData>();
|
||||
tasks.forEach((t) => taskById.set(t.taskId, t));
|
||||
|
||||
const childrenIdsByParentId: Map<TaskId, TaskId[]> = new Map<
|
||||
TaskId,
|
||||
TaskId[]
|
||||
>();
|
||||
sortedTasks.forEach((t) => {
|
||||
const pId = t.parentId;
|
||||
if (!childrenIdsByParentId.has(pId)) {
|
||||
childrenIdsByParentId.set(pId, []);
|
||||
}
|
||||
childrenIdsByParentId.get(pId)!.push(t.taskId);
|
||||
});
|
||||
|
||||
const parentIds = [...childrenIdsByParentId.keys()];
|
||||
|
||||
//// Расчеты
|
||||
// ключ - айди рута поддерева
|
||||
const subtreeSizeByNodeId: Map<TaskId, Vector2> = new Map<
|
||||
TaskId,
|
||||
Vector2
|
||||
>();
|
||||
|
||||
const allIdsToProcess = [...sortedTasks.map((t) => t.taskId), NoNodeId];
|
||||
|
||||
allIdsToProcess.forEach((id) => {
|
||||
if (childrenIdsByParentId.has(id)) {
|
||||
const childrenIds = childrenIdsByParentId.get(id)!;
|
||||
const xAgg =
|
||||
Math.max(
|
||||
...childrenIds.map(
|
||||
(x) => subtreeSizeByNodeId.get(x)!.x,
|
||||
),
|
||||
) + 1;
|
||||
const yAgg = childrenIds
|
||||
.map((x) => subtreeSizeByNodeId.get(x)!.y)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
subtreeSizeByNodeId.set(
|
||||
id,
|
||||
V2.add({ x: xAgg, y: yAgg }, parentDelta),
|
||||
);
|
||||
} else {
|
||||
subtreeSizeByNodeId.set(id, V2.One);
|
||||
}
|
||||
});
|
||||
|
||||
// Шифт дочерних нод относительно левой-верхней точки прямоугольника
|
||||
const siblingShiftsRel: Map<TaskId, Vector2> = new Map<
|
||||
TaskId,
|
||||
Vector2
|
||||
>();
|
||||
parentIds.forEach((parentId) => {
|
||||
const children = childrenIdsByParentId.get(parentId)!;
|
||||
children.forEach((childId, idx) => {
|
||||
let s = V2.mult(
|
||||
subtreeSizeByNodeId.get(childId)!,
|
||||
alignmentRatio,
|
||||
);
|
||||
if (idx > 0) {
|
||||
const previousSibling = children[idx - 1];
|
||||
const prevSize = V2.mult(
|
||||
subtreeSizeByNodeId.get(previousSibling)!,
|
||||
V2.sub(V2.One, alignmentRatio),
|
||||
);
|
||||
s = V2.add(s, prevSize);
|
||||
s = V2.add(s, siblingShiftsRel.get(previousSibling)!);
|
||||
}
|
||||
// Дополнительный сдвиг для отрисовки блокеров
|
||||
// var blockerCount = BlockerDataManager.GetConnections()
|
||||
// .Count(x => x.Item1 == childId);
|
||||
// s += blockerCount;
|
||||
siblingShiftsRel.set(childId, s);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: походу в проекте что-то не так с данными.
|
||||
// Нужно перейти на мета уровень и сделать удобный способ смотреть данные. Какой?
|
||||
// - Логировать десятки тасок в виде json может быть полезно, но не удобно.
|
||||
// - Можно добавить этап валидации данных, где будут проверяться предположения.
|
||||
// - Можно добавить дебажную инфу с данными выбранной таски. Не поможет, если проблема в удаленных.
|
||||
|
||||
// Шифт родителя относительно левой-верхней точки прямоугольника.
|
||||
// Вычитаем parentDelta, чтобы родитель был выровнен по детям
|
||||
const parentAlignmentShift: Map<TaskId, Vector2> = new Map<
|
||||
TaskId,
|
||||
Vector2
|
||||
>();
|
||||
parentIds.forEach((parentId) => {
|
||||
parentAlignmentShift.set(
|
||||
parentId,
|
||||
V2.mult(
|
||||
V2.sub(subtreeSizeByNodeId.get(parentId)!, parentDelta),
|
||||
alignmentRatio,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// Шифт дочерних нод относительно родителя и уже в нормальных координатах
|
||||
const finalChildShifts: Map<TaskId, Vector2> = new Map<
|
||||
TaskId,
|
||||
Vector2
|
||||
>();
|
||||
sortedTasks.forEach((t) => {
|
||||
const parentShift = parentAlignmentShift.get(t.parentId);
|
||||
const siblingShift = siblingShiftsRel.get(t.taskId);
|
||||
if (parentShift === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
if (siblingShift === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const verticalComponent = V2.mult(
|
||||
{ x: 0, y: this.SiblingDelta },
|
||||
V2.sub(siblingShift, parentShift),
|
||||
);
|
||||
|
||||
finalChildShifts.set(
|
||||
t.taskId,
|
||||
V2.add(
|
||||
{ x: this.ParentToChildHorizontalShift, y: 0 },
|
||||
verticalComponent,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
const rootTaskHasMultipleVisibleChildren =
|
||||
(childrenIdsByParentId.get(RootNodeId)! || []).length > 1;
|
||||
|
||||
if (
|
||||
rootTaskHasMultipleVisibleChildren &&
|
||||
[AlgorithmEnum.SingleRow, AlgorithmEnum.DoubleRow].includes(
|
||||
this.Algorithm,
|
||||
)
|
||||
) {
|
||||
const rowMinYShift = 0.3;
|
||||
const depthOneTasks = sortedTasks.filter((x) => x.depth === 1);
|
||||
|
||||
// Group by RowIndex
|
||||
const depthOneTasksByRow: Map<number, TaskData[]> = new Map<
|
||||
number,
|
||||
TaskData[]
|
||||
>();
|
||||
depthOneTasks.forEach((t) => {
|
||||
const idx = this.RowIndex(t);
|
||||
if (!depthOneTasksByRow.has(idx)) {
|
||||
depthOneTasksByRow.set(idx, []);
|
||||
}
|
||||
depthOneTasksByRow.get(idx)!.push(t);
|
||||
});
|
||||
|
||||
const yShiftByRowId: Map<number, number> = {} as Map<
|
||||
number,
|
||||
number
|
||||
>;
|
||||
Object.keys(depthOneTasksByRow).forEach((key) => {
|
||||
const rowIdx = Number(key);
|
||||
const elements = depthOneTasksByRow.get(rowIdx)!;
|
||||
|
||||
const shifts = elements.map((n) =>
|
||||
parentAlignmentShift.has(n.taskId)
|
||||
? parentAlignmentShift.get(n.taskId)!.y
|
||||
: V2.mult(alignmentRatio, V2.One).y,
|
||||
);
|
||||
|
||||
const maxYShift = shifts.length > 0 ? Math.max(...shifts) : 0; // MaxOrDefault(0) equivalent
|
||||
yShiftByRowId.set(
|
||||
rowIdx,
|
||||
(maxYShift + rowMinYShift) * this.SiblingDelta,
|
||||
);
|
||||
});
|
||||
|
||||
if (this.Algorithm === AlgorithmEnum.SingleRow) {
|
||||
let subtreeRelWidthAccumulator = this.xshift;
|
||||
depthOneTasks.forEach((x) => {
|
||||
finalChildShifts.set(x.taskId, {
|
||||
x:
|
||||
finalChildShifts.get(x.taskId)!.x +
|
||||
subtreeRelWidthAccumulator *
|
||||
this.ParentToChildHorizontalShift,
|
||||
y: yShiftByRowId.get(this.RowIndex(x))!,
|
||||
});
|
||||
subtreeRelWidthAccumulator +=
|
||||
subtreeSizeByNodeId.get(x.taskId)!.x + this.xshift;
|
||||
});
|
||||
} else if (this.Algorithm === AlgorithmEnum.DoubleRow) {
|
||||
// Размеры поддеревьев по иксу. Если 2 таски друг над другом, то используется большее.
|
||||
this.subtreeWidthByHalfPriority = {} as Map<number, number>;
|
||||
|
||||
const rootChildrenByPriority: Map<number, TaskId> = {} as Map<
|
||||
number,
|
||||
TaskId
|
||||
>;
|
||||
const rootChildren =
|
||||
childrenIdsByParentId.get(RootNodeId) ?? [];
|
||||
rootChildren.forEach((id, idx) => {
|
||||
// reenumerate priorities based on index
|
||||
rootChildrenByPriority.set(idx, id);
|
||||
});
|
||||
|
||||
rootChildrenByPriority.forEach((priority, chId) => {
|
||||
const x = taskById.get(chId)!;
|
||||
if (this.RowIndex(x) === 0) {
|
||||
const neighbourId =
|
||||
rootChildrenByPriority.get(x.priority + 1) ??
|
||||
x.taskId;
|
||||
|
||||
this.subtreeWidthByHalfPriority.set(
|
||||
Math.floor(x.priority / 2),
|
||||
Math.max(
|
||||
subtreeSizeByNodeId.get(x.taskId)!.x,
|
||||
subtreeSizeByNodeId.get(neighbourId)!.x,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
depthOneTasks.forEach((x) => {
|
||||
const t = [...this.subtreeWidthByHalfPriority]
|
||||
.filter(([k, _]) => k < Math.floor(x.priority / 2))
|
||||
.map(([_, v]) => v);
|
||||
|
||||
const tSum = t.reduce((a, b) => a + b, 0);
|
||||
|
||||
finalChildShifts.set(x.taskId, {
|
||||
x:
|
||||
finalChildShifts.get(x.taskId)!.x +
|
||||
(this.xshift + this.xshift * t.length + tSum) *
|
||||
this.ParentToChildHorizontalShift,
|
||||
y:
|
||||
this.RowIndex(x) === 0
|
||||
? yShiftByRowId.get(this.RowIndex(x))!
|
||||
: -yShiftByRowId.get(this.RowIndex(x))!,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
return finalChildShifts;
|
||||
}
|
||||
|
||||
public RowIndex(node: TaskData): number {
|
||||
switch (this.Algorithm) {
|
||||
case AlgorithmEnum.SingleRow:
|
||||
return 0;
|
||||
case AlgorithmEnum.DoubleRow:
|
||||
return node.priority % 2;
|
||||
default:
|
||||
throw new Error("AssumptionViolation");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { StatusCode, type TaskData } from "./types";
|
||||
import TaskmapPlugin from "./main";
|
||||
import { StatusCode, type TaskData, type TaskId } from "./types";
|
||||
import { NoNodeId, RootNodeId } from "./NodePositionsCalculator";
|
||||
|
||||
export class ProjectData {
|
||||
tasks = $state(new Array<TaskData>());
|
||||
curTaskId = 0;
|
||||
curTaskId = RootNodeId;
|
||||
|
||||
public static getDefault(): ProjectData {
|
||||
return new ProjectData({
|
||||
|
|
@ -24,19 +24,35 @@ export class ProjectData {
|
|||
this.tasks = obj.tasks;
|
||||
this.curTaskId = obj.curTaskId;
|
||||
if (this.tasks.length == 0) {
|
||||
this.addTask();
|
||||
this.addRootTask();
|
||||
}
|
||||
}
|
||||
|
||||
public addTask() {
|
||||
public addRootTask() {
|
||||
this.tasks.push({
|
||||
taskId: this.curTaskId,
|
||||
parentId: NoNodeId,
|
||||
status: StatusCode.IN_PROGRESS,
|
||||
name: "root",
|
||||
priority: 0,
|
||||
depth: 0,
|
||||
deleted: false,
|
||||
});
|
||||
this.curTaskId++;
|
||||
}
|
||||
|
||||
public addTask(parentId: TaskId) {
|
||||
const id = this.curTaskId;
|
||||
this.tasks.push({
|
||||
taskId: id,
|
||||
parentId: parentId,
|
||||
status: StatusCode.DRAFT,
|
||||
name: "default",
|
||||
deleted: false,
|
||||
priority: 0, // TODO: change to amount of siblings
|
||||
depth: this.getTask(parentId).depth + 1,
|
||||
});
|
||||
this.curTaskId += 1;
|
||||
this.curTaskId++;
|
||||
return id;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { mount } from "svelte";
|
|||
import { DEFAULT_DATA, ProjectData } from "./ProjectData.svelte";
|
||||
import pixi from "./components/pixi.svelte";
|
||||
import { UIState } from "./pixi/GlobalState.svelte";
|
||||
import { NodePositionsCalculator } from "./NodePositionsCalculator";
|
||||
|
||||
export const VIEW_TYPE_EXAMPLE = "example";
|
||||
|
||||
|
|
@ -47,7 +48,12 @@ export class TaskmapView extends TextFileView {
|
|||
this.pixiComponent = mount(pixi, {
|
||||
target: this.contentEl,
|
||||
props: {
|
||||
uiState: new UIState(this.projectData, this.app),
|
||||
uiState: new UIState(
|
||||
this,
|
||||
this.projectData,
|
||||
this.app,
|
||||
new NodePositionsCalculator(),
|
||||
),
|
||||
projectData: this.projectData,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
}
|
||||
function addButtonPressed (event: MouseEvent) {
|
||||
console.log('add icon clicked')
|
||||
uiState.addTask();
|
||||
uiState.addTask(taskId);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
|
|
@ -47,30 +47,37 @@
|
|||
"
|
||||
>
|
||||
</div>
|
||||
|
||||
{#if entered}
|
||||
<svg
|
||||
class="button-add"
|
||||
class:draft={taskData.status === StatusCode.DRAFT}
|
||||
class:ready={taskData.status === StatusCode.READY}
|
||||
class:in-progress={taskData.status === StatusCode.IN_PROGRESS}
|
||||
class:done={taskData.status === StatusCode.DONE}
|
||||
onmouseenter={onEnter}
|
||||
onmouseleave={onLeave}
|
||||
onclick={addButtonPressed}
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"
|
||||
style="
|
||||
position: absolute;
|
||||
left: {TASK_SIZE.width - 41/2 - 1}px;
|
||||
top: {TASK_SIZE.height/2 - 41/2}px;
|
||||
width: 41;
|
||||
height: 41;
|
||||
"
|
||||
>
|
||||
<circle
|
||||
cx="12" cy="12" r="10"
|
||||
/>
|
||||
<path d="M5 12h14"/><path d="M12 5v14"/>
|
||||
</svg>
|
||||
<svg
|
||||
class="button-add"
|
||||
class:draft={taskData.status === StatusCode.DRAFT}
|
||||
class:ready={taskData.status === StatusCode.READY}
|
||||
class:in-progress={taskData.status === StatusCode.IN_PROGRESS}
|
||||
class:done={taskData.status === StatusCode.DONE}
|
||||
onmouseenter={onEnter}
|
||||
onmouseleave={onLeave}
|
||||
onclick={addButtonPressed}
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"
|
||||
style="
|
||||
position: absolute;
|
||||
left: {TASK_SIZE.width - 41/2 - 1}px;
|
||||
top: {TASK_SIZE.height/2 - 41/2}px;
|
||||
width: 41;
|
||||
height: 41;
|
||||
"
|
||||
>
|
||||
<circle
|
||||
cx="12" cy="12" r="10"
|
||||
/>
|
||||
<path
|
||||
stroke-width="1.5"
|
||||
d="M5 12h14"
|
||||
/><path
|
||||
stroke-width="1.5"
|
||||
d="M12 5v14"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@
|
|||
|
||||
|
||||
{#if !projectData.isTaskDeleted(taskId)}
|
||||
{#key uiState.updateOnZoomCounter}
|
||||
<div
|
||||
class="task-container"
|
||||
style="
|
||||
|
|
@ -159,6 +160,7 @@
|
|||
</div>
|
||||
<AddTaskButton {uiState} {projectData} {taskId} />
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
<!--top: {coords.y}px;-->
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
import TaskmapPlugin from "../main";
|
||||
import type { ProjectData } from "../ProjectData.svelte";
|
||||
import { StatusCode } from "../types";
|
||||
import { StatusCode, type TaskId } from "../types";
|
||||
import { Spring } from "svelte/motion";
|
||||
import type { App } from "obsidian";
|
||||
import type { TaskmapView } from "../TaskmapView";
|
||||
import type { NodePositionsCalculator } from "../NodePositionsCalculator";
|
||||
|
||||
export class UIState {
|
||||
app: App;
|
||||
view: TaskmapView;
|
||||
nodePositionsCalculator: NodePositionsCalculator;
|
||||
pressedButtonIndex = $state(-1);
|
||||
selectedTaskId = $state(-1);
|
||||
toolbarStatus = $state(StatusCode.DRAFT);
|
||||
projectData: ProjectData;
|
||||
taskPositions: Array<{
|
||||
taskId: number;
|
||||
tween: Spring<{ x: number; y: number }>;
|
||||
taskId: TaskId;
|
||||
tween: Spring<{ x: number; y: number }> | null;
|
||||
}>;
|
||||
// svg elements are pixelated zooming from scale < 1 to scale > 1, so we force a redraw manually
|
||||
updateOnZoomCounter = $state(0);
|
||||
|
|
@ -22,19 +25,26 @@ export class UIState {
|
|||
private tweenOptions = { duration: 300 };
|
||||
private springOptions = { stiffness: 0.07, damping: 0.7 };
|
||||
|
||||
constructor(projectData: ProjectData, app: App) {
|
||||
constructor(
|
||||
view: TaskmapView,
|
||||
projectData: ProjectData,
|
||||
app: App,
|
||||
nodePositionsCalculator: NodePositionsCalculator,
|
||||
) {
|
||||
this.view = view;
|
||||
this.nodePositionsCalculator = nodePositionsCalculator;
|
||||
this.app = app;
|
||||
this.projectData = projectData;
|
||||
const posArray = projectData.tasks
|
||||
|
||||
this.taskPositions = projectData.tasks
|
||||
.filter((t) => !t.deleted)
|
||||
.map((task) => {
|
||||
const t = new Spring(
|
||||
this.calcTaskPosition(task.taskId),
|
||||
this.springOptions,
|
||||
);
|
||||
return { taskId: task.taskId, tween: t };
|
||||
return {
|
||||
taskId: task.taskId,
|
||||
tween: null,
|
||||
};
|
||||
});
|
||||
this.taskPositions = posArray;
|
||||
this.updateTaskPositions();
|
||||
}
|
||||
|
||||
public incrementUpdateOnZoomCounter(): void {
|
||||
|
|
@ -42,8 +52,20 @@ export class UIState {
|
|||
}
|
||||
|
||||
public updateTaskPositions() {
|
||||
const positions =
|
||||
this.nodePositionsCalculator.CalculatePositionsInGlobalFrame(
|
||||
this.projectData.tasks.filter((t) => !t.deleted),
|
||||
{ x: 0, y: 0 },
|
||||
);
|
||||
this.taskPositions.forEach((taskPos) => {
|
||||
taskPos.tween.target = this.calcTaskPosition(taskPos.taskId);
|
||||
const newPos = positions.get(taskPos.taskId);
|
||||
if (newPos === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
if (taskPos.tween === null) {
|
||||
taskPos.tween = new Spring(newPos, this.springOptions);
|
||||
}
|
||||
taskPos.tween.target = newPos;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -68,26 +90,29 @@ export class UIState {
|
|||
}
|
||||
|
||||
public getActiveView(): TaskmapView {
|
||||
const x = TaskmapPlugin.getActiveView();
|
||||
if (x) {
|
||||
return x;
|
||||
} else {
|
||||
throw new Error("Unable to get active view");
|
||||
}
|
||||
return this.view;
|
||||
// const x = TaskmapPlugin.getActiveView();
|
||||
// if (x) {
|
||||
// return x;
|
||||
// } else {
|
||||
// throw new Error("Unable to get active view");
|
||||
// }
|
||||
}
|
||||
|
||||
public addTask() {
|
||||
const id = this.projectData.addTask();
|
||||
public addTask(parentId: TaskId): void {
|
||||
const id = this.projectData.addTask(parentId);
|
||||
this.save();
|
||||
this.taskPositions.push({
|
||||
taskId: id,
|
||||
tween: new Spring(this.calcTaskPosition(id), this.springOptions),
|
||||
tween: null,
|
||||
});
|
||||
this.save();
|
||||
this.updateTaskPositions();
|
||||
}
|
||||
|
||||
public removeTask(id: number) {
|
||||
this.setSelectedTaskId(-1);
|
||||
this.projectData.removeTask(id);
|
||||
this.taskPositions = this.taskPositions.filter((t) => t.taskId !== id);
|
||||
this.updateTaskPositions();
|
||||
this.save();
|
||||
}
|
||||
|
|
@ -106,19 +131,8 @@ export class UIState {
|
|||
// change its status
|
||||
}
|
||||
|
||||
private calcTaskPosition(taskId: number) {
|
||||
const base_position = { x: 200, y: 500 };
|
||||
const idx = this.projectData.tasks
|
||||
.filter((t) => !t.deleted)
|
||||
.findIndex((t) => t.taskId == taskId);
|
||||
return {
|
||||
x: base_position.x + 200 * idx,
|
||||
y: base_position.y + 200 * idx,
|
||||
};
|
||||
}
|
||||
|
||||
public getCurrentTaskPosition(taskId: number) {
|
||||
const pos = this.taskPositions.find((t) => t.taskId === taskId)!.tween
|
||||
const pos = this.taskPositions.find((t) => t.taskId === taskId)!.tween!
|
||||
.current;
|
||||
if (taskId == 5) {
|
||||
console.log(JSON.stringify(pos));
|
||||
|
|
|
|||
11
src/types.ts
11
src/types.ts
|
|
@ -23,11 +23,20 @@ export enum StatusCode {
|
|||
DONE,
|
||||
}
|
||||
|
||||
export interface Vector2 {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
export type TaskId = number;
|
||||
|
||||
export type TaskData = {
|
||||
name: string;
|
||||
taskId: number;
|
||||
taskId: TaskId;
|
||||
status: StatusCode;
|
||||
deleted: boolean;
|
||||
parentId: TaskId;
|
||||
depth: number;
|
||||
priority: number;
|
||||
};
|
||||
|
||||
export type Context = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue