@khorsheed/dsh-game 0.1.0 → 0.1.1

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/lib/index.js CHANGED
@@ -388,6 +388,7 @@ let GameRemoteService = (() => {
388
388
  let _classSuper = TypertRemoteService;
389
389
  let _instanceExtraInitializers = [];
390
390
  let _state_decorators;
391
+ let _liveVersion_decorators;
391
392
  let _turnReport_decorators;
392
393
  let _projectCards_decorators;
393
394
  let _deleteProject_decorators;
@@ -417,6 +418,7 @@ let GameRemoteService = (() => {
417
418
  static {
418
419
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
419
420
  _state_decorators = [Remote("state")];
421
+ _liveVersion_decorators = [Remote("liveVersion")];
420
422
  _turnReport_decorators = [Remote("turnReport")];
421
423
  _projectCards_decorators = [Remote("projectCards")];
422
424
  _deleteProject_decorators = [Remote("deleteProject")];
@@ -453,6 +455,17 @@ let GameRemoteService = (() => {
453
455
  },
454
456
  metadata: _metadata
455
457
  }, null, _instanceExtraInitializers);
458
+ __esDecorate(this, null, _liveVersion_decorators, {
459
+ kind: "method",
460
+ name: "liveVersion",
461
+ static: false,
462
+ private: false,
463
+ access: {
464
+ has: (obj) => "liveVersion" in obj,
465
+ get: (obj) => obj.liveVersion
466
+ },
467
+ metadata: _metadata
468
+ }, null, _instanceExtraInitializers);
456
469
  __esDecorate(this, null, _turnReport_decorators, {
457
470
  kind: "method",
458
471
  name: "turnReport",
@@ -751,6 +764,10 @@ let GameRemoteService = (() => {
751
764
  state(sessionId) {
752
765
  return this.store.sessionState(sessionId);
753
766
  }
767
+ /** The bound project's newest version (cheap; the page polls it). */
768
+ liveVersion(sessionId) {
769
+ return this.store.liveVersion(sessionId);
770
+ }
754
771
  /** What one of the session's turns changed (null when nothing, or not recorded). */
755
772
  turnReport(sessionId, request) {
756
773
  return this.store.turnReport(sessionId, request.turn);
@@ -3067,7 +3084,13 @@ var GameService = class {
3067
3084
  manifest,
3068
3085
  ...manifestText === void 0 ? { manifestError: `${MANIFEST_FILE} 不见了` } : parsed?.ok === false ? { manifestError: parsed.error } : {},
3069
3086
  chapters,
3070
- elements,
3087
+ elements: await Promise.all(elements.map(async (e) => {
3088
+ const info = await stat(join(row.root, e.file)).catch(() => void 0);
3089
+ return info === void 0 ? e : {
3090
+ ...e,
3091
+ updatedAt: info.mtimeMs
3092
+ };
3093
+ })),
3071
3094
  elementErrors: failures,
3072
3095
  current: await readJson(join(state, "current.json")) ?? null,
3073
3096
  candidate: await readJson(join(state, "candidate.json")) ?? null,
@@ -3102,6 +3125,126 @@ var GameService = class {
3102
3125
  };
3103
3126
  }
3104
3127
  /** The tab's state for one session. */
3128
+ /**
3129
+ * The bound project's newest version (the waiting candidate, else the live
3130
+ * one), read from two small files: the page polls it to announce a version
3131
+ * the moment it lands, whoever's turn cut it. Version 0 means none yet.
3132
+ */
3133
+ async liveVersion(sessionId) {
3134
+ const row = await this.boundProject(sessionId);
3135
+ if (row === void 0) return null;
3136
+ const state = join(row.root, STATE_DIR);
3137
+ const candidate = await readJson(join(state, "candidate.json"));
3138
+ const record = candidate ?? await readJson(join(state, "current.json"));
3139
+ const show = this.shows.get(row.id);
3140
+ const extra = show === void 0 ? {} : { show };
3141
+ if (record === void 0) return {
3142
+ projectId: row.id,
3143
+ version: 0,
3144
+ kind: "none",
3145
+ at: null,
3146
+ ...extra
3147
+ };
3148
+ return {
3149
+ projectId: row.id,
3150
+ version: record.version,
3151
+ kind: candidate !== void 0 ? "candidate" : "deployed",
3152
+ at: record.at,
3153
+ ...record.by === void 0 ? {} : { by: record.by },
3154
+ ...extra
3155
+ };
3156
+ }
3157
+ /** The agent's latest 「请用户看这里」 per project (in memory: a request is for the moment, not history). */
3158
+ shows = /* @__PURE__ */ new Map();
3159
+ showSeq = 0;
3160
+ /**
3161
+ * After a passing `game_publish`: leave the pages showing this project a
3162
+ * request to take their game tab to the result. `show` is the agent's choice
3163
+ * (play, versions, elements, elements:<id or name>, design:<chapter>); left
3164
+ * out — or not found — the version decides: the one element it changed, the
3165
+ * grid when several, else the play area (a candidate always opens play,
3166
+ * where it waits for the user's OK). Pages pick it up on their poll.
3167
+ * @returns a line for the tool's answer.
3168
+ */
3169
+ async showAfterPublish(sessionId, version, candidate, show, note) {
3170
+ const row = await this.boundProject(sessionId);
3171
+ const project = row === void 0 ? void 0 : await this.view(row.id);
3172
+ if (row === void 0 || project === void 0) return "";
3173
+ let target = null;
3174
+ let refused = "";
3175
+ const asked = (show ?? "").trim();
3176
+ if (asked !== "") {
3177
+ const [view, ...rest] = asked.split(":");
3178
+ const name = rest.join(":").trim();
3179
+ if (view === "play" || view === "versions" || view === "elements" && name === "") target = { view };
3180
+ else if (view === "elements") {
3181
+ const e = project.elements.find((x) => x.id === name || x.name === name);
3182
+ if (e !== void 0) target = {
3183
+ view,
3184
+ elementId: e.id
3185
+ };
3186
+ } else if (view === "design") {
3187
+ const c = project.chapters.find((x) => x.id === name || x.title === name);
3188
+ if (c !== void 0) target = {
3189
+ view,
3190
+ chapterId: c.id
3191
+ };
3192
+ }
3193
+ if (target === null) refused = `(show「${asked}」找不到,改用默认位置)`;
3194
+ }
3195
+ let changedNames = [];
3196
+ if (target === null) {
3197
+ const decided = candidate ? {
3198
+ target: { view: "play" },
3199
+ names: []
3200
+ } : await this.changedBy(row.root, version);
3201
+ target = decided.target;
3202
+ changedNames = decided.names;
3203
+ }
3204
+ const words = (note ?? "").trim();
3205
+ const text = words !== "" ? words : candidate ? `v${version} 等你确认上线` : changedNames.length > 0 ? `v${version}:改了${changedNames.slice(0, 3).join("、")}${changedNames.length > 3 ? ` 等 ${changedNames.length} 个` : ""}` : `v${version} 已上线`;
3206
+ this.shows.set(row.id, {
3207
+ seq: ++this.showSeq,
3208
+ target,
3209
+ note: text.slice(0, 120),
3210
+ at: (/* @__PURE__ */ new Date()).toISOString()
3211
+ });
3212
+ this.notify(row.id);
3213
+ return `右栏会打开:${target.view === "play" ? "试玩" : target.view === "versions" ? "版本" : target.view === "design" ? `策划·${target.chapterId}` : target.elementId === void 0 ? "元素列表" : `元素「${target.elementId}」`}${refused}。`;
3214
+ }
3215
+ /** Where a version's change is: the one element it changed, the grid for several, else play (code, assets, a first version). */
3216
+ async changedBy(root, version) {
3217
+ const earlier = (await this.readVersions(root)).map((v) => v.version).filter((v) => v < version);
3218
+ const play = {
3219
+ target: { view: "play" },
3220
+ names: []
3221
+ };
3222
+ if (earlier.length === 0) return play;
3223
+ const prev = Math.max(...earlier);
3224
+ const dir = (v) => join(root, STATE_DIR, "versions", String(v));
3225
+ if (!await stat(dir(prev)).then(() => true, () => false)) return play;
3226
+ const [before, after] = await Promise.all([this.elementIndex(dir(prev)), this.elementIndex(dir(version))]);
3227
+ const changed = [...after].filter(([id, e]) => before.get(id)?.json !== e.json);
3228
+ const others = async (v) => new Map((await walk(dir(v), (p) => !p.startsWith("elements/"))).map((f) => [f.path, f.data.toString("base64")]));
3229
+ const [a, b] = await Promise.all([others(prev), others(version)]);
3230
+ const otherChanged = a.size !== b.size || [...b].some(([p, d]) => a.get(p) !== d);
3231
+ const names = changed.map(([, e]) => e.name);
3232
+ if (otherChanged || changed.length === 0) return {
3233
+ target: { view: "play" },
3234
+ names
3235
+ };
3236
+ if (changed.length === 1) return {
3237
+ target: {
3238
+ view: "elements",
3239
+ elementId: changed[0][0]
3240
+ },
3241
+ names
3242
+ };
3243
+ return {
3244
+ target: { view: "elements" },
3245
+ names
3246
+ };
3247
+ }
3105
3248
  async sessionState(sessionId) {
3106
3249
  const bound = await this.boundProject(sessionId);
3107
3250
  return {