mirror of
https://github.com/poanse/obsidian-taskmap.git
synced 2026-07-22 14:00:24 +00:00
cleanup
This commit is contained in:
parent
c3588ee40b
commit
ce3eafb8df
11 changed files with 21 additions and 689 deletions
|
|
@ -1,12 +1,12 @@
|
|||
import TaskmapPlugin from "../main";
|
||||
import type { ProjectData } from "../ProjectData.svelte";
|
||||
import { StatusCode, type TaskId } from "../types";
|
||||
import TaskmapPlugin from "./main";
|
||||
import type { ProjectData } from "./ProjectData.svelte.js";
|
||||
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";
|
||||
import type { TaskmapView } from "./TaskmapView";
|
||||
import type { NodePositionsCalculator } from "./NodePositionsCalculator";
|
||||
|
||||
export class UIState {
|
||||
export class Context {
|
||||
app: App;
|
||||
view: TaskmapView;
|
||||
nodePositionsCalculator: NodePositionsCalculator;
|
||||
|
|
@ -132,12 +132,8 @@ export class UIState {
|
|||
}
|
||||
|
||||
public getCurrentTaskPosition(taskId: number) {
|
||||
const pos = this.taskPositions.find((t) => t.taskId === taskId)!.tween!
|
||||
return this.taskPositions.find((t) => t.taskId === taskId)!.tween!
|
||||
.current;
|
||||
if (taskId == 5) {
|
||||
console.log(JSON.stringify(pos));
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
public serializeForDebugging() {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { IconCode, StatusCode } from "../types";
|
||||
import { IconCode, StatusCode } from "./types";
|
||||
|
||||
export const LOGO_NAME = "TaskmapLogo";
|
||||
export const LOGO_CONTENT = `
|
||||
|
|
@ -3,9 +3,9 @@
|
|||
import Toolbar from "./Toolbar.svelte";
|
||||
import Task from "./Task.svelte";
|
||||
import Panzoom, {type PanzoomObject} from '@panzoom/panzoom'
|
||||
import type {Context} from "../types";
|
||||
import type {Context} from "../Context.svelte.js";
|
||||
|
||||
let {uiState, projectData}: Context = $props();
|
||||
let {context}: {context: Context} = $props();
|
||||
|
||||
let viewportEl: HTMLDivElement | null = null;
|
||||
let sceneEl: HTMLDivElement | null = null;
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
(ev) => {
|
||||
getPanzoom().zoomWithWheel(ev);
|
||||
if (getPanzoom().getScale() > 1) {
|
||||
uiState.incrementUpdateOnZoomCounter();
|
||||
context.incrementUpdateOnZoomCounter();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
function handleKey(e: KeyboardEvent) {
|
||||
console.log('handleKey ', e.key);
|
||||
if (e.key === "Escape") {
|
||||
uiState.setSelectedTaskId(-1);
|
||||
context.setSelectedTaskId(-1);
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
|
@ -93,10 +93,10 @@
|
|||
return
|
||||
}
|
||||
mouseDown = false;
|
||||
console.log(`Window clicked + ${uiState.serializeForDebugging()}`);
|
||||
console.log('selectedTaskId ' + uiState.selectedTaskId);
|
||||
uiState.pressedButtonIndex = -1;
|
||||
uiState.setSelectedTaskId(-1);
|
||||
console.log(`Window clicked + ${context.serializeForDebugging()}`);
|
||||
console.log('selectedTaskId ' + context.selectedTaskId);
|
||||
context.pressedButtonIndex = -1;
|
||||
context.setSelectedTaskId(-1);
|
||||
sceneEl!.focus();
|
||||
}
|
||||
function getPanzoom() {
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
style="width: 100%; height: 100vh; overflow: hidden; position: relative; background: #1C1C1C;"
|
||||
>
|
||||
<div
|
||||
class="pixi-container"
|
||||
class="task-container"
|
||||
bind:this={sceneEl}
|
||||
onpointerdown={() => mouseDown = true}
|
||||
onclick={handleCanvasClick}
|
||||
|
|
@ -124,13 +124,13 @@
|
|||
style="width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;"
|
||||
role="presentation"
|
||||
>
|
||||
{#each projectData.tasks.filter(t => !t.deleted) as task}
|
||||
<Task taskId={task.taskId} {uiState} {projectData} coords={uiState.getCurrentTaskPosition(task.taskId)} />
|
||||
{#each context.projectData.tasks.filter(t => !t.deleted) as task}
|
||||
<Task taskId={task.taskId} {context} coords={context.getCurrentTaskPosition(task.taskId)} />
|
||||
{/each}
|
||||
|
||||
{#if uiState.selectedTaskId !== -1}
|
||||
{#if context.selectedTaskId !== -1}
|
||||
<Toolbar
|
||||
uiState={uiState}
|
||||
context={context}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
import {
|
||||
type Application,
|
||||
Color,
|
||||
Container,
|
||||
Graphics,
|
||||
SCALE_MODES,
|
||||
type Size,
|
||||
Sprite,
|
||||
Texture,
|
||||
} from "pixi.js";
|
||||
import { match, P } from "ts-pattern";
|
||||
|
||||
export enum ButtonState {
|
||||
INACTIVE = "INACTIVE",
|
||||
INACTIVE_HOVERED = "INACTIVE_HOVERED",
|
||||
ACTIVE = "ACTIVE",
|
||||
ACTIVE_HOVERED = "ACTIVE_HOVERED",
|
||||
// TODO: pressed
|
||||
}
|
||||
|
||||
export type ColorStateMap = {
|
||||
[key in ButtonState]: Color;
|
||||
};
|
||||
|
||||
class ButtonOptions {
|
||||
size: Size;
|
||||
backgroundColors: ColorStateMap;
|
||||
iconColors: ColorStateMap;
|
||||
}
|
||||
|
||||
export class Button extends Container {
|
||||
private app: Application;
|
||||
private state: ButtonState;
|
||||
private opts: ButtonOptions;
|
||||
private iconSource: Promise<Graphics | Texture>;
|
||||
|
||||
private background: Sprite;
|
||||
private icon: Sprite;
|
||||
|
||||
constructor(
|
||||
app: Application,
|
||||
initialState: ButtonState,
|
||||
iconSource: Promise<Graphics | Texture>,
|
||||
opts: ButtonOptions,
|
||||
) {
|
||||
super();
|
||||
this.app = app;
|
||||
this.state = initialState;
|
||||
this.iconSource = iconSource;
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.eventMode = "static";
|
||||
this.cursor = "pointer";
|
||||
|
||||
this.on("pointerover", this.hoverOn);
|
||||
this.on("pointerout", this.hoverOff);
|
||||
this.on("pointerdown", () => {
|
||||
console.log("Clicked icon");
|
||||
});
|
||||
// background
|
||||
|
||||
// tooltip
|
||||
// ...
|
||||
this.update();
|
||||
// callbacks
|
||||
// ...
|
||||
}
|
||||
|
||||
async update() {
|
||||
// TODO: я идиот и менял не тот бэкграунд
|
||||
// this.background.destroy();
|
||||
|
||||
const bgTexture = this.app.renderer.generateTexture({
|
||||
target: new Graphics()
|
||||
.roundRect(0, 0, this.opts.size.width, this.opts.size.height, 2)
|
||||
.fill({ color: this.opts.backgroundColors[this.state] }),
|
||||
// resolution: 4,
|
||||
// antialias: true,
|
||||
});
|
||||
bgTexture.source.scaleMode = "linear";
|
||||
this.background = new Sprite(bgTexture);
|
||||
this.addChild(this.background);
|
||||
|
||||
const icon = await this.iconSource;
|
||||
const iconTexture = match(icon)
|
||||
.returnType<Texture>()
|
||||
.with(P.instanceOf(Graphics), () =>
|
||||
this.app.renderer.generateTexture({
|
||||
target: icon as Graphics,
|
||||
// .fill({
|
||||
// color: this.opts.iconColors[this.state],
|
||||
// })
|
||||
// antialias: true,
|
||||
resolution: 4,
|
||||
}),
|
||||
)
|
||||
.with(P.instanceOf(Texture), () => icon as Texture)
|
||||
.exhaustive();
|
||||
iconTexture.source.scaleMode = "linear";
|
||||
this.icon = new Sprite({
|
||||
texture: iconTexture,
|
||||
anchor: 0.5,
|
||||
// roundPixels: true,
|
||||
});
|
||||
this.icon.scale = 0.6;
|
||||
this.icon.position.set(
|
||||
this.opts.size.width / 2,
|
||||
this.opts.size.height / 2,
|
||||
);
|
||||
this.addChild(this.icon);
|
||||
}
|
||||
|
||||
changeState(state: ButtonState) {
|
||||
this.state = state;
|
||||
this.icon.destroy();
|
||||
this.update();
|
||||
}
|
||||
|
||||
hoverOn() {
|
||||
if (this.state == ButtonState.ACTIVE) {
|
||||
this.changeState(ButtonState.ACTIVE_HOVERED);
|
||||
} else if (this.state == ButtonState.INACTIVE) {
|
||||
this.changeState(ButtonState.INACTIVE_HOVERED);
|
||||
}
|
||||
}
|
||||
hoverOff() {
|
||||
if (this.state == ButtonState.ACTIVE_HOVERED) {
|
||||
this.changeState(ButtonState.ACTIVE);
|
||||
} else if (this.state == ButtonState.INACTIVE_HOVERED) {
|
||||
this.changeState(ButtonState.INACTIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,219 +0,0 @@
|
|||
import { Application, Color, Container, Graphics, TextStyle } from "pixi.js";
|
||||
import TextInput from "./text-input";
|
||||
import { Button, ButtonState } from "./Button";
|
||||
import { alphaBlend } from "@napolab/alpha-blend";
|
||||
import { IconService } from "../pixi/IconService";
|
||||
|
||||
export interface TaskCardOptions {
|
||||
label: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const COLOR_WHITE = "#ffffff"; // Double-click detection
|
||||
const DOUBLE_CLICK_DELAY = 300; // milliseconds
|
||||
let lastClickTime = 0;
|
||||
|
||||
export const TASK_BACKGROUND_DEFAULT_COLOR: Color = new Color("0x1E1E1E");
|
||||
const TASK_BORDER_DEFAULT_COLOR = new Color("0x343434");
|
||||
// const TASK_BACKGROUND_DEFAULT_COLOR: Color = new Color(COLOR_WHITE);
|
||||
const ICON_BACKGROUND_COLOR = new Color("0x1E1E1E");
|
||||
const HOVERED_ICON_BACKGROUND_COLOR = new Color("0x343434");
|
||||
const TOOLBAR_BACKGROUND_DEFAULT_COLOR: string = alphaBlend(
|
||||
ICON_BACKGROUND_COLOR.toRgbaString(),
|
||||
new Color("#000000").setAlpha(0.5).toRgbaString(),
|
||||
);
|
||||
console.log("TASK BACKGROUND COLOR: ", TOOLBAR_BACKGROUND_DEFAULT_COLOR);
|
||||
export const TASK_DEFAULT_BORDER = {
|
||||
color: 0x7e7e7e,
|
||||
width: 1,
|
||||
};
|
||||
export const TASK_FOCUSED_BORDER = {
|
||||
color: 0x7e7e7e,
|
||||
width: 2,
|
||||
};
|
||||
export const TEXT_COLOR = COLOR_WHITE;
|
||||
export const TEXT_STYLE = new TextStyle({
|
||||
fill: TEXT_COLOR,
|
||||
fontSize: 10,
|
||||
fontFamily: "Segoe UI",
|
||||
lineHeight: 1.5,
|
||||
});
|
||||
|
||||
export class TaskView extends Container {
|
||||
private toolbar!: Container;
|
||||
private card!: Graphics;
|
||||
// private text!: Text;
|
||||
private app!: Application;
|
||||
private readonly opts: TaskCardOptions;
|
||||
|
||||
private inputEl: TextInput | null = null;
|
||||
private mountEl: HTMLDivElement;
|
||||
|
||||
constructor(
|
||||
app: Application,
|
||||
mountEl: HTMLDivElement,
|
||||
opts: TaskCardOptions,
|
||||
) {
|
||||
super();
|
||||
this.app = app;
|
||||
this.mountEl = mountEl;
|
||||
this.opts = opts;
|
||||
}
|
||||
|
||||
async draw() {
|
||||
// this.card = new Graphics()
|
||||
// .roundRect(0, yShift, CARD_W, CARD_H, 10)
|
||||
// .fill({ color: TASK_BACKGROUND_DEFAULT_COLOR });
|
||||
// .stroke(TASK_DEFAULT_BORDER)
|
||||
// this.addChild(this.card);
|
||||
|
||||
// TODO: почему-то поле с редактированием текста рисуется на бэкграунде и его не видно
|
||||
// TODO: после редктирования показываются оба варианта текста...
|
||||
let input = new TextInput(this.mountEl, {
|
||||
text: this.opts.label,
|
||||
input: {
|
||||
// multiline: true,
|
||||
fontFamily: TEXT_STYLE.fontFamily,
|
||||
fontSize: `${TEXT_STYLE.fontSize}pt`,
|
||||
padding: `${TEXT_STYLE.padding}px`,
|
||||
width: `${this.opts.width}px`,
|
||||
height: `${this.opts.height}px`,
|
||||
color: TEXT_STYLE.fill,
|
||||
textAlign: "center",
|
||||
// dom params
|
||||
"border-radius": 0,
|
||||
// "background-color": COLOR_WHITE,
|
||||
// "font-size": `${TEXT_STYLE.fontSize}pt`,
|
||||
border: "0px solid #ccc",
|
||||
"box-shadow": "none",
|
||||
// outline: "none",
|
||||
},
|
||||
box: {
|
||||
default: {
|
||||
fill: TOOLBAR_BACKGROUND_DEFAULT_COLOR,
|
||||
rounded: 10,
|
||||
stroke: TASK_DEFAULT_BORDER,
|
||||
},
|
||||
focused: {
|
||||
fill: TOOLBAR_BACKGROUND_DEFAULT_COLOR,
|
||||
rounded: 10,
|
||||
stroke: TASK_FOCUSED_BORDER,
|
||||
},
|
||||
disabled: {
|
||||
fill: TOOLBAR_BACKGROUND_DEFAULT_COLOR,
|
||||
rounded: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
input.placeholder = this.opts.label;
|
||||
// Background
|
||||
// input.x = 100;
|
||||
// input.y = yShift;
|
||||
// input.eventMode = "static";
|
||||
// input.on("blur", () => this.finishEditing());
|
||||
// input.on("pointerdown", (e) => {
|
||||
// const now = Date.now();
|
||||
// if (now - lastClickTime < DOUBLE_CLICK_DELAY) {
|
||||
// this.startEditing();
|
||||
// }
|
||||
// lastClickTime = now;
|
||||
// });
|
||||
this.addChild(input);
|
||||
this.inputEl = input;
|
||||
this.inputEl.render(this.app.renderer);
|
||||
// this.text = new Text({
|
||||
// text: opts.label,
|
||||
// resolution: 4,
|
||||
// style: new TextStyle({
|
||||
// fill: COLOR_WHITE,
|
||||
// fontSize: 10,
|
||||
// fontFamily: "Segoe UI",
|
||||
// lineHeight: 1.5,
|
||||
// }),
|
||||
// });
|
||||
// this.text.anchor.set(0.5);
|
||||
// this.text.position = {
|
||||
// x: CARD_W / 2,
|
||||
// y: 50 + CARD_H / 2,
|
||||
// };
|
||||
// this.text.eventMode = "static";
|
||||
// this.text.on("pointerdown", (e) => {
|
||||
// const now = Date.now();
|
||||
// if (now - lastClickTime < DOUBLE_CLICK_DELAY) {
|
||||
// this.startEditing();
|
||||
// }
|
||||
// lastClickTime = now;
|
||||
// });
|
||||
// this.addChild(this.text);
|
||||
|
||||
// const TB_W = 98;
|
||||
// const TB_H = 22;
|
||||
// const TB_PADDING = { x: 2, y: 2 };
|
||||
// const TB_GAP = 1;
|
||||
//
|
||||
// this.toolbar = new Container();
|
||||
// this.toolbar.position = { x: (CARD_W - TB_W) / 2, y: TB_H };
|
||||
// this.toolbar.addChild(
|
||||
// new Graphics()
|
||||
// .roundRect(0, 0, TB_W, TB_H, 4)
|
||||
// .fill({ color: TOOLBAR_BACKGROUND_DEFAULT_COLOR }),
|
||||
// );
|
||||
// this.addChild(this.toolbar);
|
||||
// // =========================
|
||||
// // 4) Create icons as vector shapes
|
||||
// // =========================
|
||||
//
|
||||
// const icons = [
|
||||
// IconService.makeTrashIcon(),
|
||||
// IconService.makeKeyIcon(),
|
||||
// IconService.makeLockIcon(),
|
||||
// IconService.makeFocusIcon(),
|
||||
// IconService.makeCircleIcon(),
|
||||
// ];
|
||||
//
|
||||
// icons.forEach((icon, i) => {
|
||||
// const button = new Button(this.app, ButtonState.ACTIVE, icon, {
|
||||
// size: { width: 18, height: 18 },
|
||||
// backgroundColors: {
|
||||
// [ButtonState.ACTIVE]: new Color(
|
||||
// TOOLBAR_BACKGROUND_DEFAULT_COLOR,
|
||||
// ),
|
||||
// [ButtonState.ACTIVE_HOVERED]: HOVERED_ICON_BACKGROUND_COLOR,
|
||||
// [ButtonState.INACTIVE]: new Color(ICON_BACKGROUND_COLOR),
|
||||
// [ButtonState.INACTIVE_HOVERED]:
|
||||
// HOVERED_ICON_BACKGROUND_COLOR,
|
||||
// },
|
||||
// iconColors: {
|
||||
// [ButtonState.ACTIVE]: TASK_BACKGROUND_DEFAULT_COLOR,
|
||||
// [ButtonState.ACTIVE_HOVERED]: TASK_BACKGROUND_DEFAULT_COLOR,
|
||||
// [ButtonState.INACTIVE]: TASK_BACKGROUND_DEFAULT_COLOR,
|
||||
// [ButtonState.INACTIVE_HOVERED]:
|
||||
// TASK_BACKGROUND_DEFAULT_COLOR,
|
||||
// },
|
||||
// });
|
||||
// button.x = TB_PADDING.x + i * (18 + TB_GAP);
|
||||
// button.y = TB_PADDING.y;
|
||||
//
|
||||
// button.setup();
|
||||
// this.toolbar.addChild(button);
|
||||
// });
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// LABEL EDIT MODE (HTML input overlay)
|
||||
// ==================================================
|
||||
|
||||
// private startEditing() {
|
||||
// if (this.inputEl?.visible) return;
|
||||
// this.inputEl!.visible = true;
|
||||
// this.inputEl?.focus();
|
||||
// }
|
||||
//
|
||||
// private finishEditing() {
|
||||
// if (!this.inputEl?.visible) return;
|
||||
//
|
||||
// this.text.text = this.inputEl.text;
|
||||
// this.inputEl.visible = false;
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<script lang="ts">
|
||||
import type {TaskmapView} from "../TaskmapView";
|
||||
import Task from "../components/Task.svelte";
|
||||
|
||||
let {
|
||||
taskmapView
|
||||
}: {
|
||||
taskmapView: TaskmapView;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<p>Hello world!</p>
|
||||
<Task taskData={taskmapView.projectData.taskData} taskmapView={taskmapView} />
|
||||
</div>
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
<script>
|
||||
import { Layer } from 'svelte-canvas';
|
||||
import { Spring } from 'svelte/motion';
|
||||
|
||||
let { x, y, color, onclick } = $props();
|
||||
|
||||
let dragging = $state(false);
|
||||
|
||||
let _x = $state();
|
||||
let _y = $state();
|
||||
|
||||
const radius = new Spring(80, { stiffness: 0.15, damping: 0.2 });
|
||||
|
||||
const setup = ({ width, height }) => {
|
||||
_x = new Spring(width * x, { stiffness: 0.15, damping: 0.2 });
|
||||
_y = new Spring(height * y, { stiffness: 0.15, damping: 0.2 });
|
||||
};
|
||||
|
||||
const render = ({ context }) => {
|
||||
context.globalCompositeOperation = 'screen';
|
||||
context.fillStyle = color;
|
||||
context.lineWidth = 10;
|
||||
context.beginPath();
|
||||
context.arc($_x, $_y, $radius, 0, Math.PI * 2);
|
||||
context.fill();
|
||||
context.stroke();
|
||||
};
|
||||
|
||||
const onEnter = () => {
|
||||
document.body.style.cursor = 'pointer';
|
||||
radius.set(90);
|
||||
};
|
||||
|
||||
const onLeave = () => {
|
||||
document.body.style.cursor = 'auto';
|
||||
dragging = false;
|
||||
radius.set(80);
|
||||
};
|
||||
|
||||
const onDown = (e) => {
|
||||
dragging = true;
|
||||
radius.set(120);
|
||||
onclick?.();
|
||||
};
|
||||
|
||||
const onUp = () => {
|
||||
dragging = false;
|
||||
radius.set(80);
|
||||
};
|
||||
|
||||
const onMove = ({ x, y }) => {
|
||||
if (dragging) {
|
||||
_x.set(x);
|
||||
_y.set(y);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Layer
|
||||
{setup}
|
||||
{render}
|
||||
onmouseenter={onEnter}
|
||||
onmouseleave={onLeave}
|
||||
onmousedown={onDown}
|
||||
onmousemove={onMove}
|
||||
onmouseup={onUp}
|
||||
ontouchstart={onDown}
|
||||
ontouchmove={onMove}
|
||||
ontouchend={onUp}
|
||||
/>
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<script>
|
||||
import { Canvas } from 'svelte-canvas';
|
||||
import Ball from './Ball.svelte';
|
||||
|
||||
let balls = [
|
||||
{ color: 'tomato', x: 0.5, y: 0.333 },
|
||||
{ color: 'goldenrod', x: 0.333, y: 0.625 },
|
||||
{ color: 'mediumturquoise', x: 0.667, y: 0.625 },
|
||||
];
|
||||
|
||||
const reorder = (color) => {
|
||||
balls = balls
|
||||
.filter((c) => c.color !== color)
|
||||
.concat(balls.filter((c) => c.color === color));
|
||||
};
|
||||
</script>
|
||||
|
||||
<Canvas layerEvents style="touch-action: none">
|
||||
{#each balls as { color, x, y } (color)}
|
||||
<Ball {color} {x} {y} onclick={() => reorder(color)} />
|
||||
{/each}
|
||||
</Canvas>
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
<script lang="ts">
|
||||
import {tick} from "svelte";
|
||||
import {TEXT_COLOR, TEXT_STYLE} from "../TaskView";
|
||||
import {match, P} from "ts-pattern";
|
||||
|
||||
let coords = $state({ x: 150, y: 150 });
|
||||
let dragging = $state(false);
|
||||
let taskClicked = $state(false);
|
||||
let editing = $state(false);
|
||||
const maxLength = 20;
|
||||
const taskNameEditSize = 20;
|
||||
const fontSize = 20;
|
||||
const fontFamily = match(TEXT_STYLE.fontFamily)
|
||||
.returnType<string>()
|
||||
.with(P.string, (str) => str)
|
||||
.with(P.array<string>(), (ar)=> ar[0])
|
||||
.exhaustive()
|
||||
;
|
||||
|
||||
let textInput = $state('Task 1');
|
||||
let title = $state("Task 1");
|
||||
|
||||
let task = $state({
|
||||
width: 240,
|
||||
height: 80,
|
||||
shiftX: 2,
|
||||
shiftY: 50,
|
||||
rx: 30,
|
||||
strokeWidthSelected: 4,
|
||||
strokeWidthDefault: 2,
|
||||
});
|
||||
let toolbar = $state({
|
||||
width: 100,
|
||||
height: 30,
|
||||
shiftX: 0,
|
||||
shiftY: 0,
|
||||
});
|
||||
toolbar.shiftX = (task.width - toolbar.width) / 2;
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => {
|
||||
if (dragging) {
|
||||
coords = { x: e.layerX, y: e.layerY };
|
||||
}
|
||||
};
|
||||
const onMouseUp = () => {
|
||||
if (dragging) {
|
||||
dragging = false;
|
||||
} else if (editing) {
|
||||
finishEditing(true)
|
||||
} else if (taskClicked) {
|
||||
taskClicked = false;
|
||||
}
|
||||
};
|
||||
const onMousedown = () => {
|
||||
dragging = true;
|
||||
};
|
||||
const onTaskClick = () => {
|
||||
if (!taskClicked) {
|
||||
taskClicked = true;
|
||||
} else if (!editing) {
|
||||
taskClicked = false;
|
||||
}
|
||||
};
|
||||
const onTextClick = () => {
|
||||
if (!taskClicked) {
|
||||
taskClicked = true;
|
||||
} else if (!editing) {
|
||||
editing = true;
|
||||
textInput = title;
|
||||
tick().then(() => {
|
||||
const el = document.getElementById("titleInput");
|
||||
if (el) el.focus();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const taskStrokeWidth = () => {
|
||||
return taskClicked ? task.strokeWidthSelected : task.strokeWidthDefault;
|
||||
};
|
||||
|
||||
function finishEditing(success: boolean) {
|
||||
if (success && textInput.trim() !== "") {
|
||||
title = textInput.trim();
|
||||
// taskmapView.debouncedSave();
|
||||
}
|
||||
textInput = title;
|
||||
editing = false;
|
||||
}
|
||||
|
||||
function handleKey(e: KeyboardEvent) {
|
||||
if (e.key === "Enter") finishEditing(true);
|
||||
if (e.key === "Escape") finishEditing(false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svg class="Canvas"
|
||||
width="100%"
|
||||
height="100%"
|
||||
onmousemove={onMouseMove}
|
||||
onmouseup={onMouseUp}
|
||||
role="presentation"
|
||||
>
|
||||
<svg class="Task"
|
||||
role="presentation"
|
||||
width="{task.width + task.shiftX + 2 * task.strokeWidthSelected}px"
|
||||
height="{task.height + task.shiftY + 2 *task.strokeWidthSelected}px"
|
||||
x={coords.x}
|
||||
y={coords.y}
|
||||
>
|
||||
<rect class="taskBg"
|
||||
onmousedown={onMousedown}
|
||||
onclick={onTaskClick}
|
||||
width={task.width}
|
||||
height={task.height}
|
||||
x="{task.shiftX}px"
|
||||
y="{task.shiftY}px"
|
||||
rx={task.rx}
|
||||
fill=#ff3e00
|
||||
stroke-width="{taskStrokeWidth()}"
|
||||
stroke="white"
|
||||
role="presentation"
|
||||
>
|
||||
</rect>
|
||||
|
||||
<text
|
||||
onmousedown={onMousedown}
|
||||
onclick={onTextClick}
|
||||
x={task.width / 2 + task.shiftX}
|
||||
y={task.height / 2 + task.shiftY}
|
||||
fill={TEXT_COLOR}
|
||||
stroke={TEXT_COLOR}
|
||||
font-weight="normal"
|
||||
stroke-width="1px"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="middle"
|
||||
role="presentation"
|
||||
font-size="{fontSize}px"
|
||||
font-family="{fontFamily}"
|
||||
>
|
||||
{title}
|
||||
</text>
|
||||
|
||||
{#if taskClicked}
|
||||
<rect class="toolbarBg"
|
||||
x={toolbar.shiftX}
|
||||
y={toolbar.shiftY}
|
||||
width={toolbar.width}
|
||||
height={toolbar.height}
|
||||
>
|
||||
</rect>
|
||||
{/if}
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
{#if editing}
|
||||
<div
|
||||
class="textEditDiv"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
style="
|
||||
position: absolute;
|
||||
top: {coords.y + task.shiftY + task.height /2 - fontSize/2 - 7}px;
|
||||
left: {coords.x + task.shiftX + task.strokeWidthSelected-2}px;
|
||||
"
|
||||
>
|
||||
<input
|
||||
id="titleInput"
|
||||
bind:value={textInput}
|
||||
onkeydown={handleKey}
|
||||
size={taskNameEditSize}
|
||||
maxlength={maxLength}
|
||||
style="
|
||||
font-family: {fontFamily};
|
||||
font-size: {fontSize}px;
|
||||
color: {TEXT_COLOR};
|
||||
background-color: #ff3e00;
|
||||
border: 0px;
|
||||
outline-width: 0px;
|
||||
padding-left: 5px;
|
||||
text-align: center;
|
||||
/*dominant-baseline: middle;*/
|
||||
/*text-anchor: middle;*/
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!--onblur={() => finishEditing(true)}-->
|
||||
|
||||
|
||||
<style>
|
||||
svg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
/*right: 200px;*/
|
||||
top: 0;
|
||||
/*bottom: 100px;*/
|
||||
}
|
||||
.textEditDiv {
|
||||
position: absolute;
|
||||
top: 500px;
|
||||
left: 500px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in a new issue