@unicitylabs/sphere-ui 0.1.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/README.md +192 -0
- package/dist/canvas/index.d.ts +3 -0
- package/dist/canvas/index.js +0 -0
- package/dist/forms/index.d.ts +1 -0
- package/dist/forms/index.js +0 -0
- package/dist/hooks/index.d.ts +122 -0
- package/dist/hooks/index.js +813 -0
- package/dist/index-DMHfA7fr.d.ts +90 -0
- package/dist/index-DsEO4kM1.d.ts +126 -0
- package/dist/index.d.ts +199 -0
- package/dist/index.js +1095 -0
- package/dist/panels/index.d.ts +3 -0
- package/dist/panels/index.js +0 -0
- package/package.json +60 -0
- package/src/styles/components.css +121 -0
- package/src/styles/index.css +2 -0
- package/src/styles/tokens.css +81 -0
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
// src/hooks/useCanvasState.ts
|
|
2
|
+
import { useState, useCallback, useMemo, useEffect, useRef } from "react";
|
|
3
|
+
import { arrayMove } from "@dnd-kit/sortable";
|
|
4
|
+
function chainColor(group) {
|
|
5
|
+
let hash = 0;
|
|
6
|
+
for (const c of group) hash = hash * 31 + c.charCodeAt(0) & 4294967295;
|
|
7
|
+
const hue = Math.abs(hash) % 360;
|
|
8
|
+
return `hsl(${hue}, 70%, 55%)`;
|
|
9
|
+
}
|
|
10
|
+
function parseDndId(id) {
|
|
11
|
+
if (id === "unassigned") return { type: "unassigned", rawId: "unassigned" };
|
|
12
|
+
const [type, rawId] = id.split(":");
|
|
13
|
+
return { type, rawId };
|
|
14
|
+
}
|
|
15
|
+
function normalizeLaneId(id) {
|
|
16
|
+
if (id.startsWith("lane:")) return id.replace("lane:", "track:");
|
|
17
|
+
return id;
|
|
18
|
+
}
|
|
19
|
+
function getLaneId(quest) {
|
|
20
|
+
if (quest.trackId) return `track:${quest.trackId}`;
|
|
21
|
+
return "unassigned";
|
|
22
|
+
}
|
|
23
|
+
function useCanvasState(tracks, quests) {
|
|
24
|
+
const [trackOrder, setTrackOrder] = useState([]);
|
|
25
|
+
const [questOrderByLane, setQuestOrderByLane] = useState(/* @__PURE__ */ new Map());
|
|
26
|
+
const [selectedItem, setSelectedItem] = useState(null);
|
|
27
|
+
const [activePanelTab, setActivePanelTab] = useState("quests");
|
|
28
|
+
const [pendingChanges, setPendingChanges] = useState([]);
|
|
29
|
+
const [localQuestFields, setLocalQuestFields] = useState(/* @__PURE__ */ new Map());
|
|
30
|
+
const [localTrackFields, setLocalTrackFields] = useState(/* @__PURE__ */ new Map());
|
|
31
|
+
const undoStack = useRef([]);
|
|
32
|
+
const [activeDragId, setActiveDragId] = useState(null);
|
|
33
|
+
const dragSourceLane = useRef(null);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const sortedTracks = [...tracks].sort((a, b) => a.sortOrder - b.sortOrder);
|
|
36
|
+
setTrackOrder(sortedTracks.map((t) => `track:${t._id}`));
|
|
37
|
+
const byLane = /* @__PURE__ */ new Map();
|
|
38
|
+
for (const t of sortedTracks) {
|
|
39
|
+
const laneId = `track:${t._id}`;
|
|
40
|
+
const laneQuests = quests.filter((q) => getLaneId(q) === laneId).sort((a, b) => a.sortOrder - b.sortOrder);
|
|
41
|
+
byLane.set(laneId, laneQuests.map((q) => `quest:${q._id}`));
|
|
42
|
+
}
|
|
43
|
+
const unassigned = quests.filter((q) => getLaneId(q) === "unassigned").sort((a, b) => a.sortOrder - b.sortOrder);
|
|
44
|
+
byLane.set("unassigned", unassigned.map((q) => `quest:${q._id}`));
|
|
45
|
+
setQuestOrderByLane(byLane);
|
|
46
|
+
setPendingChanges([]);
|
|
47
|
+
setLocalQuestFields(/* @__PURE__ */ new Map());
|
|
48
|
+
setLocalTrackFields(/* @__PURE__ */ new Map());
|
|
49
|
+
}, [tracks, quests]);
|
|
50
|
+
const pushUndo = useCallback(() => {
|
|
51
|
+
undoStack.current = [
|
|
52
|
+
...undoStack.current.slice(-19),
|
|
53
|
+
{
|
|
54
|
+
trackOrder,
|
|
55
|
+
questOrderByLane: new Map(questOrderByLane),
|
|
56
|
+
localQuestFields: new Map(localQuestFields),
|
|
57
|
+
localTrackFields: new Map(localTrackFields),
|
|
58
|
+
pendingChanges: [...pendingChanges]
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
}, [trackOrder, questOrderByLane, localQuestFields, localTrackFields, pendingChanges]);
|
|
62
|
+
const undo = useCallback(() => {
|
|
63
|
+
const entry = undoStack.current.pop();
|
|
64
|
+
if (!entry) return;
|
|
65
|
+
setTrackOrder(entry.trackOrder);
|
|
66
|
+
setQuestOrderByLane(entry.questOrderByLane);
|
|
67
|
+
setLocalQuestFields(entry.localQuestFields);
|
|
68
|
+
setLocalTrackFields(entry.localTrackFields);
|
|
69
|
+
setPendingChanges(entry.pendingChanges);
|
|
70
|
+
}, []);
|
|
71
|
+
const getQuest = useCallback((id) => {
|
|
72
|
+
const base = quests.find((q) => q._id === id);
|
|
73
|
+
if (!base) return void 0;
|
|
74
|
+
const overrides = localQuestFields.get(id);
|
|
75
|
+
return overrides ? { ...base, ...overrides } : base;
|
|
76
|
+
}, [quests, localQuestFields]);
|
|
77
|
+
const getTrack = useCallback((id) => {
|
|
78
|
+
const base = tracks.find((t) => t._id === id);
|
|
79
|
+
if (!base) return void 0;
|
|
80
|
+
const overrides = localTrackFields.get(id);
|
|
81
|
+
return overrides ? { ...base, ...overrides } : base;
|
|
82
|
+
}, [tracks, localTrackFields]);
|
|
83
|
+
const getQuestsForLane = useCallback((laneId) => {
|
|
84
|
+
const ids = questOrderByLane.get(laneId) ?? [];
|
|
85
|
+
return ids.map((dndId) => {
|
|
86
|
+
const { rawId } = parseDndId(dndId);
|
|
87
|
+
return getQuest(rawId);
|
|
88
|
+
}).filter((q) => Boolean(q));
|
|
89
|
+
}, [questOrderByLane, getQuest]);
|
|
90
|
+
const findLaneForQuest = useCallback((questDndId) => {
|
|
91
|
+
for (const [laneId, ids] of questOrderByLane) {
|
|
92
|
+
if (ids.includes(questDndId)) return laneId;
|
|
93
|
+
}
|
|
94
|
+
return void 0;
|
|
95
|
+
}, [questOrderByLane]);
|
|
96
|
+
const updateQuestField = useCallback((id, fields) => {
|
|
97
|
+
pushUndo();
|
|
98
|
+
setLocalQuestFields((m) => {
|
|
99
|
+
const next = new Map(m);
|
|
100
|
+
next.set(id, { ...next.get(id) ?? {}, ...fields });
|
|
101
|
+
return next;
|
|
102
|
+
});
|
|
103
|
+
setPendingChanges((p) => [...p, { kind: "quest-field", id, fields }]);
|
|
104
|
+
const laneChanged = "trackId" in fields;
|
|
105
|
+
if (laneChanged) {
|
|
106
|
+
const questDndId = `quest:${id}`;
|
|
107
|
+
const newLane = fields.trackId ? `track:${fields.trackId}` : "unassigned";
|
|
108
|
+
setQuestOrderByLane((prev) => {
|
|
109
|
+
const next = new Map(prev);
|
|
110
|
+
for (const [laneId, ids] of next) {
|
|
111
|
+
if (ids.includes(questDndId)) {
|
|
112
|
+
next.set(laneId, ids.filter((qid) => qid !== questDndId));
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const target = next.get(newLane) ?? [];
|
|
117
|
+
if (!target.includes(questDndId)) {
|
|
118
|
+
next.set(newLane, [...target, questDndId]);
|
|
119
|
+
}
|
|
120
|
+
return next;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}, [pushUndo]);
|
|
124
|
+
const updateTrackField = useCallback((id, fields) => {
|
|
125
|
+
pushUndo();
|
|
126
|
+
setLocalTrackFields((m) => {
|
|
127
|
+
const next = new Map(m);
|
|
128
|
+
next.set(id, { ...next.get(id) ?? {}, ...fields });
|
|
129
|
+
return next;
|
|
130
|
+
});
|
|
131
|
+
setPendingChanges((p) => [...p, { kind: "track-field", id, fields }]);
|
|
132
|
+
}, [pushUndo]);
|
|
133
|
+
const handleDragStart = useCallback((id) => {
|
|
134
|
+
setActiveDragId(id);
|
|
135
|
+
dragSourceLane.current = findLaneForQuest(id) ?? null;
|
|
136
|
+
}, [findLaneForQuest]);
|
|
137
|
+
const handleDragOver = useCallback((event) => {
|
|
138
|
+
const { active, over } = event;
|
|
139
|
+
if (!over) return;
|
|
140
|
+
const activeId = String(active.id);
|
|
141
|
+
const overId = String(over.id);
|
|
142
|
+
const { type: activeType } = parseDndId(activeId);
|
|
143
|
+
if (activeType !== "quest") return;
|
|
144
|
+
const sourceLane = findLaneForQuest(activeId);
|
|
145
|
+
if (!sourceLane) return;
|
|
146
|
+
let targetLane;
|
|
147
|
+
const normalizedOverId = normalizeLaneId(overId);
|
|
148
|
+
const { type: overType } = parseDndId(normalizedOverId);
|
|
149
|
+
if (overType === "track" || normalizedOverId === "unassigned") {
|
|
150
|
+
targetLane = normalizedOverId;
|
|
151
|
+
} else if (overType === "quest") {
|
|
152
|
+
targetLane = findLaneForQuest(normalizedOverId) ?? sourceLane;
|
|
153
|
+
} else {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (sourceLane === targetLane) return;
|
|
157
|
+
setQuestOrderByLane((prev) => {
|
|
158
|
+
const next = new Map(prev);
|
|
159
|
+
const srcList = (next.get(sourceLane) ?? []).filter((id) => id !== activeId);
|
|
160
|
+
const tgtList = (next.get(targetLane) ?? []).filter((id) => id !== activeId);
|
|
161
|
+
const overIdx = tgtList.indexOf(overId);
|
|
162
|
+
if (overIdx !== -1) {
|
|
163
|
+
tgtList.splice(overIdx, 0, activeId);
|
|
164
|
+
} else {
|
|
165
|
+
tgtList.push(activeId);
|
|
166
|
+
}
|
|
167
|
+
next.set(sourceLane, srcList);
|
|
168
|
+
next.set(targetLane, tgtList);
|
|
169
|
+
return next;
|
|
170
|
+
});
|
|
171
|
+
}, [findLaneForQuest]);
|
|
172
|
+
const handleDragEnd = useCallback((event) => {
|
|
173
|
+
const { active, over } = event;
|
|
174
|
+
setActiveDragId(null);
|
|
175
|
+
if (!over) return;
|
|
176
|
+
const activeId = String(active.id);
|
|
177
|
+
const overId = String(over.id);
|
|
178
|
+
const { type: activeType, rawId: activeRawId } = parseDndId(activeId);
|
|
179
|
+
if (activeType === "track") {
|
|
180
|
+
if (activeId === overId) return;
|
|
181
|
+
pushUndo();
|
|
182
|
+
const newOrder = arrayMove(
|
|
183
|
+
trackOrder,
|
|
184
|
+
trackOrder.indexOf(activeId),
|
|
185
|
+
trackOrder.indexOf(overId)
|
|
186
|
+
);
|
|
187
|
+
setTrackOrder(newOrder);
|
|
188
|
+
setPendingChanges((p) => [...p, { kind: "track-reorder" }]);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (activeType === "quest") {
|
|
192
|
+
const currentLane = findLaneForQuest(activeId);
|
|
193
|
+
if (!currentLane) return;
|
|
194
|
+
const sourceLane = dragSourceLane.current;
|
|
195
|
+
dragSourceLane.current = null;
|
|
196
|
+
const isCrossLane = sourceLane !== null && sourceLane !== currentLane;
|
|
197
|
+
if (isCrossLane) {
|
|
198
|
+
pushUndo();
|
|
199
|
+
setPendingChanges((p) => [
|
|
200
|
+
...p,
|
|
201
|
+
{ kind: "quest-reorder", laneId: currentLane },
|
|
202
|
+
{ kind: "quest-reorder", laneId: sourceLane }
|
|
203
|
+
]);
|
|
204
|
+
const track = currentLane === "unassigned" ? null : tracks.find((t) => `track:${t._id}` === currentLane);
|
|
205
|
+
const newTrackId = track?._id ?? null;
|
|
206
|
+
setPendingChanges((p) => [...p, { kind: "quest-field", id: activeRawId, fields: { trackId: newTrackId } }]);
|
|
207
|
+
setLocalQuestFields((m) => {
|
|
208
|
+
const next = new Map(m);
|
|
209
|
+
next.set(activeRawId, { ...next.get(activeRawId) ?? {}, trackId: newTrackId });
|
|
210
|
+
return next;
|
|
211
|
+
});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const laneIds = questOrderByLane.get(currentLane) ?? [];
|
|
215
|
+
if (activeId === overId) return;
|
|
216
|
+
pushUndo();
|
|
217
|
+
const newLaneIds = arrayMove(laneIds, laneIds.indexOf(activeId), laneIds.indexOf(overId));
|
|
218
|
+
setQuestOrderByLane((prev) => {
|
|
219
|
+
const next = new Map(prev);
|
|
220
|
+
next.set(currentLane, newLaneIds);
|
|
221
|
+
return next;
|
|
222
|
+
});
|
|
223
|
+
setPendingChanges((p) => [...p, { kind: "quest-reorder", laneId: currentLane }]);
|
|
224
|
+
}
|
|
225
|
+
}, [trackOrder, questOrderByLane, tracks, findLaneForQuest, pushUndo]);
|
|
226
|
+
const reset = useCallback(() => {
|
|
227
|
+
const sortedTracks = [...tracks].sort((a, b) => a.sortOrder - b.sortOrder);
|
|
228
|
+
setTrackOrder(sortedTracks.map((t) => `track:${t._id}`));
|
|
229
|
+
const byLane = /* @__PURE__ */ new Map();
|
|
230
|
+
for (const t of sortedTracks) {
|
|
231
|
+
const laneId = `track:${t._id}`;
|
|
232
|
+
const laneQuests = quests.filter((q) => getLaneId(q) === laneId).sort((a, b) => a.sortOrder - b.sortOrder);
|
|
233
|
+
byLane.set(laneId, laneQuests.map((q) => `quest:${q._id}`));
|
|
234
|
+
}
|
|
235
|
+
const unassigned = quests.filter((q) => getLaneId(q) === "unassigned").sort((a, b) => a.sortOrder - b.sortOrder);
|
|
236
|
+
byLane.set("unassigned", unassigned.map((q) => `quest:${q._id}`));
|
|
237
|
+
setQuestOrderByLane(byLane);
|
|
238
|
+
setPendingChanges([]);
|
|
239
|
+
setLocalQuestFields(/* @__PURE__ */ new Map());
|
|
240
|
+
setLocalTrackFields(/* @__PURE__ */ new Map());
|
|
241
|
+
undoStack.current = [];
|
|
242
|
+
}, [tracks, quests]);
|
|
243
|
+
const selectItem = useCallback((item) => {
|
|
244
|
+
setSelectedItem(item);
|
|
245
|
+
if (item) setActivePanelTab("settings");
|
|
246
|
+
}, []);
|
|
247
|
+
const orderedTracks = useMemo(() => {
|
|
248
|
+
return trackOrder.map((dndId) => {
|
|
249
|
+
const { rawId } = parseDndId(dndId);
|
|
250
|
+
return getTrack(rawId);
|
|
251
|
+
}).filter((t) => Boolean(t));
|
|
252
|
+
}, [trackOrder, getTrack]);
|
|
253
|
+
return {
|
|
254
|
+
// Data
|
|
255
|
+
orderedTracks,
|
|
256
|
+
getQuestsForLane,
|
|
257
|
+
getQuest,
|
|
258
|
+
getTrack,
|
|
259
|
+
trackOrder,
|
|
260
|
+
questOrderByLane,
|
|
261
|
+
// DnD
|
|
262
|
+
activeDragId,
|
|
263
|
+
handleDragStart,
|
|
264
|
+
handleDragOver,
|
|
265
|
+
handleDragEnd,
|
|
266
|
+
// Selection
|
|
267
|
+
selectedItem,
|
|
268
|
+
selectItem,
|
|
269
|
+
// Panel
|
|
270
|
+
activePanelTab,
|
|
271
|
+
setActivePanelTab,
|
|
272
|
+
// Edits
|
|
273
|
+
updateQuestField,
|
|
274
|
+
updateTrackField,
|
|
275
|
+
// Undo
|
|
276
|
+
undo,
|
|
277
|
+
// Pending changes
|
|
278
|
+
pendingChanges,
|
|
279
|
+
hasPendingChanges: pendingChanges.length > 0,
|
|
280
|
+
clearPendingChanges: () => setPendingChanges([]),
|
|
281
|
+
reset
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/hooks/useAchievementCanvasState.ts
|
|
286
|
+
import { useState as useState2, useCallback as useCallback2, useMemo as useMemo2, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
287
|
+
import { arrayMove as arrayMove2 } from "@dnd-kit/sortable";
|
|
288
|
+
function parseDndId2(id) {
|
|
289
|
+
if (id === "unassigned") return { type: "unassigned", rawId: "unassigned" };
|
|
290
|
+
const [type, rawId] = id.split(":");
|
|
291
|
+
return { type, rawId };
|
|
292
|
+
}
|
|
293
|
+
function normalizeLaneId2(id) {
|
|
294
|
+
if (id.startsWith("lane:")) return id.replace("lane:", "track:");
|
|
295
|
+
return id;
|
|
296
|
+
}
|
|
297
|
+
function getAchLaneId(ach) {
|
|
298
|
+
if (ach.trackId) return `track:${ach.trackId}`;
|
|
299
|
+
return "unassigned";
|
|
300
|
+
}
|
|
301
|
+
function useAchievementCanvasState(tracks, achievements) {
|
|
302
|
+
const [trackOrder, setTrackOrder] = useState2([]);
|
|
303
|
+
const [achOrderByLane, setAchOrderByLane] = useState2(/* @__PURE__ */ new Map());
|
|
304
|
+
const [selectedItem, setSelectedItem] = useState2(null);
|
|
305
|
+
const [activePanelTab, setActivePanelTab] = useState2("achievements");
|
|
306
|
+
const [groupMode, setGroupMode] = useState2("track");
|
|
307
|
+
const [pendingChanges, setPendingChanges] = useState2([]);
|
|
308
|
+
const [localAchFields, setLocalAchFields] = useState2(/* @__PURE__ */ new Map());
|
|
309
|
+
const [localTrackFields, setLocalTrackFields] = useState2(/* @__PURE__ */ new Map());
|
|
310
|
+
const undoStack = useRef2([]);
|
|
311
|
+
const [activeDragId, setActiveDragId] = useState2(null);
|
|
312
|
+
useEffect2(() => {
|
|
313
|
+
const sortedTracks = [...tracks].sort((a, b) => a.sortOrder - b.sortOrder);
|
|
314
|
+
setTrackOrder(sortedTracks.map((t) => `track:${t._id}`));
|
|
315
|
+
const byLane = /* @__PURE__ */ new Map();
|
|
316
|
+
for (const t of sortedTracks) {
|
|
317
|
+
const laneId = `track:${t._id}`;
|
|
318
|
+
const laneAchs = achievements.filter((a) => getAchLaneId(a) === laneId).sort((a, b) => a.sortOrder - b.sortOrder);
|
|
319
|
+
byLane.set(laneId, laneAchs.map((a) => `ach:${a._id}`));
|
|
320
|
+
}
|
|
321
|
+
const unassigned = achievements.filter((a) => getAchLaneId(a) === "unassigned").sort((a, b) => a.sortOrder - b.sortOrder);
|
|
322
|
+
byLane.set("unassigned", unassigned.map((a) => `ach:${a._id}`));
|
|
323
|
+
setAchOrderByLane(byLane);
|
|
324
|
+
setPendingChanges([]);
|
|
325
|
+
setLocalAchFields(/* @__PURE__ */ new Map());
|
|
326
|
+
setLocalTrackFields(/* @__PURE__ */ new Map());
|
|
327
|
+
}, [tracks, achievements]);
|
|
328
|
+
const pushUndo = useCallback2(() => {
|
|
329
|
+
undoStack.current = [...undoStack.current.slice(-19), {
|
|
330
|
+
trackOrder,
|
|
331
|
+
achOrderByLane: new Map(achOrderByLane),
|
|
332
|
+
localAchFields: new Map(localAchFields),
|
|
333
|
+
localTrackFields: new Map(localTrackFields),
|
|
334
|
+
pendingChanges: [...pendingChanges]
|
|
335
|
+
}];
|
|
336
|
+
}, [trackOrder, achOrderByLane, localAchFields, localTrackFields, pendingChanges]);
|
|
337
|
+
const undo = useCallback2(() => {
|
|
338
|
+
const entry = undoStack.current.pop();
|
|
339
|
+
if (!entry) return;
|
|
340
|
+
setTrackOrder(entry.trackOrder);
|
|
341
|
+
setAchOrderByLane(entry.achOrderByLane);
|
|
342
|
+
setLocalAchFields(entry.localAchFields);
|
|
343
|
+
setLocalTrackFields(entry.localTrackFields);
|
|
344
|
+
setPendingChanges(entry.pendingChanges);
|
|
345
|
+
}, []);
|
|
346
|
+
const getAchievement = useCallback2((id) => {
|
|
347
|
+
const base = achievements.find((a) => a._id === id);
|
|
348
|
+
if (!base) return void 0;
|
|
349
|
+
const overrides = localAchFields.get(id);
|
|
350
|
+
return overrides ? { ...base, ...overrides } : base;
|
|
351
|
+
}, [achievements, localAchFields]);
|
|
352
|
+
const getTrack = useCallback2((id) => {
|
|
353
|
+
const base = tracks.find((t) => t._id === id);
|
|
354
|
+
if (!base) return void 0;
|
|
355
|
+
const overrides = localTrackFields.get(id);
|
|
356
|
+
return overrides ? { ...base, ...overrides } : base;
|
|
357
|
+
}, [tracks, localTrackFields]);
|
|
358
|
+
const getAchievementsForLane = useCallback2((laneId) => {
|
|
359
|
+
const ids = achOrderByLane.get(laneId) ?? [];
|
|
360
|
+
return ids.map((dndId) => {
|
|
361
|
+
const { rawId } = parseDndId2(dndId);
|
|
362
|
+
return getAchievement(rawId);
|
|
363
|
+
}).filter((a) => Boolean(a));
|
|
364
|
+
}, [achOrderByLane, getAchievement]);
|
|
365
|
+
const findLaneForAch = useCallback2((achDndId) => {
|
|
366
|
+
for (const [laneId, ids] of achOrderByLane) {
|
|
367
|
+
if (ids.includes(achDndId)) return laneId;
|
|
368
|
+
}
|
|
369
|
+
return void 0;
|
|
370
|
+
}, [achOrderByLane]);
|
|
371
|
+
const updateAchField = useCallback2((id, fields) => {
|
|
372
|
+
pushUndo();
|
|
373
|
+
setLocalAchFields((m) => {
|
|
374
|
+
const next = new Map(m);
|
|
375
|
+
next.set(id, { ...next.get(id) ?? {}, ...fields });
|
|
376
|
+
return next;
|
|
377
|
+
});
|
|
378
|
+
setPendingChanges((p) => [...p, { kind: "ach-field", id, fields }]);
|
|
379
|
+
if ("trackId" in fields) {
|
|
380
|
+
const achDndId = `ach:${id}`;
|
|
381
|
+
const newLane = fields.trackId ? `track:${fields.trackId}` : "unassigned";
|
|
382
|
+
setAchOrderByLane((prev) => {
|
|
383
|
+
const next = new Map(prev);
|
|
384
|
+
for (const [laneId, ids] of next) {
|
|
385
|
+
if (ids.includes(achDndId)) {
|
|
386
|
+
next.set(laneId, ids.filter((aid) => aid !== achDndId));
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const target = next.get(newLane) ?? [];
|
|
391
|
+
if (!target.includes(achDndId)) next.set(newLane, [...target, achDndId]);
|
|
392
|
+
return next;
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}, [pushUndo]);
|
|
396
|
+
const updateTrackField = useCallback2((id, fields) => {
|
|
397
|
+
pushUndo();
|
|
398
|
+
setLocalTrackFields((m) => {
|
|
399
|
+
const next = new Map(m);
|
|
400
|
+
next.set(id, { ...next.get(id) ?? {}, ...fields });
|
|
401
|
+
return next;
|
|
402
|
+
});
|
|
403
|
+
setPendingChanges((p) => [...p, { kind: "track-field", id, fields }]);
|
|
404
|
+
}, [pushUndo]);
|
|
405
|
+
const handleDragStart = useCallback2((id) => {
|
|
406
|
+
setActiveDragId(id);
|
|
407
|
+
}, []);
|
|
408
|
+
const handleDragOver = useCallback2((event) => {
|
|
409
|
+
const { active, over } = event;
|
|
410
|
+
if (!over) return;
|
|
411
|
+
const activeId = String(active.id);
|
|
412
|
+
const overId = String(over.id);
|
|
413
|
+
const { type: activeType } = parseDndId2(activeId);
|
|
414
|
+
if (activeType !== "ach") return;
|
|
415
|
+
const sourceLane = findLaneForAch(activeId);
|
|
416
|
+
if (!sourceLane) return;
|
|
417
|
+
let targetLane;
|
|
418
|
+
const normalizedOverId = normalizeLaneId2(overId);
|
|
419
|
+
const { type: overType } = parseDndId2(normalizedOverId);
|
|
420
|
+
if (overType === "track" || normalizedOverId === "unassigned") targetLane = normalizedOverId;
|
|
421
|
+
else if (overType === "ach") targetLane = findLaneForAch(normalizedOverId) ?? sourceLane;
|
|
422
|
+
else return;
|
|
423
|
+
if (sourceLane === targetLane) return;
|
|
424
|
+
if (groupMode === "source") return;
|
|
425
|
+
setAchOrderByLane((prev) => {
|
|
426
|
+
const next = new Map(prev);
|
|
427
|
+
const srcList = (next.get(sourceLane) ?? []).filter((id) => id !== activeId);
|
|
428
|
+
const tgtList = [...next.get(targetLane) ?? [], activeId];
|
|
429
|
+
next.set(sourceLane, srcList);
|
|
430
|
+
next.set(targetLane, tgtList);
|
|
431
|
+
return next;
|
|
432
|
+
});
|
|
433
|
+
}, [findLaneForAch, groupMode]);
|
|
434
|
+
const handleDragEnd = useCallback2((event) => {
|
|
435
|
+
const { active, over } = event;
|
|
436
|
+
setActiveDragId(null);
|
|
437
|
+
if (!over) return;
|
|
438
|
+
const activeId = String(active.id);
|
|
439
|
+
const overId = String(over.id);
|
|
440
|
+
const { type: activeType, rawId: activeRawId } = parseDndId2(activeId);
|
|
441
|
+
if (activeType === "track") {
|
|
442
|
+
if (activeId === overId) return;
|
|
443
|
+
pushUndo();
|
|
444
|
+
setTrackOrder(arrayMove2(trackOrder, trackOrder.indexOf(activeId), trackOrder.indexOf(overId)));
|
|
445
|
+
setPendingChanges((p) => [...p, { kind: "track-reorder" }]);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (activeType === "ach") {
|
|
449
|
+
const lane = findLaneForAch(activeId);
|
|
450
|
+
if (!lane) return;
|
|
451
|
+
const laneIds = achOrderByLane.get(lane) ?? [];
|
|
452
|
+
const overIsInSameLane = laneIds.includes(overId);
|
|
453
|
+
if (!overIsInSameLane) {
|
|
454
|
+
const finalLane = findLaneForAch(activeId);
|
|
455
|
+
if (finalLane) {
|
|
456
|
+
pushUndo();
|
|
457
|
+
setPendingChanges((p) => [...p, { kind: "ach-reorder", laneId: finalLane }]);
|
|
458
|
+
if (finalLane !== "unassigned") {
|
|
459
|
+
const track = tracks.find((t) => `track:${t._id}` === finalLane);
|
|
460
|
+
if (track) updateAchField(activeRawId, { trackId: track._id });
|
|
461
|
+
} else {
|
|
462
|
+
updateAchField(activeRawId, { trackId: null });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (activeId === overId) return;
|
|
468
|
+
pushUndo();
|
|
469
|
+
const newLaneIds = arrayMove2(laneIds, laneIds.indexOf(activeId), laneIds.indexOf(overId));
|
|
470
|
+
setAchOrderByLane((prev) => {
|
|
471
|
+
const next = new Map(prev);
|
|
472
|
+
next.set(lane, newLaneIds);
|
|
473
|
+
return next;
|
|
474
|
+
});
|
|
475
|
+
setPendingChanges((p) => [...p, { kind: "ach-reorder", laneId: lane }]);
|
|
476
|
+
}
|
|
477
|
+
}, [trackOrder, achOrderByLane, tracks, findLaneForAch, pushUndo, updateAchField]);
|
|
478
|
+
const reset = useCallback2(() => {
|
|
479
|
+
const sortedTracks = [...tracks].sort((a, b) => a.sortOrder - b.sortOrder);
|
|
480
|
+
setTrackOrder(sortedTracks.map((t) => `track:${t._id}`));
|
|
481
|
+
const byLane = /* @__PURE__ */ new Map();
|
|
482
|
+
for (const t of sortedTracks) {
|
|
483
|
+
const laneId = `track:${t._id}`;
|
|
484
|
+
byLane.set(laneId, achievements.filter((a) => getAchLaneId(a) === laneId).sort((a, b) => a.sortOrder - b.sortOrder).map((a) => `ach:${a._id}`));
|
|
485
|
+
}
|
|
486
|
+
byLane.set("unassigned", achievements.filter((a) => getAchLaneId(a) === "unassigned").sort((a, b) => a.sortOrder - b.sortOrder).map((a) => `ach:${a._id}`));
|
|
487
|
+
setAchOrderByLane(byLane);
|
|
488
|
+
setPendingChanges([]);
|
|
489
|
+
setLocalAchFields(/* @__PURE__ */ new Map());
|
|
490
|
+
setLocalTrackFields(/* @__PURE__ */ new Map());
|
|
491
|
+
undoStack.current = [];
|
|
492
|
+
}, [tracks, achievements]);
|
|
493
|
+
const selectItem = useCallback2((item) => {
|
|
494
|
+
setSelectedItem(item);
|
|
495
|
+
if (item) setActivePanelTab("settings");
|
|
496
|
+
}, []);
|
|
497
|
+
const orderedTracks = useMemo2(() => {
|
|
498
|
+
return trackOrder.map((dndId) => {
|
|
499
|
+
const { rawId } = parseDndId2(dndId);
|
|
500
|
+
return getTrack(rawId);
|
|
501
|
+
}).filter((t) => Boolean(t));
|
|
502
|
+
}, [trackOrder, getTrack]);
|
|
503
|
+
return {
|
|
504
|
+
orderedTracks,
|
|
505
|
+
getAchievementsForLane,
|
|
506
|
+
getAchievement,
|
|
507
|
+
getTrack,
|
|
508
|
+
trackOrder,
|
|
509
|
+
achOrderByLane,
|
|
510
|
+
groupMode,
|
|
511
|
+
setGroupMode,
|
|
512
|
+
activeDragId,
|
|
513
|
+
handleDragStart,
|
|
514
|
+
handleDragOver,
|
|
515
|
+
handleDragEnd,
|
|
516
|
+
selectedItem,
|
|
517
|
+
selectItem,
|
|
518
|
+
activePanelTab,
|
|
519
|
+
setActivePanelTab,
|
|
520
|
+
updateAchField,
|
|
521
|
+
updateTrackField,
|
|
522
|
+
undo,
|
|
523
|
+
pendingChanges,
|
|
524
|
+
hasPendingChanges: pendingChanges.length > 0,
|
|
525
|
+
clearPendingChanges: () => setPendingChanges([]),
|
|
526
|
+
reset
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/hooks/useTestMode.ts
|
|
531
|
+
import { useState as useState3, useCallback as useCallback3 } from "react";
|
|
532
|
+
function computeQuestTestState(quest, state, allQuests) {
|
|
533
|
+
if (state.completedIds.has(quest._id)) return "passed";
|
|
534
|
+
if (state.verifyingId === quest._id) return "verifying";
|
|
535
|
+
if (state.failedIds.has(quest._id)) return "failed";
|
|
536
|
+
if (quest.status !== "ACTIVE") return "locked";
|
|
537
|
+
if (state.ignoreChain) return "ready";
|
|
538
|
+
const prereqs = quest.prerequisites ?? [];
|
|
539
|
+
for (const prereqId of prereqs) {
|
|
540
|
+
if (!state.completedIds.has(prereqId)) return "locked";
|
|
541
|
+
}
|
|
542
|
+
const chains = quest.chains ?? {};
|
|
543
|
+
for (const [group, order] of Object.entries(chains)) {
|
|
544
|
+
if (order <= 0) continue;
|
|
545
|
+
const prevOrder = order - 1;
|
|
546
|
+
const prevQuests = allQuests.filter(
|
|
547
|
+
(q) => ((q.chains ?? {})[group] ?? -1) === prevOrder
|
|
548
|
+
);
|
|
549
|
+
for (const pq of prevQuests) {
|
|
550
|
+
if (!state.completedIds.has(pq._id)) return "locked";
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return "ready";
|
|
554
|
+
}
|
|
555
|
+
function useTestMode(verifyFn) {
|
|
556
|
+
const [state, setState] = useState3({
|
|
557
|
+
active: false,
|
|
558
|
+
withVerification: false,
|
|
559
|
+
ignoreChain: false,
|
|
560
|
+
completedIds: /* @__PURE__ */ new Set(),
|
|
561
|
+
verifyingId: null,
|
|
562
|
+
failedIds: /* @__PURE__ */ new Map()
|
|
563
|
+
});
|
|
564
|
+
const enterTestMode = useCallback3(() => {
|
|
565
|
+
setState((s) => ({ ...s, active: true, completedIds: /* @__PURE__ */ new Set(), verifyingId: null, failedIds: /* @__PURE__ */ new Map() }));
|
|
566
|
+
}, []);
|
|
567
|
+
const exitTestMode = useCallback3(() => {
|
|
568
|
+
setState((s) => ({ ...s, active: false, completedIds: /* @__PURE__ */ new Set(), verifyingId: null, failedIds: /* @__PURE__ */ new Map() }));
|
|
569
|
+
}, []);
|
|
570
|
+
const toggleVerification = useCallback3(() => {
|
|
571
|
+
setState((s) => ({ ...s, withVerification: !s.withVerification }));
|
|
572
|
+
}, []);
|
|
573
|
+
const toggleIgnoreChain = useCallback3(() => {
|
|
574
|
+
setState((s) => ({ ...s, ignoreChain: !s.ignoreChain }));
|
|
575
|
+
}, []);
|
|
576
|
+
const claimQuest = useCallback3(async (questId) => {
|
|
577
|
+
if (!state.active) return;
|
|
578
|
+
if (!state.withVerification || !verifyFn) {
|
|
579
|
+
setState((s) => {
|
|
580
|
+
const next = new Set(s.completedIds);
|
|
581
|
+
next.add(questId);
|
|
582
|
+
return { ...s, completedIds: next };
|
|
583
|
+
});
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
setState((s) => ({ ...s, verifyingId: questId }));
|
|
587
|
+
try {
|
|
588
|
+
const result = await verifyFn(questId);
|
|
589
|
+
setState((s) => {
|
|
590
|
+
if (result.passed) {
|
|
591
|
+
const next = new Set(s.completedIds);
|
|
592
|
+
next.add(questId);
|
|
593
|
+
return { ...s, verifyingId: null, completedIds: next };
|
|
594
|
+
} else {
|
|
595
|
+
const nextFailed = new Map(s.failedIds);
|
|
596
|
+
nextFailed.set(questId, result.reason ?? "Verification failed");
|
|
597
|
+
return { ...s, verifyingId: null, failedIds: nextFailed };
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
} catch {
|
|
601
|
+
setState((s) => {
|
|
602
|
+
const nextFailed = new Map(s.failedIds);
|
|
603
|
+
nextFailed.set(questId, "Network error");
|
|
604
|
+
return { ...s, verifyingId: null, failedIds: nextFailed };
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}, [state.active, state.withVerification, verifyFn]);
|
|
608
|
+
const resetTest = useCallback3(() => {
|
|
609
|
+
setState((s) => ({ ...s, completedIds: /* @__PURE__ */ new Set(), verifyingId: null, failedIds: /* @__PURE__ */ new Map() }));
|
|
610
|
+
}, []);
|
|
611
|
+
return {
|
|
612
|
+
testMode: state,
|
|
613
|
+
enterTestMode,
|
|
614
|
+
exitTestMode,
|
|
615
|
+
toggleVerification,
|
|
616
|
+
toggleIgnoreChain,
|
|
617
|
+
claimQuest,
|
|
618
|
+
resetTest
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// src/hooks/useChainMode.ts
|
|
623
|
+
import { useState as useState4, useMemo as useMemo3, useCallback as useCallback4 } from "react";
|
|
624
|
+
function useChainMode(items) {
|
|
625
|
+
const [active, setActive] = useState4(false);
|
|
626
|
+
const [selectedChain, setSelectedChain] = useState4(null);
|
|
627
|
+
const chainMap = useMemo3(() => {
|
|
628
|
+
const map = /* @__PURE__ */ new Map();
|
|
629
|
+
for (const q of items) {
|
|
630
|
+
const chains = q.chains ?? {};
|
|
631
|
+
for (const group of Object.keys(chains)) {
|
|
632
|
+
const list = map.get(group) ?? [];
|
|
633
|
+
list.push(q);
|
|
634
|
+
map.set(group, list);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
for (const [group, list] of map) {
|
|
638
|
+
list.sort((a, b) => ((a.chains ?? {})[group] ?? 0) - ((b.chains ?? {})[group] ?? 0));
|
|
639
|
+
}
|
|
640
|
+
return map;
|
|
641
|
+
}, [items]);
|
|
642
|
+
const chainGroups = useMemo3(
|
|
643
|
+
() => [...chainMap.keys()].sort(),
|
|
644
|
+
[chainMap]
|
|
645
|
+
);
|
|
646
|
+
const toggle = useCallback4(() => {
|
|
647
|
+
setActive((prev) => {
|
|
648
|
+
if (prev) setSelectedChain(null);
|
|
649
|
+
return !prev;
|
|
650
|
+
});
|
|
651
|
+
}, []);
|
|
652
|
+
const selectChain = useCallback4((group) => {
|
|
653
|
+
setSelectedChain(group);
|
|
654
|
+
}, []);
|
|
655
|
+
const deselectChain = useCallback4(() => {
|
|
656
|
+
setSelectedChain(null);
|
|
657
|
+
}, []);
|
|
658
|
+
const exit = useCallback4(() => {
|
|
659
|
+
setActive(false);
|
|
660
|
+
setSelectedChain(null);
|
|
661
|
+
}, []);
|
|
662
|
+
return {
|
|
663
|
+
active,
|
|
664
|
+
selectedChain,
|
|
665
|
+
toggle,
|
|
666
|
+
selectChain,
|
|
667
|
+
deselectChain,
|
|
668
|
+
exit,
|
|
669
|
+
chainMap,
|
|
670
|
+
chainGroups
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// src/hooks/useAchievementTestMode.ts
|
|
675
|
+
import { useState as useState5, useCallback as useCallback5 } from "react";
|
|
676
|
+
function computeAchTestState(ach, state, allAchievements) {
|
|
677
|
+
const progress = state.progressMap.get(ach._id);
|
|
678
|
+
if (progress?.claimed) return "claimed";
|
|
679
|
+
if (progress?.completed) return "completed";
|
|
680
|
+
if (ach.status !== "ACTIVE") return "locked";
|
|
681
|
+
if (state.ignoreChain) return "ready";
|
|
682
|
+
const chains = ach.chains ?? {};
|
|
683
|
+
for (const [group, order] of Object.entries(chains)) {
|
|
684
|
+
if (order <= 0) continue;
|
|
685
|
+
const prevOrder = order - 1;
|
|
686
|
+
const prevAchs = allAchievements.filter(
|
|
687
|
+
(a) => ((a.chains ?? {})[group] ?? -1) === prevOrder
|
|
688
|
+
);
|
|
689
|
+
for (const pa of prevAchs) {
|
|
690
|
+
const pp = state.progressMap.get(pa._id);
|
|
691
|
+
if (!pp?.completed) return "locked";
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return "ready";
|
|
695
|
+
}
|
|
696
|
+
function useAchievementTestMode(fetchProgressFn) {
|
|
697
|
+
const [state, setState] = useState5({
|
|
698
|
+
active: false,
|
|
699
|
+
walletAddress: "",
|
|
700
|
+
ignoreChain: false,
|
|
701
|
+
progressMap: /* @__PURE__ */ new Map(),
|
|
702
|
+
loading: false,
|
|
703
|
+
error: null
|
|
704
|
+
});
|
|
705
|
+
const enterTestMode = useCallback5(() => {
|
|
706
|
+
setState((s) => ({ ...s, active: true, progressMap: /* @__PURE__ */ new Map(), error: null }));
|
|
707
|
+
}, []);
|
|
708
|
+
const exitTestMode = useCallback5(() => {
|
|
709
|
+
setState((s) => ({ ...s, active: false, walletAddress: "", progressMap: /* @__PURE__ */ new Map(), loading: false, error: null }));
|
|
710
|
+
}, []);
|
|
711
|
+
const toggleIgnoreChain = useCallback5(() => {
|
|
712
|
+
setState((s) => ({ ...s, ignoreChain: !s.ignoreChain }));
|
|
713
|
+
}, []);
|
|
714
|
+
const setWalletAddress = useCallback5((address) => {
|
|
715
|
+
setState((s) => ({ ...s, walletAddress: address }));
|
|
716
|
+
}, []);
|
|
717
|
+
const fetchProgress = useCallback5(async (address) => {
|
|
718
|
+
const wallet = address ?? state.walletAddress;
|
|
719
|
+
if (!wallet.trim() || !fetchProgressFn) return;
|
|
720
|
+
setState((s) => ({ ...s, loading: true, error: null, walletAddress: wallet }));
|
|
721
|
+
try {
|
|
722
|
+
const progress = await fetchProgressFn(wallet.trim());
|
|
723
|
+
const map = /* @__PURE__ */ new Map();
|
|
724
|
+
for (const p of progress) {
|
|
725
|
+
map.set(p.achievementId, p);
|
|
726
|
+
}
|
|
727
|
+
setState((s) => ({ ...s, progressMap: map, loading: false }));
|
|
728
|
+
} catch (err) {
|
|
729
|
+
setState((s) => ({
|
|
730
|
+
...s,
|
|
731
|
+
loading: false,
|
|
732
|
+
error: err instanceof Error ? err.message : String(err)
|
|
733
|
+
}));
|
|
734
|
+
}
|
|
735
|
+
}, [state.walletAddress, fetchProgressFn]);
|
|
736
|
+
const clearProgress = useCallback5(() => {
|
|
737
|
+
setState((s) => ({ ...s, progressMap: /* @__PURE__ */ new Map(), walletAddress: "", error: null }));
|
|
738
|
+
}, []);
|
|
739
|
+
return {
|
|
740
|
+
testMode: state,
|
|
741
|
+
enterTestMode,
|
|
742
|
+
exitTestMode,
|
|
743
|
+
toggleIgnoreChain,
|
|
744
|
+
setWalletAddress,
|
|
745
|
+
fetchProgress,
|
|
746
|
+
clearProgress
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/hooks/useAchievementChainMode.ts
|
|
751
|
+
import { useState as useState6, useMemo as useMemo4, useCallback as useCallback6 } from "react";
|
|
752
|
+
function useAchievementChainMode(achievements) {
|
|
753
|
+
const [active, setActive] = useState6(false);
|
|
754
|
+
const [selectedChain, setSelectedChain] = useState6(null);
|
|
755
|
+
const chainMap = useMemo4(() => {
|
|
756
|
+
const map = /* @__PURE__ */ new Map();
|
|
757
|
+
for (const a of achievements) {
|
|
758
|
+
const chains = a.chains ?? {};
|
|
759
|
+
for (const group of Object.keys(chains)) {
|
|
760
|
+
const list = map.get(group) ?? [];
|
|
761
|
+
list.push(a);
|
|
762
|
+
map.set(group, list);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
for (const [group, list] of map) {
|
|
766
|
+
list.sort((a, b) => ((a.chains ?? {})[group] ?? 0) - ((b.chains ?? {})[group] ?? 0));
|
|
767
|
+
}
|
|
768
|
+
return map;
|
|
769
|
+
}, [achievements]);
|
|
770
|
+
const chainGroups = useMemo4(
|
|
771
|
+
() => [...chainMap.keys()].sort(),
|
|
772
|
+
[chainMap]
|
|
773
|
+
);
|
|
774
|
+
const toggle = useCallback6(() => {
|
|
775
|
+
setActive((prev) => {
|
|
776
|
+
if (prev) setSelectedChain(null);
|
|
777
|
+
return !prev;
|
|
778
|
+
});
|
|
779
|
+
}, []);
|
|
780
|
+
const selectChain = useCallback6((group) => {
|
|
781
|
+
setSelectedChain(group);
|
|
782
|
+
}, []);
|
|
783
|
+
const deselectChain = useCallback6(() => {
|
|
784
|
+
setSelectedChain(null);
|
|
785
|
+
}, []);
|
|
786
|
+
const exit = useCallback6(() => {
|
|
787
|
+
setActive(false);
|
|
788
|
+
setSelectedChain(null);
|
|
789
|
+
}, []);
|
|
790
|
+
return {
|
|
791
|
+
active,
|
|
792
|
+
selectedChain,
|
|
793
|
+
toggle,
|
|
794
|
+
selectChain,
|
|
795
|
+
deselectChain,
|
|
796
|
+
exit,
|
|
797
|
+
chainMap,
|
|
798
|
+
chainGroups
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
export {
|
|
802
|
+
chainColor,
|
|
803
|
+
computeAchTestState,
|
|
804
|
+
computeQuestTestState,
|
|
805
|
+
getAchLaneId,
|
|
806
|
+
getLaneId,
|
|
807
|
+
useAchievementCanvasState,
|
|
808
|
+
useAchievementChainMode,
|
|
809
|
+
useAchievementTestMode,
|
|
810
|
+
useCanvasState,
|
|
811
|
+
useChainMode,
|
|
812
|
+
useTestMode
|
|
813
|
+
};
|