Compare commits
2
Commits
19d97a47b5
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
850b1e3199 | ||
|
|
a839096582 |
No files matched your search
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<groupId>de.assecutor</groupId>
|
<groupId>de.assecutor</groupId>
|
||||||
<artifactId>workflow-editor</artifactId>
|
<artifactId>workflow-editor</artifactId>
|
||||||
<version>0.9.3</version>
|
<version>0.9.5</version>
|
||||||
<name>Workflow Editor</name>
|
<name>Workflow Editor</name>
|
||||||
<description>n8n-inspired editor for task list workflows</description>
|
<description>n8n-inspired editor for task list workflows</description>
|
||||||
|
|
||||||
|
|||||||
@@ -329,7 +329,9 @@ function attach(host) {
|
|||||||
host.classList.add('task-list-editor');
|
host.classList.add('task-list-editor');
|
||||||
host.innerHTML = '';
|
host.innerHTML = '';
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.className = 'task-list-editor-container';
|
// Drawflow prüft beim Drag-Start nur classList[0]. Nach dem Verschieben
|
||||||
|
// liegt der freie Hintergrund oft außerhalb des transformierten .drawflow.
|
||||||
|
container.className = 'parent-drawflow task-list-editor-container';
|
||||||
container.style.width = '100%';
|
container.style.width = '100%';
|
||||||
container.style.height = '100%';
|
container.style.height = '100%';
|
||||||
host.appendChild(container);
|
host.appendChild(container);
|
||||||
@@ -476,11 +478,18 @@ function attach(host) {
|
|||||||
selectConnection(state, path);
|
selectConnection(state, path);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
// Rechte Maustaste: auf einem Knoten wird der Knoten verschoben (Drawflow
|
// Panning mit links und rechts über dieselben dokumentweiten Handler:
|
||||||
// zieht Knoten nur mit links), auf dem freien Canvas wird die gesamte
|
// Drawflow hört selbst nur am Container und verliert beim Verlassen des
|
||||||
// Ansicht verschoben (Panning).
|
// Canvas insbesondere das mouseup. Seine Auswahl-Logik bleibt erhalten.
|
||||||
let rightDrag = null;
|
let rightDrag = null;
|
||||||
let rightPan = null;
|
let canvasPan = null;
|
||||||
|
editor.on('clickEnd', e => {
|
||||||
|
if (e.type !== 'mousedown' || e.button !== 0 || !editor.editor_selected) return;
|
||||||
|
editor.editor_selected = false;
|
||||||
|
canvasPan = { lastX: e.clientX, lastY: e.clientY, button: 0 };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rechte Maustaste: Knoten verschieben oder auf freiem Canvas pannen.
|
||||||
container.addEventListener('mousedown', e => {
|
container.addEventListener('mousedown', e => {
|
||||||
if (e.button !== 2) return;
|
if (e.button !== 2) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -494,18 +503,30 @@ function attach(host) {
|
|||||||
lastY: e.clientY
|
lastY: e.clientY
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
rightPan = { lastX: e.clientX, lastY: e.clientY };
|
canvasPan = { lastX: e.clientX, lastY: e.clientY, button: 2 };
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
const onRightMove = e => {
|
const moveCanvas = e => {
|
||||||
if (rightPan) {
|
editor.canvas_x += e.clientX - canvasPan.lastX;
|
||||||
editor.canvas_x += e.clientX - rightPan.lastX;
|
editor.canvas_y += e.clientY - canvasPan.lastY;
|
||||||
editor.canvas_y += e.clientY - rightPan.lastY;
|
canvasPan.lastX = e.clientX;
|
||||||
rightPan.lastX = e.clientX;
|
canvasPan.lastY = e.clientY;
|
||||||
rightPan.lastY = e.clientY;
|
|
||||||
editor.precanvas.style.transform =
|
editor.precanvas.style.transform =
|
||||||
'translate(' + editor.canvas_x + 'px, ' + editor.canvas_y + 'px) scale(' + editor.zoom + ')';
|
'translate(' + editor.canvas_x + 'px, ' + editor.canvas_y + 'px) scale(' + editor.zoom + ')';
|
||||||
|
editor.dispatch('translate', { x: editor.canvas_x, y: editor.canvas_y });
|
||||||
|
};
|
||||||
|
const onDragMove = e => {
|
||||||
|
if (!canvasPan && !rightDrag) return;
|
||||||
|
// Auch ein außerhalb des Browserfensters verlorenes mouseup abfangen.
|
||||||
|
const buttonMask = canvasPan?.button === 0 ? 1 : 2;
|
||||||
|
if (!(e.buttons & buttonMask)) {
|
||||||
|
onDragEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (canvasPan) {
|
||||||
|
e.preventDefault();
|
||||||
|
moveCanvas(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!rightDrag) return;
|
if (!rightDrag) return;
|
||||||
@@ -521,15 +542,20 @@ function attach(host) {
|
|||||||
if (data) { data.pos_x = newLeft; data.pos_y = newTop; }
|
if (data) { data.pos_x = newLeft; data.pos_y = newTop; }
|
||||||
editor.updateConnectionNodes('node-' + rightDrag.id);
|
editor.updateConnectionNodes('node-' + rightDrag.id);
|
||||||
};
|
};
|
||||||
const onRightUp = () => {
|
const onDragEnd = e => {
|
||||||
|
if (e?.type === 'mouseup') {
|
||||||
|
if (e.button !== (canvasPan?.button ?? 2)) return;
|
||||||
|
if (canvasPan) moveCanvas(e);
|
||||||
|
}
|
||||||
// Ein per Rechts-Drag verschobener Knoten löst Drawflows nodeMoved
|
// Ein per Rechts-Drag verschobener Knoten löst Drawflows nodeMoved
|
||||||
// nicht aus -> Undo-Schnappschuss hier ablegen.
|
// nicht aus -> Undo-Schnappschuss hier ablegen.
|
||||||
if (rightDrag && rightDrag.moved) recordHistory(state);
|
if (rightDrag && rightDrag.moved) recordHistory(state);
|
||||||
rightDrag = null;
|
rightDrag = null;
|
||||||
rightPan = null;
|
canvasPan = null;
|
||||||
};
|
};
|
||||||
document.addEventListener('mousemove', onRightMove);
|
document.addEventListener('mousemove', onDragMove, true);
|
||||||
document.addEventListener('mouseup', onRightUp);
|
document.addEventListener('mouseup', onDragEnd, true);
|
||||||
|
window.addEventListener('blur', onDragEnd);
|
||||||
|
|
||||||
// Rechts dient nur zum Verschieben: Browser-Kontextmenü unterdrücken und
|
// Rechts dient nur zum Verschieben: Browser-Kontextmenü unterdrücken und
|
||||||
// das Event in der Capture-Phase stoppen, bevor Drawflows eigener
|
// das Event in der Capture-Phase stoppen, bevor Drawflows eigener
|
||||||
@@ -598,8 +624,9 @@ function attach(host) {
|
|||||||
}
|
}
|
||||||
document.removeEventListener('keydown', deleteSelectedConnection);
|
document.removeEventListener('keydown', deleteSelectedConnection);
|
||||||
document.removeEventListener('keydown', undoKeyHandler);
|
document.removeEventListener('keydown', undoKeyHandler);
|
||||||
document.removeEventListener('mousemove', onRightMove);
|
document.removeEventListener('mousemove', onDragMove, true);
|
||||||
document.removeEventListener('mouseup', onRightUp);
|
document.removeEventListener('mouseup', onDragEnd, true);
|
||||||
|
window.removeEventListener('blur', onDragEnd);
|
||||||
container.removeEventListener('wheel', onWheelZoom, { capture: true });
|
container.removeEventListener('wheel', onWheelZoom, { capture: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ public class BlockTypeRegistry {
|
|||||||
public synchronized int nextCustomTaskType(Collection<Integer> reserved) {
|
public synchronized int nextCustomTaskType(Collection<Integer> reserved) {
|
||||||
var occupied = new HashSet<>(reserved);
|
var occupied = new HashSet<>(reserved);
|
||||||
types.values().forEach(type -> occupied.add(type.taskType()));
|
types.values().forEach(type -> occupied.add(type.taskType()));
|
||||||
int highest = occupied.stream().mapToInt(Integer::intValue).max().orElse(CUSTOM_TYPE_START - 1);
|
int highest = occupied.stream().mapToInt(value -> value.intValue()).max().orElse(CUSTOM_TYPE_START - 1);
|
||||||
int next = highest < Integer.MAX_VALUE - 1 ? Math.max(CUSTOM_TYPE_START, highest + 1) : CUSTOM_TYPE_START;
|
int next = highest < Integer.MAX_VALUE - 1 ? Math.max(CUSTOM_TYPE_START, highest + 1) : CUSTOM_TYPE_START;
|
||||||
while (occupied.contains(next)) next++;
|
while (occupied.contains(next)) next++;
|
||||||
return next;
|
return next;
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ public final class CustomTypeValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void validateProperties(JsonNode properties, BlockType base) {
|
private static void validateProperties(JsonNode properties, BlockType base) {
|
||||||
require(properties != null && properties.isArray(), "properties must be an array");
|
if (properties == null || !properties.isArray()) {
|
||||||
|
throw new IllegalArgumentException("properties must be an array");
|
||||||
|
}
|
||||||
require(properties.size() == base.properties().size(), "Properties must match the base type");
|
require(properties.size() == base.properties().size(), "Properties must match the base type");
|
||||||
Set<String> ids = new HashSet<>();
|
Set<String> ids = new HashSet<>();
|
||||||
for (JsonNode property : properties) {
|
for (JsonNode property : properties) {
|
||||||
@@ -65,7 +67,9 @@ public final class CustomTypeValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void validateDefault(JsonNode value, PropertyType type) {
|
private static void validateDefault(JsonNode value, PropertyType type) {
|
||||||
require(value != null, "defaultValue is required");
|
if (value == null) {
|
||||||
|
throw new IllegalArgumentException("defaultValue is required");
|
||||||
|
}
|
||||||
// Leere Vorgaben sind erlaubt; die Eingabefelder behandeln sie wie einen leeren Wert.
|
// Leere Vorgaben sind erlaubt; die Eingabefelder behandeln sie wie einen leeren Wert.
|
||||||
if (value.isNull()) {
|
if (value.isNull()) {
|
||||||
return;
|
return;
|
||||||
@@ -100,15 +104,17 @@ public final class CustomTypeValidator {
|
|||||||
|
|
||||||
private static String text(JsonNode object, String field, boolean allowBlank) {
|
private static String text(JsonNode object, String field, boolean allowBlank) {
|
||||||
JsonNode value = object.get(field);
|
JsonNode value = object.get(field);
|
||||||
require(value != null && value.isTextual() && (allowBlank || !value.textValue().isBlank()),
|
if (value == null || !value.isTextual() || (!allowBlank && value.textValue().isBlank())) {
|
||||||
field + " must be a string" + (allowBlank ? "" : " and not blank"));
|
throw new IllegalArgumentException(field + " must be a string" + (allowBlank ? "" : " and not blank"));
|
||||||
|
}
|
||||||
return value.textValue();
|
return value.textValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int integer(JsonNode object, String field) {
|
private static int integer(JsonNode object, String field) {
|
||||||
JsonNode value = object.get(field);
|
JsonNode value = object.get(field);
|
||||||
require(value != null && value.isIntegralNumber() && value.canConvertToInt(),
|
if (value == null || !value.isIntegralNumber() || !value.canConvertToInt()) {
|
||||||
field + " must be an integer");
|
throw new IllegalArgumentException(field + " must be an integer");
|
||||||
|
}
|
||||||
return value.intValue();
|
return value.intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user