fix(tldraw): only send diffs updates to server
When a shape is changed, the full shape objcect was being transmitted to the server again. Do a diff to only send what changed (similarly as it was in tldraw v1) to save upload bw. TODO: Draw segments diffs (array) is still not working, so all the segments are still being sent every time.
This commit is contained in:
parent
eef565ec81
commit
f7468b6fc2
@ -50,6 +50,7 @@ class WhiteboardModel extends SystemConfiguration {
|
|||||||
val wb = getWhiteboard(wbId)
|
val wb = getWhiteboard(wbId)
|
||||||
|
|
||||||
var annotationsAdded = Array[AnnotationVO]()
|
var annotationsAdded = Array[AnnotationVO]()
|
||||||
|
var annotationsDiffAdded = Array[AnnotationVO]()
|
||||||
var newAnnotationsMap = wb.annotationsMap
|
var newAnnotationsMap = wb.annotationsMap
|
||||||
|
|
||||||
for (annotation <- annotations) {
|
for (annotation <- annotations) {
|
||||||
@ -57,21 +58,10 @@ class WhiteboardModel extends SystemConfiguration {
|
|||||||
if (oldAnnotation.isDefined) {
|
if (oldAnnotation.isDefined) {
|
||||||
val hasPermission = isPresenter || isModerator || oldAnnotation.get.userId == userId
|
val hasPermission = isPresenter || isModerator || oldAnnotation.get.userId == userId
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
// Determine if the annotation is a line shape
|
val mergedAnnotationInfo = deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo)
|
||||||
val isLineShape = annotation.annotationInfo.get("type").contains("line")
|
|
||||||
|
|
||||||
// Merge old and new annotation properties with special handling for line shape
|
|
||||||
val mergedAnnotationInfo = if (isLineShape) {
|
|
||||||
val newProps = annotation.annotationInfo.get("props").asInstanceOf[Option[Map[String, Any]]].getOrElse(Map.empty)
|
|
||||||
val oldProps = oldAnnotation.get.annotationInfo.get("props").asInstanceOf[Option[Map[String, Any]]].getOrElse(Map.empty)
|
|
||||||
val updatedProps = overwriteLineShapeHandles(oldProps, newProps)
|
|
||||||
annotation.annotationInfo + ("props" -> updatedProps)
|
|
||||||
} else {
|
|
||||||
deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply cleaning if it's an arrow annotation
|
// Apply cleaning if it's an arrow annotation
|
||||||
val finalAnnotationInfo = if (annotation.annotationInfo.get("type").contains("arrow")) {
|
val finalAnnotationInfo = if (oldAnnotation.get.annotationInfo.get("type").contains("arrow")) {
|
||||||
cleanArrowAnnotationProps(mergedAnnotationInfo)
|
cleanArrowAnnotationProps(mergedAnnotationInfo)
|
||||||
} else {
|
} else {
|
||||||
mergedAnnotationInfo
|
mergedAnnotationInfo
|
||||||
@ -80,6 +70,7 @@ class WhiteboardModel extends SystemConfiguration {
|
|||||||
val newAnnotation = oldAnnotation.get.copy(annotationInfo = finalAnnotationInfo)
|
val newAnnotation = oldAnnotation.get.copy(annotationInfo = finalAnnotationInfo)
|
||||||
newAnnotationsMap += (annotation.id -> newAnnotation)
|
newAnnotationsMap += (annotation.id -> newAnnotation)
|
||||||
annotationsAdded :+= newAnnotation
|
annotationsAdded :+= newAnnotation
|
||||||
|
annotationsDiffAdded :+= annotation
|
||||||
println(s"Updated annotation on page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
println(s"Updated annotation on page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||||
} else {
|
} else {
|
||||||
println(s"User $userId doesn't have permission to edit annotation ${annotation.id}, ignoring...")
|
println(s"User $userId doesn't have permission to edit annotation ${annotation.id}, ignoring...")
|
||||||
@ -87,6 +78,7 @@ class WhiteboardModel extends SystemConfiguration {
|
|||||||
} else if (annotation.annotationInfo.contains("type")) {
|
} else if (annotation.annotationInfo.contains("type")) {
|
||||||
newAnnotationsMap += (annotation.id -> annotation)
|
newAnnotationsMap += (annotation.id -> annotation)
|
||||||
annotationsAdded :+= annotation
|
annotationsAdded :+= annotation
|
||||||
|
annotationsDiffAdded :+= annotation
|
||||||
println(s"Adding annotation to page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
println(s"Adding annotation to page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||||
} else {
|
} else {
|
||||||
println(s"New annotation [${annotation.id}] with no type, ignoring...")
|
println(s"New annotation [${annotation.id}] with no type, ignoring...")
|
||||||
@ -97,7 +89,7 @@ class WhiteboardModel extends SystemConfiguration {
|
|||||||
|
|
||||||
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
|
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
|
||||||
saveWhiteboard(newWb)
|
saveWhiteboard(newWb)
|
||||||
annotationsAdded
|
annotationsDiffAdded
|
||||||
}
|
}
|
||||||
|
|
||||||
private def overwriteLineShapeHandles(oldProps: Map[String, Any], newProps: Map[String, Any]): Map[String, Any] = {
|
private def overwriteLineShapeHandles(oldProps: Map[String, Any], newProps: Map[String, Any]): Map[String, Any] = {
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
mapLanguage,
|
mapLanguage,
|
||||||
isValidShapeType,
|
isValidShapeType,
|
||||||
usePrevious,
|
usePrevious,
|
||||||
|
getDifferences,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { useMouseEvents, useCursor } from './hooks';
|
import { useMouseEvents, useCursor } from './hooks';
|
||||||
import { notifyShapeNumberExceeded, getCustomEditorAssetUrls, getCustomAssetUrls } from './service';
|
import { notifyShapeNumberExceeded, getCustomEditorAssetUrls, getCustomAssetUrls } from './service';
|
||||||
@ -507,7 +508,15 @@ const Whiteboard = React.memo((props) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
shapeBatchRef.current[updatedRecord.id] = updatedRecord;
|
const diff = getDifferences(prevShapesRef.current[record?.id], updatedRecord);
|
||||||
|
|
||||||
|
if (diff) {
|
||||||
|
diff.id = record.id;
|
||||||
|
|
||||||
|
shapeBatchRef.current[updatedRecord.id] = diff;
|
||||||
|
} else {
|
||||||
|
shapeBatchRef.current[updatedRecord.id] = updatedRecord;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle removed shapes immediately (not batched)
|
// Handle removed shapes immediately (not batched)
|
||||||
|
@ -30,11 +30,40 @@ const mapLanguage = (language) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDifferences = (prev, next) => {
|
||||||
|
// Check if the two values are the same
|
||||||
|
if (prev === next) return undefined;
|
||||||
|
|
||||||
|
// If both are arrays, find the differences
|
||||||
|
if (Array.isArray(prev) && Array.isArray(next)) {
|
||||||
|
const differences = next.filter((item, index) => prev[index] !== item);
|
||||||
|
return differences.length > 0 ? differences : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If both are objects, recursively check their properties
|
||||||
|
if (typeof prev === 'object' && typeof next === 'object' && prev !== null && next !== null) {
|
||||||
|
const differences = {};
|
||||||
|
|
||||||
|
Object.keys(next).forEach((key) => {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(next, key)) {
|
||||||
|
const diff = getDifferences(prev[key], next[key]);
|
||||||
|
if (diff !== undefined) {
|
||||||
|
differences[key] = diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Object.keys(differences).length > 0 ? differences : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other types, return the next value if different
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
const Utils = {
|
const Utils = {
|
||||||
usePrevious, mapLanguage, isValidShapeType,
|
usePrevious, mapLanguage, isValidShapeType, getDifferences,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Utils;
|
export default Utils;
|
||||||
export {
|
export {
|
||||||
usePrevious, mapLanguage, isValidShapeType,
|
usePrevious, mapLanguage, isValidShapeType, getDifferences,
|
||||||
};
|
};
|
||||||
|
@ -575,6 +575,17 @@ def determine_slide_number(slide, current_slide)
|
|||||||
slide
|
slide
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def clean_arrow_end_or_start_props(props)
|
||||||
|
if props['type'] == 'binding'
|
||||||
|
# Remove 'x' and 'y' for 'binding' type
|
||||||
|
props = props.except('x', 'y')
|
||||||
|
elsif props['type'] == 'point'
|
||||||
|
# Remove unwanted properties for 'point' type
|
||||||
|
props = props.except('boundShapeId', 'normalizedAnchor', 'isExact', 'isPrecise')
|
||||||
|
end
|
||||||
|
props
|
||||||
|
end
|
||||||
|
|
||||||
def events_parse_tldraw_shape(shapes, event, current_presentation, current_slide, timestamp)
|
def events_parse_tldraw_shape(shapes, event, current_presentation, current_slide, timestamp)
|
||||||
presentation = event.at_xpath('presentation')
|
presentation = event.at_xpath('presentation')
|
||||||
slide = event.at_xpath('pageNumber')
|
slide = event.at_xpath('pageNumber')
|
||||||
@ -615,6 +626,11 @@ def events_parse_tldraw_shape(shapes, event, current_presentation, current_slide
|
|||||||
prev_shape[:out] = timestamp
|
prev_shape[:out] = timestamp
|
||||||
shape[:shape_unique_id] = prev_shape[:shape_unique_id]
|
shape[:shape_unique_id] = prev_shape[:shape_unique_id]
|
||||||
shape[:shape_data] = prev_shape[:shape_data].deep_merge(shape[:shape_data])
|
shape[:shape_data] = prev_shape[:shape_data].deep_merge(shape[:shape_data])
|
||||||
|
# special handling to remove unwanted merged arrow props in tldraw v2
|
||||||
|
if shape[:shape_data]['type'] == 'arrow' && shape[:shape_data].key?('props')
|
||||||
|
shape[:shape_data]['props']['start'] = clean_arrow_end_or_start_props(shape[:shape_data]['props']['start'])
|
||||||
|
shape[:shape_data]['props']['end'] = clean_arrow_end_or_start_props(shape[:shape_data]['props']['end'])
|
||||||
|
end
|
||||||
else
|
else
|
||||||
shape[:shape_unique_id] = @svg_shape_unique_id
|
shape[:shape_unique_id] = @svg_shape_unique_id
|
||||||
@svg_shape_unique_id += 1
|
@svg_shape_unique_id += 1
|
||||||
|
Loading…
Reference in New Issue
Block a user