1
0
mirror of https://github.com/beestat/app.git synced 2026-02-26 05:00:21 -05:00
beestat/js/component/floor_plan_entity.js
2026-02-22 12:14:59 -05:00

336 lines
7.9 KiB
JavaScript

/**
* Base class for a high level element that exists on the SVG document.
*
* @param {object} floor_plan The SVG component this belongs to.
* @param {object} state Shared state.
*/
beestat.component.floor_plan_entity = function(floor_plan, state) {
this.floor_plan_ = floor_plan;
this.x_ = 0;
this.y_ = 0;
this.g_ = document.createElementNS('http://www.w3.org/2000/svg', 'g');
beestat.component.apply(this, arguments);
/**
* Override this component's state with a state common to all floor plan
* entities.
*/
this.state_ = state;
};
beestat.extend(beestat.component.floor_plan_entity, beestat.component);
/**
* Render
*
* @param {SVGGElement} parent
*
* @return {beestat.component.floor_plan_entity} This.
*/
beestat.component.floor_plan_entity.prototype.render = function(parent) {
if (this.rendered_ === false) {
const self = this;
this.decorate_(this.g_);
parent.appendChild(this.g_);
this.apply_transform_();
// The element should now exist on the DOM.
window.setTimeout(function() {
self.dispatchEvent('render');
}, 0);
// The render function was called.
this.rendered_ = true;
}
return this;
};
/**
* Rerender
*
* @return {beestat.component.floor_plan_entity} This.
*/
beestat.component.floor_plan_entity.prototype.rerender = function() {
if (this.rendered_ === true) {
this.rendered_ = false;
var old_g = this.g_;
this.g_ = document.createElementNS('http://www.w3.org/2000/svg', 'g');
this.decorate_(this.g_);
old_g.parentNode.replaceChild(this.g_, old_g);
this.apply_transform_();
var self = this;
window.setTimeout(function() {
self.dispatchEvent('render');
}, 0);
this.rendered_ = true;
}
return this;
};
/**
* Bring the current element to the front.
*/
beestat.component.floor_plan_entity.prototype.bring_to_front_ = function() {
if (this.rendered_ === true) {
this.g_.parentNode.appendChild(this.g_);
}
};
/**
* Make this draggable or not.
*
* @param {boolean} draggable Whether or not this is draggable.
*
* @return {beestat.component.floor_plan_entity} This.
*/
beestat.component.floor_plan_entity.prototype.set_draggable_ = function(draggable) {
if (draggable === true) {
this.g_.addEventListener('mousedown', this.mousedown_handler_.bind(this));
this.g_.addEventListener('touchstart', this.mousedown_handler_.bind(this));
}
this.draggable_ = draggable;
return this;
};
/**
* Set the x and y positions of this entity.
*
* @param {number} x The x position of this entity.
* @param {number} y The y position of this entity.
*
* @return {beestat.component.floor_plan_entity} This.
*/
beestat.component.floor_plan_entity.prototype.set_xy = function(x, y) {
if (x !== null) {
this.x_ = Math.round(x);
}
if (y !== null) {
this.y_ = Math.round(y);
}
this.apply_transform_();
return this;
};
/**
* Apply all of the relevant transformations to the SVG document.
*
* @return {beestat.component.floor_plan_entity} This.
*/
beestat.component.floor_plan_entity.prototype.apply_transform_ = function() {
const x = this.x_ || 0;
const y = this.y_ || 0;
if (x !== 0 || y !== 0) {
this.g_.setAttribute(
'transform',
'translate(' + (this.x_ || 0) + ',' + (this.y_ || 0) + ')'
);
} else {
this.g_.removeAttribute('transform');
}
return this;
};
/**
* Drag start handler
*
* @param {Event} e
*/
beestat.component.floor_plan_entity.prototype.mousedown_handler_ = function(e) {
// Don't propagate to things under me.
e.stopPropagation();
this.mousemove_handler_ = this.mousemove_handler_.bind(this);
window.addEventListener('mousemove', this.mousemove_handler_);
window.addEventListener('touchmove', this.mousemove_handler_);
this.mouseup_handler_ = this.mouseup_handler_.bind(this);
window.addEventListener('mouseup', this.mouseup_handler_);
window.addEventListener('touchend', this.mouseup_handler_);
this.drag_start_mouse_ = {
'x': e.clientX || e.touches[0].clientX,
'y': e.clientY || e.touches[0].clientY
};
this.dragged_ = false;
this.after_mousedown_handler_(e);
};
/**
* After mousedown.
*
* @param {Event} e
*/
beestat.component.floor_plan_entity.prototype.after_mousedown_handler_ = function(e) {
// Stub
};
/**
* This handler gets added after mousedown, so it can be assumed that we want
* to drag at this point.
*
* @param {Event} e
*/
beestat.component.floor_plan_entity.prototype.mousemove_handler_ = function(e) {
if (this.dragged_ === false) {
this.dispatchEvent('drag_start');
this.dragged_ = true;
this.floor_plan_.save_buffer();
}
this.after_mousemove_handler_(e);
};
/**
* After mousemove.
*
* @param {Event} e
*/
beestat.component.floor_plan_entity.prototype.after_mousemove_handler_ = function(e) {
// Stub
};
/**
* Drag stop handler
*
* @param {Event} e
*/
beestat.component.floor_plan_entity.prototype.mouseup_handler_ = function(e) {
window.removeEventListener('mousemove', this.mousemove_handler_);
window.removeEventListener('touchmove', this.mousemove_handler_);
window.removeEventListener('mouseup', this.mouseup_handler_);
window.removeEventListener('touchend', this.mouseup_handler_);
delete this.drag_start_entity_;
delete this.drag_start_mouse_;
// If the mouse was actually moved at all then fire the drag stop event.
if (this.dragged_ === true) {
this.dispatchEvent('drag_stop');
this.dispatchEvent('update');
}
this.after_mouseup_handler_(e);
};
/**
* After mouseup.
*
* @param {Event} e
*/
beestat.component.floor_plan_entity.prototype.after_mouseup_handler_ = function(e) {
// Stub
};
/**
* Get X
*
* @return {number} x
*/
beestat.component.floor_plan_entity.prototype.get_x = function() {
return this.x_;
};
/**
* Get Y
*
* @return {number} y
*/
beestat.component.floor_plan_entity.prototype.get_y = function() {
return this.y_;
};
/**
* Collect snap x/y coordinates from selected shape collections.
*
* @param {{
* groups: object[],
* shape_specs: Array<{collection: string, point_mode: string}>,
* should_skip_shape: (function(object, object, object): boolean)=
* }} options
*
* @return {{snap_x: number[], snap_y: number[]}}
*/
beestat.component.floor_plan_entity.prototype.collect_snap_points_ = function(options) {
const snap_x = {};
const snap_y = {};
const groups = Array.isArray(options.groups) ? options.groups : [];
const shape_specs = Array.isArray(options.shape_specs) ? options.shape_specs : [];
const should_skip_shape = typeof options.should_skip_shape === 'function'
? options.should_skip_shape
: function() {
return false;
};
groups.forEach(function(group) {
if (group === undefined || group === null) {
return;
}
shape_specs.forEach(function(shape_spec) {
const shapes = group[shape_spec.collection];
if (Array.isArray(shapes) !== true) {
return;
}
shapes.forEach(function(shape) {
if (shape === undefined || shape.editor_hidden === true) {
return;
}
if (should_skip_shape(shape, shape_spec, group) === true) {
return;
}
if (shape_spec.point_mode === 'point') {
snap_x[Number(shape.x || 0)] = true;
snap_y[Number(shape.y || 0)] = true;
return;
}
if (Array.isArray(shape.points) !== true) {
return;
}
shape.points.forEach(function(point) {
const point_x = Number(point.x || 0);
const point_y = Number(point.y || 0);
if (shape_spec.point_mode === 'absolute') {
snap_x[point_x] = true;
snap_y[point_y] = true;
return;
}
snap_x[point_x + Number(shape.x || 0)] = true;
snap_y[point_y + Number(shape.y || 0)] = true;
});
});
});
});
return {
'snap_x': Object.keys(snap_x).map(function(key) {
return Number(key);
}),
'snap_y': Object.keys(snap_y).map(function(key) {
return Number(key);
})
};
};