mirror of
https://github.com/beestat/app.git
synced 2026-02-26 05:00:21 -05:00
335 lines
8.0 KiB
JavaScript
335 lines
8.0 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 {object} options
|
|
* @param {object[]} options.groups
|
|
* @param {Array<{collection: string, point_mode: string}>} options.shape_specs
|
|
* @param {(function(object, object, object): boolean)=} options.should_skip_shape
|
|
*
|
|
* @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);
|
|
})
|
|
};
|
|
};
|