UNPKG

@empirica/core

Version:
1,255 lines (1,232 loc) 43.3 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/player/react/index.ts var react_exports = {}; __export(react_exports, { Consent: () => Consent, EmpiricaMenu: () => EmpiricaMenu, EmpiricaParticipant: () => EmpiricaParticipant, Finished: () => Finished, Globals: () => Globals, Loading: () => Loading, Logo: () => Logo, NoGames: () => NoGames, ParticipantContext: () => ParticipantContext, PlayerCreate: () => PlayerCreate, TajribaConnection: () => TajribaConnection, TajribaProvider: () => TajribaProvider, useConsent: () => useConsent, useGlobal: () => useGlobal, useParticipantContext: () => useParticipantContext, usePlayerID: () => usePlayerID, useTajriba: () => useTajriba, useTajribaConnected: () => useTajribaConnected, useTajribaConnecting: () => useTajribaConnecting }); module.exports = __toCommonJS(react_exports); // src/shared/globals.ts var import_rxjs = require("rxjs"); var Globals = class { constructor(globals) { this.attrs = /* @__PURE__ */ new Map(); this.updates = /* @__PURE__ */ new Map(); this.self = new import_rxjs.BehaviorSubject(void 0); globals.subscribe({ next: ({ attribute, done }) => { if (attribute) { let val = void 0; if (attribute.val) { val = JSON.parse(attribute.val); } this.updates.set(attribute.key, val); } if (done) { for (const [key, val] of this.updates) { this.obs(key).next(val); } this.updates.clear(); if (this.self) { this.self.next(this); } } } }); } get(key) { const o = this.attrs.get(key); if (o) { return o.getValue(); } return void 0; } obs(key) { let o = this.attrs.get(key); if (!o) { o = new import_rxjs.BehaviorSubject(void 0); this.attrs.set(key, o); } return o; } }; // src/shared/tajriba_connection.ts var import_tajriba = require("@empirica/tajriba"); // src/utils/console.ts var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; var logsMock; var colorHex = { [1 /* Bold */]: "font-weight: bold", [30 /* Black */]: "color: #000000", [31 /* Red */]: "color: #cc0000", [32 /* Green */]: "color: #4e9a06", [33 /* Yellow */]: "color: #c4a000", [34 /* Blue */]: "color: #729fcf", [35 /* Magenta */]: "color: #75507b", [36 /* Cyan */]: "color: #06989a", [37 /* White */]: "color: #d3d7cf", [90 /* DarkGray */]: "color: #555753" }; var levels = { trace: 0, debug: 1, log: 2, info: 2, warn: 3, error: 4 }; var reversLevels = {}; for (const key in levels) { reversLevels[levels[key]] = key; } var currentLevel = 2; function formatConsoleDate(date, level) { var hour = date.getHours(); var minutes = date.getMinutes(); var seconds = date.getSeconds(); var milliseconds = date.getMilliseconds(); const str = (hour < 10 ? "0" + hour : hour) + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + ("00" + milliseconds).slice(-3); if (isBrowser) { const ts = colorize(str, 90 /* DarkGray */).concat(level); return [ts[0] + " " + level[0], ts[1], level[1]]; } return colorize(str, 90 /* DarkGray */).concat(level); } var createLogger = (lvl, level) => { return (...args) => { if (lvl < currentLevel) { return; } if (logsMock) { logsMock.log({ level: reversLevels[lvl], args }); return; } if (args.length === 1) { switch (typeof args[0]) { case "string": for (const line of args[0].split("\n")) { console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(line)); } return; case "object": if (args[0] instanceof Error) { const error2 = args[0]; const prettyErr = error2.name + ": " + error2.message.replace(new RegExp(`^${error2.name}[: ]*`), "") + "\n" + (error2.stack || "").split("\n").map((line) => line.trim()).map((line) => { if (line.startsWith(error2.name + ": " + error2.message)) return null; if (line.startsWith("at")) { return " " + line; } return line; }).filter(Boolean).join("\n"); for (const line of prettyErr.split("\n")) { console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(line)); } return; } } } console.log(...formatConsoleDate(/* @__PURE__ */ new Date(), level).concat(args)); }; }; function colorize(s, ...cc) { if (isBrowser) { const attr = []; for (const c of cc) { attr.push(colorHex[c]); } return [`%c${s}`, attr.join("; ")]; } let out = ""; for (const c of cc) { out += `\x1B[${c}m`; } out += `${s}\x1B[0m`; return [out]; } var trace = createLogger(0, colorize("TRC", 35 /* Magenta */)); var debug = createLogger(1, colorize("DBG", 33 /* Yellow */)); var log = createLogger(2, colorize("LOG", 33 /* Yellow */)); var info = createLogger(2, colorize("INF", 32 /* Green */)); var warn = createLogger(3, colorize("WRN", 36 /* Cyan */)); var error = createLogger(4, colorize("ERR", 31 /* Red */, 1 /* Bold */)); // src/utils/object.ts var import_rxjs2 = require("rxjs"); function bs(init) { return new import_rxjs2.BehaviorSubject(init); } function bsu(init = void 0) { return new import_rxjs2.BehaviorSubject(init); } // src/shared/tajriba_connection.ts var ErrNotConnected = new Error("not connected"); var TajribaConnection = class { constructor(url) { this.url = url; this._connected = bs(false); this._connecting = bs(true); this._stopped = bs(false); this.tajriba = import_tajriba.Tajriba.connect(this.url); this._connected.next(this.tajriba.connected); this.tajriba.on("connected", () => { this._connected.next(true); this._connecting.next(false); }); this.tajriba.on("disconnected", () => { if (this._connected.getValue()) { this._connected.next(false); } if (!this._connecting.getValue()) { this._connecting.next(true); } }); this.tajriba.on("error", (err) => { error("connection error", err); }); } get connecting() { return this._connecting; } get connected() { return this._connected; } get stopped() { return this._stopped; } async sessionParticipant(token, pident) { if (!this._connected.getValue()) { throw ErrNotConnected; } return await this.tajriba.sessionParticipant(token, pident); } async sessionAdmin(token) { if (!this._connected.getValue()) { throw ErrNotConnected; } return await this.tajriba.sessionAdmin(token); } stop() { if (this._stopped.getValue()) { return; } if (this.tajriba) { this.tajriba.removeAllListeners("connected"); this.tajriba.removeAllListeners("disconnected"); this.tajriba.stop(); } this._connecting.next(false); this._connected.next(false); this._stopped.next(true); } }; // src/player/context.ts var import_rxjs6 = require("rxjs"); // src/admin/observables.ts var import_async_mutex = require("async-mutex"); var import_rxjs3 = require("rxjs"); function subscribeAsync(obs, fn) { const cancel = new import_rxjs3.Subject(); obs.pipe((0, import_rxjs3.concatMap)(fn), (0, import_rxjs3.takeUntil)(cancel)).subscribe(); return { closed: false, unsubscribe() { if (this.closed) { warn("closing a closed async observable subscription"); return; } this.closed = true; cancel.next(); cancel.unsubscribe(); } }; } // src/player/connection.ts var import_rxjs4 = require("rxjs"); var ParticipantConnection = class { constructor(taj, sessions, resetSession) { this.resetSession = resetSession; this._tajribaPart = bsu(); this._connected = bs(false); this._connecting = bs(false); this._stopped = bs(false); let session; let connected = false; this._sessionsSub = subscribeAsync( (0, import_rxjs4.merge)(taj.connected, sessions), async (sessionOrConnected) => { if (typeof sessionOrConnected === "boolean") { connected = sessionOrConnected; } else { session = sessionOrConnected; } if (!session || !connected) { return; } if (this._connected.getValue() || this._connecting.getValue()) { return; } this._connecting.next(true); try { const tajPart = await taj.sessionParticipant( session.token, session.participant ); this._tajribaPart.next(tajPart); if (tajPart.connected) { this._connected.next(true); this._connecting.next(false); } tajPart.on("connected", () => { if (!this._connected.getValue()) { this._connected.next(true); this._connecting.next(false); } }); tajPart.on("disconnected", () => { if (this._connected.getValue()) { this._connected.next(false); } if (this._connecting.getValue()) { this._connecting.next(false); } }); tajPart.on("error", (err) => { error("connection error", err); }); tajPart.on("accessDenied", () => { if (this._connected.getValue()) { this._connected.next(false); } if (this._connecting.getValue()) { this._connecting.next(false); } console.log( "accessDenied", session?.participant.id, session?.token ); this.resetSession(); }); } catch (err) { if (err !== ErrNotConnected) { error("new conn error", err); this.resetSession(); } } } ); } stop() { if (this._stopped.getValue()) { return; } const taj = this._tajribaPart.getValue(); if (taj) { taj.removeAllListeners("connected"); taj.removeAllListeners("disconnected"); taj.stop(); this._tajribaPart.next(void 0); } this._sessionsSub.unsubscribe(); this._connecting.next(false); this._connected.next(false); this._stopped.next(true); } get connecting() { return this._connecting; } get connected() { return this._connected; } get stopped() { return this._stopped; } get participant() { return this._tajribaPart; } }; var MemStorage = class { static clear() { this.vals = {}; } static getItem(key) { return this.vals[key]; } static removeItem(key) { delete this.vals[key]; } static setItem(key, value) { this.vals[key] = value; } }; MemStorage.vals = {}; var isBrowser2 = typeof window !== "undefined" && typeof window.document !== "undefined"; var storage; if (isBrowser2) { storage = window.localStorage; } var _ParticipantSession = class { constructor(ns, resetSession) { this.ns = ns; this._token = this.strg.getItem(this.tokenKey) || void 0; const participantStr = this.strg.getItem(this.partKey) || void 0; if (participantStr) { this._participant = JSON.parse(participantStr); } const sess = this.calcSession(); this._sessions = bsu(sess); resetSession.subscribe({ next: () => { this.clearSession(); } }); } get sessions() { return this._sessions; } get session() { return this._sessions.getValue(); } get token() { return this._token; } get participant() { return this._participant; } get tokenKey() { return `${_ParticipantSession.tokenKey}:${this.ns}`; } get partKey() { return `${_ParticipantSession.partKey}:${this.ns}`; } updateSession(token, participant) { this.strg.setItem(this.tokenKey, token); this.strg.setItem(this.partKey, JSON.stringify(participant)); this._token = token; this._participant = participant; this._sessions.next(this.calcSession()); } clearSession() { delete this._token; delete this._participant; this.strg.removeItem(this.tokenKey); this.strg.removeItem(this.partKey); this._sessions.next(void 0); } calcSession() { if (this._token && this._participant) { return { token: this._token, participant: this._participant }; } return void 0; } get strg() { return _ParticipantSession.storage; } }; var ParticipantSession = _ParticipantSession; ParticipantSession.tokenKey = "empirica:token"; ParticipantSession.partKey = "empirica:participant"; ParticipantSession.storage = storage; // src/player/provider.ts var import_rxjs5 = require("rxjs"); var TajribaProvider = class { constructor(changes, globals, setAttributes) { this.globals = globals; this.setAttributes = setAttributes; this.scopes = new import_rxjs5.Subject(); this.attributes = new import_rxjs5.Subject(); this.participants = new import_rxjs5.Subject(); this.steps = new import_rxjs5.Subject(); this.dones = new import_rxjs5.Subject(); let scopeIDs = []; changes.pipe((0, import_rxjs5.groupBy)((chg) => chg?.change?.__typename)).subscribe({ next: (group) => { switch (group.key) { case "ScopeChange": group.subscribe({ next: (msg) => { if (!msg.change || msg.removed === null || msg.removed === void 0) { trace("AttributeChange empty"); } else { this.scopes.next({ scope: msg.change, removed: msg.removed }); } if (msg.done) { this.dones.next(scopeIDs); } } }); break; case "AttributeChange": group.subscribe({ next: (msg) => { if (!msg.change || msg.removed === null || msg.removed === void 0) { trace("AttributeChange empty"); } else { const atChange = msg.change; scopeIDs.push(atChange.nodeID || atChange.node.id); this.attributes.next({ attribute: atChange, removed: msg.removed }); } if (msg.done) { this.dones.next(scopeIDs); scopeIDs = []; } } }); break; case "ParticipantChange": group.subscribe({ next: (msg) => { if (!msg.change || msg.removed === null || msg.removed === void 0) { trace("ParticipantChange empty"); } else { this.participants.next({ participant: msg.change, removed: msg.removed }); } if (msg.done) { this.dones.next([]); } } }); break; case "StepChange": group.subscribe({ next: (msg) => { if (!msg.change || msg.removed === null || msg.removed === void 0) { trace("StepChange empty"); } else { this.steps.next({ step: msg.change, removed: msg.removed }); } if (msg.done) { this.dones.next([]); } } }); break; default: group.subscribe({ next: (change) => { if (change.done) { this.dones.next([]); } } }); break; } } }); } }; // src/player/context.ts var ParticipantContext = class { constructor(url, ns) { /** @internal */ this.provider = bsu(); /** @internal */ this.globals = bsu(); this.tajriba = new TajribaConnection(url); this.resetSession = new import_rxjs6.Subject(); this.session = new ParticipantSession(ns, this.resetSession); this.participant = new ParticipantConnection( this.tajriba, this.session.sessions, this.resetSession.next.bind(this.resetSession) ); subscribeAsync(this.participant.connected, async (connected) => { const part = this.participant.participant.getValue(); if (connected && part) { if (!this.provider.getValue()) { this.provider.next( new TajribaProvider( part.changes(), this.tajriba.tajriba.globalAttributes(), part.setAttributes.bind(part) ) ); } } else { const provider = this.provider.getValue(); if (provider) { this.provider.next(void 0); } } }); subscribeAsync(this.tajriba.connected, async (connected) => { if (connected) { this.globals.next(new Globals(this.tajriba.tajriba.globalAttributes())); } else { const glob = this.globals.getValue(); if (glob) { this.globals.next(void 0); } } }); } get connecting() { return this.participant.connecting; } get connected() { return this.participant.connected; } async register(playerIdentifier) { if (!this.tajriba.connected.getValue()) { throw ErrNotConnected; } const [token, participant] = await this.tajriba.tajriba.registerParticipant( playerIdentifier ); if (!token) { throw new Error("invalid registration"); } this.session.updateSession(token, participant); } stop() { this.tajriba.stop(); this.participant.stop(); } }; var ParticipantMode = class { constructor(participant, provider, modeFunc) { this._mode = new import_rxjs6.BehaviorSubject(void 0); subscribeAsync(provider, async (provider2) => { const id = participant.getValue()?.id; if (id && provider2 && this._mode.getValue()) { warn("spurious provider condition"); window.location.reload(); } if (id && provider2) { this._mode.next(modeFunc(id, provider2)); } else { const mode = this._mode.getValue(); if (mode) { this._mode.next(void 0); } } }); } get mode() { return this._mode; } }; var ParticipantModeContext = class extends ParticipantContext { constructor(url, ns, modeFunc) { super(url, ns); this._mode = new ParticipantMode( this.participant.participant, this.provider, modeFunc ); } get mode() { return this._mode.mode; } }; // src/player/react/Consent.tsx var import_react = __toESM(require("react"), 1); var defaultTitle = "Do you consent to participate in this experiment?"; var defaultText = `This experiment is part of a scientific project. Your decision to participate in this experiment is entirely voluntary. There are no known or anticipated risks to participating in this experiment. There is no way for us to identify you. The only information we will have, in addition to your responses, is the timestamps of your interactions with our site. The results of our research may be presented at scientific meetings or published in scientific journals. Clicking on the "I AGREE" button indicates that you are at least 18 years of age, and agree to participate voluntary.`; var defaultButtonText = "I AGREE"; function Consent({ title = defaultTitle, text = defaultText, buttonText = defaultButtonText, onConsent }) { return /* @__PURE__ */ import_react.default.createElement( "div", { className: "relative h-full z-10 overflow-y-auto", "aria-labelledby": "modal-title", role: "dialog", "aria-modal": "true" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0" }, /* @__PURE__ */ import_react.default.createElement( "div", { className: "absolute inset-0 bg-gray-500 bg-opacity-75 transition-opacity", "aria-hidden": "true" } ), /* @__PURE__ */ import_react.default.createElement( "span", { className: "hidden sm:inline-block sm:align-middle sm:h-screen", "aria-hidden": "true" }, "\u200B" ), /* @__PURE__ */ import_react.default.createElement("div", { className: "inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("div", { className: "mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100" }, /* @__PURE__ */ import_react.default.createElement( "svg", { className: "h-6 w-6 text-green-600", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true" }, /* @__PURE__ */ import_react.default.createElement( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M5 13l4 4L19 7" } ) )), /* @__PURE__ */ import_react.default.createElement("div", { className: "mt-3 sm:mt-5" }, /* @__PURE__ */ import_react.default.createElement( "h3", { className: "text-lg text-center leading-6 font-medium text-gray-900", id: "modal-title" }, title ), /* @__PURE__ */ import_react.default.createElement("div", { className: "mt-2" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "text-sm text-gray-500 text-justify" }, text)))), /* @__PURE__ */ import_react.default.createElement("div", { className: "mt-5 sm:mt-6" }, /* @__PURE__ */ import_react.default.createElement( "button", { type: "button", className: "inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-empirica-600 text-base font-medium text-white hover:bg-empirica-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-empirica-500 sm:text-sm", onClick: onConsent }, buttonText )))) ); } // src/player/react/EmpiricaMenu.tsx var import_react5 = __toESM(require("react"), 1); // src/player/utils.ts var isDevelopment = process.env.NODE_ENV === "development"; var isProduction = process.env.NODE_ENV === "production"; var isTest = process.env.NODE_ENV === "test"; var createNewParticipant = (key = "participantKey") => { const url = new URL(document.location.href); url.searchParams.set(key, (/* @__PURE__ */ new Date()).getTime().toString()); window.open(url.href, "_blank")?.focus(); }; // src/player/react/Logo.tsx var import_react2 = __toESM(require("react"), 1); function Logo() { return /* @__PURE__ */ import_react2.default.createElement( "svg", { className: "h-full w-full fill-current", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 195 185" }, /* @__PURE__ */ import_react2.default.createElement("path", { d: "M25.164 81.9737C22.31 81.9737 19.998 84.2877 20 87.1417L20.028 128.458C20.03 131.309 22.341 133.619 25.192 133.619C28.046 133.619 30.359 131.304 30.357 128.451L30.328 87.1347C30.326 84.2837 28.015 81.9737 25.164 81.9737Z" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M87.1367 61.3158C84.2837 61.3158 81.9707 63.6298 81.9727 66.4828L82.0017 118.128C82.0037 120.979 84.3147 123.29 87.1667 123.29C90.0197 123.29 92.3327 120.976 92.3307 118.122L92.3007 66.4778C92.2997 63.6258 89.9877 61.3158 87.1367 61.3158Z" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M107.793 20C104.94 20 102.628 22.313 102.629 25.166L102.661 159.442C102.662 162.294 104.974 164.605 107.826 164.605C110.679 164.605 112.991 162.293 112.99 159.44L112.957 25.163C112.957 22.311 110.645 20 107.793 20Z" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M128.451 20C125.598 20 123.286 22.313 123.287 25.166L123.319 159.442C123.32 162.294 125.632 164.605 128.484 164.605C131.337 164.605 133.649 162.293 133.648 159.44L133.615 25.163C133.615 22.311 131.303 20 128.451 20Z" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M149.11 20C146.257 20 143.944 22.313 143.946 25.167L143.977 102.634C143.978 105.485 146.29 107.796 149.141 107.796C151.994 107.796 154.307 105.483 154.306 102.63L154.274 25.162C154.273 22.311 151.961 20 149.11 20Z" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M169.75 40.6579C166.897 40.6579 164.585 42.9709 164.586 45.8239L164.617 138.785C164.618 141.637 166.93 143.948 169.782 143.948C172.635 143.948 174.947 141.634 174.946 138.781L174.914 45.8209C174.914 42.9689 172.602 40.6579 169.75 40.6579Z" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M45.8203 61.3158C42.9673 61.3158 40.6553 63.6288 40.6563 66.4818L40.6873 159.443C40.6883 162.295 43.0003 164.606 45.8523 164.606C48.7053 164.606 51.0173 162.292 51.0163 159.439L50.9843 66.4788C50.9843 63.6268 48.6723 61.3158 45.8203 61.3158Z" }), /* @__PURE__ */ import_react2.default.createElement("path", { d: "M66.4785 61.3158C63.6255 61.3158 61.3135 63.6288 61.3145 66.4818L61.3455 159.443C61.3465 162.295 63.6585 164.606 66.5105 164.606C69.3635 164.606 71.6755 162.292 71.6745 159.439L71.6425 66.4788C71.6415 63.6268 69.3305 61.3158 66.4785 61.3158Z" }) ); } // src/player/react/hooks.ts var import_react4 = require("react"); var import_rxjs7 = require("rxjs"); // src/player/react/EmpiricaParticipant.tsx var import_react3 = __toESM(require("react"), 1); var ParticipantCtx = import_react3.default.createContext(void 0); var contexts = {}; function EmpiricaParticipant({ url, ns, modeFunc, children }) { let partCtx; if (ns in contexts) { partCtx = contexts[ns]; } else { if (modeFunc) { partCtx = new ParticipantModeContext(url, ns, modeFunc); } else { partCtx = new ParticipantContext(url, ns); } contexts[ns] = partCtx; } return /* @__PURE__ */ import_react3.default.createElement(ParticipantCtx.Provider, { value: partCtx }, children); } // src/player/react/hooks.ts function useParticipantContext() { return (0, import_react4.useContext)(ParticipantCtx); } function useTajribaConnecting() { return useTajribaCtxKey("connecting"); } function useTajribaConnected() { return useTajribaCtxKey("connected"); } function useTajriba() { const ctx = useParticipantContext(); return ctx?.tajriba; } function useGlobal() { const ctx = usePartCtxKey("globals"); const [val, setVal] = (0, import_react4.useState)({ g: void 0 }); (0, import_react4.useEffect)(() => { if (!ctx || !ctx.self) { return; } const sub = ctx.self.subscribe({ next(g) { setVal({ g }); } }); return sub.unsubscribe.bind(sub); }, [ctx]); return val.g; } var defaultConsentKey = "empirica:consent"; function useConsent(ns = "") { const key = `${defaultConsentKey}${ns ? `:${ns}` : ""}`; const getConsented = () => Boolean(window.localStorage[key]); const [consented, setConsented] = (0, import_react4.useState)(getConsented()); function onConsent() { window.localStorage[key] = true; setConsented(true); } return [consented, consented ? void 0 : onConsent]; } function usePlayerID() { const ctx = useParticipantContext(); const [connecting, setConnecting] = (0, import_react4.useState)(true); const [playerID, setPlayerID] = (0, import_react4.useState)(void 0); const [changePlayerID, setChangePlayerID] = (0, import_react4.useState)(void 0); (0, import_react4.useEffect)(() => { if (!ctx) { return; } let _connecting = true; let session; const sub = (0, import_rxjs7.merge)( ctx.participant.connecting, ctx.session.sessions ).subscribe({ next(sessionOrConnecting) { if (typeof sessionOrConnecting === "boolean") { setConnecting(sessionOrConnecting); _connecting = sessionOrConnecting; } else { session = sessionOrConnecting; } if (_connecting) { setPlayerID(void 0); setChangePlayerID(void 0); } else if (!session) { setPlayerID(void 0); setChangePlayerID(() => async (playerIdentifier) => { await ctx.register(playerIdentifier); }); } else { setPlayerID(session.participant.identifier); setChangePlayerID(void 0); } } }); return sub.unsubscribe.bind(sub); }, [ctx]); return [connecting, playerID, changePlayerID]; } function useTajribaCtxKey(name) { return useCtxKey(useTajriba, name); } function usePartCtxKey(name) { return useCtxKey(useParticipantContext, name); } function useCtxKey(ctxFunc, name) { const ctx = ctxFunc(); const [val, setVal] = (0, import_react4.useState)(void 0); (0, import_react4.useEffect)(() => { if (!ctx || !ctx[name]) { return; } const obs = ctx[name]; const sub = obs.subscribe({ next(g) { setVal(g); } }); return sub.unsubscribe.bind(sub); }, [ctx]); return val; } // src/player/react/EmpiricaMenu.tsx function EmpiricaMenu({ position = "bottom-left" }) { const ctx = useParticipantContext(); if (!ctx) { return null; } function resetSession() { ctx.session.clearSession(); window.location.reload(); } let className = "backdrop-blur-md bg-gray-200/50 rounded fixed z-20 flex space-x-1 text-gray-500"; switch (position) { case "top": className += " top-0 mt-2 ml-1/2 -translate-x-1/2"; break; case "top-left": className += " top-0 left-0 mt-2 ml-2"; break; case "top-right": className += " top-0 right-0 mt-2 mr-2"; break; case "bottom": className += " bottom-0 mb-2 ml-1/2 -translate-x-1/2"; break; case "bottom-right": className += " bottom-0 right-0 mb-2 mr-2"; break; case "bottom-left": default: className += " bottom-0 left-0 mb-2 ml-2"; break; } const buttons = [ { onClick: () => { window.open("https://empirica.ly", "_blank"); }, icon: /* @__PURE__ */ import_react5.default.createElement(Logo, null), title: "Empirica", inDevOnly: position === "top" || position === "bottom" }, { onClick: () => createNewParticipant(), icon: /* @__PURE__ */ import_react5.default.createElement( "svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-full w-full stroke-current" }, /* @__PURE__ */ import_react5.default.createElement("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }), /* @__PURE__ */ import_react5.default.createElement("circle", { cx: "9", cy: "7", r: "4" }), /* @__PURE__ */ import_react5.default.createElement("line", { x1: "19", x2: "19", y1: "8", y2: "14" }), /* @__PURE__ */ import_react5.default.createElement("line", { x1: "22", x2: "16", y1: "11", y2: "11" }) ), inDevOnly: true, title: "New Participant" }, { onClick: resetSession, icon: /* @__PURE__ */ import_react5.default.createElement( "svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-full w-full stroke-current" }, /* @__PURE__ */ import_react5.default.createElement("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }), /* @__PURE__ */ import_react5.default.createElement("path", { d: "M3 3v5h5" }) ), inDevOnly: true, title: "Reset Session" }, { onClick: () => { window.open("/admin", "_blank"); }, icon: /* @__PURE__ */ import_react5.default.createElement( "svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-full w-full stroke-current" }, /* @__PURE__ */ import_react5.default.createElement("path", { d: "M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" }) ), inDevOnly: true, title: "Admin" }, { onClick: () => { window.open("https://docs.empirica.ly", "_blank"); }, icon: /* @__PURE__ */ import_react5.default.createElement( "svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-full w-full stroke-current" }, /* @__PURE__ */ import_react5.default.createElement("path", { d: "M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" }), /* @__PURE__ */ import_react5.default.createElement("path", { d: "M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" }) ), inDevOnly: true, title: "Documentation" } ]; return /* @__PURE__ */ import_react5.default.createElement("div", { className }, buttons.map((button, i) => { let sizing = ""; if (i === 0) { sizing = "w-9 h-8 p-1.5 pl-2.5"; if (buttons.length === 0) { sizing += " pr-2.5"; } } else if (i === buttons.length - 1) { sizing += "w-8.5 h-8 p-2 pr-2.5"; } return /* @__PURE__ */ import_react5.default.createElement(ToolButton, { key: i, ...button, sizing }); })); } function ToolButton({ onClick, icon, title, sizing = "", inDevOnly = false }) { if (inDevOnly && !isDevelopment) { return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null); } let size = "w-8 h-8 p-2"; if (sizing) { size = sizing; } let className = "block bg-transparent hover:text-empirica-600 hover:bg-gray-300 rounded " + size; return /* @__PURE__ */ import_react5.default.createElement("button", { onClick, className, title }, icon); } // src/player/react/Finished.tsx var import_react6 = __toESM(require("react"), 1); function Finished() { return /* @__PURE__ */ import_react6.default.createElement("div", { className: "h-full flex flex-col items-center justify-center" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: "font-medium text-gray-700" }, "Finished"), /* @__PURE__ */ import_react6.default.createElement("p", { className: "mt-2 text-gray-400" }, "Thank you for participating")); } // src/player/react/Loading.tsx var import_react7 = __toESM(require("react"), 1); function Loading() { return /* @__PURE__ */ import_react7.default.createElement("div", { className: "h-full w-full flex items-center justify-center" }, /* @__PURE__ */ import_react7.default.createElement( "svg", { width: "44", height: "44", viewBox: "0 0 44 44", xmlns: "http://www.w3.org/2000/svg", className: "text-empirica-200 stroke-current" }, /* @__PURE__ */ import_react7.default.createElement("g", { fill: "none", fillRule: "evenodd", strokeWidth: "2" }, /* @__PURE__ */ import_react7.default.createElement("circle", { cx: "22", cy: "22", r: "1" }, /* @__PURE__ */ import_react7.default.createElement( "animate", { attributeName: "r", begin: "0s", dur: "1.8s", values: "1; 20", calcMode: "spline", keyTimes: "0; 1", keySplines: "0.165, 0.84, 0.44, 1", repeatCount: "indefinite" } ), /* @__PURE__ */ import_react7.default.createElement( "animate", { attributeName: "stroke-opacity", begin: "0s", dur: "1.8s", values: "1; 0", calcMode: "spline", keyTimes: "0; 1", keySplines: "0.3, 0.61, 0.355, 1", repeatCount: "indefinite" } )), /* @__PURE__ */ import_react7.default.createElement("circle", { cx: "22", cy: "22", r: "1" }, /* @__PURE__ */ import_react7.default.createElement( "animate", { attributeName: "r", begin: "-0.9s", dur: "1.8s", values: "1; 20", calcMode: "spline", keyTimes: "0; 1", keySplines: "0.165, 0.84, 0.44, 1", repeatCount: "indefinite" } ), /* @__PURE__ */ import_react7.default.createElement( "animate", { attributeName: "stroke-opacity", begin: "-0.9s", dur: "1.8s", values: "1; 0", calcMode: "spline", keyTimes: "0; 1", keySplines: "0.3, 0.61, 0.355, 1", repeatCount: "indefinite" } ))) )); } // src/player/react/NoGames.tsx var import_react8 = __toESM(require("react"), 1); function NoGames() { return /* @__PURE__ */ import_react8.default.createElement("div", { className: "h-screen flex items-center justify-center" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "w-92 flex flex-col items-center" }, /* @__PURE__ */ import_react8.default.createElement("h2", { className: "text-gray-700 font-medium" }, "No experiments available"), /* @__PURE__ */ import_react8.default.createElement("p", { className: "mt-2 text-gray-400 text-justify" }, "There are currently no available experiments. Please wait until an experiment becomes available or come back at a later date."), isDevelopment ? /* @__PURE__ */ import_react8.default.createElement("p", { className: "mt-4 text-gray-700" }, "Go to", " ", /* @__PURE__ */ import_react8.default.createElement( "a", { href: "/admin", target: "empirica-admin", className: "text-empirica-500" }, "Admin" ), " ", "to get started") : "")); } // src/player/react/PlayerCreate.tsx var import_react9 = __toESM(require("react"), 1); function PlayerCreate({ onPlayerID, connecting }) { const [playerID, setPlayerID] = (0, import_react9.useState)(""); const handleSubmit = (evt) => { evt.preventDefault(); if (!playerID || playerID.trim() === "") { return; } onPlayerID(playerID); }; return /* @__PURE__ */ import_react9.default.createElement("div", { className: "min-h-screen bg-empirica-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "sm:mx-auto sm:w-full sm:max-w-md" }, /* @__PURE__ */ import_react9.default.createElement("h2", { className: "mt-6 text-center text-3xl font-extrabold text-gray-900" }, "Enter your Player Identifier")), /* @__PURE__ */ import_react9.default.createElement("div", { className: "mt-8 sm:mx-auto sm:w-full sm:max-w-md" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10" }, /* @__PURE__ */ import_react9.default.createElement( "form", { className: "space-y-6", action: "#", method: "POST", onSubmit: handleSubmit }, /* @__PURE__ */ import_react9.default.createElement("fieldset", { disabled: connecting }, /* @__PURE__ */ import_react9.default.createElement("div", null, /* @__PURE__ */ import_react9.default.createElement( "label", { htmlFor: "email", className: "block text-sm font-medium text-gray-700" }, "Identifier" ), /* @__PURE__ */ import_react9.default.createElement("div", { className: "mt-1" }, /* @__PURE__ */ import_react9.default.createElement( "input", { id: "playerID", name: "playerID", type: "text", autoComplete: "off", required: true, autoFocus: true, className: "appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-empirica-500 focus:border-empirica-500 sm:text-sm", value: playerID, onChange: (e) => setPlayerID(e.target.value) } ), /* @__PURE__ */ import_react9.default.createElement( "p", { className: "mt-2 text-sm text-gray-500", id: "playerID-description" }, "This should be given to you. E.g. email, code..." ))), /* @__PURE__ */ import_react9.default.createElement("div", null, /* @__PURE__ */ import_react9.default.createElement( "button", { type: "submit", className: "w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-empirica-600 hover:bg-empirica-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-empirica-500" }, "Enter" ))) )))); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Consent, EmpiricaMenu, EmpiricaParticipant, Finished, Globals, Loading, Logo, NoGames, ParticipantContext, PlayerCreate, TajribaConnection, TajribaProvider, useConsent, useGlobal, useParticipantContext, usePlayerID, useTajriba, useTajribaConnected, useTajribaConnecting }); //# sourceMappingURL=player-react.cjs.map