mirror of
https://github.com/pandorafms/pandorafms.git
synced 2025-07-28 16:24:54 +02:00
Added a functionality to allows the live vc item resizement
This commit is contained in:
parent
ee7fb4db5f
commit
dbf15a759f
@ -148,6 +148,7 @@ function createVisualConsole(
|
||||
};
|
||||
var taskId = "visual-console-item-move-" + id;
|
||||
|
||||
// Persist the new position.
|
||||
asyncTaskManager
|
||||
.add(taskId, function(done) {
|
||||
var abortable = updateVisualConsoleItem(
|
||||
@ -156,8 +157,8 @@ function createVisualConsole(
|
||||
id,
|
||||
data,
|
||||
function(error, data) {
|
||||
if (!error && !data) return;
|
||||
if (error) {
|
||||
// if (!error && !data) return;
|
||||
if (error || !data) {
|
||||
console.log(
|
||||
"[ERROR]",
|
||||
"[VISUAL-CONSOLE-CLIENT]",
|
||||
@ -181,6 +182,49 @@ function createVisualConsole(
|
||||
})
|
||||
.init();
|
||||
});
|
||||
// VC Item resized.
|
||||
visualConsole.onItemResized(function(e) {
|
||||
var id = e.item.props.id;
|
||||
var data = {
|
||||
width: e.newSize.width,
|
||||
height: e.newSize.height
|
||||
};
|
||||
var taskId = "visual-console-item-resize-" + id;
|
||||
|
||||
// Persist the new size.
|
||||
asyncTaskManager
|
||||
.add(taskId, function(done) {
|
||||
var abortable = updateVisualConsoleItem(
|
||||
baseUrl,
|
||||
visualConsole.props.id,
|
||||
id,
|
||||
data,
|
||||
function(error, data) {
|
||||
// if (!error && !data) return;
|
||||
if (error || !data) {
|
||||
console.log(
|
||||
"[ERROR]",
|
||||
"[VISUAL-CONSOLE-CLIENT]",
|
||||
"[API]",
|
||||
error ? error.message : "Invalid response"
|
||||
);
|
||||
|
||||
// Resize the element to its initial Size.
|
||||
e.item.resize(e.prevSize.width, e.prevSize.height);
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
cancel: function() {
|
||||
abortable.abort();
|
||||
}
|
||||
};
|
||||
})
|
||||
.init();
|
||||
});
|
||||
|
||||
if (updateInterval != null && updateInterval > 0) {
|
||||
// Start an interval to update the Visual Console.
|
||||
|
@ -15,7 +15,8 @@ import {
|
||||
humanDate,
|
||||
humanTime,
|
||||
addMovementListener,
|
||||
debounce
|
||||
debounce,
|
||||
addResizementListener
|
||||
} from "./lib";
|
||||
import TypedEvent, { Listener, Disposable } from "./lib/TypedEvent";
|
||||
|
||||
@ -76,6 +77,12 @@ export interface ItemMovedEvent {
|
||||
newPosition: Position;
|
||||
}
|
||||
|
||||
export interface ItemResizedEvent {
|
||||
item: VisualConsoleItem<ItemProps>;
|
||||
prevSize: Size;
|
||||
newSize: Size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a valid enum value from a raw label positi9on value.
|
||||
* @param labelPosition Raw value.
|
||||
@ -143,6 +150,8 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
|
||||
private readonly clickEventManager = new TypedEvent<ItemClickEvent<Props>>();
|
||||
// Event manager for moved events.
|
||||
private readonly movedEventManager = new TypedEvent<ItemMovedEvent>();
|
||||
// Event manager for resized events.
|
||||
private readonly resizedEventManager = new TypedEvent<ItemResizedEvent>();
|
||||
// Event manager for remove events.
|
||||
private readonly removeEventManager = new TypedEvent<
|
||||
ItemRemoveEvent<Props>
|
||||
@ -163,6 +172,9 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
|
||||
if (!this.positionChanged(prevPosition, newPosition)) return;
|
||||
|
||||
// Save the new position to the props.
|
||||
this.move(x, y);
|
||||
// Emit the movement event.
|
||||
@ -202,6 +214,61 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
|
||||
}
|
||||
}
|
||||
|
||||
// This function will only run the 2nd arg function after the time
|
||||
// of the first arg have passed after its last execution.
|
||||
private debouncedResizementSave = debounce(
|
||||
500, // ms.
|
||||
(width: Size["width"], height: Size["height"]) => {
|
||||
const prevSize = {
|
||||
width: this.props.width,
|
||||
height: this.props.height
|
||||
};
|
||||
const newSize = {
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
|
||||
if (!this.sizeChanged(prevSize, newSize)) return;
|
||||
|
||||
// Save the new position to the props.
|
||||
this.resize(width, height);
|
||||
// Emit the resizement event.
|
||||
this.resizedEventManager.emit({
|
||||
item: this,
|
||||
prevSize: prevSize,
|
||||
newSize: newSize
|
||||
});
|
||||
}
|
||||
);
|
||||
// This property will store the function
|
||||
// to clean the resizement listener.
|
||||
private removeResizement: Function | null = null;
|
||||
|
||||
/**
|
||||
* Start the resizement funtionality.
|
||||
* @param element Element to move inside its container.
|
||||
*/
|
||||
private initResizementListener(element: HTMLElement): void {
|
||||
this.removeResizement = addResizementListener(
|
||||
element,
|
||||
(width: Size["width"], height: Size["height"]) => {
|
||||
// Move the DOM element.
|
||||
this.resizeElement(width, height);
|
||||
// Run the save function.
|
||||
this.debouncedResizementSave(width, height);
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Stop the resizement functionality.
|
||||
*/
|
||||
private stopResizementListener(): void {
|
||||
if (this.removeResizement) {
|
||||
this.removeResizement();
|
||||
this.removeResizement = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To create a new element which will be inside the item box.
|
||||
* @return Item.
|
||||
@ -269,6 +336,8 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
|
||||
box.classList.add("is-editing");
|
||||
// Init the movement listener.
|
||||
this.initMovementListener(box);
|
||||
// Init the resizement listener.
|
||||
this.initResizementListener(box);
|
||||
}
|
||||
if (this.meta.isFetching) {
|
||||
box.classList.add("is-fetching");
|
||||
@ -503,9 +572,11 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
|
||||
if (this.meta.editMode) {
|
||||
this.elementRef.classList.add("is-editing");
|
||||
this.initMovementListener(this.elementRef);
|
||||
this.initResizementListener(this.elementRef);
|
||||
} else {
|
||||
this.elementRef.classList.remove("is-editing");
|
||||
this.stopMovementListener();
|
||||
this.stopResizementListener();
|
||||
}
|
||||
}
|
||||
if (!prevMeta || prevMeta.isFetching !== this.meta.isFetching) {
|
||||
@ -693,6 +764,22 @@ abstract class VisualConsoleItem<Props extends ItemProps> {
|
||||
return disposable;
|
||||
}
|
||||
|
||||
/**
|
||||
* To add an event handler to the resizement of visual console elements.
|
||||
* @param listener Function which is going to be executed when a linked console is moved.
|
||||
*/
|
||||
public onResized(listener: Listener<ItemResizedEvent>): Disposable {
|
||||
/*
|
||||
* The '.on' function returns a function which will clean the event
|
||||
* listener when executed. We store all the 'dispose' functions to
|
||||
* call them when the item should be cleared.
|
||||
*/
|
||||
const disposable = this.resizedEventManager.on(listener);
|
||||
this.disposables.push(disposable);
|
||||
|
||||
return disposable;
|
||||
}
|
||||
|
||||
/**
|
||||
* To add an event handler to the removal of the item.
|
||||
* @param listener Function which is going to be executed when a item is removed.
|
||||
|
@ -11,7 +11,8 @@ import Item, {
|
||||
ItemProps,
|
||||
ItemClickEvent,
|
||||
ItemRemoveEvent,
|
||||
ItemMovedEvent
|
||||
ItemMovedEvent,
|
||||
ItemResizedEvent
|
||||
} from "./Item";
|
||||
import StaticGraph, { staticGraphPropsDecoder } from "./items/StaticGraph";
|
||||
import Icon, { iconPropsDecoder } from "./items/Icon";
|
||||
@ -207,6 +208,8 @@ export default class VisualConsole {
|
||||
>();
|
||||
// Event manager for move events.
|
||||
private readonly movedEventManager = new TypedEvent<ItemMovedEvent>();
|
||||
// Event manager for resize events.
|
||||
private readonly resizedEventManager = new TypedEvent<ItemResizedEvent>();
|
||||
// List of references to clean the event listeners.
|
||||
private readonly disposables: Disposable[] = [];
|
||||
|
||||
@ -228,6 +231,15 @@ export default class VisualConsole {
|
||||
// console.log(`Moved element #${e.item.props.id}`, e);
|
||||
};
|
||||
|
||||
/**
|
||||
* React to a resizement on an element.
|
||||
* @param e Event object.
|
||||
*/
|
||||
private handleElementResizement: (e: ItemResizedEvent) => void = e => {
|
||||
this.resizedEventManager.emit(e);
|
||||
// console.log(`Resized element #${e.item.props.id}`, e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear some element references.
|
||||
* @param e Event object.
|
||||
@ -277,6 +289,7 @@ export default class VisualConsole {
|
||||
// Item event handlers.
|
||||
itemInstance.onClick(this.handleElementClick);
|
||||
itemInstance.onMoved(this.handleElementMovement);
|
||||
itemInstance.onResized(this.handleElementResizement);
|
||||
itemInstance.onRemove(this.handleElementRemove);
|
||||
// Add the item to the DOM.
|
||||
this.containerRef.append(itemInstance.elementRef);
|
||||
@ -595,6 +608,22 @@ export default class VisualConsole {
|
||||
return disposable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event handler to the resizement of the visual console elements.
|
||||
* @param listener Function which is going to be executed when a linked console is moved.
|
||||
*/
|
||||
public onItemResized(listener: Listener<ItemResizedEvent>): Disposable {
|
||||
/*
|
||||
* The '.on' function returns a function which will clean the event
|
||||
* listener when executed. We store all the 'dispose' functions to
|
||||
* call them when the item should be cleared.
|
||||
*/
|
||||
const disposable = this.resizedEventManager.on(listener);
|
||||
this.disposables.push(disposable);
|
||||
|
||||
return disposable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the edition mode.
|
||||
*/
|
||||
|
@ -558,3 +558,115 @@ export function addMovementListener(
|
||||
handleEnd();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the grab & resize functionality to a certain element.
|
||||
*
|
||||
* @param element Element to move.
|
||||
* @param onResized Function to execute when the element is resized.
|
||||
*
|
||||
* @return A function which will clean the event handlers when executed.
|
||||
*/
|
||||
export function addResizementListener(
|
||||
element: HTMLElement,
|
||||
onResized: (x: Position["x"], y: Position["y"]) => void
|
||||
): Function {
|
||||
const resizeDraggable = document.createElement("div");
|
||||
resizeDraggable.className = "resize-draggable";
|
||||
element.appendChild(resizeDraggable);
|
||||
|
||||
// Store the initial draggable state.
|
||||
const isDraggable = element.draggable;
|
||||
// Init the coordinates.
|
||||
let lastWidth: Size["width"] = 0;
|
||||
let lastHeight: Size["height"] = 0;
|
||||
let lastMouseX: Position["x"] = 0;
|
||||
let lastMouseY: Position["y"] = 0;
|
||||
let mouseElementOffsetX: Position["x"] = 0;
|
||||
let mouseElementOffsetY: Position["y"] = 0;
|
||||
|
||||
// Will run onResized 32ms after its last execution.
|
||||
const debouncedResizement = debounce(
|
||||
32,
|
||||
(width: Size["width"], height: Size["height"]) => onResized(width, height)
|
||||
);
|
||||
// Will run onResized one time max every 16ms.
|
||||
const throttledResizement = throttle(
|
||||
16,
|
||||
(width: Size["width"], height: Size["height"]) => onResized(width, height)
|
||||
);
|
||||
|
||||
const handleResize = (e: MouseEvent) => {
|
||||
// Calculate the new element coordinates.
|
||||
let width = lastWidth + (e.pageX - lastMouseX);
|
||||
let height = lastHeight + (e.pageY - lastMouseY);
|
||||
|
||||
// TODO: Document.
|
||||
|
||||
// Minimum value.
|
||||
if (width <= 0) width = 10;
|
||||
if (height <= 0) height = 10;
|
||||
|
||||
// Run the movement events.
|
||||
throttledResizement(width, height);
|
||||
debouncedResizement(width, height);
|
||||
|
||||
// Store the coordinates of the element.
|
||||
lastWidth = width;
|
||||
lastHeight = height;
|
||||
// Store the last mouse coordinates.
|
||||
lastMouseX = e.pageX;
|
||||
lastMouseY = e.pageY;
|
||||
};
|
||||
const handleEnd = () => {
|
||||
// Reset the positions.
|
||||
lastWidth = 0;
|
||||
lastHeight = 0;
|
||||
lastMouseX = 0;
|
||||
lastMouseY = 0;
|
||||
mouseElementOffsetX = 0;
|
||||
mouseElementOffsetY = 0;
|
||||
// Remove the move event.
|
||||
document.removeEventListener("mousemove", handleResize);
|
||||
// Clean itself.
|
||||
document.removeEventListener("mouseup", handleEnd);
|
||||
// Reset the draggable property to its initial state.
|
||||
element.draggable = isDraggable;
|
||||
// Reset the body selection property to a default state.
|
||||
document.body.style.userSelect = "auto";
|
||||
};
|
||||
const handleStart = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// Disable the drag temporarily.
|
||||
element.draggable = false;
|
||||
|
||||
// Store the difference between the cursor and
|
||||
// the initial coordinates of the element.
|
||||
const { width, height } = element.getBoundingClientRect();
|
||||
lastWidth = width;
|
||||
lastHeight = height;
|
||||
// Store the mouse position.
|
||||
lastMouseX = e.pageX;
|
||||
lastMouseY = e.pageY;
|
||||
// Store the relative position between the mouse and the element.
|
||||
mouseElementOffsetX = e.offsetX;
|
||||
mouseElementOffsetY = e.offsetY;
|
||||
|
||||
// Listen to the mouse movement.
|
||||
document.addEventListener("mousemove", handleResize);
|
||||
// Listen to the moment when the mouse click is not pressed anymore.
|
||||
document.addEventListener("mouseup", handleEnd);
|
||||
// Limit the mouse selection of the body.
|
||||
document.body.style.userSelect = "none";
|
||||
};
|
||||
|
||||
// Event to listen the init of the movement.
|
||||
resizeDraggable.addEventListener("mousedown", handleStart);
|
||||
|
||||
// Returns a function to clean the event listeners.
|
||||
return () => {
|
||||
resizeDraggable.remove();
|
||||
handleEnd();
|
||||
};
|
||||
}
|
||||
|
@ -16,7 +16,18 @@
|
||||
}
|
||||
|
||||
.visual-console-item.is-editing {
|
||||
border: 2px dashed #33ccff;
|
||||
border: 2px dashed #b2b2b2;
|
||||
transform: translateX(-2px) translateY(-2px);
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.visual-console-item.is-editing > .resize-draggable {
|
||||
float: right;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background: url(./resize-handle.svg);
|
||||
cursor: se-resize;
|
||||
}
|
||||
|
24
visual_console_client/src/resize-handle.svg
Normal file
24
visual_console_client/src/resize-handle.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<svg version="1.1" id="Capa_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="15px" height="15px" viewBox="0 0 15 15" enable-background="new 0 0 15 15" xml:space="preserve">
|
||||
<line fill="none" stroke="#B2B2B2" stroke-width="1.5" stroke-miterlimit="10" x1="0.562" y1="36.317" x2="14.231" y2="22.648"/>
|
||||
<line fill="none" stroke="#B2B2B2" stroke-width="1.5" stroke-miterlimit="10" x1="4.971" y1="36.595" x2="14.409" y2="27.155"/>
|
||||
<line fill="none" stroke="#B2B2B2" stroke-width="1.5" stroke-miterlimit="10" x1="10.017" y1="36.433" x2="14.231" y2="32.218"/>
|
||||
<g id="jGEeKn_1_">
|
||||
|
||||
<image overflow="visible" width="46" height="37" id="jGEeKn" xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/7AARRHVja3kAAQAEAAAAHgAA/+4AIUFkb2JlAGTAAAAAAQMA
|
||||
EAMCAwYAAAGRAAABswAAAgL/2wCEABALCwsMCxAMDBAXDw0PFxsUEBAUGx8XFxcXFx8eFxoaGhoX
|
||||
Hh4jJSclIx4vLzMzLy9AQEBAQEBAQEBAQEBAQEABEQ8PERMRFRISFRQRFBEUGhQWFhQaJhoaHBoa
|
||||
JjAjHh4eHiMwKy4nJycuKzU1MDA1NUBAP0BAQEBAQEBAQEBAQP/CABEIACYALwMBIgACEQEDEQH/
|
||||
xAB5AAEBAQEBAAAAAAAAAAAAAAAAAQQCBgEBAAAAAAAAAAAAAAAAAAAAABAAAQQDAAAAAAAAAAAA
|
||||
AAAAAQAxAgMQMBIRAAEBBgUFAQAAAAAAAAAAAAECABEhQWEDECBxkRIwMYEyEwQSAQAAAAAAAAAA
|
||||
AAAAAAAAADD/2gAMAwEAAhEDEQAAAPfAS5zTZTkGPrULZQAAD//aAAgBAgABBQDT/9oACAEDAAEF
|
||||
ANP/2gAIAQEAAQUAzJg2SQBC2d0w2bZSvVNc5oMuAuQuAuRp/9oACAECAgY/AB//2gAIAQMCBj8A
|
||||
H//aAAgBAQEGPwDE6ZXmAYqtwsJBHI91GlMqnvT+e37QL1kS0b7XBwADrVoQCRXGe5ae5ae5afk9
|
||||
H//Z" transform="matrix(1 0 0 1 -46.875 -9.8906)">
|
||||
</image>
|
||||
</g>
|
||||
<line fill="none" stroke="#B2B2B2" stroke-width="1.5" stroke-miterlimit="10" x1="13.483" y1="0.644" x2="0.828" y2="13.301"/>
|
||||
<line fill="none" stroke="#B2B2B2" stroke-width="1.5" stroke-miterlimit="10" x1="13.734" y1="5.141" x2="5.325" y2="13.549"/>
|
||||
<line fill="none" stroke="#B2B2B2" stroke-width="1.5" stroke-miterlimit="10" x1="14.231" y1="9.388" x2="9.806" y2="13.813"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
Loading…
x
Reference in New Issue
Block a user