@popp0102/questify 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/questify.js CHANGED
@@ -1,196 +1,280 @@
1
1
  import { jsx as T } from "react/jsx-runtime";
2
- import { createContext as L, useState as A, useEffect as N, useContext as R } from "react";
3
- const O = {}, j = {
4
- store: async (r, e) => {
5
- O[r] = e;
2
+ import { createContext as Q, useState as I, useEffect as q, useContext as B } from "react";
3
+ const g = {}, F = {
4
+ store: async (i, t) => {
5
+ g[i] = t;
6
6
  },
7
- load: async (r) => O[r]
7
+ load: async (i) => g[i]
8
8
  };
9
- class w {
10
- constructor(e, t, n, o, i) {
11
- this.id = e, this.type = t, this.title = n, this.solution = o, this.blueprint = i, this.isSolved = !1;
9
+ class y {
10
+ constructor(t, e, r, s, c) {
11
+ this.id = t, this.type = e, this.title = r, this.solution = s, this.blueprint = c, this.isSolved = !1;
12
12
  }
13
- clone({ isSolved: e }) {
14
- const t = Object.create(w.prototype);
15
- return t.id = this.id, t.type = this.type, t.title = this.title, t.solution = this.solution, t.blueprint = this.blueprint, t.isSolved = e, t;
13
+ clone({ isSolved: t }) {
14
+ const e = Object.create(y.prototype);
15
+ return e.id = this.id, e.type = this.type, e.title = this.title, e.solution = this.solution, e.blueprint = this.blueprint, e.isSolved = t, e;
16
16
  }
17
17
  markAsSolved() {
18
18
  return this.clone({ isSolved: !0 });
19
19
  }
20
20
  }
21
- class v {
22
- constructor(e) {
23
- this.name = e.name, this.trials = e.trials.map(
24
- (t, n) => new w(n, t.type, t.title, t.solution, t.blueprint)
25
- );
26
- }
27
- clone(e) {
28
- const t = Object.create(v.prototype);
29
- return t.name = this.name, t.trials = e.trials ?? this.trials, t;
30
- }
31
- submitAnswer(e, t) {
32
- const n = this.trials.find((o) => o.id === e);
33
- if (!n)
21
+ class h {
22
+ constructor(t) {
23
+ this.id = t.id, this.name = t.name, this.trials = t.trials.map(
24
+ (e, r) => new y(r, e.type, e.title, e.solution, e.blueprint)
25
+ ), this.updateStatus();
26
+ }
27
+ static constructFromTrials(t, e, r) {
28
+ const s = Object.create(h.prototype);
29
+ return s.id = t, s.name = e, s.trials = r, s.updateStatus(), s;
30
+ }
31
+ submitAnswer(t, e) {
32
+ const r = this.trials.find((s) => s.id === t);
33
+ if (!r)
34
34
  return null;
35
- if (n.solution === t.trim().toLowerCase()) {
36
- const o = this.trials.map(
37
- (i) => i.id === e ? i.markAsSolved() : i
35
+ if (r.solution === e.trim().toLowerCase()) {
36
+ const s = this.trials.map(
37
+ (c) => c.id === t ? c.markAsSolved() : c
38
38
  );
39
- return this.clone({ trials: o });
39
+ return this.clone({ trials: s });
40
40
  }
41
41
  return null;
42
42
  }
43
+ updateStatus() {
44
+ const t = this.trials.filter((s) => s.isSolved).length, e = this.trials.length;
45
+ let r;
46
+ t === 0 ? r = "not-started" : t === e ? r = "completed" : r = "in-progress", this.progressSummary = {
47
+ status: r,
48
+ solvedCount: t,
49
+ totalTrials: e,
50
+ percentageComplete: e > 0 ? t / e * 100 : 0
51
+ };
52
+ }
53
+ clone(t) {
54
+ const e = Object.create(h.prototype);
55
+ return e.id = this.id, e.name = this.name, e.trials = t.trials ?? this.trials, e.updateStatus(), e;
56
+ }
43
57
  }
44
- const g = -1;
45
- class b {
46
- constructor({ numScreens: e }) {
47
- this.numScreens = e, this.screen = g;
58
+ class H {
59
+ constructor(t = null, e = null) {
60
+ this.activeHuntId = t, this.activeTrialIndex = e;
61
+ }
62
+ clone() {
63
+ const t = Object.create(H.prototype);
64
+ return t.activeHuntId = this.activeHuntId, t.activeTrialIndex = this.activeTrialIndex, t;
65
+ }
66
+ isOnHuntSelection() {
67
+ return this.activeHuntId === null;
68
+ }
69
+ isOnTrialSelection() {
70
+ return this.activeHuntId !== null && this.activeTrialIndex === null;
48
71
  }
49
- clone({ screen: e }) {
50
- const t = Object.create(b.prototype);
51
- return t.numScreens = this.numScreens, t.screen = e, t;
72
+ isOnActiveTrial() {
73
+ return this.activeHuntId !== null && this.activeTrialIndex !== null;
52
74
  }
53
- get value() {
54
- return this.screen;
75
+ moveToHuntSelection() {
76
+ const t = this.clone();
77
+ return t.activeHuntId = null, t.activeTrialIndex = null, t;
55
78
  }
56
- isOnSelectScreen() {
57
- return this.screen === g;
79
+ moveToHunt(t) {
80
+ const e = this.clone();
81
+ return e.activeHuntId = t, e.activeTrialIndex = null, e;
58
82
  }
59
- moveToSelectScreen() {
60
- return this.clone({ screen: g });
83
+ moveToTrialSelectScreen() {
84
+ if (this.activeHuntId === null)
85
+ throw new Error("Cannot move to trial select when no hunt is active");
86
+ const t = this.clone();
87
+ return t.activeTrialIndex = null, t;
61
88
  }
62
- moveToTrialScreen(e) {
63
- return this.clone({ screen: e });
89
+ moveToTrialScreen(t) {
90
+ if (this.activeHuntId === null)
91
+ throw new Error("Cannot move to trial when no hunt is active");
92
+ const e = this.clone();
93
+ return e.activeTrialIndex = t, e;
64
94
  }
65
95
  }
66
- const P = "questify-solved-trials", x = L({
67
- hunt: null,
68
- submitAnswer: () => {
96
+ const b = Q({
97
+ hunts: [],
98
+ playerLocation: null,
99
+ activeHunt: () => null,
100
+ activeTrial: () => null,
101
+ isOnHuntSelection: () => !1,
102
+ isOnTrialSelection: () => !1,
103
+ isOnActiveTrial: () => !1,
104
+ activateHunt: () => {
69
105
  },
70
- selectedTrial: () => {
106
+ moveToHuntSelection: () => {
71
107
  },
72
- playerOnTrialSelectScreen: () => {
108
+ moveToTrialSelectScreen: () => {
73
109
  },
74
- moveToSelectScreen: () => {
110
+ moveToTrialScreen: () => {
75
111
  },
76
- moveToScreen: () => {
112
+ submitAnswer: () => {
77
113
  }
78
114
  });
79
- function M({ huntConfig: r, persister: e, children: t }) {
80
- const n = e || j, [o, i] = A(new v(r)), [s, c] = A(new b({ numScreens: r.trials.length }));
81
- N(() => {
82
- async function d() {
83
- const p = await n.load(P);
84
- if (p) {
85
- const S = JSON.parse(p), h = new v(r), m = h.trials.map(
86
- (f) => S.includes(f.id) ? f.markAsSolved() : f
87
- );
88
- h.trials = m, i(h);
89
- }
90
- }
91
- d();
92
- }, [n, r]);
93
- async function l(d) {
94
- const p = a(), S = o.submitAnswer(p.id, d);
95
- if (S) {
96
- i(S);
97
- const h = S.trials.filter((m) => m.isSolved).map((m) => m.id);
98
- return await n.store(P, JSON.stringify(h)), !0;
115
+ function R({ huntConfigs: i, persister: t, children: e }) {
116
+ const r = t || F, [s, c] = I(i.map((o) => new h(o))), [n, l] = I(new H());
117
+ q(() => {
118
+ async function o() {
119
+ const d = await Promise.all(
120
+ i.map(async (a) => {
121
+ const m = `questify-${a.id}-solved-trials`, p = await r.load(m), f = new h(a);
122
+ if (p) {
123
+ const S = JSON.parse(p), v = f.trials.map((w) => S.includes(w.id) ? w.markAsSolved() : w);
124
+ return h.constructFromTrials(a.id, a.name, v);
125
+ }
126
+ return f;
127
+ })
128
+ );
129
+ c(d);
99
130
  }
100
- return !1;
131
+ o();
132
+ }, [r, i]);
133
+ function u() {
134
+ return n.activeHuntId === null ? null : s.find((o) => o.id === n.activeHuntId) || null;
101
135
  }
102
- function a() {
103
- return o.trials[s.value];
136
+ function O() {
137
+ const o = u();
138
+ return !o || n.activeTrialIndex === null ? null : o.trials[n.activeTrialIndex] || null;
104
139
  }
105
- function y() {
106
- return s.isOnSelectScreen();
140
+ function A() {
141
+ return n.isOnHuntSelection();
107
142
  }
108
- function u() {
109
- c(s.moveToSelectScreen());
110
- }
111
- function E(d) {
112
- c(s.moveToTrialScreen(d.id));
113
- }
114
- const k = {
115
- hunt: o,
116
- submitAnswer: l,
117
- selectedTrial: a,
118
- playerOnTrialSelectScreen: y,
119
- moveToSelectScreen: u,
120
- moveToScreen: E
143
+ function C() {
144
+ return n.isOnTrialSelection();
145
+ }
146
+ function E() {
147
+ return n.isOnActiveTrial();
148
+ }
149
+ function L(o) {
150
+ if (!s.find((a) => a.id === o))
151
+ throw new Error(`Hunt with id "${o}" not found`);
152
+ l(n.moveToHunt(o));
153
+ }
154
+ function P() {
155
+ l(n.moveToHuntSelection());
156
+ }
157
+ function k() {
158
+ l(n.moveToTrialSelectScreen());
159
+ }
160
+ function $(o) {
161
+ const d = u();
162
+ if (!d)
163
+ throw new Error("Cannot move to trial when no hunt is selected");
164
+ if (o < 0 || o >= d.trials.length)
165
+ throw new Error(`Trial index ${o} is out of bounds for hunt "${d.id}"`);
166
+ l(n.moveToTrialScreen(o));
167
+ }
168
+ async function j(o) {
169
+ const d = O(), a = u();
170
+ if (!d || !a)
171
+ return !1;
172
+ const m = a.submitAnswer(d.id, o);
173
+ if (m) {
174
+ const p = s.map((v) => v.id === a.id ? m : v);
175
+ c(p);
176
+ const f = `questify-${a.id}-solved-trials`, S = m.trials.filter((v) => v.isSolved).map((v) => v.id);
177
+ return await r.store(f, JSON.stringify(S)), !0;
178
+ }
179
+ return !1;
180
+ }
181
+ const N = {
182
+ hunts: s,
183
+ playerLocation: n,
184
+ activeHunt: u,
185
+ activeTrial: O,
186
+ isOnHuntSelection: A,
187
+ isOnTrialSelection: C,
188
+ isOnActiveTrial: E,
189
+ activateHunt: L,
190
+ moveToHuntSelection: P,
191
+ moveToTrialSelectScreen: k,
192
+ moveToTrialScreen: $,
193
+ submitAnswer: j
121
194
  };
122
- return /* @__PURE__ */ T(x.Provider, { value: k, children: t });
195
+ return /* @__PURE__ */ T(b.Provider, { value: N, children: e });
123
196
  }
124
- class H {
197
+ class J {
125
198
  constructor() {
126
199
  this.registry = /* @__PURE__ */ new Map();
127
200
  }
128
- register(e, t) {
129
- this.registry.set(e, t);
201
+ register(t, e) {
202
+ this.registry.set(t, e);
130
203
  }
131
- get(e) {
132
- return this.registry.get(e);
204
+ get(t) {
205
+ return this.registry.get(t);
133
206
  }
134
- has(e) {
135
- return this.registry.has(e);
207
+ has(t) {
208
+ return this.registry.has(t);
136
209
  }
137
210
  }
138
- const C = new H();
139
- function q() {
140
- const r = R(x);
141
- if (!r.hunt) throw new Error("No hunt found. Make sure you pass in the correct huntConfig to the ProgressContextProvider");
142
- function e(s, c) {
143
- if (r.playerOnTrialSelectScreen()) {
144
- const y = n().map((u) => ({
211
+ const x = new J();
212
+ function V() {
213
+ const i = B(b);
214
+ if (!i.hunts || i.hunts.length === 0)
215
+ throw new Error("No hunts found. Make sure you pass huntConfigs to the QuestifyProvider");
216
+ function t(r, s, c) {
217
+ if (i.isOnHuntSelection()) {
218
+ const n = i.hunts.map((l) => ({
219
+ id: l.id,
220
+ name: l.name,
221
+ progressSummary: l.progressSummary,
222
+ onSelectHunt: () => i.activateHunt(l.id)
223
+ }));
224
+ return /* @__PURE__ */ T(r, { huntCards: n });
225
+ }
226
+ if (i.isOnTrialSelection()) {
227
+ const n = i.activeHunt();
228
+ if (!n)
229
+ throw new Error("Cannot render trial selection without an active hunt");
230
+ const l = n.trials.map((u) => ({
231
+ id: u.id,
145
232
  title: u.title,
146
233
  type: u.type,
147
- onSelectTrial: () => r.moveToScreen(u)
234
+ isSolved: u.isSolved,
235
+ onSelectTrial: () => i.moveToTrialScreen(u.id)
148
236
  }));
149
- return /* @__PURE__ */ T(s, { trialSelectors: y });
237
+ return /* @__PURE__ */ T(
238
+ s,
239
+ {
240
+ hunt: n,
241
+ trialCards: l,
242
+ onBack: i.moveToHuntSelection
243
+ }
244
+ );
150
245
  }
151
- const l = o(), a = C.get(l.type);
152
- if (!a)
153
- throw new Error(`No trial component registered for type: ${l.type}`);
154
- return /* @__PURE__ */ T(
155
- c,
156
- {
157
- title: l.title,
158
- onBack: r.moveToSelectScreen,
159
- TrialComponent: () => /* @__PURE__ */ T(a, { ...l.blueprint, title: l.title })
160
- }
161
- );
162
- }
163
- function t(s) {
164
- return r.submitAnswer(s);
165
- }
166
- function n() {
167
- return r.hunt.trials.filter((s) => s.isSolved);
168
- }
169
- function o() {
170
- return r.selectedTrial();
171
- }
172
- function i() {
173
- const s = r.hunt.trials.filter((c) => c.isSolved);
174
- return {
175
- solvedTrials: s,
176
- totalTrials: r.hunt.trials.length,
177
- solvedCount: s.length,
178
- percentageComplete: s.length / r.hunt.trials.length * 100
179
- };
246
+ if (i.isOnActiveTrial()) {
247
+ const n = i.activeTrial();
248
+ if (!n)
249
+ throw new Error("Cannot render trial screen without an active trial");
250
+ const l = x.get(n.type);
251
+ if (!l)
252
+ throw new Error(`No trial component registered for type: ${n.type}`);
253
+ return /* @__PURE__ */ T(
254
+ c,
255
+ {
256
+ title: n.title,
257
+ onBack: i.moveToTrialSelectScreen,
258
+ TrialComponent: () => /* @__PURE__ */ T(l, { ...n.blueprint, title: n.title })
259
+ }
260
+ );
261
+ }
262
+ throw new Error("Invalid player location state");
263
+ }
264
+ function e(r) {
265
+ return i.submitAnswer(r);
180
266
  }
181
267
  return {
182
- renderScreen: e,
183
- submitAnswer: t,
184
- huntSummary: i
268
+ renderScreen: t,
269
+ submitAnswer: e
185
270
  };
186
271
  }
187
- const B = (r, e) => {
188
- C.register(r, e);
272
+ const z = (i, t) => {
273
+ x.register(i, t);
189
274
  };
190
275
  export {
191
- x as ProgressContext,
192
- M as ProgressContextProvider,
193
- B as registerTrialComponent,
194
- q as useQuestify
276
+ R as QuestifyProvider,
277
+ z as registerTrialComponent,
278
+ V as useQuestify
195
279
  };
196
280
  //# sourceMappingURL=questify.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"questify.js","sources":["../lib/store/MemoryPersister.ts","../lib/models/Trial.ts","../lib/models/Hunt.ts","../lib/models/PlayerLocation.ts","../lib/store/ProgressContext.tsx","../lib/registry/TrialRegistry.ts","../lib/hooks/useQuestify.tsx","../lib/index.ts"],"sourcesContent":["const storage = {};\nexport const memoryPersister = {\n store: async (key, value) => {\n storage[key] = value;\n },\n load: async (key) => {\n return storage[key];\n },\n};\n","class Trial {\n id: number;\n type: string;\n title: string;\n solution: string;\n blueprint: Record<string, any>;\n isSolved: boolean;\n\n constructor(id: number, type: string, title: string, solution: string, blueprint: Record<string, any>) {\n this.id = id;\n this.type = type;\n this.title = title;\n this.solution = solution;\n this.blueprint = blueprint;\n this.isSolved = false;\n }\n\n private clone({ isSolved }: { isSolved: boolean }): Trial {\n const cloned = Object.create(Trial.prototype);\n cloned.id = this.id;\n cloned.type = this.type;\n cloned.title = this.title;\n cloned.solution = this.solution;\n cloned.blueprint = this.blueprint;\n cloned.isSolved = isSolved;\n return cloned;\n }\n\n markAsSolved(): Trial {\n return this.clone({ isSolved: true });\n }\n}\n\nexport default Trial;\n","import Trial from '@models/Trial';\n\ninterface TrialConfig {\n type: string;\n title: string;\n solution: string;\n blueprint: Record<string, any>;\n}\n\ninterface HuntConfig {\n name: string;\n trials: TrialConfig[];\n}\n\nclass Hunt {\n name: string;\n trials: Trial[];\n\n constructor(config: HuntConfig) {\n this.name = config.name;\n this.trials = config.trials.map((trialConfig, index) =>\n new Trial(index, trialConfig.type, trialConfig.title, trialConfig.solution, trialConfig.blueprint)\n );\n }\n\n private clone(updates: Partial<{ trials: Trial[] }>): Hunt {\n const cloned = Object.create(Hunt.prototype);\n cloned.name = this.name;\n cloned.trials = updates.trials ?? this.trials;\n return cloned;\n }\n\n submitAnswer(trialId: number, answer: string): Hunt | null {\n const trial = this.trials.find(t => t.id === trialId);\n\n if (!trial) {\n return null;\n }\n\n if (trial.solution === answer.trim().toLowerCase()) {\n const updatedTrials = this.trials.map(t =>\n t.id === trialId ? t.markAsSolved() : t\n );\n return this.clone({ trials: updatedTrials });\n }\n\n return null;\n }\n}\n\nexport default Hunt;\n","const TRIAL_SELECT_SCREEN = -1;\n\nclass PlayerLocation {\n private screen: number;\n private numScreens: number;\n\n constructor({ numScreens }: { numScreens: number }) {\n this.numScreens = numScreens;\n this.screen = TRIAL_SELECT_SCREEN;\n }\n\n private clone({ screen }: { screen: number }): PlayerLocation {\n const cloned = Object.create(PlayerLocation.prototype);\n cloned.numScreens = this.numScreens;\n cloned.screen = screen;\n return cloned;\n }\n\n get value(): number {\n return this.screen;\n }\n\n isOnSelectScreen(): boolean {\n return this.screen === TRIAL_SELECT_SCREEN;\n }\n\n moveToSelectScreen(): PlayerLocation {\n return this.clone({ screen: TRIAL_SELECT_SCREEN });\n }\n\n moveToTrialScreen(trialIdx: number): PlayerLocation {\n return this.clone({ screen: trialIdx });\n }\n}\n\nexport default PlayerLocation;\n","import { useState, useEffect, createContext } from \"react\";\nimport { memoryPersister } from '@store/MemoryPersister';\nimport Hunt from '@models/Hunt';\nimport PlayerLocation from '@models/PlayerLocation';\n\nconst STORAGE_KEY = \"questify-solved-trials\";\n\nexport const ProgressContext = createContext({\n hunt: null,\n submitAnswer: () => {},\n selectedTrial: () => {},\n playerOnTrialSelectScreen: () => {},\n moveToSelectScreen: () => {},\n moveToScreen: () => {},\n});\n\nexport default function ProgressContextProvider({ huntConfig, persister, children }) {\n const progressPersister = persister || memoryPersister;\n const [hunt, setHunt] = useState(new Hunt(huntConfig));\n const [playerLocation, setPlayerLocation] = useState(new PlayerLocation({ numScreens: huntConfig.trials.length }));\n\n useEffect(() => {\n async function loadProgress() {\n const savedTrialsStr = await progressPersister.load(STORAGE_KEY);\n if (savedTrialsStr) {\n const solvedTrialIds = JSON.parse(savedTrialsStr);\n const restoredHunt = new Hunt(huntConfig);\n const updatedTrials = restoredHunt.trials.map(trial =>\n solvedTrialIds.includes(trial.id) ? trial.markAsSolved() : trial\n );\n restoredHunt.trials = updatedTrials;\n setHunt(restoredHunt);\n }\n }\n\n loadProgress();\n }, [progressPersister, huntConfig]);\n\n async function handleSubmitAnswer(answer) {\n const trial = selectedTrial();\n const newHunt = hunt.submitAnswer(trial.id, answer);\n\n if (newHunt) {\n setHunt(newHunt);\n const solvedTrialIds = newHunt.trials\n .filter(t => t.isSolved)\n .map(t => t.id);\n await progressPersister.store(STORAGE_KEY, JSON.stringify(solvedTrialIds));\n return true;\n }\n\n return false;\n }\n\n function selectedTrial() {\n return hunt.trials[playerLocation.value];\n }\n\n function playerOnTrialSelectScreen() {\n return playerLocation.isOnSelectScreen();\n }\n\n function moveToSelectScreen() {\n setPlayerLocation(playerLocation.moveToSelectScreen());\n }\n\n function moveToScreen(trial) {\n setPlayerLocation(playerLocation.moveToTrialScreen(trial.id));\n }\n\n const progressCtxValue = {\n hunt: hunt,\n submitAnswer: handleSubmitAnswer,\n selectedTrial: selectedTrial,\n playerOnTrialSelectScreen: playerOnTrialSelectScreen,\n moveToSelectScreen: moveToSelectScreen,\n moveToScreen: moveToScreen,\n };\n\n return (\n <ProgressContext.Provider value={progressCtxValue}>\n {children}\n </ProgressContext.Provider>\n );\n}\n","type TrialComponent = React.ComponentType<any>;\n\nclass TrialRegistry {\n private registry: Map<string, TrialComponent> = new Map();\n\n register(type: string, component: TrialComponent): void {\n this.registry.set(type, component);\n }\n\n get(type: string): TrialComponent | undefined {\n return this.registry.get(type);\n }\n\n has(type: string): boolean {\n return this.registry.has(type);\n }\n}\n\nexport const trialRegistry = new TrialRegistry();\n","import { useContext, useState } from \"react\";\nimport { ProgressContext } from \"@store/ProgressContext\";\nimport { trialRegistry } from \"@registry/TrialRegistry\";\n\nexport function useQuestify() {\n const context = useContext(ProgressContext);\n\n if (!context.hunt) throw new Error(\"No hunt found. Make sure you pass in the correct huntConfig to the ProgressContextProvider\");\n\n function renderScreen(SelectScreenComponent, TrialScreenComponent) {\n if (context.playerOnTrialSelectScreen()) {\n const trialSelectors = solvedTrials().map((trial) => ({\n title: trial.title,\n type: trial.type,\n onSelectTrial: () => context.moveToScreen(trial)\n }));\n return <SelectScreenComponent trialSelectors={trialSelectors} />;\n }\n\n const trial = selectedTrial();\n const TrialComponent = trialRegistry.get(trial.type);\n\n if (!TrialComponent) {\n throw new Error(`No trial component registered for type: ${trial.type}`);\n }\n\n return <TrialScreenComponent\n title={trial.title}\n onBack={context.moveToSelectScreen}\n TrialComponent={() => <TrialComponent {...trial.blueprint} title={trial.title} />}\n />;\n }\n\n function submitAnswer(answer) {\n return context.submitAnswer(answer);\n }\n\n function solvedTrials() {\n return context.hunt.trials.filter(trial => trial.isSolved);\n }\n\n function selectedTrial() {\n return context.selectedTrial();\n }\n\n function huntSummary() {\n const solvedTrials = context.hunt.trials.filter(trial => trial.isSolved);\n return {\n solvedTrials: solvedTrials,\n totalTrials: context.hunt.trials.length,\n solvedCount: solvedTrials.length,\n percentageComplete: (solvedTrials.length / context.hunt.trials.length) * 100\n };\n }\n\n return {\n renderScreen: renderScreen,\n submitAnswer: submitAnswer,\n huntSummary: huntSummary,\n };\n}\n","import { useQuestify } from '@hooks/useQuestify';\nimport ProgressContextProvider, { ProgressContext } from '@store/ProgressContext';\nimport { trialRegistry } from '@registry/TrialRegistry';\n\nexport const registerTrialComponent = (type: string, component: React.ComponentType<any>) => {\n trialRegistry.register(type, component);\n};\n\nexport { ProgressContextProvider, ProgressContext, useQuestify };\n"],"names":["storage","memoryPersister","key","value","Trial","id","type","title","solution","blueprint","isSolved","cloned","Hunt","config","trialConfig","index","updates","trialId","answer","trial","t","updatedTrials","TRIAL_SELECT_SCREEN","PlayerLocation","numScreens","screen","trialIdx","STORAGE_KEY","ProgressContext","createContext","ProgressContextProvider","huntConfig","persister","children","progressPersister","hunt","setHunt","useState","playerLocation","setPlayerLocation","useEffect","loadProgress","savedTrialsStr","solvedTrialIds","restoredHunt","handleSubmitAnswer","selectedTrial","newHunt","playerOnTrialSelectScreen","moveToSelectScreen","moveToScreen","progressCtxValue","TrialRegistry","component","trialRegistry","useQuestify","context","useContext","renderScreen","SelectScreenComponent","TrialScreenComponent","trialSelectors","solvedTrials","jsx","TrialComponent","submitAnswer","huntSummary","registerTrialComponent"],"mappings":"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"}
1
+ {"version":3,"file":"questify.js","sources":["../lib/store/MemoryPersister.ts","../lib/models/Trial.ts","../lib/models/Hunt.ts","../lib/models/PlayerLocation.ts","../lib/store/QuestifyContext.tsx","../lib/registry/TrialRegistry.ts","../lib/hooks/useQuestify.tsx","../lib/index.ts"],"sourcesContent":["const storage = {};\nexport const memoryPersister = {\n store: async (key, value) => {\n storage[key] = value;\n },\n load: async (key) => {\n return storage[key];\n },\n};\n","class Trial {\n id: number;\n type: string;\n title: string;\n solution: string;\n blueprint: Record<string, any>;\n isSolved: boolean;\n\n constructor(id: number, type: string, title: string, solution: string, blueprint: Record<string, any>) {\n this.id = id;\n this.type = type;\n this.title = title;\n this.solution = solution;\n this.blueprint = blueprint;\n this.isSolved = false;\n }\n\n private clone({ isSolved }: { isSolved: boolean }): Trial {\n const cloned = Object.create(Trial.prototype);\n cloned.id = this.id;\n cloned.type = this.type;\n cloned.title = this.title;\n cloned.solution = this.solution;\n cloned.blueprint = this.blueprint;\n cloned.isSolved = isSolved;\n return cloned;\n }\n\n markAsSolved(): Trial {\n return this.clone({ isSolved: true });\n }\n}\n\nexport default Trial;\n","import Trial from '@models/Trial';\n\nenum HuntStatus {\n NOT_STARTED = 'not-started',\n IN_PROGRESS = 'in-progress',\n COMPLETED = 'completed'\n}\n\ninterface TrialConfig {\n type: string;\n title: string;\n solution: string;\n blueprint: Record<string, any>;\n}\n\ninterface HuntConfig {\n id: string;\n name: string;\n trials: TrialConfig[];\n}\n\ninterface ProgressSummary {\n status: HuntStatus;\n solvedCount: number;\n totalTrials: number;\n percentageComplete: number;\n}\n\nclass Hunt {\n id: string;\n name: string;\n trials: Trial[];\n progressSummary: ProgressSummary;\n\n constructor(config: HuntConfig) {\n this.id = config.id;\n this.name = config.name;\n this.trials = config.trials.map((trialConfig, index) =>\n new Trial(index, trialConfig.type, trialConfig.title, trialConfig.solution, trialConfig.blueprint)\n );\n this.updateStatus();\n }\n\n static constructFromTrials(id: string, name: string, trials: Trial[]): Hunt {\n const hunt = Object.create(Hunt.prototype);\n hunt.id = id;\n hunt.name = name;\n hunt.trials = trials;\n hunt.updateStatus();\n return hunt;\n }\n\n submitAnswer(trialId: number, answer: string): Hunt | null {\n const trial = this.trials.find(trial => trial.id === trialId);\n\n if (!trial) {\n return null;\n }\n\n if (trial.solution === answer.trim().toLowerCase()) {\n const updatedTrials = this.trials.map(trial =>\n trial.id === trialId ? trial.markAsSolved() : trial\n );\n return this.clone({ trials: updatedTrials });\n }\n\n return null;\n }\n\n private updateStatus(): void {\n const solvedCount = this.trials.filter(trial => trial.isSolved).length;\n const totalTrials = this.trials.length;\n\n let status: HuntStatus;\n if (solvedCount === 0) {\n status = HuntStatus.NOT_STARTED;\n } else if (solvedCount === totalTrials) {\n status = HuntStatus.COMPLETED;\n } else {\n status = HuntStatus.IN_PROGRESS;\n }\n\n this.progressSummary = {\n status,\n solvedCount,\n totalTrials,\n percentageComplete: totalTrials > 0 ? (solvedCount / totalTrials) * 100 : 0\n };\n }\n\n private clone(updates: Partial<{ trials: Trial[] }>): Hunt {\n const cloned = Object.create(Hunt.prototype);\n cloned.id = this.id;\n cloned.name = this.name;\n cloned.trials = updates.trials ?? this.trials;\n cloned.updateStatus();\n return cloned;\n }\n}\n\nexport default Hunt;\n","class PlayerLocation {\n activeHuntId: string | null;\n activeTrialIndex: number | null;\n\n constructor(huntId: string | null = null, trialIndex: number | null = null) {\n this.activeHuntId = huntId;\n this.activeTrialIndex = trialIndex;\n }\n\n private clone(): PlayerLocation {\n const cloned = Object.create(PlayerLocation.prototype);\n cloned.activeHuntId = this.activeHuntId;\n cloned.activeTrialIndex = this.activeTrialIndex;\n return cloned;\n }\n\n isOnHuntSelection(): boolean {\n return this.activeHuntId === null;\n }\n\n isOnTrialSelection(): boolean {\n return this.activeHuntId !== null && this.activeTrialIndex === null;\n }\n\n isOnActiveTrial(): boolean {\n return this.activeHuntId !== null && this.activeTrialIndex !== null;\n }\n\n moveToHuntSelection(): PlayerLocation {\n const newLocation = this.clone();\n newLocation.activeHuntId = null;\n newLocation.activeTrialIndex = null;\n return newLocation;\n }\n\n moveToHunt(huntId: string): PlayerLocation {\n const newLocation = this.clone();\n newLocation.activeHuntId = huntId;\n newLocation.activeTrialIndex = null;\n return newLocation;\n }\n\n moveToTrialSelectScreen(): PlayerLocation {\n if (this.activeHuntId === null) {\n throw new Error('Cannot move to trial select when no hunt is active');\n }\n\n const newLocation = this.clone();\n newLocation.activeTrialIndex = null;\n return newLocation;\n }\n\n moveToTrialScreen(trialIdx: number): PlayerLocation {\n if (this.activeHuntId === null) {\n throw new Error('Cannot move to trial when no hunt is active');\n }\n\n const newLocation = this.clone();\n newLocation.activeTrialIndex = trialIdx;\n return newLocation;\n }\n}\n\nexport default PlayerLocation;\n","import { useState, useEffect, createContext } from \"react\";\nimport { memoryPersister } from '@store/MemoryPersister';\nimport Hunt from '@models/Hunt';\nimport PlayerLocation from '@models/PlayerLocation';\n\nexport const QuestifyContext = createContext({\n hunts: [],\n playerLocation: null,\n activeHunt: () => null,\n activeTrial: () => null,\n isOnHuntSelection: () => false,\n isOnTrialSelection: () => false,\n isOnActiveTrial: () => false,\n activateHunt: () => {},\n moveToHuntSelection: () => {},\n moveToTrialSelectScreen: () => {},\n moveToTrialScreen: () => {},\n submitAnswer: () => {},\n});\n\nexport default function QuestifyProvider({ huntConfigs, persister, children }) {\n const progressPersister = persister || memoryPersister;\n const [hunts, setHunts] = useState(huntConfigs.map(config => new Hunt(config)));\n const [playerLocation, setPlayerLocation] = useState(new PlayerLocation());\n\n useEffect(() => {\n async function loadProgress() {\n const restoredHunts = await Promise.all(\n huntConfigs.map(async (config) => {\n const storageKey = `questify-${config.id}-solved-trials`;\n const savedTrialsStr = await progressPersister.load(storageKey);\n const restoredHunt = new Hunt(config);\n\n if (savedTrialsStr) {\n const solvedTrialIds = JSON.parse(savedTrialsStr);\n const updatedTrials = restoredHunt.trials.map(trial => solvedTrialIds.includes(trial.id) ? trial.markAsSolved() : trial);\n return Hunt.constructFromTrials(config.id, config.name, updatedTrials);\n }\n\n return restoredHunt;\n })\n );\n\n setHunts(restoredHunts);\n }\n\n loadProgress();\n }, [progressPersister, huntConfigs]);\n\n function activeHunt() {\n if (playerLocation.activeHuntId === null) {\n return null;\n }\n return hunts.find(hunt => hunt.id === playerLocation.activeHuntId) || null;\n }\n\n function activeTrial() {\n const hunt = activeHunt();\n if (!hunt || playerLocation.activeTrialIndex === null) {\n return null;\n }\n return hunt.trials[playerLocation.activeTrialIndex] || null;\n }\n\n function isOnHuntSelection() {\n return playerLocation.isOnHuntSelection();\n }\n\n function isOnTrialSelection() {\n return playerLocation.isOnTrialSelection();\n }\n\n function isOnActiveTrial() {\n return playerLocation.isOnActiveTrial();\n }\n\n function activateHunt(huntId) {\n const hunt = hunts.find(hunt => hunt.id === huntId);\n if (!hunt) {\n throw new Error(`Hunt with id \"${huntId}\" not found`);\n }\n setPlayerLocation(playerLocation.moveToHunt(huntId));\n }\n\n function moveToHuntSelection() {\n setPlayerLocation(playerLocation.moveToHuntSelection());\n }\n\n function moveToTrialSelectScreen() {\n setPlayerLocation(playerLocation.moveToTrialSelectScreen());\n }\n\n function moveToTrialScreen(trialIdx) {\n const hunt = activeHunt();\n if (!hunt) {\n throw new Error('Cannot move to trial when no hunt is selected');\n }\n if (trialIdx < 0 || trialIdx >= hunt.trials.length) {\n throw new Error(`Trial index ${trialIdx} is out of bounds for hunt \"${hunt.id}\"`);\n }\n setPlayerLocation(playerLocation.moveToTrialScreen(trialIdx));\n }\n\n async function handleSubmitAnswer(answer) {\n const trial = activeTrial();\n const currentHunt = activeHunt();\n\n if (!trial || !currentHunt) {\n return false;\n }\n\n const newHunt = currentHunt.submitAnswer(trial.id, answer);\n\n if (newHunt) {\n const updatedHunts = hunts.map(hunt => hunt.id === currentHunt.id ? newHunt : hunt);\n setHunts(updatedHunts);\n\n const storageKey = `questify-${currentHunt.id}-solved-trials`;\n const solvedTrialIds = newHunt.trials.filter(trial => trial.isSolved).map(trial => trial.id);\n await progressPersister.store(storageKey, JSON.stringify(solvedTrialIds));\n return true;\n }\n\n return false;\n }\n\n const questifyCtxValue = {\n hunts: hunts,\n playerLocation: playerLocation,\n activeHunt: activeHunt,\n activeTrial: activeTrial,\n isOnHuntSelection: isOnHuntSelection,\n isOnTrialSelection: isOnTrialSelection,\n isOnActiveTrial: isOnActiveTrial,\n activateHunt: activateHunt,\n moveToHuntSelection: moveToHuntSelection,\n moveToTrialSelectScreen: moveToTrialSelectScreen,\n moveToTrialScreen: moveToTrialScreen,\n submitAnswer: handleSubmitAnswer,\n };\n\n return (\n <QuestifyContext.Provider value={questifyCtxValue}>\n {children}\n </QuestifyContext.Provider>\n );\n}\n","type TrialComponent = React.ComponentType<any>;\n\nclass TrialRegistry {\n private registry: Map<string, TrialComponent> = new Map();\n\n register(type: string, component: TrialComponent): void {\n this.registry.set(type, component);\n }\n\n get(type: string): TrialComponent | undefined {\n return this.registry.get(type);\n }\n\n has(type: string): boolean {\n return this.registry.has(type);\n }\n}\n\nexport const trialRegistry = new TrialRegistry();\n","import { useContext } from \"react\";\nimport { QuestifyContext } from \"@store/QuestifyContext\";\nimport { trialRegistry } from \"@registry/TrialRegistry\";\n\nexport function useQuestify() {\n const context = useContext(QuestifyContext);\n\n if (!context.hunts || context.hunts.length === 0) {\n throw new Error(\"No hunts found. Make sure you pass huntConfigs to the QuestifyProvider\");\n }\n\n function renderScreen(HuntSelectComponent, TrialSelectComponent, TrialScreenComponent) {\n if (context.isOnHuntSelection()) {\n const huntCards = context.hunts.map((hunt) => ({\n id: hunt.id,\n name: hunt.name,\n progressSummary: hunt.progressSummary,\n onSelectHunt: () => context.activateHunt(hunt.id)\n }));\n return <HuntSelectComponent huntCards={huntCards} />;\n }\n\n if (context.isOnTrialSelection()) {\n const hunt = context.activeHunt();\n if (!hunt) {\n throw new Error(\"Cannot render trial selection without an active hunt\");\n }\n\n const trialCards = hunt.trials.map((trial) => ({\n id: trial.id,\n title: trial.title,\n type: trial.type,\n isSolved: trial.isSolved,\n onSelectTrial: () => context.moveToTrialScreen(trial.id)\n }));\n\n return <TrialSelectComponent\n hunt={hunt}\n trialCards={trialCards}\n onBack={context.moveToHuntSelection}\n />;\n }\n\n if (context.isOnActiveTrial()) {\n const trial = context.activeTrial();\n if (!trial) {\n throw new Error(\"Cannot render trial screen without an active trial\");\n }\n\n const TrialComponent = trialRegistry.get(trial.type);\n\n if (!TrialComponent) {\n throw new Error(`No trial component registered for type: ${trial.type}`);\n }\n\n return <TrialScreenComponent\n title={trial.title}\n onBack={context.moveToTrialSelectScreen}\n TrialComponent={() => <TrialComponent {...trial.blueprint} title={trial.title} />}\n />;\n }\n\n throw new Error(\"Invalid player location state\");\n }\n\n function submitAnswer(answer) {\n return context.submitAnswer(answer);\n }\n\n return {\n renderScreen: renderScreen,\n submitAnswer: submitAnswer,\n };\n}\n","import { useQuestify } from '@hooks/useQuestify';\nimport QuestifyProvider from '@store/QuestifyContext';\nimport { trialRegistry } from '@registry/TrialRegistry';\n\nexport const registerTrialComponent = (type: string, component: React.ComponentType<any>) => {\n trialRegistry.register(type, component);\n};\n\nexport { QuestifyProvider, useQuestify };\n"],"names":["storage","memoryPersister","key","value","Trial","id","type","title","solution","blueprint","isSolved","cloned","Hunt","config","trialConfig","index","name","trials","hunt","trialId","answer","trial","updatedTrials","solvedCount","totalTrials","status","updates","PlayerLocation","huntId","trialIndex","newLocation","trialIdx","QuestifyContext","createContext","QuestifyProvider","huntConfigs","persister","children","progressPersister","hunts","setHunts","useState","playerLocation","setPlayerLocation","useEffect","loadProgress","restoredHunts","storageKey","savedTrialsStr","restoredHunt","solvedTrialIds","activeHunt","activeTrial","isOnHuntSelection","isOnTrialSelection","isOnActiveTrial","activateHunt","moveToHuntSelection","moveToTrialSelectScreen","moveToTrialScreen","handleSubmitAnswer","currentHunt","newHunt","updatedHunts","questifyCtxValue","TrialRegistry","component","trialRegistry","useQuestify","context","useContext","renderScreen","HuntSelectComponent","TrialSelectComponent","TrialScreenComponent","huntCards","jsx","trialCards","TrialComponent","submitAnswer","registerTrialComponent"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@popp0102/questify",
3
3
  "private": false,
4
- "version": "0.8.0",
4
+ "version": "0.9.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite --config config/vite.config.ts public",