react-planner
Version:
react-planner is a React Component for plans design. Draw a 2D floorplan and navigate it in 3D mode.
292 lines (226 loc) • 11.8 kB
JavaScript
import { List } from 'immutable';
import { Project, Area, Line, Hole, Item, Vertex } from './export';
import {
GraphInnerCycles,
GeometryUtils,
IDBroker
} from '../utils/export';
import { Layer as LayerModel } from '../models';
const sameSet = (set1, set2) => set1.size === set2.size && set1.isSuperset(set2) && set1.isSubset(set2);
class Layer{
static create( state, name, altitude ) {
let layerID = IDBroker.acquireID();
name = name || `layer ${layerID}`;
altitude = altitude || 0;
let layer = new LayerModel({ id: layerID, name, altitude });
state = state.setIn(['scene', 'selectedLayer'], layerID );
state = state.setIn(['scene', 'layers', layerID], layer);
return { updatedState: state };
}
static select( state, layerID ) {
if( !state.get('alterate') ) state = Project.unselectAll( state ).updatedState;
state = state.setIn(['scene', 'selectedLayer'], layerID);
return { updatedState: state };
}
static selectElement( state, layerID, elementPrototype, elementID ){
state = state.setIn(['scene', 'layers', layerID, elementPrototype, elementID, 'selected'], true);
state = state.updateIn(['scene', 'layers', layerID, 'selected', elementPrototype], elems => elems.push(elementID));
return { updatedState: state };
}
static unselect( state, layerID, elementPrototype, elementID ){
state = state.setIn(['scene', 'layers', layerID, elementPrototype, elementID, 'selected'], false);
state = state.updateIn(['scene', 'layers', layerID, 'selected', elementPrototype], elems => elems.filter( el => el.id === elementID ));
return { updatedState: state };
}
static unselectAll( state, layerID ) {
let { lines, holes, items, areas } = state.getIn(['scene', 'layers', layerID]);
if( lines ) lines.forEach( line => { state = Line.unselect( state, layerID, line.id ).updatedState; });
if( holes ) holes.forEach( hole => { state = Hole.unselect( state, layerID, hole.id ).updatedState; });
if( items ) items.forEach( item => { state = Item.unselect( state, layerID, item.id ).updatedState; });
if( areas ) areas.forEach( area => { state = Area.unselect( state, layerID, area.id ).updatedState; });
return { updatedState: state };
}
static setProperties( state, layerID, properties ) {
state = state.mergeIn(['scene', 'layers', layerID], properties);
state = state.updateIn(['scene', 'layers'], layers => layers.sort( ( a, b ) => a.altitude !== b.altitude ? a.altitude - b.altitude : a.order - b.order ));
return { updatedState: state };
}
static remove( state, layerID ) {
state = state.removeIn(['scene', 'layers', layerID]);
state = state.setIn(
['scene', 'selectedLayer'],
state.scene.selectedLayer !== layerID ? state.scene.selectedLayer : state.scene.layers.first().id
);
return { updatedState: state };
}
static removeElement( state, layerID, elementPrototype, elementID ) {
state = state.deleteIn(['scene', 'layers', layerID, elementPrototype, elementID]);
return { updatedState: state };
}
static detectAndUpdateAreas( state, layerID ) {
let verticesArray = []; //array with vertices coords
let linesArray; //array with edges
let vertexID_to_verticesArrayIndex = {};
let verticesArrayIndex_to_vertexID = {};
state.getIn(['scene', 'layers', layerID, 'vertices']).forEach(vertex => {
let verticesCount = verticesArray.push([vertex.x, vertex.y]);
let latestVertexIndex = verticesCount - 1;
vertexID_to_verticesArrayIndex[vertex.id] = latestVertexIndex;
verticesArrayIndex_to_vertexID[latestVertexIndex] = vertex.id;
});
linesArray = state.getIn(['scene', 'layers', layerID, 'lines'])
.map(line => line.vertices.map(vertexID => vertexID_to_verticesArrayIndex[vertexID]).toArray());
let innerCyclesByVerticesArrayIndex = GraphInnerCycles.calculateInnerCycles(verticesArray, linesArray);
let innerCyclesByVerticesID = new List(innerCyclesByVerticesArrayIndex)
.map(cycle => new List(cycle.map(vertexIndex => verticesArrayIndex_to_vertexID[vertexIndex])));
// All area vertices should be ordered in counterclockwise order
innerCyclesByVerticesID = innerCyclesByVerticesID.map( ( area ) =>
GraphInnerCycles.isClockWiseOrder( area.map(vertexID => state.getIn(['scene', 'layers', layerID, 'vertices', vertexID]) ) ) ? area.reverse() : area
);
let areaIDs = [];
//remove areas
state.getIn(['scene', 'layers', layerID, 'areas']).forEach(area => {
let areaInUse = innerCyclesByVerticesID.some(vertices => sameSet(vertices, area.vertices));
if (!areaInUse) {
state = Area.remove( state, layerID, area.id ).updatedState;
}
});
//add new areas
innerCyclesByVerticesID.forEach((cycle, ind) => {
let areaInUse = state.getIn(['scene', 'layers', layerID, 'areas']).find(area => sameSet(area.vertices, cycle));
if (areaInUse) {
areaIDs[ind] = areaInUse.id;
state = state.setIn(['scene', 'layers', layerID, 'areas', areaIDs[ind], 'holes'], new List());
} else {
let areaVerticesCoords = cycle.map(vertexID => state.getIn(['scene', 'layers', layerID, 'vertices', vertexID]));
let resultAdd = Area.add(state, layerID, 'area', areaVerticesCoords, state.catalog);
areaIDs[ind] = resultAdd.area.id;
state = resultAdd.updatedState;
}
});
// Build a relationship between areas and their coordinates
let verticesCoordsForArea = areaIDs.map(id => {
let vertices = state.getIn(['scene', 'layers', layerID, 'areas', id]).vertices.map(vertexID => {
let { x, y } = state.getIn(['scene', 'layers', layerID, 'vertices', vertexID]);
return new List([x,y]);
});
return { id, vertices };
});
// Find all holes for an area
let i, j;
for (i = 0; i < verticesCoordsForArea.length; i++) {
let holesList = new List(); // The holes for this area
let areaVerticesList = verticesCoordsForArea[i].vertices.flatten().toArray();
for (j = 0; j < verticesCoordsForArea.length; j++) {
if (i !== j) {
let isHole = GeometryUtils.ContainsPoint(areaVerticesList,
verticesCoordsForArea[j].vertices.get(0).get(0),
verticesCoordsForArea[j].vertices.get(0).get(1));
if (isHole) {
holesList = holesList.push(verticesCoordsForArea[j].id);
}
}
}
state = state.setIn(['scene', 'layers', layerID, 'areas', verticesCoordsForArea[i].id, 'holes'], holesList);
}
// Remove holes which are already holes for other areas
areaIDs.forEach(areaID => {
let doubleHoles = new Set();
let areaHoles = state.getIn(['scene', 'layers', layerID, 'areas', areaID, 'holes']);
areaHoles.forEach((areaHoleID) => {
let holesOfholes = state.getIn(['scene', 'layers', layerID, 'areas', areaHoleID, 'holes']);
holesOfholes.forEach((holeID) => {
if (areaHoles.indexOf(holeID) !== -1) doubleHoles.add(holeID);
});
});
doubleHoles.forEach(doubleHoleID => {
areaHoles = areaHoles.remove( areaHoles.indexOf(doubleHoleID) );
});
state = state.setIn(['scene', 'layers', layerID, 'areas', areaID, 'holes'], areaHoles);
});
return { updatedState: state };
}
static removeZeroLengthLines( state, layerID ) {
let updatedState = state.getIn(['scene', 'layers', layerID, 'lines']).reduce(
( newState, line ) =>
{
let v_id0 = line.getIn(['vertices', 0]);
let v_id1 = line.getIn(['vertices', 1]);
let v0 = newState.getIn(['scene', 'layers', layerID, 'vertices', v_id0]);
let v1 = newState.getIn(['scene', 'layers', layerID, 'vertices', v_id1]);
if( GeometryUtils.verticesDistance( v0, v1 ) === 0 )
{
newState = Line.remove( newState, layerID, line.id ).updatedState;
}
return newState;
},
state
);
return { updatedState };
}
static mergeEqualsVertices( state, layerID, vertexID ) {
//1. find vertices to remove
let vertex = state.getIn(['scene', 'layers', layerID, 'vertices', vertexID]);
let doubleVertices = state.getIn(['scene', 'layers', layerID, 'vertices'])
.filter(v => {
return (
v.id !== vertexID &&
GeometryUtils.samePoints(vertex, v)// &&
//!v.lines.contains( vertexID ) &&
//!v.areas.contains( vertexID )
);
});
if (doubleVertices.isEmpty()) return { updatedState: state };
doubleVertices.forEach(doubleVertex => {
let reduced = doubleVertex.lines.reduce(
( reducedState, lineID ) => {
reducedState = reducedState.updateIn(['scene', 'layers', layerID, 'lines', lineID, 'vertices'], vertices => {
if( vertices ) {
return vertices.map(v => v === doubleVertex.id ? vertexID : v);
}
});
reducedState = Vertex.addElement( reducedState, layerID, vertexID, 'lines', lineID ).updatedState;
return reducedState;
},
state
);
let biReduced = doubleVertex.areas.reduce(
( reducedState, areaID ) => {
reducedState = reducedState.updateIn(['scene', 'layers', layerID, 'areas', areaID, 'vertices'], vertices => {
if( vertices ) return vertices.map(v => v === doubleVertex.id ? vertexID : v);
});
reducedState = Vertex.addElement( reducedState, layerID, vertexID, 'areas', areaID ).updatedState;
return reducedState;
},
reduced
);
state = Vertex.remove( biReduced, layerID, doubleVertex.id, null, null, true ).updatedState;
});
return { updatedState: state };
}
static setPropertiesOnSelected( state, layerID, properties ) {
let selected = state.getIn(['scene', 'layers', layerID, 'selected']);
selected.lines.forEach(lineID => state = Line.setProperties(state, layerID, lineID, properties).updatedState);
selected.holes.forEach(holeID => state = Hole.setProperties(state, layerID, holeID, properties).updatedState);
selected.areas.forEach(areaID => state = Area.setProperties(state, layerID, areaID, properties).updatedState);
selected.items.forEach(itemID => state = Item.setProperties(state, layerID, itemID, properties).updatedState);
return { updatedState: state };
}
static updatePropertiesOnSelected( state, layerID, properties ) {
let selected = state.getIn(['scene', 'layers', layerID, 'selected']);
selected.lines.forEach(lineID => state = Line.updateProperties(state, layerID, lineID, properties).updatedState);
selected.holes.forEach(holeID => state = Hole.updateProperties(state, layerID, holeID, properties).updatedState);
selected.areas.forEach(areaID => state = Area.updateProperties(state, layerID, areaID, properties).updatedState);
selected.items.forEach(itemID => state = Item.updateProperties(state, layerID, itemID, properties).updatedState);
return { updatedState: state };
}
static setAttributesOnSelected( state, layerID, attributes ) {
let selected = state.getIn(['scene', 'layers', layerID, 'selected']);
selected.lines.forEach(lineID => state = Line.setAttributes( state, layerID, lineID, attributes ).updatedState);
selected.holes.forEach(holeID => state = Hole.setAttributes( state, layerID, holeID, attributes ).updatedState);
selected.items.forEach(itemID => state = Item.setAttributes( state, layerID, itemID, attributes ).updatedState);
//selected.areas.forEach(areaID => state = Area.setAttributes( state, layerID, areaID, attributes ).updatedState);
return { updatedState: state };
}
}
export { Layer as default };