grapesjs-clot
Version:
Free and Open Source Web Builder Framework
535 lines (485 loc) • 17.8 kB
JavaScript
import Stomp from 'stompjs';
import SockJS from 'sockjs-client';
import { myEditor } from '../index.js';
import { parse, stringify } from 'flatted';
import CircularJSON from 'circular-json';
import axios from 'axios';
import Cookie from './Cookie';
import {
setComponentIds,
setComponentRemoteUnSelected,
setComponentRemoteSelected,
checkComponentsChooser,
} from '../dom_components/model/Components';
import {
applyDeleteComponent,
applyUpdateTrait,
applyUpdateContent,
applyAddSelected,
applyRemoveSelected,
applySetImageSrc,
} from './applyOp.js';
import { TMD, TMM, TMA, TAD, TAM, TAA } from './OT.js';
export var stompClient = null;
export var email = '';
export var username = '';
export var noteId = '';
export var queue = [];
export var isConnected = false;
var sessionId = '';
var setQueue;
var url;
export const ClientStateEnum = {
Synced: 1,
AwaitingACK: 2,
AwaitingWithBuffer: 3,
ApplyingRemoteOp: 4,
ApplyingLocalOp: 5,
ApplyingRemoteOpWithoutACK: 6,
ApplyingBufferedLocalOp: 7,
CreatingLocalOpFromBuffer: 8,
ApplyingRemoteOpWithBuffer: 9,
SendingOpToController: 10,
EditorInitializing: 11,
};
export var ClientState = ClientStateEnum.EditorInitializing;
var localTS = 0;
var localOp = null;
var remoteOp = null;
var remoteTS = 0;
var localOpPrime = null;
var remoteOpPrime = null;
var opBuffer = new Array();
var initBuffer = new Array();
var topic = null;
var privateMsg = null;
export const connectWebSocket = (tempNoteId, tempEmail, tempUsername, tempSetQueue, tempUrl) => {
//username = makeId(5);
email = tempEmail;
noteId = tempNoteId;
username = tempUsername;
setQueue = tempSetQueue;
url = tempUrl;
ClientState = ClientStateEnum.EditorInitializing;
localTS = 0;
if (!stompClient) {
let socket = new SockJS(`${url}websocket`);
stompClient = Stomp.over(socket);
stompClient.connect({}, onConnected, onError);
} else {
topic = stompClient.subscribe(`/topic/public/${noteId}`, onMessageReceived);
privateMsg = stompClient.subscribe(`/user/${email}/msg`, onMessageReceived);
stompClient.send(
`/app/chat.register/${noteId}`,
{},
JSON.stringify({ senderName: username, senderEmail: email, type: 'JOIN', noteId: noteId })
);
isConnected = true;
}
};
const onConnected = () => {
// Subscribe to the Public Topic
//stompClient.subscribe('/topic/public', onMessageReceived);
// Todo
topic = stompClient.subscribe(`/topic/public/${noteId}`, onMessageReceived);
privateMsg = stompClient.subscribe(`/user/${email}/msg`, onMessageReceived);
// Tell your username to the server
//stompClient.send('/app/chat.register', {}, JSON.stringify({ sender: email, type: 'JOIN' }));
// Todo
stompClient.send(
`/app/chat.register/${noteId}`,
{},
JSON.stringify({ senderName: username, senderEmail: email, type: 'JOIN', noteId: noteId })
);
isConnected = true;
};
export const setIsConnected = flag => {
isConnected = flag;
};
export const sendLeave = () => {
topic.unsubscribe();
privateMsg.unsubscribe();
stompClient.send(
`/app/chat.send/${noteId}`,
{},
JSON.stringify({ senderName: username, senderEmail: email, type: 'LEAVE', noteId: noteId })
);
isConnected = false;
};
const onError = () => {
console.log('Error!!');
};
const onMessageReceived = async payload => {
let StoC_msg = CircularJSON.parse(payload.body);
if (StoC_msg.type === 'JOIN') {
if (StoC_msg.senderEmail === email) {
sessionId = StoC_msg.sessionId;
let queueStr = StoC_msg.queue;
queue = queueStr.split(' ');
setQueue(queue);
let clientNum = parseInt(StoC_msg.op);
if (clientNum == 1) {
// set state to Synced
ClientState = ClientStateEnum.Synced;
console.log('state: Synced');
}
//stompClient.subscribe('/user/' + sessionId + '/msg', onMessageReceived);
}
// join msg of other clients
else {
// Todo: get and set the queue
let queueStr = StoC_msg.queue;
const cookieParser = new Cookie(document.cookie);
const saveUrl = `${url}note/saveTempCollaborationNote/`;
queue = queueStr.split(' ');
setQueue(queue);
if (queue[0] == email) {
let wrapper = myEditor.getWrapper();
let components = myEditor.getComponents();
let style = myEditor.getStyle();
setComponentIds(components);
checkComponentsChooser(components);
let id = wrapper.get('attributes').id;
let op = {
action: 'copy-wrapper',
};
let dataStored = {
components: components,
style: style,
id: id,
};
axios
.put(saveUrl + `${noteId}`, dataStored, {
headers: {
Authorization: 'Bearer ' + cookieParser.getCookieByName('token'),
},
})
.then(res => {
let CtoS_Msg = {
senderName: username,
senderEmail: email,
sessionId: sessionId,
type: 'COPY',
ts: localTS,
op: CircularJSON.stringify(op),
newcomer: StoC_msg.senderEmail,
noteId: noteId,
};
//stompClient.send('/app/chat.send', {}, CircularJSON.stringify(CtoS_Msg));
stompClient.send(`/app/chat.send/${noteId}`, {}, CircularJSON.stringify(CtoS_Msg));
console.log('state: ' + ClientState);
})
.catch(err => {
console.log('error!!!!', err);
});
}
}
} else if (StoC_msg.type === 'COPY') {
// handle it only when the state is EditorInitializing (newly client)
if (ClientState == ClientStateEnum.EditorInitializing) {
let remoteOp = CircularJSON.parse(StoC_msg.op);
let remoteTS = StoC_msg.ts;
const cookieParser = new Cookie(document.cookie);
const loadUrl = `${url}note/loadTempCollaborationNote/`;
localTS = remoteTS;
if (remoteOp.action == 'copy-wrapper') {
axios
.get(loadUrl + `${noteId}`, {
headers: {
Authorization: 'Bearer ' + cookieParser.getCookieByName('token'),
},
})
.then(res => {
let opts = res.data.res;
let wrapper = myEditor.getWrapper();
// init the editor
wrapper.set('attributes', { id: opts.id });
myEditor.setComponents(opts.components);
myEditor.setStyle(opts.style);
while (initBuffer.length != 0) {
let msg = initBuffer.shift();
if (msg.ts > localTS) {
remoteOp = CircularJSON.parse(msg.op);
applyOp(remoteOp.action, remoteOp.opts);
}
}
let components = myEditor.getComponents();
setComponentRemoteSelected(components);
// set state to Synced
ClientState = ClientStateEnum.Synced;
console.log('state: Synced');
})
.catch(err => {
console.log('error!!!!', err);
});
}
}
} else if (StoC_msg.type === 'LEAVE') {
let components = myEditor.getComponents();
console.log('leaver', StoC_msg.senderEmail);
setComponentRemoteUnSelected(components, StoC_msg.senderName);
// Todo: get the queue and set it
let queueStr = StoC_msg.queue;
queue = queueStr.split(' ');
setQueue(queue);
} else if (StoC_msg.type === 'ACK') {
//-------------------------- State: AwaitingACK ------------------------------
if (ClientState == ClientStateEnum.AwaitingACK) {
ClientState = ClientStateEnum.Synced;
console.log('state: Synced');
}
//-------------------------- State: AwaitingWithBuffer ------------------------------
else if (ClientState == ClientStateEnum.AwaitingWithBuffer) {
/***** CreatingLocalOpFromBuffer *****/
ClientState = ClientStateEnum.CreatingLocalOpFromBuffer;
console.log('state: CreatingLocalOpFromBuffer');
await CreatingLocalOpFromBuffer();
}
//-------------------------- State: Others ------------------------------
else {
if (
ClientState != ClientStateEnum.AwaitingACK &&
ClientState != ClientStateEnum.AwaitingWithBuffer &&
ClientState != ClientStateEnum.EditorInitializing &&
ClientState != ClientStateEnum.Synced
) {
onMessageReceived(payload);
}
}
} else if (StoC_msg.type === 'OP') {
//--------------------------- State: Synced -----------------------------
if (ClientState == ClientStateEnum.Synced) {
/***** ApplyRemoteOp *****/
ClientState = ClientStateEnum.ApplyingRemoteOp;
console.log('state: ApplyingRemoteOp');
await ApplyingRemoteOp(StoC_msg);
}
//-------------------------- State: AwaitingACK ------------------------------
else if (ClientState == ClientStateEnum.AwaitingACK) {
/***** ApplyingRemoteOpWithoutACK *****/
ClientState = ClientStateEnum.ApplyingRemoteOpWithoutACK;
console.log('state: ApplyingRemoteOpWithoutACK');
await ApplyingRemoteOpWithoutACK(StoC_msg);
}
//-------------------------- State: AwaitingWithBuffer ------------------------------
else if (ClientState == ClientStateEnum.AwaitingWithBuffer) {
/***** ApplyingRemoteOpWithBuffer *****/
ClientState = ClientStateEnum.ApplyingRemoteOpWithBuffer;
console.log('state: ApplyingRemoteOpWithBuffer');
await ApplyingRemoteOpWithBuffer(StoC_msg);
}
//-------------------------- State: EditorInitializing ------------------------------
else if (ClientState == ClientStateEnum.EditorInitializing) {
initBuffer.push(StoC_msg);
}
//-------------------------- State: Others ------------------------------
else {
if (
ClientState != ClientStateEnum.Synced &&
ClientState != ClientStateEnum.AwaitingACK &&
ClientState != ClientStateEnum.AwaitingWithBuffer &&
ClientState != ClientStateEnum.EditorInitializing
) {
onMessageReceived(payload);
}
}
}
};
const applyOp = (action, opts) => {
const droppable = myEditor.getModel().getCurrentFrame().droppable;
if (action === 'delete-component') {
applyDeleteComponent(myEditor.getModel().getEditor(), opts);
} else if (action === 'add-component') {
if (!opts.dropContent) return;
droppable.applyAppendOrMoveComponent(opts, 'add-component');
let components = myEditor.getComponents();
setComponentIds(components);
} else if (action === 'move-component') {
droppable.applyAppendOrMoveComponent(opts, 'move-component');
} else if (action === 'select-component') {
applyAddSelected(opts);
} else if (action === 'unselect-component') {
applyRemoveSelected(opts);
} else if (action === 'copy-component') {
} else if (action === 'update-content') {
applyUpdateContent(opts);
} else if (action === 'update-trait') {
applyUpdateTrait(opts);
} else if (action === 'update-style') {
myEditor.getModel().get('StyleManager').applyUpdateStyle(opts);
} else if (action === 'set-image-src') {
applySetImageSrc(opts);
}
};
// finish
export const setState = state => {
ClientState = state;
};
// finish
export const SendingOpToController = () => {
// send Op to controller
let CtoS_Msg = {
senderName: username,
senderEmail: email,
sessionId: sessionId,
type: 'OP',
ts: localTS,
op: CircularJSON.stringify(localOp),
noteId: noteId,
};
stompClient.send(`/app/chat.send/${noteId}`, {}, CircularJSON.stringify(CtoS_Msg));
// buffer is empty => AwaitingACK state
if (opBuffer.length <= 0) {
ClientState = ClientStateEnum.AwaitingACK;
console.log('state: AwaitingACK');
}
// buffer is not empty => AwaitingWithBuffer state
else {
ClientState = ClientStateEnum.AwaitingWithBuffer;
console.log('state: AwaitingWithBuffer');
}
};
// finish
export const ApplyingLocalOp = op => {
//console.log("state: ApplyingLocalOp");
// step 1: set localOp to the Op in the received LocalChange event
op.username = username;
localOp = op;
// step 2: increment localTS
localTS += 1;
/* don't need
// step 3: call applyOp(localOp) (don't need)
applyOp(localOp);
*/
// next state: SendingOpToController
ClientState = ClientStateEnum.SendingOpToController;
console.log('state: SendingOpToController');
SendingOpToController();
};
// finish
export const ApplyingBufferedLocalOp = op => {
//console.log("state: ApplyingBufferedLocalOp");
// step 1: add Op from the received LocalChange event to opBuffer
op.username = username;
opBuffer.push(op);
/* don't need
// step 2: call applyOp(opBuffer.last)
applyOp(opBuffer[opBuffer.length-1]);
*/
// next state: AwaitingWithBuffer
ClientState = ClientStateEnum.AwaitingWithBuffer;
console.log('state: AwaitingWithBuffer');
};
const CreatingLocalOpFromBuffer = () => {
//console.log("state: CreatingLocalOpFromBuffer");
// step 1: increment localTS
localTS += 1;
// step 2: set localOp to opBuffer.first
localOp = opBuffer[0];
// step 3: remove opBuffer.first from opBuffer
opBuffer.shift();
// next state: SendingOpToController
ClientState = ClientStateEnum.SendingOpToController;
console.log('state: SendingOpToController');
SendingOpToController();
};
// finish
const ApplyingRemoteOp = StoC_msg => {
//console.log("state: ApplyRemoteOp");
// step 1: set remoteTS and remoteOp to the values within the received StoC Msg event
remoteOp = CircularJSON.parse(StoC_msg.op);
remoteTS = StoC_msg.ts;
// step 2: set localTS to the value of remoteTS
localTS = remoteTS;
// step 3: call applyOp(remoteOp)
applyOp(remoteOp.action, remoteOp.opts);
// next state: Synced
ClientState = ClientStateEnum.Synced;
console.log('state: Synced');
};
// finish
const ApplyingRemoteOpWithoutACK = StoC_msg => {
//console.log("state: ApplyingRemoteOpWithoutACK");
// step 1: set localTS to remoteTS
localTS = StoC_msg.ts;
// step 2: increment localTS
localTS += 1;
// step 3: set remoteTS and remoteOp to the values within the received StoC Msg event
remoteTS = StoC_msg.ts;
remoteOp = CircularJSON.parse(StoC_msg.op);
// step 4: obtain remoteOpPrime and localOpPrime by evaluating xform(remoteOp, localOp)
//console.log("local: " + JSON.stringify(localOp));
//console.log("remote: " + JSON.stringify(remoteOp));
remoteOpPrime = OT(remoteOp, localOp);
localOpPrime = OT(localOp, remoteOp);
// step 5: call applyOp(remoteOpPrime)
//console.log(JSON.stringify(remoteOpPrime));
applyOp(remoteOpPrime.action, remoteOpPrime.opts);
// step 6: set localOp to the value of localOpPrime
localOp = localOpPrime;
// next state: SendingOpToController
ClientState = ClientStateEnum.SendingOpToController;
console.log('state: SendingOpToController');
SendingOpToController();
};
const ApplyingRemoteOpWithBuffer = StoC_msg => {
remoteOp = CircularJSON.parse(StoC_msg.op);
remoteTS = StoC_msg.ts;
let remoteOpPrimeArray = new Array();
// step 1: set localTS to remoteTS
localTS = remoteTS;
// step 2: increment localTS
localTS += 1;
// step 3: obtain remoteOpPrime[0] by evaluating xform(remoteOp, localOp)
remoteOpPrimeArray[0] = OT(remoteOp, localOp);
// step 4: obtain remoteOpPrime[i+1] by evaluating xform(remoteOpPrime[i], opBuffer[i])
for (let i = 0; i < opBuffer.length; i++) {
remoteOpPrimeArray[i + 1] = OT(remoteOpPrimeArray[i], opBuffer[i]);
}
// step 5: call applyOp(remoteOpPrime.last)
applyOp(
remoteOpPrimeArray[remoteOpPrimeArray.length - 1].action,
remoteOpPrimeArray[remoteOpPrimeArray.length - 1].opts
);
// step 6: obtain localOpPrime by evaluating xform(localOp, remoteOp)
localOpPrime = OT(localOp, remoteOp);
// step 7: set localOp to the value of localOpPrime
localOp = localOpPrime;
// step 8: obtain opBuffer[i] by evaluating xform(opBuffer[i], remoteOpPrime[i]) & send
for (let j = 0; j < opBuffer.length; j++) {
opBuffer[j] = OT(opBuffer[j], remoteOpPrimeArray[j]);
}
// next state: SendingOpToController
ClientState = ClientStateEnum.SendingOpToController;
console.log('state: SendingOpToController');
SendingOpToController();
};
const OT = (tarOp, refOp) => {
let tarAction = tarOp.action;
let refAction = refOp.action;
let tarOpPrime;
if (tarAction === 'add-component') {
if (refAction === 'delete-component') {
tarOpPrime = TAD(tarOp, refOp); // get A'
} else if (refAction === 'move-component') {
tarOpPrime = TAM(tarOp, refOp); // get A'
} else if (refAction === 'add-component') {
tarOpPrime = TAA(tarOp, refOp); // get A'
} else {
tarOpPrime = tarOp;
}
} else if (tarAction === 'move-component') {
if (refAction === 'delete-component') {
tarOpPrime = TMD(tarOp, refOp); // get A'
} else if (refAction === 'move-component') {
tarOpPrime = TMM(tarOp, refOp); // get A'
} else if (refAction === 'add-component') {
tarOpPrime = TMA(tarOp, refOp); // get A'
} else {
tarOpPrime = tarOp;
}
} else {
tarOpPrime = tarOp;
}
return tarOpPrime;
};