Skip to content

Commit

Permalink
[#66149] extract hiding usernames to separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
Trzcin committed Oct 10, 2024
1 parent 72feb67 commit 4d14033
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 54 deletions.
65 changes: 65 additions & 0 deletions src/extensions/hideUsernames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";

/**
* @param {Object} param0
* @param {Y.Text} param0.ytext
* @param {WebsocketProvider} param0.prov
* @param {number} param0.hideDelay
* @returns
*/
export function hideUsernames({ ytext, prov, parent, hideDelay = 5000 }) {
const changeMap = new Map();
const loadDate = Date.now();

ytext.observe((_, tr) => {
if (!tr.local) return;
prov.awareness.setLocalStateField("lastChanged", Date.now());
});
prov.awareness.on("change", () => {
prov.awareness.getStates().forEach((state, id) => {
const localState = id === prov.awareness.clientID;
const newChange = state.lastChanged && changeMap.get(id)?.lastChanged !== state.lastChanged && state.lastChanged >= loadDate;
if (localState || !newChange) return;

const oldTimer = changeMap.get(id)?.timer;
clearTimeout(oldTimer);

const showUsername = () => {
parent.querySelectorAll(".cm-ySelectionInfo").forEach((/** @type {HTMLElement} */ n) => {
if (n.innerText !== state.user.name) return;
n.classList.add("active");
});
};

const hideUsername = () => {
parent.querySelectorAll(".cm-ySelectionInfo").forEach((/** @type {HTMLElement} */ n) => {
if (n.innerText !== state.user.name) return;
n.classList.remove("active");
});
};

if (!oldTimer) {
showUsername();
// y-codemirror.next does not expose anything to modify decorations so we need this hack
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
const editorTarget = mutation.target.className.includes("cm-line");
const caretChanged = Boolean([...mutation.removedNodes].find((n) => n.classList?.contains?.("cm-ySelectionCaret")));
if (editorTarget && caretChanged) {
showUsername();
}
}
});
observer.observe(parent, { childList: true, subtree: true });
changeMap.set(id, { ...changeMap.get(id), observer });
}
const timer = setTimeout(() => {
hideUsername();
changeMap.get(id).observer.disconnect();
changeMap.set(id, { ...changeMap.get(id), timer: null });
}, hideDelay);
changeMap.set(id, { ...changeMap.get(id), timer, lastChanged: state.lastChanged });
});
});
}
57 changes: 3 additions & 54 deletions src/hooks/useCollaboration.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as Y from "yjs";
import * as awarenessProtocol from "y-protocols/awareness.js";
import { WebsocketProvider } from "y-websocket";
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import { useEffect, useMemo, useState } from "preact/hooks";
import { hideUsernames } from "../extensions/hideUsernames";

WebsocketProvider.prototype.watchCollabolators = function (hook) {
this.awareness.on("change", ({ added, removed }) => {
Expand All @@ -17,8 +18,6 @@ WebsocketProvider.prototype.watchCollabolators = function (hook) {
});
};

const loadDate = Date.now();

export default function useCollaboration(settings, parent) {
if (!settings.enabled) {
return {};
Expand All @@ -29,7 +28,6 @@ export default function useCollaboration(settings, parent) {
const [connected, setConnected] = useState(false);
const [error, setError] = useState(false);
const ytext = useMemo(() => ydoc.getText("codemirror"), []);
const changeMap = useRef(new Map());

// Y.js does not throw errors, it only logs them. We want to raise a
// fatal error when there are any errors in the collaborative state
Expand Down Expand Up @@ -61,56 +59,7 @@ export default function useCollaboration(settings, parent) {
prov.on("sync", setSynced);
prov.on("status", ({ status }) => setConnected(status == "connected"));

ytext.observe((_, tr) => {
if (!tr.local) return;
prov.awareness.setLocalStateField("lastChanged", Date.now());
});
prov.awareness.on("change", () => {
prov.awareness.getStates().forEach((state, id) => {
const localState = id === prov.awareness.clientID;
const newChange = state.lastChanged && changeMap.current.get(id)?.lastChanged !== state.lastChanged && state.lastChanged >= loadDate;
if (localState || !newChange) return;

const oldTimer = changeMap.current.get(id)?.timer;
clearTimeout(oldTimer);

const showUsername = () => {
parent.querySelectorAll(".cm-ySelectionInfo").forEach((/** @type {HTMLElement} */ n) => {
if (n.innerText !== state.user.name) return;
n.classList.add("active");
});
};

const hideUsername = () => {
parent.querySelectorAll(".cm-ySelectionInfo").forEach((/** @type {HTMLElement} */ n) => {
if (n.innerText !== state.user.name) return;
n.classList.remove("active");
});
};

if (!oldTimer) {
showUsername();
// y-codemirror.next does not expose anything to modify decorations so we need this hack
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
const editorTarget = mutation.target.className.includes("cm-line");
const caretChanged = Boolean([...mutation.removedNodes].find((n) => n.classList?.contains?.("cm-ySelectionCaret")));
if (editorTarget && caretChanged) {
showUsername();
}
}
});
observer.observe(parent, { childList: true, subtree: true });
changeMap.current.set(id, { ...changeMap.current.get(id), observer });
}
const timer = setTimeout(() => {
hideUsername();
changeMap.current.get(id).observer.disconnect();
changeMap.current.set(id, { ...changeMap.current.get(id), timer: null });
}, settings.hideUsernameDelay ?? 5000);
changeMap.current.set(id, { ...changeMap.current.get(id), timer, lastChanged: state.lastChanged });
});
});
hideUsernames({ ytext, prov, parent, hideDelay: settings.hideUsernameDelay });

return prov;
}, []);
Expand Down

0 comments on commit 4d14033

Please sign in to comment.