Skip to content

Commit f23d843

Browse files
author
Ives van Hoorne
committed
Nice
1 parent a7e8835 commit f23d843

File tree

8 files changed

+148
-89
lines changed

8 files changed

+148
-89
lines changed

packages/app/src/app/components/CodeEditor/Monaco/index.js

Lines changed: 105 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -228,52 +228,7 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
228228
const { isLive, sendTransforms, onCodeReceived } = this.props;
229229

230230
if (isLive && sendTransforms && !this.receivingCode) {
231-
let code = this.currentModule.code || '';
232-
const t = changes
233-
.map(change => {
234-
const startPos = change.range.getStartPosition();
235-
const lines = code.split('\n');
236-
let index = 0;
237-
const totalLength = code.length;
238-
let currentLine = 0;
239-
240-
while (currentLine + 1 < startPos.lineNumber) {
241-
index += lines[currentLine].length;
242-
index += 1; // Linebreak character
243-
currentLine += 1;
244-
}
245-
246-
index += startPos.column - 1;
247-
248-
const operation = new TextOperation();
249-
if (index) {
250-
operation.retain(index);
251-
}
252-
253-
if (change.rangeLength > 0) {
254-
// Deletion
255-
operation.delete(change.rangeLength);
256-
257-
index += change.rangeLength;
258-
}
259-
if (change.text) {
260-
// Insertion
261-
operation.insert(change.text);
262-
}
263-
264-
operation.retain(Math.max(0, totalLength - index));
265-
266-
if (changes.length > 1) {
267-
code = operation.apply(code);
268-
}
269-
270-
return operation;
271-
})
272-
.reduce((prev, next) => prev.compose(next));
273-
274-
sendTransforms(t);
275-
} else if (onCodeReceived) {
276-
onCodeReceived();
231+
this.addChangesOperation(changes);
277232
}
278233
});
279234

@@ -364,6 +319,82 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
364319
});
365320
};
366321

322+
changes = { code: '', changes: [] };
323+
changeTimeout: ?TimeoutID;
324+
/**
325+
* Throttle the changes and handle them after a desired amount of time as one array of changes
326+
*/
327+
addChangesOperation = (changes: Array<any>) => {
328+
if (!this.changes.code) {
329+
this.changes.code = this.currentModule.code || '';
330+
}
331+
332+
changes.forEach(change => {
333+
this.changes.changes.push(change);
334+
});
335+
336+
if (this.changeTimeout) {
337+
clearTimeout(this.changeTimeout);
338+
}
339+
this.changeTimeout = setTimeout(() => {
340+
this.sendChangeOperations();
341+
}, 10);
342+
};
343+
344+
sendChangeOperations = () => {
345+
const { sendTransforms, isLive, onCodeReceived } = this.props;
346+
347+
if (sendTransforms && this.changes.changes) {
348+
let code = this.changes.code;
349+
const t = this.changes.changes
350+
.map(change => {
351+
const startPos = change.range.getStartPosition();
352+
const lines = code.split('\n');
353+
let index = 0;
354+
const totalLength = code.length;
355+
let currentLine = 0;
356+
357+
while (currentLine + 1 < startPos.lineNumber) {
358+
index += lines[currentLine].length;
359+
index += 1; // Linebreak character
360+
currentLine += 1;
361+
}
362+
363+
index += startPos.column - 1;
364+
365+
const operation = new TextOperation();
366+
if (index) {
367+
operation.retain(index);
368+
}
369+
370+
if (change.rangeLength > 0) {
371+
// Deletion
372+
operation.delete(change.rangeLength);
373+
374+
index += change.rangeLength;
375+
}
376+
if (change.text) {
377+
// Insertion
378+
operation.insert(change.text);
379+
}
380+
381+
operation.retain(Math.max(0, totalLength - index));
382+
383+
if (this.changes.changes.length > 1) {
384+
code = operation.apply(code);
385+
}
386+
387+
return operation;
388+
})
389+
.reduce((prev, next) => prev.compose(next));
390+
391+
sendTransforms(t);
392+
} else if (!isLive && onCodeReceived) {
393+
onCodeReceived();
394+
}
395+
this.changes = { code: '', changes: [] };
396+
};
397+
367398
changeSandbox = (
368399
newSandbox: Sandbox,
369400
newCurrentModule: Module,
@@ -407,46 +438,46 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
407438
}
408439
};
409440

410-
applyOperations = ops => {
411-
const monacoEditOperations = [];
412-
413-
ops.forEach(operation => {
414-
const lines = this.editor.getModel().getLinesContent();
415-
416-
let index = 0;
417-
for (let i = 0; i < operation.ops.length; i++) {
418-
let op = operation.ops[i];
419-
if (TextOperation.isRetain(op)) {
420-
index += op;
421-
} else if (TextOperation.isInsert(op)) {
422-
const { lineNumber, column } = indexToLineAndColumn(lines, index);
423-
monacoEditOperations.push({
441+
applyOperation = (operation: any) => {
442+
let index = 0;
443+
for (let i = 0; i < operation.ops.length; i++) {
444+
const op = operation.ops[i];
445+
if (TextOperation.isRetain(op)) {
446+
index += op;
447+
} else if (TextOperation.isInsert(op)) {
448+
const { lineNumber, column } = indexToLineAndColumn(
449+
this.editor.getModel().getLinesContent(),
450+
index
451+
);
452+
this.editor.getModel().applyEdits([
453+
{
424454
range: new this.monaco.Range(
425455
lineNumber,
426456
column,
427457
lineNumber,
428458
column
429459
),
430460
text: op,
431-
});
432-
index += op.length;
433-
} else if (TextOperation.isDelete(op)) {
434-
const from = indexToLineAndColumn(lines, index);
435-
const to = indexToLineAndColumn(lines, index - op);
436-
monacoEditOperations.push({
461+
},
462+
]);
463+
index += op.length;
464+
} else if (TextOperation.isDelete(op)) {
465+
const lines = this.editor.getModel().getLinesContent();
466+
const from = indexToLineAndColumn(lines, index);
467+
const to = indexToLineAndColumn(lines, index - op);
468+
this.editor.getModel().applyEdits([
469+
{
437470
range: new this.monaco.Range(
438471
from.lineNumber,
439472
from.column,
440473
to.lineNumber,
441474
to.column
442475
),
443476
text: '',
444-
});
445-
}
477+
},
478+
]);
446479
}
447-
});
448-
449-
this.editor.getModel().applyEdits(monacoEditOperations);
480+
}
450481
};
451482

452483
changeDependencies = (
@@ -770,6 +801,9 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
770801
// the old extraLib definition and defining a new one.
771802
modelCache[currentId].lib.dispose();
772803
modelCache[currentId].lib = this.addLib(currentModule.code || '', path);
804+
805+
// Reset changes
806+
this.changes = { code: '', changes: [] };
773807
}
774808
}
775809

packages/app/src/app/components/CodeEditor/types.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export interface Editor {
3939
currentModule?: Module;
4040
setTSConfig?: (tsConfig: Object) => void;
4141
setReceivingCode?: (receivingCode: boolean) => void;
42-
applyOperations?: (operations: Array<any>) => void;
42+
applyOperation?: (operation: any) => void;
4343
}
4444

4545
export type Props = {

packages/app/src/app/pages/Sandbox/Editor/Content/index.js

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -208,22 +208,26 @@ class EditorPreview extends React.Component<Props, State> {
208208
}
209209
);
210210

211-
const disposeOperationsToApplyHandler = reaction(
212-
() => store.editor.operationsToApply.map(x => x),
211+
const disposePendingOperationHandler = reaction(
212+
() =>
213+
store.editor.pendingOperation &&
214+
store.editor.pendingOperation.map(x => x),
213215
() => {
214-
if (editor.applyOperations) {
215-
editor.applyOperations(
216-
store.editor.operationsToApply.map(TextOperation.fromJSON)
217-
);
218-
219-
this.props.signals.live.onOperationsApplied();
220-
} else {
221-
console.log('not applying; setting code manually');
222-
// TODO apply logic itself and call `editor.changeCode` manually
216+
if (store.editor.pendingOperation) {
217+
if (editor.applyOperation) {
218+
editor.applyOperation(
219+
TextOperation.fromJSON(store.editor.pendingOperation)
220+
);
221+
222+
this.props.signals.live.onOperationApplied();
223+
} else {
224+
console.log('not applying; setting code manually');
225+
// TODO apply logic itself and call `editor.changeCode` manually
226+
}
223227
}
224228
}
225229
);
226-
console.log(disposeOperationsToApplyHandler);
230+
227231
const disposeModuleHandler = reaction(
228232
() => [store.editor.currentModule, store.editor.currentModule.code],
229233
([newModule]) => {
@@ -262,7 +266,7 @@ class EditorPreview extends React.Component<Props, State> {
262266
disposeResizeHandler();
263267
disposeGlyphsHandler();
264268
disposeLiveHandler();
265-
disposeOperationsToApplyHandler();
269+
disposePendingOperationHandler();
266270
};
267271
};
268272

packages/app/src/app/store/modules/editor/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default Module({
3030
errors: [],
3131
glyphs: [],
3232
corrections: [],
33-
operationsToApply: [],
33+
pendingOperation: null,
3434
isInProjectView: false,
3535
forceRender: 0,
3636
initialPath: '/',

packages/app/src/app/store/modules/editor/model.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export default {
9797
error: types.maybe(types.string),
9898
isResizing: types.boolean,
9999
changedModuleShortids: types.array(types.string),
100-
operationsToApply: types.array(
100+
pendingOperation: types.maybe(
101101
types.array(types.union(types.string, types.number))
102102
),
103103
tabs: types.array(

packages/app/src/app/store/modules/live/actions.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,17 @@ export function acknowledgeOperation({ props, ot }) {
220220

221221
ot.serverAck(moduleShortid);
222222
}
223+
224+
export function computePendingOperation({ props, state }) {
225+
const existingPendingOperation = state.get('editor.pendingOperation');
226+
227+
if (!existingPendingOperation) {
228+
return { pendingOperation: props.operation };
229+
}
230+
231+
const newPendingOperation = TextOperation.fromJSON(existingPendingOperation)
232+
.compose(TextOperation.fromJSON(props.operation))
233+
.toJSON();
234+
235+
return { pendingOperation: newPendingOperation };
236+
}

packages/app/src/app/store/modules/live/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ export default Module({
2222
onTransformMade: sequences.sendTransform,
2323
applyTransformation: sequences.applyTransformation,
2424
onCodeReceived: sequences.unSetReceivingStatus,
25-
onOperationsApplied: sequences.onOperationsApplied,
25+
onOperationApplied: sequences.clearPendingOperation,
2626
},
2727
});

packages/app/src/app/store/modules/live/sequences.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,17 @@ export const applyTransformation = [
169169
(s1, s2) => s1 === s2
170170
),
171171
{
172-
true: [push(state`editor.operationsToApply`, props`operation`)],
172+
true: [
173+
actions.computePendingOperation,
174+
set(state`editor.pendingOperation`, props`pendingOperation`),
175+
],
173176
false: [actions.applyTransformation, changeCode],
174177
},
175178
actions.unSetReceivingStatus,
176179
];
177180

178181
export const unSetReceivingStatus = [actions.unSetReceivingStatus];
182+
183+
export const clearPendingOperation = [
184+
set(state`editor.pendingOperation`, null),
185+
];

0 commit comments

Comments
 (0)