fsm-designer/js/fsm-document.js

196 lines
6.6 KiB
JavaScript

class FSMDocument {
constructor(name) {
this.id = guid();
this.name = name || null;
this.createdAt = Date.now();
this.lastModified = Date.now();
this.element = null;
this.unsaved = true;
this.lastSavedHash = '';
this.states = [];
this.connections = [];
this.changes = [];
this.changesIndex = -1;
}
physicsTick() {
this.states.forEach(stateA => {
this.states.forEach(stateB => {
if (stateA !== stateB && stateA.intersects(stateB)) {
const inter = stateA.intersection(stateB);
const angle1 = stateA.directionTo(stateB);
const angle2 = angle1 + Math.PI;
stateA.x += Math.cos(angle1) * inter;
stateA.y += Math.sin(angle1) * inter;
stateB.x += Math.cos(angle2) * inter;
stateB.y += Math.sin(angle2) * inter;
}
});
});
}
tick() {
this.states.forEach(state => {
if (state.v) {
state.moveToStep();
}
});
if (!this.unsaved) {
if (this.hashCode() !== this.lastSavedHash) {
this.unsaved = true;
this.element.classList.add('unsaved');
}
}
}
draw() {
this.states.forEach(state => state.draw());
this.connections.forEach(connection => connection.draw());
}
onDblClick(x, y) {
if (!selectedObject) {
selectedObject = new State(x, y);
this.states.push(selectedObject);
this.addChange('state', selectedObject.id, 'add', Object.keys(selectedObject), Object.values(selectedObject));
resetCaret();
} else if (selectedObject instanceof State) {
selectedObject.isAcceptState = !selectedObject.isAcceptState;
this.addChange('state', selectedObject.id, 'edit', ['isAcceptState'], [!selectedObject.isAcceptState]);
}
}
onRightClick(x, y) {
if (!selectedObject) {
}
}
deleteCurrentObject() {
if (!!selectedObject) {
if (selectedObject instanceof State) {
this.states.splice(this.states.findIndex(state => state === selectedObject), 1);
}
for (let i = 0; i < this.connections.length; i++) {
const con = this.connections[i];
if (con === selectedObject || con.state === selectedObject || con.stateA === selectedObject || con.stateB === selectedObject) {
this.connections.splice(i--, 1);
}
}
selectedObject = null;
}
}
getConnectionsOfState(state) {
return this.connections.filter(connection => connection instanceof Connection && (connection.stateA === state || connection.stateB === state));
}
getConnectionsBetweenStates(stateA,stateB) {
return this.getConnectionsOfState(stateA).filter(connection => connection.stateA === stateB || connection.stateB === stateB);
}
addChange(objectType, id, action, fields, oldValues, tryCombiningChanges) {
if (this.changesIndex < this.changes.length - 1) {
this.changes.splice(this.changesIndex + 1, this.changes.length - this.changesIndex - 1);
}
if (tryCombiningChanges) {
let prevChange = this.changes[this.changesIndex];
while (!!prevChange && prevChange.type === objectType && prevChange.id === id && prevChange.action === action && prevChange.fields.length === fields.length) {
oldValues = prevChange.prevValues;
this.changes.splice(this.changesIndex, 1);
this.changesIndex--;
prevChange = this.changes[this.changesIndex];
}
}
this.changes.push({
type: objectType,
id,
action,
fields,
prevValues: oldValues,
date: Date.now()
});
this.changesIndex++;
}
undo(stepAmount) {
if (stepAmount === 0)
return;
const loopAmount = Math.abs(stepAmount);
const isRedo = stepAmount < 0;
for (let i = 0; i < loopAmount; i++) {
if (this.changes.length === 0 || (!isRedo && this.changesIndex < 0) || (isRedo && this.changesIndex >= this.changes.length - 1)) {
return;
}
let change;
if (isRedo) {
this.changesIndex++;
change = this.changes[this.changesIndex];
} else {
change = Object.assign({}, this.changes[this.changesIndex]);
this.changesIndex--;
change.action = change.action === 'add' ? 'remove' : change.action === 'remove' ? 'add' : 'edit';
}
if (change.type === 'state') {
if (change.action === 'remove') {
this.states.splice(this.states.findIndex(item => item.id === change.id), 1);
return;
}
let state;
if (change.action === 'add')
state = new State();
else if (change.action === 'edit')
state = this.states.find(item => item.id === change.id);
change.fields.forEach((field, index) => {
state[field] = change.prevValues[index];
});
if (change.action === 'add')
this.states.push(state);
}
if (change.type.indexOf('connection') !== -1) {
if (change.action === 'remove') {
this.connections.splice(this.connections.findIndex(item => item.id === change.id), 1);
return;
}
let connection;
if (change.action === 'add') {
if(change.type === 'startconnection')
connection = new StartConnection();
else if(change.type === 'selfconnection')
connection = new SelfConnection();
else
connection = new Connection();
} else if (change.action === 'edit') {
connection = this.connections.find(item => item.id === change.id);
}
change.fields.forEach((field, index) => {
connection[field] = change.prevValues[index];
});
if (change.action === 'add')
this.connections.push(connection);
}
}
}
}