pixelkiln 0.78.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.
- package/dist/cli.js +13 -1
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +18 -0
- package/dist/client/gallery.js +262 -19
- package/dist/index.cjs +13 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +12 -0
- package/docs/CLI.md +13 -0
- package/package.json +1 -1
package/dist/client/gallery.css
CHANGED
|
@@ -466,3 +466,21 @@ td.col-on { background:rgba(255,107,53,.08); }
|
|
|
466
466
|
.selbar .msg { font-size:12px; color:var(--dim); }
|
|
467
467
|
.selbar .msg.bad { color:var(--bad); } .selbar .msg.ok { color:var(--ok); }
|
|
468
468
|
@media (max-width: 640px) { .selbar { padding:10px 12px; } }
|
|
469
|
+
.lg-add { width:76px; height:76px; padding:0; font:400 22px/1 var(--mono); color:var(--dim); border:1px dashed var(--line-strong); background:transparent; }
|
|
470
|
+
.lg-add:hover, .lg-add.on { color:var(--accent-soft); border-color:var(--accent); }
|
|
471
|
+
.gap-panel { margin-top:10px; border:1px solid var(--accent); padding:12px; display:grid; gap:8px; max-width:720px; }
|
|
472
|
+
.gap-panel h4 { margin:0; font:650 12.5px/1.2 var(--mono); color:var(--accent-soft); }
|
|
473
|
+
.gap-options { display:grid; gap:4px; }
|
|
474
|
+
.gap-options .field.check { display:flex; align-items:baseline; gap:6px; font-size:12.5px; }
|
|
475
|
+
.gap-price { font:11.5px/1.45 var(--mono); }
|
|
476
|
+
.gap-panel .actions { display:flex; gap:6px; flex-wrap:wrap; }
|
|
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; }
|
package/dist/client/gallery.js
CHANGED
|
@@ -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) || "{}");
|
|
@@ -2645,7 +2646,8 @@
|
|
|
2645
2646
|
panel: null,
|
|
2646
2647
|
anims: [],
|
|
2647
2648
|
columns: /* @__PURE__ */ new Map(),
|
|
2648
|
-
turn: null
|
|
2649
|
+
turn: null,
|
|
2650
|
+
gap: null
|
|
2649
2651
|
};
|
|
2650
2652
|
renderFamily();
|
|
2651
2653
|
}
|
|
@@ -2938,7 +2940,20 @@
|
|
|
2938
2940
|
const td = el("td");
|
|
2939
2941
|
column(d, td);
|
|
2940
2942
|
const item = cells.find((l) => l.character.direction === d);
|
|
2941
|
-
td.append(
|
|
2943
|
+
if (item) td.append(loopCell(item, parent, d));
|
|
2944
|
+
else if (fillable(root, parent, cells, d)) {
|
|
2945
|
+
const open = F.gap && F.gap.row === k && F.gap.direction === d;
|
|
2946
|
+
const add = el("button", "lg-add" + (open ? " on" : ""), "+");
|
|
2947
|
+
add.type = "button";
|
|
2948
|
+
add.title = "Add " + name + " facing " + d;
|
|
2949
|
+
add.setAttribute("aria-label", add.title);
|
|
2950
|
+
add.onclick = () => {
|
|
2951
|
+
F.gap = open ? null : { row: k, direction: d };
|
|
2952
|
+
F.drawn = null;
|
|
2953
|
+
renderFamily();
|
|
2954
|
+
};
|
|
2955
|
+
td.append(add);
|
|
2956
|
+
} else td.append(el("span", "lg-none", "·"));
|
|
2942
2957
|
tr.append(td);
|
|
2943
2958
|
}
|
|
2944
2959
|
tbody.append(tr);
|
|
@@ -2947,9 +2962,167 @@
|
|
|
2947
2962
|
const scroller = el("div", "lg-scroll");
|
|
2948
2963
|
scroller.append(table);
|
|
2949
2964
|
wrap.append(scroller);
|
|
2965
|
+
const gap = F.gap && rows.get(F.gap.row);
|
|
2966
|
+
if (gap) {
|
|
2967
|
+
wrap.append(gapPanel(root, gap, F.gap.direction));
|
|
2968
|
+
} else F.gap = null;
|
|
2950
2969
|
wrap.append(el("small", "state-dim", "Every loop plays on one clock. ⇋ marks a mirror, flipped locally at no cost. A dimmed cell is the parent's rotation, standing in until that loop is generated."));
|
|
2951
2970
|
return wrap;
|
|
2952
2971
|
}
|
|
2972
|
+
var MIRRORED = {
|
|
2973
|
+
south: "south",
|
|
2974
|
+
north: "north",
|
|
2975
|
+
east: "west",
|
|
2976
|
+
west: "east",
|
|
2977
|
+
"south-east": "south-west",
|
|
2978
|
+
"south-west": "south-east",
|
|
2979
|
+
"north-east": "north-west",
|
|
2980
|
+
"north-west": "north-east"
|
|
2981
|
+
};
|
|
2982
|
+
var ANIMATION_FIELDS = ["of", "template", "frames", "fps", "mode", "subject", "enhancePrompt"];
|
|
2983
|
+
function fillable(root, parent, cells, direction) {
|
|
2984
|
+
if (!EDITABLE || !projectOf(root)?.manifestSha256) return false;
|
|
2985
|
+
if (!turnOrder(parent).includes(direction)) return false;
|
|
2986
|
+
return cells.some((l) => !l.mirrorOfKey && l.declaredAs && l.asset);
|
|
2987
|
+
}
|
|
2988
|
+
function gapOptions(cells, direction) {
|
|
2989
|
+
const drawn = cells.filter((l) => !l.mirrorOfKey);
|
|
2990
|
+
const sample = drawn[0];
|
|
2991
|
+
const name = loopName(sample);
|
|
2992
|
+
const shorthand = sample.declaredAs !== sample.assetId ? sample.declaredAs : null;
|
|
2993
|
+
const flip = MIRRORED[direction];
|
|
2994
|
+
const options = [];
|
|
2995
|
+
if (shorthand) {
|
|
2996
|
+
const now = drawn.map((l) => l.character.direction);
|
|
2997
|
+
const next = COMPASS.filter((d) => d === direction || now.includes(d));
|
|
2998
|
+
const freeFlip = flip !== direction && !cells.some((l) => l.character.direction === flip);
|
|
2999
|
+
options.push({
|
|
3000
|
+
id: "draw",
|
|
3001
|
+
label: "Draw it",
|
|
3002
|
+
note: "adds " + direction + " to " + shorthand + "'s directions" + (freeFlip ? "; " + flip + " comes with it as a free mirror" : ""),
|
|
3003
|
+
edit: { action: "patch-asset", assetId: shorthand, patch: { loopDirections: next } }
|
|
3004
|
+
});
|
|
3005
|
+
return options;
|
|
3006
|
+
}
|
|
3007
|
+
const newId = name + "." + direction;
|
|
3008
|
+
const flipped = drawn.find((l) => l.character.direction === flip && flip !== direction);
|
|
3009
|
+
if (flipped) {
|
|
3010
|
+
const mirror = { mirror: flipped.declaredAs };
|
|
3011
|
+
if (flipped.asset?.styles) mirror.styles = flipped.asset.styles;
|
|
3012
|
+
options.push({ id: "mirror", label: "Mirror " + flip, note: "flips " + flipped.assetId + " locally; free", edit: { action: "add-asset", assetId: newId, asset: mirror } });
|
|
3013
|
+
}
|
|
3014
|
+
const base = sample.asset || {};
|
|
3015
|
+
const animation = { direction };
|
|
3016
|
+
for (const key of ANIMATION_FIELDS) if (base.animation && base.animation[key] !== void 0) animation[key] = base.animation[key];
|
|
3017
|
+
const asset = { animation };
|
|
3018
|
+
for (const key of ["prompt", "styles", "tags", "category"]) if (base[key] !== void 0) asset[key] = base[key];
|
|
3019
|
+
options.push({ id: "draw", label: "Draw it", note: "a copy of " + sample.assetId + " facing " + direction, edit: { action: "add-asset", assetId: newId, asset } });
|
|
3020
|
+
return options;
|
|
3021
|
+
}
|
|
3022
|
+
function gapPanel(root, cells, direction) {
|
|
3023
|
+
const panel = el("div", "gap-panel");
|
|
3024
|
+
const name = loopName(cells[0]);
|
|
3025
|
+
panel.append(el("h4", null, "Add " + (name.startsWith(root.assetId + ".") ? name.slice(root.assetId.length + 1) : name) + " facing " + direction));
|
|
3026
|
+
const options = gapOptions(cells, direction);
|
|
3027
|
+
let chosen = options[0];
|
|
3028
|
+
const list = el("div", "gap-options");
|
|
3029
|
+
const priceLine = el("div", "gap-price state-dim", "pricing…");
|
|
3030
|
+
const msg = el("div", "msg");
|
|
3031
|
+
for (const option of options) {
|
|
3032
|
+
const label = el("label", "field check");
|
|
3033
|
+
const radio = el("input");
|
|
3034
|
+
radio.type = "radio";
|
|
3035
|
+
radio.name = "gap-option";
|
|
3036
|
+
radio.checked = option === chosen;
|
|
3037
|
+
radio.onchange = () => {
|
|
3038
|
+
chosen = option;
|
|
3039
|
+
price();
|
|
3040
|
+
};
|
|
3041
|
+
label.append(radio, el("b", null, " " + option.label), el("span", "state-dim", " — " + option.note));
|
|
3042
|
+
list.append(label);
|
|
3043
|
+
}
|
|
3044
|
+
const actions = el("div", "actions");
|
|
3045
|
+
const add = el("button", null, "Add");
|
|
3046
|
+
add.type = "button";
|
|
3047
|
+
const addGen = el("button", "primary", "Add & generate");
|
|
3048
|
+
addGen.type = "button";
|
|
3049
|
+
const cancel = el("button", null, "Cancel");
|
|
3050
|
+
cancel.type = "button";
|
|
3051
|
+
cancel.onclick = () => {
|
|
3052
|
+
F.gap = null;
|
|
3053
|
+
F.drawn = null;
|
|
3054
|
+
renderFamily();
|
|
3055
|
+
};
|
|
3056
|
+
actions.append(add);
|
|
3057
|
+
if (GENERATION) actions.append(addGen);
|
|
3058
|
+
actions.append(cancel);
|
|
3059
|
+
panel.append(list, priceLine, actions, msg);
|
|
3060
|
+
const pr = projectOf(root);
|
|
3061
|
+
const request = () => {
|
|
3062
|
+
const body = { ...chosen.edit, expectedSha256: pr.manifestSha256 };
|
|
3063
|
+
if (root.project) body.project = root.project;
|
|
3064
|
+
return body;
|
|
3065
|
+
};
|
|
3066
|
+
let quote = null;
|
|
3067
|
+
let seq = 0;
|
|
3068
|
+
async function price() {
|
|
3069
|
+
const mine = ++seq;
|
|
3070
|
+
quote = null;
|
|
3071
|
+
priceLine.textContent = "pricing…";
|
|
3072
|
+
add.disabled = addGen.disabled = true;
|
|
3073
|
+
try {
|
|
3074
|
+
const res = await fetch("/api/price", {
|
|
3075
|
+
method: "POST",
|
|
3076
|
+
headers: { "Content-Type": "application/json", "X-Pixelkiln-Session": SESSION ?? "" },
|
|
3077
|
+
body: JSON.stringify(request())
|
|
3078
|
+
});
|
|
3079
|
+
if (mine !== seq) return;
|
|
3080
|
+
if (!res.ok) throw new Error(await res.text());
|
|
3081
|
+
quote = await res.json();
|
|
3082
|
+
const rows = quote.items.map((i) => i.key.split("/").slice(1).join("/") + " " + (i.cost ? fmtCost(i.costUnit, i.cost) : "free"));
|
|
3083
|
+
const totals = Object.entries(quote.totals).filter(([, n]) => n);
|
|
3084
|
+
priceLine.textContent = (rows.length ? rows.join(" · ") : "nothing new") + " — total " + (totals.map(([u, n]) => fmtCost(u, Math.round(n * 100) / 100)).join(" + ") || "free");
|
|
3085
|
+
const over = totals.some(([unit, n]) => {
|
|
3086
|
+
const provider = quote.items.find((i) => i.costUnit === unit)?.provider;
|
|
3087
|
+
const left = provider ? remainingBudget(provider) : null;
|
|
3088
|
+
return left !== null && n > left;
|
|
3089
|
+
});
|
|
3090
|
+
add.disabled = !quote.items.length;
|
|
3091
|
+
addGen.disabled = !quote.items.length || over;
|
|
3092
|
+
addGen.textContent = "Add & generate" + (totals.length ? " · " + totals.map(([u, n]) => fmtCost(u, Math.round(n * 100) / 100)).join(" + ") : "");
|
|
3093
|
+
if (over) addGen.title = "More than the session budget has left";
|
|
3094
|
+
} catch (err) {
|
|
3095
|
+
if (mine !== seq) return;
|
|
3096
|
+
priceLine.className = "gap-price msg bad";
|
|
3097
|
+
priceLine.textContent = err.message;
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
const save = async (generate) => {
|
|
3101
|
+
if (!quote) return;
|
|
3102
|
+
const keys = quote.items.map((i) => i.key);
|
|
3103
|
+
add.disabled = addGen.disabled = true;
|
|
3104
|
+
msg.className = "msg";
|
|
3105
|
+
msg.textContent = "saving…";
|
|
3106
|
+
try {
|
|
3107
|
+
S.snap = await postEdit(request());
|
|
3108
|
+
if (generate) {
|
|
3109
|
+
await postGenerate({ keys, ...root.project ? { project: root.project } : {} });
|
|
3110
|
+
pollJobs();
|
|
3111
|
+
}
|
|
3112
|
+
F.gap = null;
|
|
3113
|
+
F.drawn = null;
|
|
3114
|
+
render();
|
|
3115
|
+
} catch (err) {
|
|
3116
|
+
msg.className = "msg bad";
|
|
3117
|
+
msg.textContent = err.status === 409 ? "The manifest changed on disk; refresh and try again." : err.message;
|
|
3118
|
+
add.disabled = addGen.disabled = false;
|
|
3119
|
+
}
|
|
3120
|
+
};
|
|
3121
|
+
add.onclick = () => save(false);
|
|
3122
|
+
addGen.onclick = () => save(true);
|
|
3123
|
+
price();
|
|
3124
|
+
return panel;
|
|
3125
|
+
}
|
|
2953
3126
|
function column(direction, node) {
|
|
2954
3127
|
if (!F.columns.has(direction)) F.columns.set(direction, []);
|
|
2955
3128
|
F.columns.get(direction).push(node);
|
|
@@ -3880,6 +4053,66 @@
|
|
|
3880
4053
|
const host = document.querySelector(".drawer .dbody > div");
|
|
3881
4054
|
return host ? host.__frames ?? null : null;
|
|
3882
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
|
+
}
|
|
3883
4116
|
var CHARACTER_KIND = {
|
|
3884
4117
|
base: "base",
|
|
3885
4118
|
state: "state (a pose or outfit of its parent)",
|
|
@@ -3957,11 +4190,12 @@
|
|
|
3957
4190
|
}
|
|
3958
4191
|
if (canEdit && ui.editing === item.id) body.append(editForm(item));
|
|
3959
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;
|
|
3960
4195
|
{
|
|
3961
4196
|
const { s, dl } = section("Generation");
|
|
3962
4197
|
row(dl, "provider", item.provider);
|
|
3963
4198
|
row(dl, "generator", item.generator + (item.tileFeature ? " · " + item.tileFeature : ""));
|
|
3964
|
-
row(dl, "lock status", item.status || "none (nothing submitted)");
|
|
3965
4199
|
const pr = el("div", "prompt", item.prompt);
|
|
3966
4200
|
row(dl, item.status ? "prompt sent" : "prompt", pr);
|
|
3967
4201
|
if (item.currentPrompt) row(dl, "prompt now", el("div", "prompt state-warn", item.currentPrompt));
|
|
@@ -3987,13 +4221,19 @@
|
|
|
3987
4221
|
row(dl, "estimate now", fmtCost(item.costUnit, item.estimatedCost) + (item.candidates && item.candidates > 1 ? " for " + item.candidates + " candidates" : ""));
|
|
3988
4222
|
}
|
|
3989
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)");
|
|
3990
4229
|
row(dl, "submitted", fmtWhen(item.submittedAt));
|
|
3991
4230
|
row(dl, "downloaded", fmtWhen(item.downloadedAt));
|
|
3992
4231
|
row(dl, "job id", item.jobId, { mono: true, copy: item.jobId });
|
|
3993
4232
|
row(dl, "object id", item.objectId, { mono: true, copy: item.objectId });
|
|
3994
4233
|
row(dl, "review object", item.reviewObjectId, { mono: true, copy: item.reviewObjectId });
|
|
3995
|
-
|
|
4234
|
+
tab.details.append(s);
|
|
3996
4235
|
}
|
|
4236
|
+
pane = tab.files;
|
|
3997
4237
|
{
|
|
3998
4238
|
const s = el("section", "meta");
|
|
3999
4239
|
s.append(el("h3", null, item.outputs.length === 1 ? "Output" : "Outputs (" + item.outputs.length + ")"));
|
|
@@ -4019,14 +4259,15 @@
|
|
|
4019
4259
|
box.append(dl);
|
|
4020
4260
|
s.append(box);
|
|
4021
4261
|
}
|
|
4022
|
-
|
|
4262
|
+
pane.append(s);
|
|
4023
4263
|
}
|
|
4024
4264
|
const hand = handEditSection(item);
|
|
4025
|
-
if (hand)
|
|
4265
|
+
if (hand) pane.append(hand);
|
|
4026
4266
|
const upstream = upstreamSection(item);
|
|
4027
|
-
if (upstream)
|
|
4267
|
+
if (upstream) pane.append(upstream);
|
|
4028
4268
|
const versions = historySection(item);
|
|
4029
|
-
if (versions)
|
|
4269
|
+
if (versions) tab.history.append(versions);
|
|
4270
|
+
pane = tab.overview;
|
|
4030
4271
|
if (item.character) {
|
|
4031
4272
|
const c = item.character;
|
|
4032
4273
|
const { s, dl } = section("Character");
|
|
@@ -4077,19 +4318,19 @@
|
|
|
4077
4318
|
if (ui.editing === stateKey) s.append(newStateForm(item));
|
|
4078
4319
|
if (ui.editing === animKey) s.append(newAnimationForm(item));
|
|
4079
4320
|
}
|
|
4080
|
-
|
|
4321
|
+
pane.append(s);
|
|
4081
4322
|
}
|
|
4082
4323
|
if (item.mirrorOfKey) {
|
|
4083
4324
|
const { s, dl } = section("Mirror");
|
|
4084
4325
|
row(dl, "flipped from", keyLink(item, item.mirrorOfKey));
|
|
4085
4326
|
row(dl, "cost", "none; made locally from that asset's files");
|
|
4086
|
-
|
|
4327
|
+
pane.append(s);
|
|
4087
4328
|
}
|
|
4088
4329
|
const mirrors = S.snap.items.filter((i) => i.mirrorOfKey === item.key && i.project === item.project);
|
|
4089
4330
|
if (mirrors.length) {
|
|
4090
4331
|
const { s, dl } = section(mirrors.length === 1 ? "Mirror of this asset" : "Mirrors of this asset");
|
|
4091
4332
|
for (const m of mirrors) row(dl, m.character && m.character.direction ? m.character.direction : "flipped", keyLink(item, m.key));
|
|
4092
|
-
|
|
4333
|
+
pane.append(s);
|
|
4093
4334
|
}
|
|
4094
4335
|
if (item.revision || item.revisionParentKey) {
|
|
4095
4336
|
const { s, dl } = section("Lineage");
|
|
@@ -4125,7 +4366,7 @@
|
|
|
4125
4366
|
row(dl, "poses", el("span", "state-warn", "the keypoints file does not exist yet"));
|
|
4126
4367
|
}
|
|
4127
4368
|
}
|
|
4128
|
-
|
|
4369
|
+
pane.append(s);
|
|
4129
4370
|
}
|
|
4130
4371
|
const children = S.snap.items.filter((i) => i.revisionParentKey === item.key && i.project === item.project);
|
|
4131
4372
|
const reviseKey = "revise:" + item.id;
|
|
@@ -4158,8 +4399,9 @@
|
|
|
4158
4399
|
if (ui.editing === reviseKey) s.append(newRevisionForm(item));
|
|
4159
4400
|
if (ui.editing === skeletonKey) s.append(skeletonAnimationForm(item));
|
|
4160
4401
|
}
|
|
4161
|
-
|
|
4402
|
+
pane.append(s);
|
|
4162
4403
|
}
|
|
4404
|
+
pane = tab.details;
|
|
4163
4405
|
if (item.quality) {
|
|
4164
4406
|
const q = item.quality;
|
|
4165
4407
|
const { s, dl } = section("Quality");
|
|
@@ -4191,7 +4433,7 @@
|
|
|
4191
4433
|
row(dl, "output", q.output, { mono: true });
|
|
4192
4434
|
row(dl, "record", q.record + (q.recordExists ? "" : " (not written yet)"), { mono: true });
|
|
4193
4435
|
if (q.check && !q.check.safe) row(dl, "check", el("div", "state-warn", q.check.reasons.join("\n")));
|
|
4194
|
-
|
|
4436
|
+
pane.append(s);
|
|
4195
4437
|
if (q.outputs.some((o) => o.url)) {
|
|
4196
4438
|
const strip = el("div", "members");
|
|
4197
4439
|
for (const o of q.outputs.filter((o2) => o2.url)) {
|
|
@@ -4226,15 +4468,16 @@
|
|
|
4226
4468
|
row(dl, "category", item.category);
|
|
4227
4469
|
row(dl, "tags", item.tags && item.tags.length ? item.tags.join(", ") : null);
|
|
4228
4470
|
row(dl, "source art", item.source, { mono: true });
|
|
4229
|
-
|
|
4471
|
+
pane.append(s);
|
|
4230
4472
|
}
|
|
4231
4473
|
if (item.asset) {
|
|
4232
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)));
|
|
4233
|
-
|
|
4475
|
+
pane.append(jsonDetails("Manifest asset", declared));
|
|
4234
4476
|
}
|
|
4235
4477
|
if (item.providerMetadata && Object.keys(item.providerMetadata).length) {
|
|
4236
|
-
|
|
4478
|
+
pane.append(jsonDetails("Provider metadata", item.providerMetadata));
|
|
4237
4479
|
}
|
|
4480
|
+
body.append(drawerTabs(item, tab));
|
|
4238
4481
|
drawer.append(head, body);
|
|
4239
4482
|
host.append(scrim, drawer);
|
|
4240
4483
|
close.focus({ preventScroll: true });
|
package/dist/index.cjs
CHANGED
|
@@ -15409,7 +15409,12 @@ var AssetPatchSchema = import_zod8.z.object({
|
|
|
15409
15409
|
width: import_zod8.z.number().int().nullable().optional(),
|
|
15410
15410
|
height: import_zod8.z.number().int().nullable().optional(),
|
|
15411
15411
|
size: import_zod8.z.number().int().nullable().optional(),
|
|
15412
|
-
tags: import_zod8.z.array(import_zod8.z.string()).optional()
|
|
15412
|
+
tags: import_zod8.z.array(import_zod8.z.string()).optional(),
|
|
15413
|
+
/**
|
|
15414
|
+
* A loop shorthand's `animation.directions`, replaced whole: each named
|
|
15415
|
+
* direction is drawn, and each one's flip comes free as a mirror.
|
|
15416
|
+
*/
|
|
15417
|
+
loopDirections: import_zod8.z.array(CharacterDirectionSchema).min(1).optional()
|
|
15413
15418
|
}).strict();
|
|
15414
15419
|
var NewStateSchema = import_zod8.z.object({
|
|
15415
15420
|
/** Asset id of the parent this states; must already exist. */
|
|
@@ -15758,6 +15763,13 @@ function applyEdit(raw, edit) {
|
|
|
15758
15763
|
if (patch.tags.length) asset.tags = patch.tags;
|
|
15759
15764
|
else delete asset.tags;
|
|
15760
15765
|
}
|
|
15766
|
+
if (patch.loopDirections) {
|
|
15767
|
+
const animation = asset.animation;
|
|
15768
|
+
if (!animation || !Array.isArray(animation.directions)) {
|
|
15769
|
+
throw new ManifestEditError(`asset "${edit.assetId}" is not a loop declared with animation.directions`);
|
|
15770
|
+
}
|
|
15771
|
+
asset.animation = { ...animation, directions: patch.loopDirections };
|
|
15772
|
+
}
|
|
15761
15773
|
}
|
|
15762
15774
|
async function withCandidate(manifestPath, edit, use, unchanged) {
|
|
15763
15775
|
const absolute = import_node_path27.default.resolve(manifestPath);
|