Skip to content

Commit ced4f52

Browse files
committed
v3
this update fixes approximately 60 bugs in collaboration, adds real time costume editting, the viewer role and fixes a bunch of misc bugs also
1 parent 4cbb942 commit ced4f52

43 files changed

Lines changed: 3667 additions & 909 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/addons/addons/collaboration/helpers/blocklyApplier.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ function reconcileState(Blockly, workspace, target, blockId) {
193193

194194
export function reconcileBlocks(target, blockIds) {
195195
const Blockly = constants.mutableRefs.BlocklyInstance;
196-
const workspace = Blockly?.getMainWorkspace?.();
196+
const workspace = constants.editorWorkspace();
197197
if (!workspace || !target || typeof target.blocks.blockToXML !== 'function') return false;
198198
Blockly.Events.disable();
199199
try {

src/addons/addons/collaboration/helpers/collaboration-ui.js

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as constants from './constants.js';
2+
import * as presence from './presence.js';
23
import * as helper from './helper.js';
34

45
export const CURSOR_UPDATE_THROTTLE_MS = 50;
@@ -515,9 +516,11 @@ export function ensureCollaborationLayerOnTop() {
515516
}
516517

517518
export function setupCollaborationLayer() {
518-
const workspace = constants.mutableRefs.BlocklyInstance.getMainWorkspace();
519+
const workspace = constants.editorWorkspace();
519520
if (!workspace) return;
520521

522+
if (constants.mutableRefs.isViewer) applyViewerWorkspace(workspace);
523+
521524
const newWorkspaceSvg = workspace.getParentSvg();
522525
const workspaceGroup = newWorkspaceSvg?.querySelector('.blocklyBlockCanvas');
523526

@@ -593,7 +596,7 @@ export function setupCollaborationLayer() {
593596

594597
if (!constants.mutableRefs.throttledMouseMoveHandler) {
595598
constants.mutableRefs.throttledMouseMoveHandler = helper.throttle((e) => {
596-
const currentWorkspace = constants.mutableRefs.BlocklyInstance.getMainWorkspace();
599+
const currentWorkspace = constants.editorWorkspace();
597600
if (!currentWorkspace || !currentWorkspace.getParentSvg() || !currentWorkspace.getCanvas() || !constants.mutableRefs.yjsAwarenessInstance) return;
598601
if (e.target?.closest('.blocklyFlyout') || currentWorkspace.isFlyout) return;
599602

@@ -659,7 +662,7 @@ export function setupCollaborationLayer() {
659662
}
660663

661664
if (constants.mutableRefs.yjsAwarenessInstance && constants.mutableRefs.collaborationLayerGroup) {
662-
const states = constants.mutableRefs.yjsAwarenessInstance.getStates();
665+
const states = presence.collabStates();
663666
const localClientID = constants.mutableRefs.yjsAwarenessInstance.clientID;
664667
states.forEach((state, clientID) => {
665668
if (clientID === localClientID) return;
@@ -672,14 +675,6 @@ export function setupCollaborationLayer() {
672675
}
673676
}
674677

675-
let backpackInsertOverride = false;
676-
window.handleBackpackCollaboratorOverride = function () {
677-
backpackInsertOverride = true;
678-
setTimeout(() => {
679-
backpackInsertOverride = false;
680-
}, 1000);
681-
};
682-
683678
let undoRedoOverride = false;
684679
export function setUndoRedoOverride() {
685680
undoRedoOverride = true;
@@ -691,7 +686,7 @@ export function setUndoRedoOverride() {
691686
export function updateUserMenuBarIcons() {
692687
if (!constants.mutableRefs.yjsAwarenessInstance || !constants.mutableRefs.userIconContainer) return;
693688

694-
const states = constants.mutableRefs.yjsAwarenessInstance.getStates();
689+
const states = presence.collabStates();
695690
const localClientID = constants.mutableRefs.yjsAwarenessInstance.clientID;
696691
const currentlyDisplayed = new Set(constants.remoteUserIcons.keys());
697692
const activeRemoteClientIDs = new Set();
@@ -753,7 +748,7 @@ export function updateUserMenuBarIcons() {
753748
export function updateSpriteUserIcons() {
754749
if (!constants.mutableRefs.yjsAwarenessInstance || !constants.mutableRefs.vm) return;
755750

756-
const states = constants.mutableRefs.yjsAwarenessInstance.getStates();
751+
const states = presence.collabStates();
757752
const localClientID = constants.mutableRefs.yjsAwarenessInstance.clientID;
758753
const spriteListElement = document.querySelector('[class*="sprite-selector_items-wrapper"]');
759754
const stageElement = document.querySelectorAll('[class*="stage-selector_stage-selector"]')[0];
@@ -885,7 +880,7 @@ export function updateSpriteUserIcons() {
885880
export function updateTabUserIcons() {
886881
if (!constants.mutableRefs.yjsAwarenessInstance || !constants.mutableRefs.vm) return;
887882

888-
const states = constants.mutableRefs.yjsAwarenessInstance.getStates();
883+
const states = presence.collabStates();
889884
const localClientID = constants.mutableRefs.yjsAwarenessInstance.clientID;
890885
const tabElements = document.querySelectorAll(constants.TAB_SELECTOR);
891886
const localUserCurrentTargetId = constants.localUserInfo.currentTargetId;
@@ -1194,3 +1189,66 @@ export function setupCSS() {
11941189
export function getActiveRemoteClientIDs() {
11951190
return new Set(cursorElements.keys());
11961191
}
1192+
1193+
/*
1194+
* Telling somebody they are watching.
1195+
*/
1196+
const VIEWER_BANNER_ID = 'collaboration-viewer-banner';
1197+
1198+
export function showViewerBanner() {
1199+
if (document.getElementById(VIEWER_BANNER_ID)) return;
1200+
const banner = document.createElement('div');
1201+
banner.id = VIEWER_BANNER_ID;
1202+
banner.textContent = 'You are watching this project. Ask the owner for edit access.';
1203+
banner.style.cssText = [
1204+
'position:fixed', 'left:50%', 'transform:translateX(-50%)', 'bottom:16px',
1205+
'z-index:9999', 'pointer-events:none',
1206+
'background:rgba(35,39,50,0.94)', 'color:#ffffff',
1207+
'font-family:"Helvetica Neue", Helvetica, Arial, sans-serif', 'font-size:13px',
1208+
'padding:8px 16px', 'border-radius:16px', 'box-shadow:0 2px 8px rgba(0,0,0,.35)'
1209+
].join(';');
1210+
document.body.appendChild(banner);
1211+
}
1212+
1213+
export function hideViewerBanner() {
1214+
document.getElementById(VIEWER_BANNER_ID)?.remove();
1215+
}
1216+
1217+
const VIEWER_STYLE_ID = 'collaboration-viewer-styles';
1218+
1219+
export function applyViewerWorkspace(workspace) {
1220+
if (!document.getElementById(VIEWER_STYLE_ID)) {
1221+
const style = document.createElement('style');
1222+
style.id = VIEWER_STYLE_ID;
1223+
1224+
style.textContent = `
1225+
.blocklyBlockCanvas, .blocklyFlyout { pointer-events: none; }
1226+
1227+
[class*="sprite-selector_add-button"],
1228+
[class*="stage-selector_add-button"],
1229+
[class*="selector_new-buttons"],
1230+
[class*="sprite-selector-item_delete-button"] { display: none !important; }
1231+
1232+
[class*="sprite-info_sprite-info"] { pointer-events: none; opacity: .6; }
1233+
1234+
[class*="paint-editor_canvas-container"],
1235+
[class*="paint-editor_mode-selector"],
1236+
[class*="paint-editor_editor-container-top"] { pointer-events: none; }
1237+
1238+
[class*="sound-editor_effects"],
1239+
[class*="sound-editor_button-group"],
1240+
[class*="sound-editor_name-input"],
1241+
[class*="sound-editor_input-group"] { pointer-events: none; opacity: .6; }
1242+
`;
1243+
document.head.appendChild(style);
1244+
}
1245+
if (!workspace || !workspace.options) return;
1246+
workspace.options.readOnly = true;
1247+
const flyout = workspace.getFlyout && workspace.getFlyout();
1248+
const flyoutWorkspace = flyout && flyout.getWorkspace && flyout.getWorkspace();
1249+
if (flyoutWorkspace && flyoutWorkspace.options) flyoutWorkspace.options.readOnly = true;
1250+
}
1251+
1252+
export function clearViewerWorkspace() {
1253+
document.getElementById(VIEWER_STYLE_ID)?.remove();
1254+
}

src/addons/addons/collaboration/helpers/constants.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,35 @@ export const debugging = true;
22

33
export const apiHostURL = process.env.API_HOST;
44
export const devMode = process.env.COLLABORATION_DEV_MODE === 'true';
5+
export const recordMode = process.env.COLLABORATION_RECORD_MODE === 'true';
56
export const WEBSOCKETBASEURL = process.env.COLLABORATION_HOST;
67

78
export const INACTIVITY_THRESHOLD_X_MS = 30 * 1000;
89

9-
export const INACTIVITY_THRESHOLD_Y_MS = 5 * 60 * 1000;
10+
export const INACTIVITY_THRESHOLD_Y_MS = 60 * 1000;
11+
12+
export const CLOSE_STALE_GENERATION = 4001;
1013

1114
export const LOCAL_EVENT_SYNC_ORIGIN = Symbol('local-event-sync');
1215

16+
let remoteApplyDepth = 0;
17+
18+
export function beginRemoteApply() {
19+
remoteApplyDepth++;
20+
}
21+
22+
export function endRemoteApply() {
23+
if (remoteApplyDepth > 0) remoteApplyDepth--;
24+
}
25+
26+
export function isApplyingRemote() {
27+
return remoteApplyDepth > 0;
28+
}
29+
30+
export function resetRemoteApply() {
31+
remoteApplyDepth = 0;
32+
}
33+
1334
export const COLLABORATION_USER_ICON_CONTAINER_ID = 'collaboration-users-container';
1435
export const SPRITE_ICON_CONTAINER_CLASS = 'collaboration-sprite-icon-container';
1536
export const SPRITE_USER_ICON_CLASS = 'collaboration-sprite-user-icon';
@@ -21,6 +42,8 @@ export const TAB_USER_ICON_CLASS = 'collaboration-tab-user-icon';
2142

2243
export const mutableRefs = {
2344

45+
isViewer: false,
46+
2447
ydoc: null,
2548

2649
provider: null,
@@ -37,10 +60,19 @@ export const mutableRefs = {
3760

3861
sharedCostumes: null,
3962

63+
sharedCostumeData: null,
64+
sharedCostumeArt: null,
65+
66+
redrawCostumeArt: null,
67+
4068
sharedSounds: null,
4169

70+
sharedSoundData: null,
71+
4272
sharedSprites: null,
4373

74+
sharedSpriteData: null,
75+
4476
sharedExtensions: null,
4577

4678
addon: null,
@@ -56,6 +88,7 @@ export const mutableRefs = {
5688
blocklyCanvasObserver: null,
5789
localChatElementsRef: null,
5890
currentWorkspaceSvg: null,
91+
awarenessFrameCancel: null,
5992
throttledMouseMoveHandler: null,
6093
pointerLeaveHandler: null,
6194
inactivityTimerX: null,
@@ -71,6 +104,8 @@ export const mutableRefs = {
71104
costumeIndexMaps: new Map(),
72105

73106
isUiTransition: false,
107+
monitorsPublishDeferred: false,
108+
publishMonitorsNow: null,
74109

75110
roomUUID: null
76111
};
@@ -87,3 +122,9 @@ export const remoteDraggingBlocks = new Map();
87122
export const remoteUserIcons = new Map();
88123
export const spriteIconContainers = new Map();
89124
export const tabIconContainers = new Map();
125+
126+
export function editorWorkspace() {
127+
const traps = mutableRefs.addon && mutableRefs.addon.tab && mutableRefs.addon.tab.traps;
128+
if (!traps || typeof traps.getWorkspace !== 'function') return null;
129+
return traps.getWorkspace() || null;
130+
}

0 commit comments

Comments
 (0)