castle-web-cli 0.4.118 → 0.4.120
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/api.js +13 -2
- package/dist/castle-host/host.d.ts +16 -0
- package/dist/castle-host/host.js +249 -11
- package/dist/castle-host/leaderboardPanel.d.ts +73 -0
- package/dist/castle-host/leaderboardPanel.js +714 -0
- package/dist/ide.d.ts +2 -0
- package/dist/ide.js +35 -0
- package/dist/index.js +1 -6
- package/dist/preview.d.ts +0 -1
- package/dist/preview.js +18 -23
- package/dist/save-deck.js +33 -2
- package/dist/serve.js +9 -4
- package/dist/shell/assets/index-BkJ87APM.css +1 -0
- package/dist/shell/assets/index-Cxmq9Sed.js +434 -0
- package/dist/shell/index.html +2 -2
- package/kits/physics-2d/CLAUDE.md +8 -4
- package/kits/physics-2d/castle.json +1 -1
- package/kits/physics-2d/editors/SceneEditor.jsx +11 -2
- package/kits/physics-2d/editors/pixelInspector.jsx +30 -9
- package/kits/physics-2d/editors/pxArtTimeline.jsx +147 -18
- package/kits/physics-2d/engine/ui.module.css +1 -0
- package/package.json +1 -1
- package/dist/shell/assets/index-DsOo_SWO.js +0 -144
- package/dist/shell/assets/index-Y6cJRRCX.css +0 -1
package/dist/api.js
CHANGED
|
@@ -18,10 +18,21 @@ export async function graphql(query, variables) {
|
|
|
18
18
|
});
|
|
19
19
|
return res.json();
|
|
20
20
|
}
|
|
21
|
+
// Castle masks every ClientError's message to "Unexpected error." and puts the
|
|
22
|
+
// real one in `extensions.code` -- so the bare message is the same string for a
|
|
23
|
+
// permissions failure, a missing deck, and a bad argument. Carry the code in the
|
|
24
|
+
// message too, since that is all most callers (and the top-level handler) print.
|
|
21
25
|
function handleAPIError(data) {
|
|
22
26
|
if (data?.errors?.length) {
|
|
23
|
-
const
|
|
24
|
-
|
|
27
|
+
const first = data.errors[0];
|
|
28
|
+
const code = first?.extensions?.code;
|
|
29
|
+
// `path` names the field that failed, which is the operation for a mutation
|
|
30
|
+
// -- without it "Unexpected error." doesn't even say which call broke.
|
|
31
|
+
const where = Array.isArray(first?.path) ? first.path.join('.') : '';
|
|
32
|
+
const parts = [where, typeof code === 'string' ? code : ''].filter(Boolean);
|
|
33
|
+
const message = first?.message ?? 'GraphQL error';
|
|
34
|
+
const err = new Error(parts.length ? `${message} [${parts.join(': ')}]` : message);
|
|
35
|
+
err.extensions = first?.extensions;
|
|
25
36
|
throw err;
|
|
26
37
|
}
|
|
27
38
|
}
|
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
// Node16 resolution rejects. Mirror the public surface of
|
|
5
5
|
// castle-experimental-web/sdk/src/host.ts; update if that signature changes.
|
|
6
6
|
|
|
7
|
+
import type { CastleLeaderboardPayload } from './leaderboardPanel.js';
|
|
8
|
+
|
|
7
9
|
export interface HostContext {
|
|
8
10
|
deckId: string | null;
|
|
9
11
|
sessionId: string | null;
|
|
10
12
|
userId: string | null;
|
|
11
13
|
username: string | null;
|
|
14
|
+
isAnonymous?: boolean;
|
|
15
|
+
deepLinkUri?: string | null;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
export interface GraphqlError {
|
|
@@ -36,6 +40,18 @@ export interface HostCapabilities {
|
|
|
36
40
|
graphqlFetch: GraphqlFetch;
|
|
37
41
|
platformHandler?: PlatformHandler;
|
|
38
42
|
localPassGrant?: (passId: string) => boolean;
|
|
43
|
+
showLeaderboard?: (payload: CastleLeaderboardPayload) => void;
|
|
44
|
+
canShowLeaderboard?: () => boolean;
|
|
45
|
+
leaderboardShowsParties?: boolean;
|
|
46
|
+
onStreakCredit?: (credit: LeaderboardStreakCredit) => void;
|
|
47
|
+
logAnalyticsEvent?: (event: string, properties: Record<string, unknown>) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface LeaderboardStreakCredit {
|
|
51
|
+
deckId: string | null;
|
|
52
|
+
kind: string;
|
|
53
|
+
priorStreak: number;
|
|
54
|
+
newStreak: number;
|
|
39
55
|
}
|
|
40
56
|
|
|
41
57
|
export interface SerializedCommandError {
|
package/dist/castle-host/host.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED — do not edit. Vendored from castle-experimental-web/sdk/dist/
|
|
1
|
+
// GENERATED — do not edit. Vendored from castle-experimental-web/sdk/dist/
|
|
2
2
|
// Re-run `npm run copy-host` in castle-experimental-web after an SDK change.
|
|
3
3
|
|
|
4
4
|
// Host-side command executor — the shared module run by every trusted host
|
|
@@ -22,6 +22,7 @@ const COMMAND_NAMES = [
|
|
|
22
22
|
"sharedDeckStorage.update",
|
|
23
23
|
"leaderboard.fetch",
|
|
24
24
|
"leaderboard.save",
|
|
25
|
+
"leaderboard.show",
|
|
25
26
|
"user.getCurrent",
|
|
26
27
|
"time.getServerTime",
|
|
27
28
|
"pass.has",
|
|
@@ -41,6 +42,10 @@ const PLATFORM_COMMAND_NAMES = [
|
|
|
41
42
|
"portal.prefetch",
|
|
42
43
|
"haptics.play",
|
|
43
44
|
];
|
|
45
|
+
// Mirrors CASTLE_LEADERBOARD_PAYLOAD_VERSION in leaderboardPanel.ts. Duplicated
|
|
46
|
+
// rather than imported because every import here has to stay type-only; bump
|
|
47
|
+
// both together — the mobile panel ships in app binaries and will skew.
|
|
48
|
+
const LEADERBOARD_PAYLOAD_VERSION = 1;
|
|
44
49
|
function isCommandName(value) {
|
|
45
50
|
return (typeof value === "string" &&
|
|
46
51
|
COMMAND_NAMES.includes(value));
|
|
@@ -94,9 +99,11 @@ function runCommand(ctx, command, params, caps) {
|
|
|
94
99
|
case "sharedDeckStorage.update":
|
|
95
100
|
return sharedDeckStorageUpdate(ctx, params, gql);
|
|
96
101
|
case "leaderboard.fetch":
|
|
97
|
-
return leaderboardFetch(ctx, params,
|
|
102
|
+
return leaderboardFetch(ctx, params, caps);
|
|
98
103
|
case "leaderboard.save":
|
|
99
|
-
return leaderboardSave(ctx, params,
|
|
104
|
+
return leaderboardSave(ctx, params, caps);
|
|
105
|
+
case "leaderboard.show":
|
|
106
|
+
return leaderboardShow(ctx, params, caps);
|
|
100
107
|
case "user.getCurrent":
|
|
101
108
|
return Promise.resolve(userGetCurrent(ctx));
|
|
102
109
|
case "time.getServerTime":
|
|
@@ -275,12 +282,12 @@ async function sharedDeckStorageUpdate(ctx, params, gql) {
|
|
|
275
282
|
await graphql(gql, UPDATE_SHARED_DECK_STORAGE_MUTATION, { deckId, sessionId: ctx.sessionId, userId, updates: asUpdates(params.updates) }, "sharedDeckStorage.update");
|
|
276
283
|
return { ok: true };
|
|
277
284
|
}
|
|
278
|
-
async function leaderboardFetch(ctx, params,
|
|
285
|
+
async function leaderboardFetch(ctx, params, caps) {
|
|
279
286
|
const deckId = requireDeckId(ctx, "leaderboard.fetch");
|
|
280
287
|
const variables = {
|
|
281
288
|
deckId,
|
|
282
289
|
variable: asString(params.variable, "variable", "leaderboard.fetch"),
|
|
283
|
-
type: asLeaderboardType(params.type),
|
|
290
|
+
type: asLeaderboardType(params.type, "leaderboard.fetch"),
|
|
284
291
|
filter: "dedupUsers",
|
|
285
292
|
includeFollowList: false,
|
|
286
293
|
includeParties: false,
|
|
@@ -292,22 +299,147 @@ async function leaderboardFetch(ctx, params, gql) {
|
|
|
292
299
|
// No score → plain read of the settled leaderboard.
|
|
293
300
|
const score = asOptionalNumber(params.score);
|
|
294
301
|
if (score !== null) {
|
|
295
|
-
const data = await graphql(
|
|
302
|
+
const data = await graphql(caps.graphqlFetch, LEADERBOARD_V2_MUTATION, { ...variables, score }, "leaderboard.fetch");
|
|
303
|
+
relayStreakCredit(caps, deckId, data.leaderboardV2?.streakCredit);
|
|
296
304
|
return { leaderboard: data.leaderboardV2, currentUserId: ctx.userId };
|
|
297
305
|
}
|
|
298
|
-
const data = await graphql(
|
|
306
|
+
const data = await graphql(caps.graphqlFetch, LEADERBOARD_QUERY, variables, "leaderboard.fetch");
|
|
299
307
|
return { leaderboard: data.leaderboard, currentUserId: ctx.userId };
|
|
300
308
|
}
|
|
301
|
-
async function leaderboardSave(ctx, params,
|
|
309
|
+
async function leaderboardSave(ctx, params, caps) {
|
|
302
310
|
const deckId = requireDeckId(ctx, "leaderboard.save");
|
|
303
|
-
|
|
311
|
+
// saveVariableToLeaderboard returns a Json scalar shaped { streakCredit },
|
|
312
|
+
// so there is no selection set to add — read the key off the scalar.
|
|
313
|
+
const data = await graphql(caps.graphqlFetch, SAVE_LEADERBOARD_MUTATION, {
|
|
304
314
|
deckId,
|
|
305
315
|
variable: asString(params.variable, "variable", "leaderboard.save"),
|
|
306
316
|
score: asNumber(params.score, "score", "leaderboard.save"),
|
|
307
317
|
scope: asOptionalString(params.scope) ?? null,
|
|
308
318
|
}, "leaderboard.save");
|
|
319
|
+
relayStreakCredit(caps, deckId, data.saveVariableToLeaderboard?.streakCredit);
|
|
309
320
|
return { ok: true };
|
|
310
321
|
}
|
|
322
|
+
// The host renders the board itself, so nothing here flows back to the deck
|
|
323
|
+
// beyond whether it was presented. With a score this is the same write-and-read
|
|
324
|
+
// the classic leaderboard performs when it opens.
|
|
325
|
+
async function leaderboardShow(ctx, params, caps) {
|
|
326
|
+
const show = caps.showLeaderboard;
|
|
327
|
+
if (!show)
|
|
328
|
+
return { status: "unavailable" };
|
|
329
|
+
if (caps.canShowLeaderboard && !caps.canShowLeaderboard()) {
|
|
330
|
+
return { status: "unavailable" };
|
|
331
|
+
}
|
|
332
|
+
const deckId = requireDeckId(ctx, "leaderboard.show");
|
|
333
|
+
const variable = asString(params.variable, "variable", "leaderboard.show");
|
|
334
|
+
const type = asLeaderboardType(params.type, "leaderboard.show");
|
|
335
|
+
const scope = asOptionalString(params.scope) ?? null;
|
|
336
|
+
const label = asOptionalString(params.label) ?? "Score";
|
|
337
|
+
const empty = {
|
|
338
|
+
v: LEADERBOARD_PAYLOAD_VERSION,
|
|
339
|
+
deckId,
|
|
340
|
+
variable,
|
|
341
|
+
scope,
|
|
342
|
+
type,
|
|
343
|
+
label,
|
|
344
|
+
isAnonymous: ctx.isAnonymous === true,
|
|
345
|
+
showParties: caps.leaderboardShowsParties === true,
|
|
346
|
+
yourScore: null,
|
|
347
|
+
list: [],
|
|
348
|
+
followList: [],
|
|
349
|
+
parties: [],
|
|
350
|
+
};
|
|
351
|
+
// Present, then fetch. The round trip is the slow part of an open, so the
|
|
352
|
+
// board goes up on its spinner the moment the deck asks for it and fills in
|
|
353
|
+
// when the rows land — the same order the engine's leaderboard uses.
|
|
354
|
+
show({ ...empty, loading: true });
|
|
355
|
+
let raw;
|
|
356
|
+
try {
|
|
357
|
+
raw = await fetchFullLeaderboard(caps, {
|
|
358
|
+
deckId,
|
|
359
|
+
variable,
|
|
360
|
+
type,
|
|
361
|
+
scope,
|
|
362
|
+
label,
|
|
363
|
+
url: ctx.deepLinkUri ?? null,
|
|
364
|
+
score: asOptionalNumber(params.score),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
// The board is already up: leave it on an empty list rather than a spinner
|
|
369
|
+
// that never stops. The deck still gets the error.
|
|
370
|
+
show(empty);
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
relayStreakCredit(caps, deckId, raw?.streakCredit);
|
|
374
|
+
show({
|
|
375
|
+
...empty,
|
|
376
|
+
yourScore: asNumberish(raw?.yourScore?.score),
|
|
377
|
+
list: leaderboardRows(raw?.list),
|
|
378
|
+
followList: leaderboardRows(raw?.followList),
|
|
379
|
+
parties: leaderboardParties(raw?.parties),
|
|
380
|
+
});
|
|
381
|
+
caps.logAnalyticsEvent?.("VIEW_LEADERBOARD", {
|
|
382
|
+
deckId,
|
|
383
|
+
variableName: variable,
|
|
384
|
+
scope: scope ?? "",
|
|
385
|
+
leaderboardType: type,
|
|
386
|
+
});
|
|
387
|
+
return { status: "shown" };
|
|
388
|
+
}
|
|
389
|
+
// A buffered score turns the read into the engine's write-and-read, so the
|
|
390
|
+
// player's own result is on the board the first time they see it.
|
|
391
|
+
function fetchFullLeaderboard(caps, request) {
|
|
392
|
+
const { score, ...rest } = request;
|
|
393
|
+
const variables = {
|
|
394
|
+
...rest,
|
|
395
|
+
filter: "dedupUsers",
|
|
396
|
+
includeFollowList: true,
|
|
397
|
+
includeParties: true,
|
|
398
|
+
};
|
|
399
|
+
if (score === null) {
|
|
400
|
+
return graphql(caps.graphqlFetch, LEADERBOARD_FULL_QUERY, variables, "leaderboard.show").then((data) => data.leaderboard);
|
|
401
|
+
}
|
|
402
|
+
return graphql(caps.graphqlFetch, LEADERBOARD_V2_FULL_MUTATION, { ...variables, score }, "leaderboard.show").then((data) => data.leaderboardV2);
|
|
403
|
+
}
|
|
404
|
+
function relayStreakCredit(caps, deckId, credit) {
|
|
405
|
+
const kind = credit?.kind;
|
|
406
|
+
if (!caps.onStreakCredit || typeof kind !== "string" || kind.length === 0)
|
|
407
|
+
return;
|
|
408
|
+
caps.onStreakCredit({
|
|
409
|
+
deckId,
|
|
410
|
+
kind,
|
|
411
|
+
priorStreak: asNumberish(credit?.priorStreak) ?? 0,
|
|
412
|
+
newStreak: asNumberish(credit?.newStreak) ?? 0,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
function leaderboardRows(entries) {
|
|
416
|
+
return (entries ?? []).map((entry) => ({
|
|
417
|
+
place: asNumberish(entry?.place),
|
|
418
|
+
score: entry?.score === null || entry?.score === undefined ? "" : String(entry.score),
|
|
419
|
+
isDeckCreator: entry?.isDeckCreator === true,
|
|
420
|
+
isLinkSharer: entry?.isLinkSharer === true,
|
|
421
|
+
isRequiredUser: entry?.isRequiredUser === true,
|
|
422
|
+
user: {
|
|
423
|
+
userId: asOptionalString(entry?.user?.userId),
|
|
424
|
+
username: asOptionalString(entry?.user?.username),
|
|
425
|
+
isAnonymous: entry?.user?.isAnonymous === true,
|
|
426
|
+
color: asOptionalString(entry?.user?.usernameStyle?.color),
|
|
427
|
+
avatarUrl: asOptionalString(entry?.user?.photo?.smallAvatarUrl),
|
|
428
|
+
badgeUrl: asOptionalString(entry?.user?.usernameBadge?.smallBadgeUrl),
|
|
429
|
+
frameUrl: asOptionalString(entry?.user?.photoFrame?.smallFrameUrl),
|
|
430
|
+
},
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
function leaderboardParties(parties) {
|
|
434
|
+
return (parties ?? []).map((party) => ({
|
|
435
|
+
partyId: asOptionalString(party?.partyId),
|
|
436
|
+
name: asOptionalString(party?.name) ?? "",
|
|
437
|
+
avatarUrls: (party?.highlightUsers ?? [])
|
|
438
|
+
.map((user) => asOptionalString(user?.photo?.smallAvatarUrl))
|
|
439
|
+
.filter((url) => url !== null),
|
|
440
|
+
lastMessageTime: asOptionalString(party?.chatChannel?.lastChatMessageForDeck?.createdTime),
|
|
441
|
+
}));
|
|
442
|
+
}
|
|
311
443
|
function userGetCurrent(ctx) {
|
|
312
444
|
if (!ctx.userId || !ctx.username)
|
|
313
445
|
return { user: null };
|
|
@@ -381,10 +513,10 @@ function asScope(value, command) {
|
|
|
381
513
|
return value;
|
|
382
514
|
throw new HostCommandError("CASTLE_STORAGE_INVALID_SCOPE", 'SharedStorage scope must be "deck" or "user".', command);
|
|
383
515
|
}
|
|
384
|
-
function asLeaderboardType(value) {
|
|
516
|
+
function asLeaderboardType(value, command) {
|
|
385
517
|
if (value === "high" || value === "low")
|
|
386
518
|
return value;
|
|
387
|
-
throw new HostCommandError("INVALID_LEADERBOARD_TYPE", "Leaderboard type must be high or low.",
|
|
519
|
+
throw new HostCommandError("INVALID_LEADERBOARD_TYPE", "Leaderboard type must be high or low.", command);
|
|
388
520
|
}
|
|
389
521
|
function asString(value, field, command) {
|
|
390
522
|
if (typeof value === "string" && value.length > 0)
|
|
@@ -402,6 +534,16 @@ function asOptionalString(value) {
|
|
|
402
534
|
function asOptionalNumber(value) {
|
|
403
535
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
404
536
|
}
|
|
537
|
+
// GraphQL hands numeric fields back as numbers or as strings depending on the
|
|
538
|
+
// scalar, so leaderboard payload shaping accepts both.
|
|
539
|
+
function asNumberish(value) {
|
|
540
|
+
if (typeof value === "number")
|
|
541
|
+
return Number.isFinite(value) ? value : null;
|
|
542
|
+
if (typeof value !== "string")
|
|
543
|
+
return null;
|
|
544
|
+
const parsed = Number.parseFloat(value);
|
|
545
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
546
|
+
}
|
|
405
547
|
const DECK_STORAGE_QUERY = `
|
|
406
548
|
query CastleDeckStorage($deckId: ID!, $sessionId: ID) {
|
|
407
549
|
deckStorage(deckId: $deckId, sessionId: $sessionId)
|
|
@@ -507,8 +649,104 @@ const LEADERBOARD_V2_MUTATION = `
|
|
|
507
649
|
username
|
|
508
650
|
}
|
|
509
651
|
}
|
|
652
|
+
streakCredit { kind priorStreak newStreak }
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
`;
|
|
656
|
+
// The selection the host's own leaderboard UI renders from: the engine's full
|
|
657
|
+
// one (core/src/leaderboards.cpp getLeaderboard), which is a superset of the
|
|
658
|
+
// deck-facing one above. It is the only net-new artifact of `leaderboard.show`
|
|
659
|
+
// — the query text; every behavioural decision stays in the SDK's one copy.
|
|
660
|
+
const LEADERBOARD_FULL_FIELDS = `
|
|
661
|
+
parties {
|
|
662
|
+
partyId
|
|
663
|
+
status
|
|
664
|
+
userIds
|
|
665
|
+
name
|
|
666
|
+
users {
|
|
667
|
+
userId
|
|
668
|
+
username
|
|
669
|
+
usernameStyle { color }
|
|
670
|
+
usernameBadge { smallBadgeUrl }
|
|
671
|
+
photo { smallAvatarUrl }
|
|
672
|
+
}
|
|
673
|
+
highlightUsers {
|
|
674
|
+
userId
|
|
675
|
+
photo { smallAvatarUrl }
|
|
676
|
+
}
|
|
677
|
+
chatChannel {
|
|
678
|
+
chatChannelId
|
|
679
|
+
lastChatMessageForDeck {
|
|
680
|
+
createdTime
|
|
681
|
+
fromUser { userId }
|
|
682
|
+
}
|
|
510
683
|
}
|
|
511
684
|
}
|
|
685
|
+
yourScore { score }
|
|
686
|
+
list {
|
|
687
|
+
place
|
|
688
|
+
score
|
|
689
|
+
isDeckCreator
|
|
690
|
+
isLinkSharer
|
|
691
|
+
user {
|
|
692
|
+
userId
|
|
693
|
+
username
|
|
694
|
+
usernameStyle { color }
|
|
695
|
+
usernameBadge { smallBadgeUrl }
|
|
696
|
+
isAnonymous
|
|
697
|
+
photoFrame { smallFrameUrl }
|
|
698
|
+
photo { smallAvatarUrl }
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
followList {
|
|
702
|
+
place
|
|
703
|
+
score
|
|
704
|
+
isRequiredUser
|
|
705
|
+
isDeckCreator
|
|
706
|
+
isLinkSharer
|
|
707
|
+
user {
|
|
708
|
+
userId
|
|
709
|
+
username
|
|
710
|
+
usernameStyle { color }
|
|
711
|
+
usernameBadge { smallBadgeUrl }
|
|
712
|
+
isAnonymous
|
|
713
|
+
photoFrame { smallFrameUrl }
|
|
714
|
+
photo { smallAvatarUrl }
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
streakCredit { kind priorStreak newStreak }
|
|
718
|
+
`;
|
|
719
|
+
const LEADERBOARD_FULL_ARGS = `
|
|
720
|
+
deckId: $deckId
|
|
721
|
+
variable: $variable
|
|
722
|
+
type: $type
|
|
723
|
+
filter: $filter
|
|
724
|
+
label: $label
|
|
725
|
+
includeFollowList: $includeFollowList
|
|
726
|
+
includeParties: $includeParties
|
|
727
|
+
scope: $scope
|
|
728
|
+
url: $url
|
|
729
|
+
`;
|
|
730
|
+
const LEADERBOARD_FULL_VARS = `
|
|
731
|
+
$deckId: ID!
|
|
732
|
+
$variable: String!
|
|
733
|
+
$type: LeaderboardType!
|
|
734
|
+
$filter: LeaderboardFilter!
|
|
735
|
+
$label: String
|
|
736
|
+
$includeFollowList: Boolean
|
|
737
|
+
$includeParties: Boolean
|
|
738
|
+
$scope: String
|
|
739
|
+
$url: String
|
|
740
|
+
`;
|
|
741
|
+
const LEADERBOARD_FULL_QUERY = `
|
|
742
|
+
query CastleLeaderboardFull(${LEADERBOARD_FULL_VARS}) {
|
|
743
|
+
leaderboard(${LEADERBOARD_FULL_ARGS}) {${LEADERBOARD_FULL_FIELDS}}
|
|
744
|
+
}
|
|
745
|
+
`;
|
|
746
|
+
const LEADERBOARD_V2_FULL_MUTATION = `
|
|
747
|
+
mutation CastleLeaderboardV2Full(${LEADERBOARD_FULL_VARS}, $score: Float!) {
|
|
748
|
+
leaderboardV2(${LEADERBOARD_FULL_ARGS}, score: $score) {${LEADERBOARD_FULL_FIELDS}}
|
|
749
|
+
}
|
|
512
750
|
`;
|
|
513
751
|
const SAVE_LEADERBOARD_MUTATION = `
|
|
514
752
|
mutation CastleSaveVariableToLeaderboard(
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Hand-written declaration for the vendored, self-contained leaderboardPanel.js
|
|
2
|
+
// (runtime copied here by castle-experimental-web/scripts/copy-host-module.mjs).
|
|
3
|
+
// Kept tiny and import-free on purpose. TypeScript pairs this with the sibling
|
|
4
|
+
// .js automatically. Mirror the public surface of
|
|
5
|
+
// castle-experimental-web/sdk/src/leaderboardPanel.ts; update if it changes.
|
|
6
|
+
|
|
7
|
+
export declare const CASTLE_LEADERBOARD_PAYLOAD_VERSION: number;
|
|
8
|
+
|
|
9
|
+
export type CastleLeaderboardTab = 'global' | 'forYou';
|
|
10
|
+
|
|
11
|
+
export interface CastleLeaderboardUser {
|
|
12
|
+
userId: string | null;
|
|
13
|
+
username: string | null;
|
|
14
|
+
isAnonymous: boolean;
|
|
15
|
+
color: string | null;
|
|
16
|
+
avatarUrl: string | null;
|
|
17
|
+
badgeUrl: string | null;
|
|
18
|
+
frameUrl: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CastleLeaderboardRow {
|
|
22
|
+
place: number | null;
|
|
23
|
+
score: string;
|
|
24
|
+
isDeckCreator: boolean;
|
|
25
|
+
isLinkSharer: boolean;
|
|
26
|
+
isRequiredUser: boolean;
|
|
27
|
+
user: CastleLeaderboardUser;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CastleLeaderboardParty {
|
|
31
|
+
partyId: string | null;
|
|
32
|
+
name: string;
|
|
33
|
+
avatarUrls: string[];
|
|
34
|
+
lastMessageTime: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CastleLeaderboardPayload {
|
|
38
|
+
v: number;
|
|
39
|
+
deckId: string | null;
|
|
40
|
+
variable: string;
|
|
41
|
+
scope: string | null;
|
|
42
|
+
type: 'high' | 'low';
|
|
43
|
+
label: string;
|
|
44
|
+
isAnonymous: boolean;
|
|
45
|
+
showParties: boolean;
|
|
46
|
+
// Set while the host has put the board up but its rows haven't landed yet.
|
|
47
|
+
loading?: boolean;
|
|
48
|
+
yourScore: number | null;
|
|
49
|
+
list: CastleLeaderboardRow[];
|
|
50
|
+
followList: CastleLeaderboardRow[];
|
|
51
|
+
parties: CastleLeaderboardParty[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type CastleLeaderboardEvent =
|
|
55
|
+
| { type: 'ready' }
|
|
56
|
+
| { type: 'close' }
|
|
57
|
+
| { type: 'share' }
|
|
58
|
+
| { type: 'openProfile'; userId: string | null; username: string | null }
|
|
59
|
+
| { type: 'savePrompt'; userId: string | null }
|
|
60
|
+
| { type: 'switchTab'; tab: CastleLeaderboardTab };
|
|
61
|
+
|
|
62
|
+
export interface CastleLeaderboardPanel {
|
|
63
|
+
render(payload: CastleLeaderboardPayload): void;
|
|
64
|
+
clear(): void;
|
|
65
|
+
destroy(): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export declare function mountCastleLeaderboardPanel(
|
|
69
|
+
root: HTMLElement,
|
|
70
|
+
onEvent: (event: CastleLeaderboardEvent) => void
|
|
71
|
+
): CastleLeaderboardPanel;
|
|
72
|
+
|
|
73
|
+
export declare function castleLeaderboardPanelPageBootstrap(): void;
|