pixelkiln 0.79.0 → 0.80.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.
@@ -475,3 +475,12 @@ td.col-on { background:rgba(255,107,53,.08); }
475
475
  .gap-price { font:11.5px/1.45 var(--mono); }
476
476
  .gap-panel .actions { display:flex; gap:6px; flex-wrap:wrap; }
477
477
  @media (max-width: 640px) { .lg-add { width:56px; height:56px; } }
478
+
479
+ /* ---- drawer tabs ---- */
480
+ .dtabs { margin-top:14px; }
481
+ .dtabs .tablist { display:flex; gap:0; border-bottom:1px solid var(--line-strong); position:sticky; top:0; z-index:2; background:var(--panel); }
482
+ .dtabs .tablist button { border:0; border-bottom:2px solid transparent; margin-bottom:-1px; padding:8px 12px; font:600 12.5px/1 var(--mono); color:var(--dim); }
483
+ .dtabs .tablist button:hover { color:var(--text); }
484
+ .dtabs .tablist button.on { color:var(--accent-soft); border-bottom-color:var(--accent); }
485
+ .dtabs .tabpanel[hidden] { display:none; }
486
+ .dtabs .tabpanel > section.meta:first-child { border-top:0; }
@@ -46,10 +46,11 @@
46
46
  fps: null,
47
47
  onion: false,
48
48
  selecting: false,
49
- selected: /* @__PURE__ */ new Set()
49
+ selected: /* @__PURE__ */ new Set(),
50
+ drawerTab: "overview"
50
51
  };
51
52
  var PREFS_KEY = "pixelkiln.gallery.prefs";
52
- var PREF_NAMES = ["playLoops", "backdrop", "notify", "onion"];
53
+ var PREF_NAMES = ["playLoops", "backdrop", "notify", "onion", "drawerTab"];
53
54
  function loadPrefs() {
54
55
  try {
55
56
  const saved = JSON.parse(localStorage.getItem(PREFS_KEY) || "{}");
@@ -4052,6 +4053,66 @@
4052
4053
  const host = document.querySelector(".drawer .dbody > div");
4053
4054
  return host ? host.__frames ?? null : null;
4054
4055
  }
4056
+ var TAB_LABEL = { overview: "Overview", files: "Files", history: "History", details: "Details" };
4057
+ var OVERVIEW_FORMS = /^(state|anim|poses|revise|skeleton):/;
4058
+ function drawerTabs(item, panels) {
4059
+ const wrap = el("div", "dtabs");
4060
+ const available = Object.keys(panels).filter((name) => panels[name].childNodes.length);
4061
+ if (ui.editing && OVERVIEW_FORMS.test(ui.editing)) ui.drawerTab = "overview";
4062
+ const current = available.includes(ui.drawerTab) ? ui.drawerTab : "overview";
4063
+ const list = el("div", "tablist");
4064
+ list.setAttribute("role", "tablist");
4065
+ const count = {
4066
+ files: item.outputs.length,
4067
+ history: item.history.length
4068
+ };
4069
+ const buttons = [];
4070
+ for (const name of available) {
4071
+ const b = el("button", name === current ? "on" : null, TAB_LABEL[name] + (count[name] ? " " + count[name] : ""));
4072
+ b.type = "button";
4073
+ b.id = "dtab-" + name;
4074
+ b.setAttribute("role", "tab");
4075
+ b.setAttribute("aria-selected", String(name === current));
4076
+ b.setAttribute("aria-controls", "dpanel-" + name);
4077
+ b.tabIndex = name === current ? 0 : -1;
4078
+ b.onclick = () => {
4079
+ ui.drawerTab = name;
4080
+ show(name);
4081
+ };
4082
+ b.onkeydown = (e) => {
4083
+ const at = available.indexOf(name);
4084
+ const next = e.key === "ArrowRight" ? available[(at + 1) % available.length] : e.key === "ArrowLeft" ? available[(at - 1 + available.length) % available.length] : null;
4085
+ if (!next) return;
4086
+ e.preventDefault();
4087
+ e.stopPropagation();
4088
+ ui.drawerTab = next;
4089
+ show(next);
4090
+ buttons[available.indexOf(next)].focus();
4091
+ };
4092
+ buttons.push(b);
4093
+ list.append(b);
4094
+ }
4095
+ const show = (name) => {
4096
+ for (const [i, b] of buttons.entries()) {
4097
+ const on = available[i] === name;
4098
+ b.classList.toggle("on", on);
4099
+ b.setAttribute("aria-selected", String(on));
4100
+ b.tabIndex = on ? 0 : -1;
4101
+ }
4102
+ for (const n of available) panels[n].hidden = n !== name;
4103
+ };
4104
+ wrap.append(list);
4105
+ for (const name of available) {
4106
+ const panel = panels[name];
4107
+ panel.className = "tabpanel";
4108
+ panel.id = "dpanel-" + name;
4109
+ panel.setAttribute("role", "tabpanel");
4110
+ panel.setAttribute("aria-labelledby", "dtab-" + name);
4111
+ wrap.append(panel);
4112
+ }
4113
+ show(current);
4114
+ return wrap;
4115
+ }
4055
4116
  var CHARACTER_KIND = {
4056
4117
  base: "base",
4057
4118
  state: "state (a pose or outfit of its parent)",
@@ -4129,11 +4190,12 @@
4129
4190
  }
4130
4191
  if (canEdit && ui.editing === item.id) body.append(editForm(item));
4131
4192
  if (item.error && !item.reason.includes(item.error)) body.append(stateNode("failed", item.error));
4193
+ const tab = { overview: el("div"), files: el("div"), history: el("div"), details: el("div") };
4194
+ let pane = tab.overview;
4132
4195
  {
4133
4196
  const { s, dl } = section("Generation");
4134
4197
  row(dl, "provider", item.provider);
4135
4198
  row(dl, "generator", item.generator + (item.tileFeature ? " · " + item.tileFeature : ""));
4136
- row(dl, "lock status", item.status || "none (nothing submitted)");
4137
4199
  const pr = el("div", "prompt", item.prompt);
4138
4200
  row(dl, item.status ? "prompt sent" : "prompt", pr);
4139
4201
  if (item.currentPrompt) row(dl, "prompt now", el("div", "prompt state-warn", item.currentPrompt));
@@ -4159,13 +4221,19 @@
4159
4221
  row(dl, "estimate now", fmtCost(item.costUnit, item.estimatedCost) + (item.candidates && item.candidates > 1 ? " for " + item.candidates + " candidates" : ""));
4160
4222
  }
4161
4223
  if (item.candidateIndex !== null) row(dl, "candidate", "#" + (item.candidateIndex + 1) + " chosen in review");
4224
+ pane.append(s);
4225
+ }
4226
+ {
4227
+ const { s, dl } = section("Provider record");
4228
+ row(dl, "lock status", item.status || "none (nothing submitted)");
4162
4229
  row(dl, "submitted", fmtWhen(item.submittedAt));
4163
4230
  row(dl, "downloaded", fmtWhen(item.downloadedAt));
4164
4231
  row(dl, "job id", item.jobId, { mono: true, copy: item.jobId });
4165
4232
  row(dl, "object id", item.objectId, { mono: true, copy: item.objectId });
4166
4233
  row(dl, "review object", item.reviewObjectId, { mono: true, copy: item.reviewObjectId });
4167
- body.append(s);
4234
+ tab.details.append(s);
4168
4235
  }
4236
+ pane = tab.files;
4169
4237
  {
4170
4238
  const s = el("section", "meta");
4171
4239
  s.append(el("h3", null, item.outputs.length === 1 ? "Output" : "Outputs (" + item.outputs.length + ")"));
@@ -4191,14 +4259,15 @@
4191
4259
  box.append(dl);
4192
4260
  s.append(box);
4193
4261
  }
4194
- body.append(s);
4262
+ pane.append(s);
4195
4263
  }
4196
4264
  const hand = handEditSection(item);
4197
- if (hand) body.append(hand);
4265
+ if (hand) pane.append(hand);
4198
4266
  const upstream = upstreamSection(item);
4199
- if (upstream) body.append(upstream);
4267
+ if (upstream) pane.append(upstream);
4200
4268
  const versions = historySection(item);
4201
- if (versions) body.append(versions);
4269
+ if (versions) tab.history.append(versions);
4270
+ pane = tab.overview;
4202
4271
  if (item.character) {
4203
4272
  const c = item.character;
4204
4273
  const { s, dl } = section("Character");
@@ -4249,19 +4318,19 @@
4249
4318
  if (ui.editing === stateKey) s.append(newStateForm(item));
4250
4319
  if (ui.editing === animKey) s.append(newAnimationForm(item));
4251
4320
  }
4252
- body.append(s);
4321
+ pane.append(s);
4253
4322
  }
4254
4323
  if (item.mirrorOfKey) {
4255
4324
  const { s, dl } = section("Mirror");
4256
4325
  row(dl, "flipped from", keyLink(item, item.mirrorOfKey));
4257
4326
  row(dl, "cost", "none; made locally from that asset's files");
4258
- body.append(s);
4327
+ pane.append(s);
4259
4328
  }
4260
4329
  const mirrors = S.snap.items.filter((i) => i.mirrorOfKey === item.key && i.project === item.project);
4261
4330
  if (mirrors.length) {
4262
4331
  const { s, dl } = section(mirrors.length === 1 ? "Mirror of this asset" : "Mirrors of this asset");
4263
4332
  for (const m of mirrors) row(dl, m.character && m.character.direction ? m.character.direction : "flipped", keyLink(item, m.key));
4264
- body.append(s);
4333
+ pane.append(s);
4265
4334
  }
4266
4335
  if (item.revision || item.revisionParentKey) {
4267
4336
  const { s, dl } = section("Lineage");
@@ -4297,7 +4366,7 @@
4297
4366
  row(dl, "poses", el("span", "state-warn", "the keypoints file does not exist yet"));
4298
4367
  }
4299
4368
  }
4300
- body.append(s);
4369
+ pane.append(s);
4301
4370
  }
4302
4371
  const children = S.snap.items.filter((i) => i.revisionParentKey === item.key && i.project === item.project);
4303
4372
  const reviseKey = "revise:" + item.id;
@@ -4330,8 +4399,9 @@
4330
4399
  if (ui.editing === reviseKey) s.append(newRevisionForm(item));
4331
4400
  if (ui.editing === skeletonKey) s.append(skeletonAnimationForm(item));
4332
4401
  }
4333
- body.append(s);
4402
+ pane.append(s);
4334
4403
  }
4404
+ pane = tab.details;
4335
4405
  if (item.quality) {
4336
4406
  const q = item.quality;
4337
4407
  const { s, dl } = section("Quality");
@@ -4363,7 +4433,7 @@
4363
4433
  row(dl, "output", q.output, { mono: true });
4364
4434
  row(dl, "record", q.record + (q.recordExists ? "" : " (not written yet)"), { mono: true });
4365
4435
  if (q.check && !q.check.safe) row(dl, "check", el("div", "state-warn", q.check.reasons.join("\n")));
4366
- body.append(s);
4436
+ pane.append(s);
4367
4437
  if (q.outputs.some((o) => o.url)) {
4368
4438
  const strip = el("div", "members");
4369
4439
  for (const o of q.outputs.filter((o2) => o2.url)) {
@@ -4398,15 +4468,16 @@
4398
4468
  row(dl, "category", item.category);
4399
4469
  row(dl, "tags", item.tags && item.tags.length ? item.tags.join(", ") : null);
4400
4470
  row(dl, "source art", item.source, { mono: true });
4401
- body.append(s);
4471
+ pane.append(s);
4402
4472
  }
4403
4473
  if (item.asset) {
4404
4474
  const declared = Object.fromEntries(Object.entries(item.asset).filter(([, v]) => !(Array.isArray(v) && !v.length) && !(v && typeof v === "object" && !Array.isArray(v) && !Object.keys(v).length)));
4405
- body.append(jsonDetails("Manifest asset", declared));
4475
+ pane.append(jsonDetails("Manifest asset", declared));
4406
4476
  }
4407
4477
  if (item.providerMetadata && Object.keys(item.providerMetadata).length) {
4408
- body.append(jsonDetails("Provider metadata", item.providerMetadata));
4478
+ pane.append(jsonDetails("Provider metadata", item.providerMetadata));
4409
4479
  }
4480
+ body.append(drawerTabs(item, tab));
4410
4481
  drawer.append(head, body);
4411
4482
  host.append(scrim, drawer);
4412
4483
  close.focus({ preventScroll: true });
package/docs/CLI.md CHANGED
@@ -458,6 +458,19 @@ provider, and never writes anything. Stop it with Ctrl+C. See the
458
458
  [Getting started guide](GETTING_STARTED.md#start-a-new-project) for a
459
459
  screenshot.
460
460
 
461
+ A record's drawer keeps the preview, its state, and its actions at the
462
+ top. The rest of the record is split into tabs:
463
+
464
+ - **Overview:** the generation, the character family, mirrors, lineage and
465
+ revisions, and the forms that add to them.
466
+ - **Files:** outputs, hand edits, and upstream edits.
467
+ - **History:** earlier generations, which can be brought back.
468
+ - **Details:** the provider record (job and object ids, times), the quality
469
+ record, identity, and the raw manifest asset and provider metadata.
470
+
471
+ The tab in view stays as you step through records, and ← and → move
472
+ between tabs once one has focus.
473
+
461
474
  #### Looking at sprites
462
475
 
463
476
  - **Loops on the grid:** a loop's card plays while the pointer is over it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixelkiln",
3
- "version": "0.79.0",
3
+ "version": "0.80.0",
4
4
  "description": "Manifest-driven pixel-art generation, review, recovery, and packaging with deterministic provenance.",
5
5
  "type": "module",
6
6
  "sideEffects": false,