196 lines
6.6 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|