@empirica/core
Version:
Empirica Core
1 lines • 73.4 kB
Source Map (JSON)
{"version":3,"sources":["../src/player/classic/index.ts","../src/shared/attributes.ts","../src/utils/console.ts","../src/shared/globals.ts","../src/player/provider.ts","../src/shared/scopes.ts","../src/player/scopes.ts","../src/player/steps.ts","../src/player/utils.ts","../src/player/classic/classic.ts"],"sourcesContent":["export { Attribute, Attributes } from \"../../shared/attributes\";\nexport type {\n AttributeChange,\n AttributeOptions,\n AttributeUpdate,\n} from \"../../shared/attributes\";\nexport { Globals } from \"../../shared/globals\";\nexport type { Constructor } from \"../../shared/helpers\";\nexport type {\n AttributeInput,\n ScopeConstructor,\n ScopeIdent,\n ScopeUpdate,\n Scope as SharedScope,\n} from \"../../shared/scopes\";\nexport type { Json, JsonArray, JsonValue } from \"../../utils/json\";\nexport { TajribaProvider } from \"../provider\";\nexport type { ParticipantUpdate } from \"../provider\";\nexport { Scope, Scopes } from \"../scopes\";\nexport { Step, Steps } from \"../steps\";\nexport type { Epoch, StepChange, StepTick, StepUpdate } from \"../steps\";\nexport {\n createNewParticipant,\n isDevelopment,\n isProduction,\n isTest,\n} from \"../utils\";\nexport {\n EmpiricaClassic,\n Game,\n Player,\n PlayerGame,\n PlayerRound,\n PlayerStage,\n Round,\n Stage,\n} from \"./classic\";\nexport type {\n Context,\n EmpiricaClassicContext,\n EmpiricaClassicKinds,\n} from \"./classic\";\nimport \"../index.css\";\n","import { SetAttributeInput } from \"@empirica/tajriba\";\nimport { BehaviorSubject, Observable } from \"rxjs\";\nimport { error, trace } from \"../utils/console\";\nimport { JsonValue } from \"../utils/json\";\n\nexport interface AttributeChange {\n /** deleted is true with the attribute was deleted. */\n deleted?: boolean;\n /** deletedAt is the time when the Attribute was deleted. int64 Date + Time\n * value given in Epoch with ns precision */\n deletedAt?: number;\n /** createdAt is the time the Attribute was created. int64 Date + Time\n * value given in Epoch with ns precision */\n createdAt?: string;\n /** id is the identifier for the Attribute. */\n id: string;\n /** index is the index of the attribute if the value is a vector. */\n index?: number | null;\n /** isNew is true if the Attribute was just created. */\n isNew?: boolean;\n /** key is the attribute key being updated. */\n key: string;\n /** nodeID is the identifier for the Attribute's Node. */\n nodeID?: string;\n /** node is the Attribute's Node. */\n node?: {\n __typename: \"Scope\";\n id: string;\n kind?: string;\n name?: string;\n };\n /** value is the value of the updated attribute. */\n val?: string | null;\n /** vector indicates whether the value is a vector. */\n vector: boolean;\n /** version is the version number of this Attribute, starting at 1. */\n version: number;\n}\n\nexport interface AttributeUpdate {\n attribute: AttributeChange;\n removed: boolean;\n}\n\nexport class Attributes {\n protected attrs = new Map<string, Map<string, Attribute>>();\n protected updates = new Map<string, Map<string, AttributeChange | boolean>>();\n\n constructor(\n attributesObs: Observable<AttributeUpdate>,\n donesObs: Observable<string[]>,\n readonly setAttributes: (input: SetAttributeInput[]) => Promise<unknown>\n ) {\n attributesObs.subscribe({\n next: ({ attribute, removed }) => {\n this.update(attribute, removed);\n },\n });\n\n donesObs.subscribe({\n next: (scopeIDs) => {\n this.next(scopeIDs);\n },\n });\n }\n\n attribute(scopeID: string, key: string): Attribute {\n let scopeMap = this.attrs.get(scopeID);\n if (!scopeMap) {\n scopeMap = new Map();\n this.attrs.set(scopeID, scopeMap);\n }\n\n let attr = scopeMap.get(key);\n if (!attr) {\n attr = new Attribute(this.setAttributes, scopeID, key);\n scopeMap.set(key, attr);\n }\n\n return attr;\n }\n\n attributes(scopeID: string): Attribute[] {\n let scopeMap = this.attrs.get(scopeID);\n if (!scopeMap) {\n scopeMap = new Map();\n this.attrs.set(scopeID, scopeMap);\n }\n\n return Array.from(scopeMap.values());\n }\n\n attributePeek(scopeID: string, key: string): Attribute | undefined {\n let scopeUpdateMap = this.updates.get(scopeID);\n if (scopeUpdateMap) {\n const updated = scopeUpdateMap.get(key);\n if (updated) {\n if (typeof updated === \"boolean\") {\n return;\n } else {\n if (!updated.val) {\n return;\n } else {\n const attr = new Attribute(this.setAttributes, scopeID, key);\n attr._update(updated);\n return attr;\n }\n }\n }\n }\n\n let scopeMap = this.attrs.get(scopeID);\n if (!scopeMap) {\n return;\n }\n\n let attr = scopeMap.get(key);\n if (!attr) {\n return;\n }\n\n if (attr.value === undefined) {\n return;\n }\n\n return attr;\n }\n\n nextAttributeValue(scopeID: string, key: string): JsonValue | undefined {\n const attr = this.attributePeek(scopeID, key);\n if (!attr) {\n return;\n }\n\n return attr.value;\n }\n\n private update(attr: AttributeChange, removed: boolean) {\n let nodeID = attr.nodeID;\n if (!nodeID) {\n if (!attr.node?.id) {\n error(`new attribute without node ID`);\n return;\n }\n nodeID = attr.node.id;\n }\n\n let scopeMap = this.updates.get(nodeID);\n if (!scopeMap) {\n scopeMap = new Map();\n this.updates.set(nodeID, scopeMap);\n }\n\n if (removed) {\n scopeMap.set(attr.key, true);\n } else {\n let key = attr.key;\n if (attr.index !== undefined && attr.index !== null) {\n key = `${key}[${attr.index}]`;\n }\n scopeMap.set(key, attr);\n }\n }\n\n scopeWasUpdated(scopeID?: string): boolean {\n if (!scopeID) {\n return false;\n }\n\n return this.updates.has(scopeID);\n }\n\n protected next(scopeIDs: string[]) {\n for (const [scopeID, attrs] of this.updates) {\n if (!scopeIDs.includes(scopeID)) {\n continue;\n }\n\n let scopeMap = this.attrs.get(scopeID);\n\n if (!scopeMap) {\n scopeMap = new Map();\n this.attrs.set(scopeID, scopeMap);\n }\n\n for (const [key, attrOrDel] of attrs) {\n if (typeof attrOrDel === \"boolean\") {\n let attr = scopeMap.get(key);\n if (attr) {\n attr._update(undefined);\n }\n } else {\n let attr = scopeMap.get(attrOrDel.key);\n if (!attr) {\n attr = new Attribute(this.setAttributes, scopeID, attrOrDel.key);\n scopeMap.set(attrOrDel.key, attr);\n }\n\n attr._update(attrOrDel);\n }\n }\n }\n\n for (const scopeID of scopeIDs) {\n this.updates.delete(scopeID);\n }\n }\n}\n\nexport interface AttributeOptions {\n /**\n * Private indicates the attribute will not be visible to other Participants.\n */\n private: boolean;\n /**\n * Protected indicates the attribute will not be updatable by other\n * Participants.\n */\n protected: boolean;\n /** Immutable creates an Attribute that cannot be updated. */\n immutable: boolean;\n /** ephemeral indicates the Attribute should not be persisted. Ephemeral\n * Attributes are not stored in the database and are only synced to the\n * connected clients. An ephemeral Attribute cannot become non-ephemeral and\n * vice versa. */\n ephemeral: boolean;\n /**\n * Index, only used if the Attribute is a vector, indicates which index to\n * update the value at.\n */\n index: number | null;\n /**\n * Append, only used if the Attribute is a vector, indicates to append the\n * attribute to the vector.\n */\n append: boolean | null;\n}\n\nexport class Attribute {\n private attr?: AttributeChange;\n private attrs?: Attribute[];\n\n private val = new BehaviorSubject<JsonValue | undefined>(undefined);\n private serVal?: string;\n\n constructor(\n private setAttributes: (input: SetAttributeInput[]) => Promise<unknown>,\n readonly scopeID: string,\n readonly key: string\n ) {}\n\n get id() {\n return this.attr?.id;\n }\n\n get createdAt() {\n return this.attr ? new Date(this.attr!.createdAt!) : null;\n }\n\n get obs(): Observable<JsonValue | undefined> {\n return this.val;\n }\n\n get value() {\n return this.val.getValue();\n }\n\n get nodeID() {\n return this.scopeID;\n }\n\n // items returns the attribute changes for the current attribute, if it is a\n // vector. Otherwise it returns null;\n get items() {\n if (!this.attrs) {\n return null;\n }\n\n return this.attrs;\n }\n\n set(value: JsonValue, ao?: Partial<AttributeOptions>) {\n const attrProps = this._prepSet(value, ao);\n if (!attrProps) {\n return;\n }\n\n this.setAttributes([attrProps]);\n trace(`SET ${this.key} = ${value} (${this.scopeID})`);\n }\n\n _prepSet(\n value: JsonValue,\n ao?: Partial<AttributeOptions>,\n item?: boolean\n ): SetAttributeInput | undefined {\n if (ao?.append !== undefined && ao!.index !== undefined) {\n error(`cannot set both append and index`);\n\n throw new Error(`cannot set both append and index`);\n }\n\n const serVal = JSON.stringify(value);\n\n if (!item && (ao?.index !== undefined || ao?.append)) {\n let index = ao!.index || 0;\n if (ao?.append) {\n index = this.attrs?.length || 0;\n }\n\n if (!this.attrs) {\n this.attrs = [];\n }\n\n // if (index + 1 > (this.attrs?.length || 0)) {\n // this.attrs.length = index! + 1;\n // }\n\n if (!this.attrs[index]) {\n this.attrs[index] = new Attribute(\n this.setAttributes,\n this.scopeID,\n this.key\n );\n } else {\n const existing = this.attrs[index];\n if (existing && existing.serVal === serVal) {\n return;\n }\n }\n\n this.attrs![index]!._prepSet(value, ao, true);\n const v = this._recalcVectorVal();\n this.val.next(v);\n } else {\n if (this.serVal === serVal) {\n return;\n }\n\n this.val.next(value);\n }\n\n this.serVal = serVal;\n\n const attrProps: SetAttributeInput = {\n key: this.key,\n nodeID: this.scopeID,\n val: serVal,\n };\n\n if (ao) {\n // TODO Fix this. Should check if compatible with existing attribute and\n // only set fields set on ao.\n attrProps.private = ao.private;\n attrProps.protected = ao.protected;\n attrProps.immutable = ao.immutable;\n attrProps.ephemeral = ao.ephemeral;\n attrProps.append = ao.append;\n attrProps.index = ao.index;\n }\n\n return attrProps;\n }\n\n private _recalcVectorVal(): JsonValue {\n return this.attrs!.map((a) =>\n !a || a.val == undefined ? null : a.value || null\n );\n }\n\n // internal only\n _update(attr?: AttributeChange, item?: boolean) {\n if (attr && this.attr && this.attr.id === attr.id) {\n return;\n }\n\n if (attr && attr.vector && !item) {\n // TODO check if is vector\n\n if (attr.index === undefined) {\n error(`vector attribute missing index`);\n return;\n }\n\n if (this.attrs == undefined) {\n this.attrs = [];\n }\n\n while (this.attrs.length < attr.index! + 1) {\n const newAttr = new Attribute(\n this.setAttributes,\n this.scopeID,\n this.key\n );\n this.attrs.push(newAttr);\n }\n\n const newAttr = new Attribute(this.setAttributes, this.scopeID, this.key);\n newAttr._update(attr, true);\n this.attrs[attr.index!] = newAttr;\n const value = this._recalcVectorVal();\n this.val.next(value);\n\n return;\n }\n\n this.attr = attr;\n this.serVal = attr?.val === undefined || attr?.val === null ? \"\" : attr.val;\n let value: JsonValue | undefined = undefined;\n if (this.attr?.val) {\n value = JSON.parse(this.attr.val);\n }\n this.val.next(value);\n }\n}\n","/* c8 ignore start */\n\nconst isBrowser =\n typeof window !== \"undefined\" && typeof window.document !== \"undefined\";\n\nenum Color {\n Bold = 1,\n\n Black = 30,\n Red,\n Green,\n Yellow,\n Blue,\n Magenta,\n Cyan,\n White,\n\n DarkGray = 90,\n}\n\nexport type LogLine = { level: string; args: any[] };\nexport class LogsMock {\n public logs: LogLine[] = [];\n\n log(line: LogLine) {\n this.logs.push(line);\n }\n\n clear() {\n this.logs = [];\n }\n}\n\nlet logsMock: LogsMock | undefined;\nexport function captureLogs(cb: () => void): LogLine[] {\n const lm = mockLogging();\n cb();\n const ret = lm.logs;\n stopMockLogging();\n\n return ret;\n}\n\nexport async function captureLogsAsync(\n cb: () => Promise<void>\n): Promise<LogLine[]> {\n const lm = mockLogging();\n await cb();\n const ret = lm.logs;\n stopMockLogging();\n\n return ret;\n}\n\nexport function mockLogging() {\n if (!logsMock) {\n logsMock = new LogsMock();\n }\n\n return logsMock;\n}\n\nexport function stopMockLogging() {\n logsMock = undefined;\n}\n\nconst colorHex = {\n [Color.Bold]: \"font-weight: bold\",\n [Color.Black]: \"color: #000000\",\n [Color.Red]: \"color: #cc0000\",\n [Color.Green]: \"color: #4e9a06\",\n [Color.Yellow]: \"color: #c4a000\",\n [Color.Blue]: \"color: #729fcf\",\n [Color.Magenta]: \"color: #75507b\",\n [Color.Cyan]: \"color: #06989a\",\n [Color.White]: \"color: #d3d7cf\",\n [Color.DarkGray]: \"color: #555753\",\n};\n\nexport const levels: { [key: string]: number } = {\n trace: 0,\n debug: 1,\n log: 2,\n info: 2,\n warn: 3,\n error: 4,\n};\n\nconst reversLevels: { [key: number]: string } = {};\nfor (const key in levels) {\n reversLevels[levels[key]!] = key;\n}\n\nlet currentLevel = 2;\n\nexport function setLogLevel(level: keyof typeof levels) {\n const lvl = levels[level];\n if (lvl === undefined) {\n return;\n }\n\n currentLevel = lvl;\n}\n\nfunction formatConsoleDate(date: Date, level: string[]) {\n var hour = date.getHours();\n var minutes = date.getMinutes();\n var seconds = date.getSeconds();\n var milliseconds = date.getMilliseconds();\n\n const str =\n (hour < 10 ? \"0\" + hour : hour) +\n \":\" +\n (minutes < 10 ? \"0\" + minutes : minutes) +\n \":\" +\n (seconds < 10 ? \"0\" + seconds : seconds) +\n \".\" +\n (\"00\" + milliseconds).slice(-3);\n\n if (isBrowser) {\n const ts = colorize(str, Color.DarkGray).concat(level);\n return [ts[0] + \" \" + level[0], ts[1], level[1]];\n }\n\n return colorize(str, Color.DarkGray).concat(level);\n}\n\nconst createLogger = (lvl: number, level: string[]) => {\n return (...args: any[]) => {\n if (lvl < currentLevel) {\n return;\n }\n\n if (logsMock) {\n logsMock.log({ level: reversLevels[lvl]!, args: args });\n\n return;\n }\n\n if (args.length === 1) {\n switch (typeof args[0]) {\n case \"string\":\n for (const line of args[0].split(\"\\n\")) {\n console.log(...formatConsoleDate(new Date(), level).concat(line));\n }\n return;\n\n case \"object\":\n if (args[0] instanceof Error) {\n const error = args[0] as Error;\n const prettyErr =\n error.name +\n \": \" +\n error.message.replace(new RegExp(`^${error.name}[: ]*`), \"\") +\n \"\\n\" +\n (error.stack || \"\")\n .split(\"\\n\")\n .map((line) => line.trim())\n .map((line) => {\n if (line.startsWith(error.name + \": \" + error.message))\n return null;\n\n if (line.startsWith(\"at\")) {\n return \" \" + line;\n }\n\n return line;\n })\n .filter(Boolean)\n .join(\"\\n\");\n\n for (const line of prettyErr.split(\"\\n\")) {\n console.log(...formatConsoleDate(new Date(), level).concat(line));\n }\n\n return;\n }\n }\n }\n\n console.log(...formatConsoleDate(new Date(), level).concat(args));\n };\n};\n\nfunction colorize(s: string, ...cc: Color[]): string[] {\n if (isBrowser) {\n const attr = [];\n for (const c of cc) {\n attr.push(colorHex[c]);\n }\n\n return [`%c${s}`, attr.join(\"; \")];\n }\n\n let out = \"\";\n for (const c of cc) {\n out += `\\x1b[${c}m`;\n }\n out += `${s}\\x1b[0m`;\n\n return [out];\n}\n\nexport const trace = createLogger(0, colorize(\"TRC\", Color.Magenta));\nexport const debug = createLogger(1, colorize(\"DBG\", Color.Yellow));\nexport const log = createLogger(2, colorize(\"LOG\", Color.Yellow));\nexport const info = createLogger(2, colorize(\"INF\", Color.Green));\nexport const warn = createLogger(3, colorize(\"WRN\", Color.Cyan));\nexport const error = createLogger(4, colorize(\"ERR\", Color.Red, Color.Bold));\n\n// export {\n// trace,\n// debug,\n// log,\n// info,\n// warn,\n// error,\n// };\n\n// export function warn(...args: string[]) {}\n","import { SubAttributesPayload } from \"@empirica/tajriba\";\nimport { BehaviorSubject, Observable } from \"rxjs\";\nimport { JsonValue } from \"../utils/json\";\n\nexport class Globals {\n protected attrs = new Map<string, BehaviorSubject<JsonValue | undefined>>();\n private updates = new Map<string, JsonValue | undefined>();\n public self: BehaviorSubject<Globals | undefined>;\n\n constructor(globals: Observable<SubAttributesPayload>) {\n this.self = new BehaviorSubject<Globals | undefined>(undefined);\n\n globals.subscribe({\n next: ({ attribute, done }) => {\n if (attribute) {\n let val = undefined;\n if (attribute.val) {\n val = JSON.parse(attribute.val);\n }\n\n this.updates.set(attribute.key, val);\n }\n\n if (done) {\n for (const [key, val] of this.updates) {\n this.obs(key).next(val);\n }\n\n this.updates.clear();\n\n if (this.self) {\n this.self.next(this);\n }\n }\n },\n });\n }\n\n get(key: string): JsonValue | undefined {\n const o = this.attrs.get(key);\n if (o) {\n return o.getValue();\n }\n\n return undefined;\n }\n\n obs(key: string) {\n let o = this.attrs.get(key);\n if (!o) {\n o = new BehaviorSubject<JsonValue | undefined>(undefined);\n this.attrs.set(key, o);\n }\n\n return o;\n }\n}\n","import {\n ChangePayload,\n ParticipantChange,\n SetAttributeInput,\n SubAttributesPayload,\n} from \"@empirica/tajriba\";\nimport { Observable, Subject, groupBy } from \"rxjs\";\nimport { AttributeChange, AttributeUpdate } from \"../shared/attributes\";\nimport { ScopeIdent, ScopeUpdate } from \"../shared/scopes\";\nimport { trace } from \"../utils/console\";\nimport { StepChange, StepUpdate } from \"./steps\";\n\nexport interface ParticipantUpdate {\n participant: ParticipantChange;\n removed: boolean;\n}\n\nexport class TajribaProvider {\n public scopes = new Subject<ScopeUpdate>();\n public attributes = new Subject<AttributeUpdate>();\n public participants = new Subject<ParticipantUpdate>();\n public steps = new Subject<StepUpdate>();\n public dones = new Subject<string[]>();\n\n constructor(\n changes: Observable<ChangePayload>,\n readonly globals: Observable<SubAttributesPayload>,\n readonly setAttributes: (input: SetAttributeInput[]) => Promise<any>\n ) {\n let scopeIDs: string[] = [];\n changes.pipe(groupBy((chg) => chg?.change?.__typename)).subscribe({\n next: (group) => {\n switch (group.key) {\n case \"ScopeChange\":\n group.subscribe({\n next: (msg) => {\n if (\n !msg.change ||\n msg.removed === null ||\n msg.removed === undefined\n ) {\n trace(\"AttributeChange empty\");\n } else {\n this.scopes.next({\n scope: <ScopeIdent>msg.change,\n removed: msg.removed,\n });\n }\n\n if (msg.done) {\n this.dones.next(scopeIDs);\n }\n },\n });\n\n break;\n case \"AttributeChange\":\n group.subscribe({\n next: (msg) => {\n if (\n !msg.change ||\n msg.removed === null ||\n msg.removed === undefined\n ) {\n trace(\"AttributeChange empty\");\n } else {\n const atChange = <AttributeChange>msg.change;\n scopeIDs.push(atChange.nodeID || atChange.node!.id);\n this.attributes.next({\n attribute: atChange,\n removed: msg.removed,\n });\n }\n\n if (msg.done) {\n this.dones.next(scopeIDs);\n scopeIDs = [];\n }\n },\n });\n\n break;\n case \"ParticipantChange\":\n group.subscribe({\n next: (msg) => {\n if (\n !msg.change ||\n msg.removed === null ||\n msg.removed === undefined\n ) {\n trace(\"ParticipantChange empty\");\n } else {\n this.participants.next({\n participant: <ParticipantChange>msg.change,\n removed: msg.removed,\n });\n }\n\n if (msg.done) {\n this.dones.next([]);\n }\n },\n });\n\n break;\n case \"StepChange\":\n group.subscribe({\n next: (msg) => {\n if (\n !msg.change ||\n msg.removed === null ||\n msg.removed === undefined\n ) {\n trace(\"StepChange empty\");\n } else {\n this.steps.next({\n step: <StepChange>msg.change,\n removed: msg.removed,\n });\n }\n\n if (msg.done) {\n this.dones.next([]);\n }\n },\n });\n\n break;\n default:\n group.subscribe({\n next: (change) => {\n if (change.done) {\n this.dones.next([]);\n }\n },\n });\n\n break;\n }\n },\n });\n }\n}\n","import { BehaviorSubject, Observable } from \"rxjs\";\nimport { Attribute, AttributeOptions, Attributes } from \"../shared/attributes\";\nimport { Constructor } from \"../shared/helpers\";\nimport { warn } from \"../utils/console\";\nimport { JsonValue } from \"../utils/json\";\n\nexport type Attributable = {\n get: (key: string) => JsonValue | undefined;\n getAttribute: (key: string) => Attribute | undefined;\n set: (key: string, value: JsonValue, ao?: Partial<AttributeOptions>) => void;\n append: (\n key: string,\n value: JsonValue,\n ao?: Partial<AttributeOptions>\n ) => void;\n};\n\nexport interface ScopeIdent {\n id: string;\n kind: string;\n}\n\nexport interface ScopeUpdate {\n scope: ScopeIdent;\n removed: boolean;\n}\n\nexport type ScopeConstructor<\n Context,\n Kinds extends { [key: string]: ScopeConstructor<Context, Kinds> }\n> = Constructor<Scope<Context, Kinds>>;\n\nexport class Scopes<\n Context,\n Kinds extends { [key: string]: ScopeConstructor<Context, Kinds> },\n Skope extends Scope<Context, Kinds> = Scope<Context, Kinds>\n> {\n protected scopes = new Map<string, BehaviorSubject<Skope>>();\n // newScopes is used to track scopes that have appeared for the first time.\n protected newScopes = new Map<string, boolean>();\n protected scopesByKind = new Map<keyof Kinds, Map<string, Skope>>();\n protected kindUpdated = new Set<keyof Kinds>();\n\n constructor(\n scopesObs: Observable<ScopeUpdate>,\n donesObs: Observable<string[]>,\n protected ctx: Context,\n protected kinds: Kinds,\n protected attributes: Attributes\n ) {\n scopesObs.subscribe({\n next: ({ scope, removed }) => {\n this.update(scope, removed);\n },\n });\n\n donesObs.subscribe({\n next: (scopeIDs) => {\n this.next(scopeIDs);\n },\n });\n }\n\n scope(id: string): Skope | undefined {\n return this.scopes.get(id)?.getValue();\n }\n\n scopeObs(id: string): Observable<Skope> | undefined {\n return this.scopes.get(id);\n }\n\n byKind<T extends Skope>(kind: keyof Kinds) {\n let map = this.scopesByKind.get(kind);\n if (!map) {\n map = new Map();\n this.scopesByKind.set(kind, map);\n }\n\n return map! as Map<string, T>;\n }\n\n kindWasUpdated(kind: keyof Kinds): boolean {\n return this.kindUpdated.has(kind);\n }\n\n protected next(scopeIDs: string[]) {\n this.kindUpdated.clear();\n for (const [_, scopeSubject] of this.scopes) {\n const scope = scopeSubject.getValue();\n if (\n (scope._updated || this.attributes.scopeWasUpdated(scope.id)) &&\n scopeIDs.includes(scope.id)\n ) {\n scope._updated = false;\n scopeSubject.next(scope);\n }\n }\n }\n\n protected update(scope: ScopeIdent, removed: boolean) {\n const existing = this.scopes.get(scope.id)?.getValue();\n\n if (removed) {\n if (!existing) {\n warn(\"scopes: missing scope on removal\", scope.id, scope.kind);\n\n return;\n }\n\n existing._deleted = true;\n existing._updated = true;\n this.scopes.delete(scope.id);\n\n if (!scope.kind) {\n warn(\"scopes: scope missing kind on scope on removal\");\n\n return;\n }\n\n const kind = scope.kind as keyof Kinds;\n\n // Using ! because scopes by kind must exist, since this scope was found.\n this.scopesByKind.get(kind)!.delete(scope.id);\n\n this.kindUpdated.add(kind);\n\n return;\n }\n\n if (existing) {\n existing._deleted = false;\n return;\n }\n\n if (!scope.kind) {\n warn(\"scopes: scope missing kind on scope\");\n\n return;\n }\n\n const kind = scope.kind as keyof Kinds;\n const scopeClass = this.kinds[kind];\n if (!scopeClass) {\n warn(`scopes: unknown scope kind: ${scope.kind}`);\n\n return;\n }\n\n const obj = this.create(scopeClass, scope);\n const subj = new BehaviorSubject(obj);\n this.scopes.set(scope.id, subj);\n this.newScopes.set(scope.id, true);\n\n let skm = this.scopesByKind.get(kind);\n if (!skm) {\n skm = new Map();\n this.scopesByKind.set(kind, skm);\n }\n\n skm.set(scope.id, obj);\n\n obj._updated = true;\n this.kindUpdated.add(kind);\n }\n\n protected create(\n scopeClass: ScopeConstructor<Context, Kinds>,\n scope: ScopeIdent\n ) {\n return new scopeClass!(this.ctx, scope, this.attributes) as Skope;\n }\n}\n\nexport type AttributeInput = {\n key: string;\n value: JsonValue;\n ao?: Partial<AttributeOptions>;\n};\n\nexport class Scope<\n Context,\n Kinds extends { [key: string]: ScopeConstructor<Context, Kinds> }\n> {\n /**\n * @internal\n */\n _deleted = false;\n\n /**\n * @internal\n */\n _updated = false;\n\n constructor(\n /**\n * @internal\n */\n readonly ctx: Context,\n /**\n * @internal\n */\n readonly scope: ScopeIdent,\n /**\n * @internal\n */\n protected attributes: Attributes\n ) {}\n\n get id() {\n return this.scope.id;\n }\n\n /**\n * @internal\n */\n get kind() {\n // Using ! because we don't allow scopes without kind\n return this.scope.kind!;\n }\n\n get(key: string): JsonValue | undefined {\n return this.attributes.attribute(this.scope.id, key).value;\n }\n\n getAttribute(key: string): Attribute | undefined {\n return this.attributes.attribute(this.scope.id, key);\n }\n\n obs(key: string): Observable<JsonValue | undefined> {\n return this.attributes.attribute(this.scope.id, key).obs;\n }\n\n set(values: AttributeInput[]): void;\n set(key: string, value: JsonValue, ao?: Partial<AttributeOptions>): void;\n set(\n keyOrAttributes: string | AttributeInput[],\n value?: JsonValue,\n ao?: Partial<AttributeOptions>\n ) {\n if (typeof keyOrAttributes === \"string\") {\n if (value === undefined) {\n value = null;\n }\n\n return this.attributes\n .attribute(this.scope.id, keyOrAttributes)\n .set(value, ao);\n }\n\n const nextProps = [];\n for (const attr of keyOrAttributes) {\n const at = this.attributes\n .attribute(this.scope.id, attr.key)\n ._prepSet(attr.value, attr.ao);\n if (!at) {\n continue;\n }\n\n nextProps.push(at);\n }\n\n if (nextProps.length === 0) {\n return;\n }\n\n this.attributes.setAttributes(nextProps);\n }\n\n append(key: string, value: JsonValue, ao?: Partial<AttributeOptions>) {\n if (!ao) {\n ao = {};\n }\n ao.append = true;\n\n return this.attributes.attribute(this.scope.id, key).set(value, ao);\n }\n\n inspect() {\n const attrs = this.attributes.attributes(this.scope.id);\n\n const out: { [key: string]: JsonValue | undefined } = {};\n for (const attr of attrs) {\n out[attr.key] = attr.value;\n }\n\n return out;\n }\n\n /**\n * @internal\n */\n hasUpdated() {\n return this._updated || this.attributes.scopeWasUpdated(this.id);\n }\n}\n","import { Observable } from \"rxjs\";\nimport { Attributes } from \"../shared/attributes\";\nimport {\n Scope as SharedScope,\n ScopeConstructor,\n ScopeIdent,\n Scopes as SharedScopes,\n ScopeUpdate,\n} from \"../shared/scopes\";\nimport { Steps } from \"./steps\";\n\nexport class Scopes<\n Context,\n Kinds extends { [key: string]: ScopeConstructor<Context, Kinds> }\n> extends SharedScopes<Context, Kinds, Scope<Context, Kinds>> {\n constructor(\n scopesObs: Observable<ScopeUpdate>,\n donesObs: Observable<string[]>,\n ctx: Context,\n kinds: Kinds,\n attributes: Attributes,\n private steps: Steps\n ) {\n super(scopesObs, donesObs, ctx, kinds, attributes);\n }\n\n protected create(\n scopeClass: ScopeConstructor<Context, Kinds>,\n scope: ScopeIdent\n ) {\n return new scopeClass!(\n this.ctx,\n scope,\n this,\n this.attributes,\n this.steps\n ) as Scope<Context, Kinds>;\n }\n}\n\nexport class Scope<\n Context,\n Kinds extends { [key: string]: ScopeConstructor<Context, Kinds> }\n> extends SharedScope<Context, Kinds> {\n constructor(\n ctx: Context,\n scope: ScopeIdent,\n readonly scopes: Scopes<Context, Kinds>,\n attributes: Attributes,\n private steps: Steps\n ) {\n super(ctx, scope, attributes);\n }\n\n scopeByKey(key: string) {\n const id = this.get(key);\n if (!id || typeof id !== \"string\") {\n return;\n }\n\n return this.scopes.scope(id);\n }\n\n protected ticker(id: string) {\n return this.steps.step(id);\n }\n\n protected tickerByKey(key: string) {\n const id = this.get(key);\n if (!id || typeof id !== \"string\") {\n return;\n }\n\n return this.ticker(id);\n }\n}\n","import { BehaviorSubject, map, Observable } from \"rxjs\";\n\nexport interface StepChange {\n id: string;\n running: boolean;\n elapsed?: number;\n remaining?: number;\n}\n\nexport interface StepUpdate {\n step: StepChange;\n removed: boolean;\n}\n\nexport interface StepTick {\n started: boolean;\n ended: boolean;\n elapsed: number;\n remaining: number;\n duration: number;\n}\n\ntype schds = {\n cb: (args: void) => void;\n from: number;\n dur: number;\n};\n\nlet scheduled: schds[] = [];\n\nexport let mockNow: number | null = null;\nexport function setNow(now: number) {\n if (mockNow && mockNow > now) {\n if (now === 0) {\n // This is a reset scheduled should be empty\n scheduled = [];\n /* c8 ignore next 3 */\n } else {\n throw \"time must move forward\";\n }\n }\n\n mockNow = now;\n\n const rescheduled: schds[] = [];\n for (const s of scheduled) {\n if (mockNow >= s.from + s.dur) {\n s.cb();\n } else {\n rescheduled.push(s);\n }\n }\n\n scheduled = rescheduled;\n}\n\nfunction pnow() {\n if (mockNow !== null) {\n return mockNow;\n } else {\n // TODO sync time\n return performance.now();\n }\n}\n\nfunction timeout(callback: (args: void) => void, ms: number) {\n if (mockNow !== null) {\n const schd = {\n cb: callback,\n from: mockNow,\n dur: ms,\n };\n scheduled.push(schd);\n } else {\n setTimeout(callback, ms);\n }\n}\n\n// The number of milliseconds elapsed since January 1, 1970 00:00:00 UTC, with\n// leap seconds ignored.\nexport type Epoch = number;\n\nexport class Step {\n private running = false;\n private ticker = new BehaviorSubject<StepTick | undefined>(undefined);\n private startAt: number = 0;\n private endAt: number = 0;\n\n constructor(step: StepChange, ticker: Observable<DOMHighResTimeStamp>) {\n ticker.pipe(map(this.recalc.bind(this))).subscribe({\n next: (val) => {\n this.ticker.next(val);\n },\n });\n\n this._update(step);\n }\n\n private recalc(t: DOMHighResTimeStamp) {\n if (!this.running) {\n return undefined;\n }\n\n return {\n started: t >= this.startAt,\n ended: t >= this.endAt,\n elapsed: Math.round(t - this.startAt),\n remaining: Math.round(this.endAt - t),\n duration: this.endAt - this.startAt,\n } as StepTick;\n }\n\n obs(): Observable<StepTick | undefined> {\n return this.ticker;\n }\n\n get current() {\n return this.recalc(pnow());\n }\n\n // internal only\n _update(step: StepChange) {\n if (!step.running) {\n this.running = false;\n this.ticker.next(undefined);\n\n return;\n }\n\n if (\n step.elapsed === null ||\n step.remaining === null ||\n step.elapsed === undefined ||\n step.remaining === undefined\n ) {\n this.running = false;\n\n return;\n }\n\n const now = pnow();\n\n this.startAt = now - step.elapsed * 1000;\n this.endAt = now + step.remaining * 1000;\n this.running = step.elapsed >= 0 && step.remaining >= 0;\n\n this.ticker.next(this.recalc(now));\n }\n\n // internal only\n _stop() {\n this.running = false;\n this.ticker.next(undefined);\n }\n}\n\nexport class Steps {\n private steps = new Map<string, Step>();\n private updates = new Map<string, StepChange | boolean>();\n private _hadUpdates = false;\n\n private ticker: BehaviorSubject<Epoch>;\n\n constructor(stepsObs: Observable<StepUpdate>, donesObs: Observable<void>) {\n stepsObs.subscribe({\n next: ({ step, removed }) => {\n this.update(step, removed);\n },\n });\n\n donesObs.subscribe({\n next: () => {\n this.next();\n },\n });\n\n this.ticker = new BehaviorSubject<Epoch>(Math.floor(pnow()));\n const controller = new AbortController();\n timerInterval(1000, controller.signal, (t) => {\n this.ticker.next(t);\n });\n }\n\n step(stepID: string): Step | undefined {\n return this.steps.get(stepID);\n }\n\n hadUpdates() {\n const hadUpdates = this._hadUpdates;\n this._hadUpdates = false;\n\n return hadUpdates;\n }\n\n private update(step: StepChange, removed: boolean) {\n if (removed) {\n this.updates.set(step.id, true);\n } else {\n this.updates.set(step.id, step);\n }\n\n this._hadUpdates = true;\n }\n\n private next() {\n for (const [id, stepOrDel] of this.updates) {\n let step = this.steps.get(id);\n if (typeof stepOrDel === \"boolean\") {\n if (step) {\n step._stop();\n this.steps.delete(id);\n }\n } else {\n if (!step) {\n step = new Step(stepOrDel, this.ticker);\n this.steps.set(id, step);\n }\n\n step._update(stepOrDel);\n }\n }\n\n this.updates.clear();\n }\n}\n\n/* c8 ignore next 6 */\nexport const root: any =\n typeof self === \"object\" && self.self == self\n ? self\n : typeof global === \"object\" && global.global == global\n ? global\n : {};\n\n// nodejs support\nif (!root[\"requestAnimationFrame\"]) {\n type timecb = (t: DOMHighResTimeStamp) => void;\n root[\"requestAnimationFrame\"] = (cb: timecb) => cb(pnow());\n}\n\n// Inspiration:\n// https://www.youtube.com/watch?v=MCi6AZMkxcU\n// https://gist.github.com/jakearchibald/cb03f15670817001b1157e62a076fe95\nfunction timerInterval(\n ms: number = 1000,\n signal: AbortSignal,\n callback: (time: number) => void\n) {\n // Performance should be available in Nodejs 10+.\n // Get the last rounded second, which will go negative, but that's fine, since\n // it will immediately send out its first tick, then be on the second.\n const start = Math.floor(pnow() / 1000) * 1000;\n\n function frame(time: number) {\n /* c8 ignore next */\n if (signal.aborted) return;\n callback(time);\n scheduleFrame(time);\n }\n\n function scheduleFrame(time: number) {\n const elapsed = time - start;\n const roundedElapsed = Math.round(elapsed / ms) * ms;\n const targetNext = start + roundedElapsed + ms;\n const delay = targetNext - pnow();\n timeout(() => requestAnimationFrame(frame), delay);\n }\n\n scheduleFrame(start);\n}\n\n// export function useAnimationInterval(\n// ms: number,\n// callback: (time: number) => void\n// ) {\n// const callbackRef = React.useRef(callback);\n// React.useEffect(() => {\n// callbackRef.current = callback;\n// }, [callback]);\n\n// React.useEffect(() => {\n// const controller = new AbortController();\n// animationInterval(ms, controller.signal, callbackRef.current);\n// return () => controller.abort();\n// }, [ms]);\n// }\n","export const isDevelopment = process.env.NODE_ENV === \"development\";\nexport const isProduction = process.env.NODE_ENV === \"production\";\nexport const isTest = process.env.NODE_ENV === \"test\";\n\nexport const createNewParticipant = (key = \"participantKey\") => {\n const url = new URL(document.location.href);\n url.searchParams.set(key, new Date().getTime().toString());\n window.open(url.href, \"_blank\")?.focus();\n};\n","import { BehaviorSubject, Observable, Subject } from \"rxjs\";\nimport { Attributes } from \"../../shared/attributes\";\nimport { Globals } from \"../../shared/globals\";\nimport { Constructor } from \"../../shared/helpers\";\nimport { TajribaProvider } from \"../provider\";\nimport { Scope, Scopes } from \"../scopes\";\nimport { Steps } from \"../steps\";\n\nexport const endedStatuses = [\"ended\", \"terminated\", \"failed\"];\nexport type EndedStatuses = typeof endedStatuses[number];\n\nexport class Game extends Scope<Context, EmpiricaClassicKinds> {\n get hasEnded() {\n return endedStatuses.includes(this.get(\"status\") as EndedStatuses);\n }\n\n get stage() {\n return this.scopeByKey(\"stageID\") as Stage | undefined;\n }\n\n get round() {\n return this.stage?.round;\n }\n}\n\nexport class Player extends Scope<Context, EmpiricaClassicKinds> {\n get game() {\n const { game } = this.ctx;\n if (!game) {\n return;\n }\n\n const key = `playerGameID-${game.id}`;\n return this.scopeByKey(key) as PlayerGame | undefined;\n }\n\n get round() {\n const { stage } = this.ctx;\n if (!stage) {\n return;\n }\n\n const { round } = stage;\n if (!round) {\n return;\n }\n\n const key = `playerRoundID-${round.id}`;\n return this.scopeByKey(key) as PlayerRound | undefined;\n }\n\n get stage() {\n const { stage } = this.ctx;\n if (!stage) {\n return;\n }\n\n const key = `playerStageID-${stage.id}`;\n return this.scopeByKey(key) as PlayerStage | undefined;\n }\n\n hasUpdated() {\n if (super.hasUpdated()) {\n return true;\n }\n\n return Boolean(\n this.round?.hasUpdated() ||\n this.stage?.hasUpdated() ||\n this.game?.hasUpdated()\n );\n }\n}\n\nexport class PlayerGame extends Scope<Context, EmpiricaClassicKinds> {}\n\nexport class PlayerRound extends Scope<Context, EmpiricaClassicKinds> {}\n\nexport class PlayerStage extends Scope<Context, EmpiricaClassicKinds> {}\n\nexport class Round extends Scope<Context, EmpiricaClassicKinds> {}\n\nexport class Stage extends Scope<Context, EmpiricaClassicKinds> {\n get round() {\n return this.scopeByKey(\"roundID\") as Round | undefined;\n }\n\n get timer() {\n return this.tickerByKey(\"timerID\");\n }\n}\n\n// TODO update context\nexport class Context {\n public game?: Game | null;\n public stage?: Stage | null;\n}\n\nexport type EmpiricaClassicKinds = {\n game: Constructor<Game>;\n player: Constructor<Player>;\n playerGame: Constructor<PlayerGame>;\n playerRound: Constructor<PlayerRound>;\n playerStage: Constructor<PlayerStage>;\n round: Constructor<Round>;\n stage: Constructor<Stage>;\n};\n\nexport const kinds = {\n game: Game,\n player: Player,\n playerGame: PlayerGame,\n playerRound: PlayerRound,\n playerStage: PlayerStage,\n round: Round,\n stage: Stage,\n};\n\nexport type EmpiricaClassicContext = {\n game: BehaviorSubject<Game | null | undefined>;\n player: BehaviorSubject<Player | null | undefined>;\n players: BehaviorSubject<Player[] | undefined>;\n round: BehaviorSubject<Round | null | undefined>;\n stage: BehaviorSubject<Stage | null | undefined>;\n globals: BehaviorSubject<Globals | undefined>;\n};\n\nexport function EmpiricaClassic(\n participantID: string,\n provider: TajribaProvider\n): EmpiricaClassicContext {\n const attributesDones = new Subject<string[]>();\n const scopesDones = new Subject<string[]>();\n\n const ctx = new Context();\n const attributes = new Attributes(\n provider.attributes,\n attributesDones,\n provider.setAttributes\n );\n const steps = new Steps(\n provider.steps,\n provider.dones as unknown as Observable<void>\n );\n const scopes = new Scopes(\n provider.scopes,\n scopesDones,\n ctx,\n kinds,\n attributes,\n steps\n );\n const participantIDs = new Set<string>();\n\n const glob = new Globals(provider.globals);\n\n const ret = {\n game: new BehaviorSubject<Game | null | undefined>(undefined),\n player: new BehaviorSubject<Player | null | undefined>(undefined),\n players: new BehaviorSubject<Player[] | undefined>(undefined),\n round: new BehaviorSubject<Round | null | undefined>(undefined),\n stage: new BehaviorSubject<Stage | null | undefined>(undefined),\n globals: glob.self,\n };\n\n provider.participants.subscribe({\n next: ({ participant, removed }) => {\n if (removed) {\n if (participantIDs.has(participant.id)) {\n participantIDs.delete(participant.id);\n }\n } else {\n if (!participantIDs.has(participant.id)) {\n participantIDs.add(participant.id);\n }\n }\n },\n });\n\n let scopesUpdated = new Set<string>();\n provider.attributes.subscribe({\n next: (attr) => {\n const nodeID = attr.attribute.node?.id || attr.attribute.nodeID;\n if (!nodeID) {\n return;\n }\n\n scopesUpdated.add(nodeID);\n },\n });\n\n provider.dones.subscribe({\n next: () => {\n const current = getCurrent(ret);\n const updated = getMainObjects(participantID, scopes, attributes);\n ctx.game = updated.game;\n ctx.stage = updated.stage;\n\n if (scopeChanged(current.game, updated.game)) {\n ret.game.next(updated.game);\n }\n\n if (scopeChanged(current.player, updated.player)) {\n ret.player.next(updated.player);\n }\n\n if (scopeChanged(current.round, updated.round)) {\n ret.round.next(updated.round);\n }\n\n if (scopeChanged(current.stage, updated.stage) || steps.hadUpdates()) {\n ret.stage.next(updated.stage);\n }\n\n let playersChanged = false;\n const players: Player[] = [];\n for (let i = 0; i < (updated.players || []).length; i++) {\n let p = updated.players![i];\n\n if (p) {\n const partID = attributes.nextAttributeValue(\n p.id,\n \"participantID\"\n ) as string;\n if (!participantIDs.has(partID)) {\n p = undefined;\n }\n }\n\n if (!playersChanged && scopeChanged(p, (current.players || [])[i])) {\n playersChanged = true;\n }\n\n if (\n !playersChanged &&\n scopeChanged(p?.stage, (current.players || [])[i]?.stage)\n ) {\n playersChanged = true;\n }\n\n if (\n !playersChanged &&\n scopeChanged(p?.round, (current.players || [])[i]?.round)\n ) {\n playersChanged = true;\n }\n\n if (\n !playersChanged &&\n scopeChanged(p?.game, (current.players || [])[i]?.game)\n ) {\n playersChanged = true;\n }\n\n if (p) {\n players.push(p);\n }\n }\n\n if (playersChanged) {\n ret.players.next(players);\n }\n\n const scopeIDs = Array.from(scopesUpdated);\n scopesDones.next(scopeIDs);\n attributesDones.next(scopeIDs);\n scopesUpdated.clear();\n },\n });\n\n return ret;\n}\n\ntype mainObjects = {\n game?: Game | null;\n player?: Player | null;\n round?: Round | null;\n stage?: Stage | null;\n players?: Player[];\n};\n\nfunction scopeChanged(\n current?: Scope<Context, EmpiricaClassicKinds> | null,\n updated?: Scope<Context, EmpiricaClassicKinds> | null\n): boolean {\n if (!current && !updated) {\n if (current === undefined && updated === null) {\n return true;\n }\n\n return false;\n }\n\n if (!current || !updated) {\n return true;\n }\n\n return current.id !== updated.id || updated.hasUpdated();\n}\n\nfunction getCurrent(ctx: EmpiricaClassicContext): mainObjects {\n return {\n game: ctx.game.getValue(),\n player: ctx.player.getValue(),\n round: ctx.round.getValue(),\n stage: ctx.stage.getValue(),\n players: ctx.players.getValue(),\n };\n}\n\nfunction getMainObjects(\n participantID: string,\n scopes: Scopes<Context, EmpiricaClassicKinds>,\n attributes: Attributes\n): mainObjects {\n const players = Array.from(scopes.byKind(\"player\").values()) as Player[];\n players.sort();\n\n const res: mainObjects = {\n players,\n game: null,\n player: null,\n round: null,\n stage: null,\n };\n\n if (players.length === 0) {\n return res;\n }\n\n res.player = players.find((p) => {\n const pID = attributes.nextAttributeValue(p.id, \"participantID\") as string;\n return pID === participantID;\n }) as Player;\n\n if (!res.player) {\n return res;\n }\n\n res.game = nextScopeByKey(scopes, attributes, res.player, \"gameID\") as Game;\n if (!res.game) {\n return res;\n }\n\n for (const player of players || []) {\n const key = `playerGameID-${res.game.id}`;\n if (!nextScopeByKey(scopes, attributes, player, key)) {\n return res;\n }\n }\n\n res.stage = nextScopeByKey(scopes, attributes, res.game, \"stageID\") as Stage;\n if (!res.stage) {\n return res;\n }\n\n for (const player of players || []) {\n const key = `playerStageID-${res.stage.id}`;\n if (!nextScopeByKey(scopes, attributes, player, key)) {\n delete res.stage;\n return res;\n }\n }\n\n res.round = nextScopeByKey(scopes, attributes, res.stage, \"roundID\") as Round;\n if (!res.round) {\n return res;\n }\n\n for (const player of players || []) {\n const key = `playerRoundID-${res.round.id}`;\n if (!nextScopeByKey(scopes, attributes, player, key)) {\n delete res.stage;\n delete res.round;\n return res;\n }\n }\n\n return res;\n}\n\nfunction nextScopeByKey(\n scopes: Scopes<Context, EmpiricaClassicKinds>,\n attributes: Attributes,\n scope: Scope<Context, EmpiricaClassicKinds>,\n key: string\n) {\n const id = attributes.nextAttributeValue(scope.id, key);\n if (!id || typeof id !== \"string\") {\n return null;\n }\n\n return scopes.scope(id) || null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAAAA;AAAA,EAAA,cAAAC;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,kBAA4C;;;ACC5C,IAAM,YACJ,OAAO,WAAW,eAAe,OAAO,OAAO,aAAa;AA8B9D,IAAI;AAiCJ,IAAM,WAAW;AAAA,EACf,CAAC,YAAU,GAAG;AAAA,EACd,CAAC,cAAW,GAAG;AAAA,EACf,CAAC,YAAS,GAAG;AAAA,EACb,CAAC,cAAW,GAAG;AAAA,EACf,CAAC,eAAY,GAAG;AAAA,EAChB,CAAC,aAAU,GAAG;AAAA,EACd,CAAC,gBAAa,GAAG;AAAA,EACjB,CAAC,aAAU,GAAG;AAAA,EACd,CAAC,cAAW,GAAG;AAAA,EACf,CAAC,iBAAc,GAAG;AACpB;AAEO,IAAM,SAAoC;AAAA,EAC/C,OAAO;AAAA,EACP,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAEA,IAAM,eAA0C,CAAC;AACjD,WAAW,OAAO,QAAQ;AACxB,eAAa,OAAO,GAAG,CAAE,IAAI;AAC/B;AAEA,IAAI,eAAe;AAWnB,SAAS,kBAAkB,MAAY,OAAiB;AACtD,MAAI,OAAO,KAAK,SAAS;AACzB,MAAI,UAAU,KAAK,WAAW;AAC9B,MAAI,UAAU,KAAK,WAAW;AAC9B,MAAI,eAAe,KAAK,gBAAgB;AAExC,QAAM,OACH,OAAO,KAAK,MAAM,OAAO,QAC1B,OACC,UAAU,KAAK,MAAM,UAAU,WAChC,OACC,UAAU,KAAK,MAAM,UAAU,WAChC,OACC,OAAO,cAAc,MAAM,EAAE;AAEhC,MAAI,WAAW;AACb,UAAM,KAAK,SAAS,KAAK,iBAAc,EAAE,OAAO,KAAK;AACrD,WAAO,CAAC,GAAG,CAAC,IAAI,MAAM,MAAM,CAAC,GAAG,GAAG