UNPKG

react-together

Version:

A library to seamlessly add real-time multi-user interaction to your React app!

355 lines (354 loc) 9.83 kB
import { useRef as E, useCallback as U, useState as M, useEffect as S, useMemo as z } from "react"; import _ from "./hooks/useMyId.js"; import B from "./hooks/useReactTogetherContext.js"; import { useMultisynqContext as I, useJoinedViews as D, useModelRoot as W, useView as x, useSessionParams as y, useIsJoined as q } from "@multisynq/react"; import G from "object-hash"; import H from "./hooks/getNewValue.js"; import "./hooks/useChat.js"; import "./hooks/useCreateRandomSession.js"; import "./models/ReactTogetherModel.js"; import "./context/ReactTogetherContext.js"; import "unique-names-generator"; import "./hooks/useFunctionTogether.js"; import { getJoinUrl as $ } from "./utils/urls.js"; import "./hooks/useLeaveSession.js"; function A(e, t) { const s = E(0), r = E(null), a = R(); return U( (...n) => { if (!a) { t(...n); return; } const o = Date.now(); if (o - s.current >= e) s.current = o, t(...n); else { const l = e - (o - s.current); r.current && clearTimeout(r.current), r.current = setTimeout(() => { t(...n), s.current = Date.now(); }, l); } }, [t, e, a] ); } const T = Object.freeze({}); function F(e) { return Object.fromEntries(e.entries()); } function Y(e, t) { const s = F(e), r = G(s); return t && delete s[t], { allValues: s, allValuesHash: r }; } function Q({ prevLocalValue: e, initialValue: t, sessionValue: s, resetOnConnect: r, overwriteSessionValue: a }) { return r ? t : e === void 0 ? s ?? t : s === void 0 ? e ?? t : a ? e : s; } function N(e, t, s = {}) { const { resetOnDisconnect: r = !1, resetOnConnect: a = !1, keepValues: n = !1, overwriteSessionValue: o = !1, omitMyValue: l = !1, throttleDelay: f = 100 } = s, [c] = M(t), { session: h, view: u, model: i } = I(), d = _(), [m, V] = M(() => { if (!u || !i || d === null) return { localValue: c, allValues: T, allValuesHash: null }; const g = i.statePerUser.get(e) ?? /* @__PURE__ */ new Map([[d, c]]), { allValues: p, allValuesHash: v } = Y( g, l ? d : void 0 ); return { localValue: g.get(d) ?? c, allValues: p, allValuesHash: v }; }); S(() => { if (u && i && d) { const g = i.statePerUserConfig.get(e); (!g || g.keepValues !== n) && u.publish(i.id, "configureStatePerUser", { rtKey: e, options: { // intentionally not passing other options to save bandwidth. // These values do not need to be synchronized // across users, and are only used locally to determine the behavior of the setter keepValues: n } }); } }, [u, i, e, r, a, n, d]), S(() => { if (!h || !u || !i || d === null) { V((v) => ({ localValue: r ? c : v.localValue, allValues: T, allValuesHash: null })); return; } (() => { V((v) => { const w = new Map(i.statePerUser.get(e)), L = w.get(d), b = Q({ prevLocalValue: v.localValue, initialValue: c, sessionValue: L, resetOnConnect: a, overwriteSessionValue: o }); b !== L && (u.publish(i.id, "setStatePerUser", { rtKey: e, userId: d, value: b }), w.set(d, b)); const { allValues: j, allValuesHash: J } = Y( w, l ? d : void 0 ); return { localValue: b, allValues: j, allValuesHash: J }; }); })(); const p = () => { V((v) => { const w = i.statePerUser.get(e) ?? /* @__PURE__ */ new Map(), { allValues: L, allValuesHash: b } = Y(w, l ? d : void 0); return v.allValuesHash === b ? v : { localValue: w.get(d) ?? c, allValues: L, allValuesHash: b }; }); }; return u.subscribe( e, { event: "updated", handling: "oncePerFrame" }, p ), () => u.unsubscribe(e, "updated", p); }, [ e, h, u, i, c, d, r, a, n, o, l ]); const C = A( f, U( (g) => { if (!u || !i || d === null) V((p) => { const v = H(p.localValue, g); return p.localValue === v ? p : { localValue: v, allValues: T, allValuesHash: null }; }); else { const p = i.statePerUser.get(e); let v = p == null ? void 0 : p.get(d); v === void 0 && (console.warn( `[useStateTogetherWithPerUserValues:setter] prevLocalValue is undefined.Using initialValue: ${c}` ), v = c); const w = H(v, g); u.publish(i.id, "setStatePerUser", { rtKey: e, userId: d, value: w }); } }, [e, u, i, c, d] ) ), { localValue: k, allValues: P } = m; return [k, C, P]; } const X = "__rt-nickname"; function O() { const { deriveNickname: e, rememberUsers: t } = B(), s = _() ?? Math.random().toString(36).substring(2, 15), r = localStorage.getItem(X), a = t && r !== null ? r : e(s), [n, o, l] = N("__nicknames", a, { // Storing all nicknames in the session so that // they are available even after users leave keepValues: !0, overwriteSessionValue: !0 }), f = U( (c) => { t && localStorage.setItem(X, c), o(c); }, [o, t] ); return [n, f, l]; } function Z() { const [, , e] = O(); return e; } const K = []; function he() { D(); const e = Z(), t = W(), s = _(); return t ? Array.from(t.userIdCount.keys()).map((r) => { const a = e[r]; return { userId: r, isYou: r === s, nickname: a, get name() { return console.warn( "useConnectedUsers: name is deprecated. Use nickname instead." ), a; } }; }) : K; } function ve(e = {}) { const { throttleDelay: t = 50, deleteOnLeave: s = !1, omitMyValue: r = !0 } = e, [a, n, o] = N("__cursors", null, { omitMyValue: r, throttleDelay: t }), l = E(null); return S(() => { const f = (m) => { l.current = m, n(m); }, c = (m) => { f({ pageX: m.pageX, pageY: m.pageY, clientX: m.clientX, clientY: m.clientY, percentX: m.pageX / document.body.scrollWidth, percentY: m.pageY / document.body.scrollHeight }); }, h = () => { s && n(null); }, u = (m) => { const V = m.touches[0]; V && c(V); }, i = (m) => { const V = m.touches[0]; V && c(V); }, d = () => { if (l.current) { const m = l.current.clientX + window.scrollX, V = l.current.clientY + window.scrollY, C = l.current.clientX, k = l.current.clientY, P = m / document.body.scrollWidth, g = V / document.body.scrollHeight; f({ pageX: m, pageY: V, clientX: C, clientY: k, percentX: P, percentY: g }); } }; return document.addEventListener("mousemove", c), document.addEventListener("mouseleave", h), document.addEventListener("touchstart", u), document.addEventListener("touchmove", i), document.addEventListener("scroll", d), () => { document.removeEventListener("mousemove", c), document.removeEventListener("mouseleave", h), document.removeEventListener("touchstart", u), document.removeEventListener("touchmove", i), document.removeEventListener("scroll", d); }; }, [n, s, t]), { myCursor: a, allCursors: o }; } function Ve(e, t = {}) { const s = E(null), [r, a, n] = N(e, !1); S(() => { const l = s.current; if (!l) return; const f = (h) => { const u = h.rtProcessedBy; u === void 0 ? (a(!0), h.rtProcessedBy = e) : u !== e && a(!1); }, c = () => a(!1); return l.addEventListener("mouseover", f), l.addEventListener("mouseleave", c), () => { l.removeEventListener("mouseover", f), l.removeEventListener("mouseleave", c); }; }, [a, e]); const o = Object.entries(n).filter(([, l]) => l).map(([l]) => l); return [s, o, r]; } function pe() { const e = x() !== null, { view: t, model: s } = I(), [r, a] = M(!1), [, , n] = O(), o = Object.keys(n).length > 0; return S(() => { if (!t || !s) { a(!1); return; } return t.subscribe(t.viewId, "synced", a), () => t.unsubscribe(t.viewId, "synced", a); }, [t, s]), o && e && r; } function ge() { const { name: e, password: t } = y(), s = R(); return z(() => !s || !e || !t ? null : $(new URL(window.location.href), e, t).toString(), [e, t, s]); } function we(e, t, { resetOnDisconnect: s = !1, throttleDelay: r = 100 } = {}) { const { session: a, view: n, model: o } = I(), [l, f] = M(() => !n || !o ? t : o.state.has(e) ? o.state.get(e) : (n.publish(o.id, "setState", { rtKey: e, value: t }), t)); S(() => { if (!a || !n || !o) { s && f(t); return; } const h = () => { f((u) => { if (!o.state.has(e)) return n.publish(o.id, "setState", { rtKey: e, value: u }), u; const i = o.state.get(e); return u === i ? u : i; }); }; return n.subscribe( e, { event: "updated", handling: "oncePerFrame" }, h ), h(), () => n.unsubscribe(e, "updated", h); }, [a, n, o, e, f, t, s]); const c = A( r, U( (h) => { if (o && n) { const u = o.state.get(e); n.publish(o.id, "setState", { rtKey: e, value: H(u, h) }); } else f(h); }, [f, o, n, e] ) ); return [l, c]; } const R = q; export { he as a, ve as b, Ve as c, pe as d, R as e, ge as f, O as g, we as h, N as i, A as j, Z as u };