UNPKG

react-together

Version:

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

344 lines (343 loc) 9.47 kB
import { useRef as C, useCallback as E, useState as k, useEffect as L, useMemo as j } from "react"; import _ from "./hooks/useMyId.js"; import B from "./hooks/useReactTogetherContext.js"; import { useCroquetContext as N, useJoinedViews as D, useModelRoot as W, useSessionParams as x, useIsJoined as q } from "@croquet/react"; import z 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 G } from "./utils/urls.js"; import "./hooks/useLeaveSession.js"; function R(e, t) { const s = C(0), r = C(null), u = I(); return E( (...n) => { if (!u) { 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, u] ); } const T = Object.freeze({}); function $(e) { return Object.fromEntries(e.entries()); } function Y(e, t) { const s = $(e), r = z(s); return t && delete s[t], { allValues: s, allValuesHash: r }; } function F({ prevLocalValue: e, initialValue: t, sessionValue: s, resetOnConnect: r, overwriteSessionValue: u }) { return r ? t : e === void 0 ? s ?? t : s === void 0 ? e ?? t : u ? e : s; } function X(e, t, s = {}) { const { resetOnDisconnect: r = !1, resetOnConnect: u = !1, keepValues: n = !1, overwriteSessionValue: o = !1, omitMyValue: l = !1, throttleDelay: f = 100 } = s, [c] = k(t), { session: h, view: a, model: i } = N(), d = _(), [m, V] = k(() => { if (!a || !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 }; }); L(() => { if (a && i && d) { const g = i.statePerUserConfig.get(e); (!g || g.keepValues !== n) && a.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 } }); } }, [a, i, e, r, u, n, d]), L(() => { if (!h || !a || !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)), S = w.get(d), b = F({ prevLocalValue: v.localValue, initialValue: c, sessionValue: S, resetOnConnect: u, overwriteSessionValue: o }); b !== S && (a.publish(i.id, "setStatePerUser", { rtKey: e, userId: d, value: b }), w.set(d, b)); const { allValues: O, allValuesHash: J } = Y( w, l ? d : void 0 ); return { localValue: b, allValues: O, allValuesHash: J }; }); })(); const p = () => { V((v) => { const w = i.statePerUser.get(e) ?? /* @__PURE__ */ new Map(), { allValues: S, allValuesHash: b } = Y(w, l ? d : void 0); return v.allValuesHash === b ? v : { localValue: w.get(d) ?? c, allValues: S, allValuesHash: b }; }); }; return a.subscribe( e, { event: "updated", handling: "oncePerFrame" }, p ), () => a.unsubscribe(e, "updated", p); }, [ e, h, a, i, c, d, r, u, n, o, l ]); const U = R( f, E( (g) => { if (!a || !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); a.publish(i.id, "setStatePerUser", { rtKey: e, userId: d, value: w }); } }, [e, a, i, c, d] ) ), { localValue: M, allValues: P } = m; return [M, U, P]; } const A = "__rt-nickname"; function Q() { const { deriveNickname: e, rememberUsers: t } = B(), s = _() ?? Math.random().toString(36).substring(2, 15), r = localStorage.getItem(A), u = t && r !== null ? r : e(s), [n, o, l] = X("__nicknames", u, { // Storing all nicknames in the session so that // they are available even after users leave keepValues: !0, overwriteSessionValue: !0 }), f = E( (c) => { t && localStorage.setItem(A, c), o(c); }, [o, t] ); return [n, f, l]; } function Z() { const [, , e] = Q(); return e; } const y = []; function fe() { D(); const e = Z(), t = W(), s = _(); return t ? Array.from(t.userIdCount.keys()).map((r) => { const u = e[r]; return { userId: r, isYou: r === s, nickname: u, get name() { return console.warn( "useConnectedUsers: name is deprecated. Use nickname instead." ), u; } }; }) : y; } function he(e = {}) { const { throttleDelay: t = 50, deleteOnLeave: s = !1, omitMyValue: r = !0 } = e, [u, n, o] = X("__cursors", null, { omitMyValue: r, throttleDelay: t }), l = C(null); return L(() => { 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); }, a = (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, U = l.current.clientX, M = l.current.clientY, P = m / document.body.scrollWidth, g = V / document.body.scrollHeight; f({ pageX: m, pageY: V, clientX: U, clientY: M, percentX: P, percentY: g }); } }; return document.addEventListener("mousemove", c), document.addEventListener("mouseleave", h), document.addEventListener("touchstart", a), document.addEventListener("touchmove", i), document.addEventListener("scroll", d), () => { document.removeEventListener("mousemove", c), document.removeEventListener("mouseleave", h), document.removeEventListener("touchstart", a), document.removeEventListener("touchmove", i), document.removeEventListener("scroll", d); }; }, [n, s, t]), { myCursor: u, allCursors: o }; } function ve(e, t = {}) { const s = C(null), [r, u, n] = X(e, !1); L(() => { const l = s.current; if (!l) return; const f = (h) => { const a = h.rtProcessedBy; a === void 0 ? (u(!0), h.rtProcessedBy = e) : a !== e && u(!1); }, c = () => u(!1); return l.addEventListener("mouseover", f), l.addEventListener("mouseleave", c), () => { l.removeEventListener("mouseover", f), l.removeEventListener("mouseleave", c); }; }, [u, e]); const o = Object.entries(n).filter(([, l]) => l).map(([l]) => l); return [s, o, r]; } function Ve() { const { name: e, password: t } = x(), s = I(); return j(() => !s || !e || !t ? null : G(new URL(window.location.href), e, t).toString(), [e, t, s]); } function pe(e, t, { resetOnDisconnect: s = !1, throttleDelay: r = 100 } = {}) { const { session: u, view: n, model: o } = N(), [l, f] = k(() => !n || !o ? t : o.state.has(e) ? o.state.get(e) : (n.publish(o.id, "setState", { rtKey: e, value: t }), t)); L(() => { if (!u || !n || !o) { s && f(t); return; } const h = () => { f((a) => { if (!o.state.has(e)) return n.publish(o.id, "setState", { rtKey: e, value: a }), a; const i = o.state.get(e); return a === i ? a : i; }); }; return n.subscribe( e, { event: "updated", handling: "oncePerFrame" }, h ), h(), () => n.unsubscribe(e, "updated", h); }, [u, n, o, e, f, t, s]); const c = R( r, E( (h) => { if (o && n) { const a = o.state.get(e); n.publish(o.id, "setState", { rtKey: e, value: H(a, h) }); } else f(h); }, [f, o, n, e] ) ); return [l, c]; } const I = q; export { fe as a, he as b, ve as c, I as d, Ve as e, Q as f, pe as g, X as h, R as i, Z as u };