react-together
Version:
A library to seamlessly add real-time multi-user interaction to your React app!
344 lines (343 loc) • 9.47 kB
JavaScript
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
};