pixelkiln 0.28.0 → 0.29.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 +308 -112
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +303 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -5
- package/dist/index.d.ts +33 -5
- package/dist/index.js +300 -103
- package/dist/index.js.map +1 -1
- package/docs/ARTIFACTS.md +19 -13
- package/docs/CLI.md +17 -9
- package/docs/MANIFEST.md +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -872,12 +872,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
|
|
|
872
872
|
}
|
|
873
873
|
validate(spec, styleImages) {
|
|
874
874
|
const options = resolvedOptions(spec);
|
|
875
|
-
if (spec.source && !spec.revision)
|
|
876
|
-
if (spec.generator === "frames") {
|
|
877
|
-
throw new Error("ComfyUI frame sets require generated provider outputs, not asset.source");
|
|
878
|
-
}
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
875
|
+
if (spec.source && !spec.revision && spec.generator !== "frames") return;
|
|
881
876
|
if (spec.width < 16 || spec.height < 16 || spec.width > 4096 || spec.height > 4096) {
|
|
882
877
|
throw new Error("ComfyUI output dimensions must be between 16 and 4096 pixels");
|
|
883
878
|
}
|
|
@@ -3673,13 +3668,25 @@ function resolveOutputPath(recordedPath, manifestDir) {
|
|
|
3673
3668
|
return path5.resolve(manifestDir, recordedPath.split(/[\\/]/).join(path5.sep));
|
|
3674
3669
|
}
|
|
3675
3670
|
function expectedOutputPath(spec, role, index, total, mediaType) {
|
|
3676
|
-
|
|
3671
|
+
return memberPath(spec.outFile, role, index, total, mediaType);
|
|
3672
|
+
}
|
|
3673
|
+
function memberPath(file, role, index, total, mediaType) {
|
|
3674
|
+
const originalExt = path5.extname(file);
|
|
3677
3675
|
const ext = mediaType ? mediaExtension(mediaType) : originalExt || ".png";
|
|
3678
|
-
const stem = originalExt ?
|
|
3676
|
+
const stem = originalExt ? file.slice(0, -originalExt.length) : file;
|
|
3679
3677
|
if (total === 1) return `${stem}${ext}`;
|
|
3680
3678
|
const safeRole = (role ?? fallbackOutputRole(index)).replace(/[^a-zA-Z0-9_-]+/g, "-");
|
|
3681
3679
|
return `${stem}-${safeRole}${ext}`;
|
|
3682
3680
|
}
|
|
3681
|
+
function isFrameSetEntry(entry) {
|
|
3682
|
+
return entry.generator === "frames" && entry.outputs.length > 1;
|
|
3683
|
+
}
|
|
3684
|
+
function sourceOutputPath(source, entry, index, manifestDir) {
|
|
3685
|
+
const absolute = path5.resolve(manifestDir, source);
|
|
3686
|
+
if (!isFrameSetEntry(entry)) return absolute;
|
|
3687
|
+
const output = entry.outputs[index];
|
|
3688
|
+
return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
|
|
3689
|
+
}
|
|
3683
3690
|
function currentOutputPath(output, spec, index, total) {
|
|
3684
3691
|
return expectedOutputPath(spec, output.role, index, total, output.mediaType);
|
|
3685
3692
|
}
|
|
@@ -4336,12 +4343,15 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
|
|
|
4336
4343
|
for (const [key, entry] of entries) {
|
|
4337
4344
|
const id = key.slice(prefix.length);
|
|
4338
4345
|
locked.add(id);
|
|
4339
|
-
|
|
4346
|
+
let outputs = resolveEntryOutputs(entry, id, manifestDir);
|
|
4340
4347
|
const source = sources[id];
|
|
4341
4348
|
if (source && outputs.length <= 1) {
|
|
4342
4349
|
inputs.push({ id, path: path7.resolve(manifestDir, source) });
|
|
4343
4350
|
continue;
|
|
4344
4351
|
}
|
|
4352
|
+
if (source && isFrameSetEntry(entry)) {
|
|
4353
|
+
outputs = outputs.map((output) => ({ ...output, absolutePath: sourceOutputPath(source, entry, output.index, manifestDir) }));
|
|
4354
|
+
}
|
|
4345
4355
|
let selected = outputs;
|
|
4346
4356
|
if (options.outputRoles?.length) {
|
|
4347
4357
|
selected = outputs.filter(
|
|
@@ -4463,11 +4473,21 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
|
|
|
4463
4473
|
for (const id of ids) {
|
|
4464
4474
|
const cell = cells[id];
|
|
4465
4475
|
const source = sources[id];
|
|
4466
|
-
|
|
4476
|
+
const entry = lock.entries[`${styleId}/${id}`];
|
|
4477
|
+
if (source && !(entry && isFrameSetEntry(entry))) {
|
|
4467
4478
|
placements.push({ id, path: path7.resolve(manifestDir, source), cell });
|
|
4468
4479
|
continue;
|
|
4469
4480
|
}
|
|
4470
|
-
|
|
4481
|
+
if (source && entry) {
|
|
4482
|
+
const selection2 = selectEntryOutput(entry, outputRoles[id]);
|
|
4483
|
+
if (!selection2.ok) {
|
|
4484
|
+
skipped.push({ id, reason: selection2.reason });
|
|
4485
|
+
continue;
|
|
4486
|
+
}
|
|
4487
|
+
const index = entry.outputs.findIndex((output) => output.path === selection2.output.path);
|
|
4488
|
+
placements.push({ id, path: sourceOutputPath(source, entry, Math.max(index, 0), manifestDir), cell });
|
|
4489
|
+
continue;
|
|
4490
|
+
}
|
|
4471
4491
|
if (!entry) {
|
|
4472
4492
|
skipped.push({
|
|
4473
4493
|
id,
|
|
@@ -6036,7 +6056,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
6036
6056
|
} else if (opts.force) {
|
|
6037
6057
|
state = "missing";
|
|
6038
6058
|
reason = "--force";
|
|
6039
|
-
} else if (!entry && spec.source) {
|
|
6059
|
+
} else if (!entry && spec.source && spec.generator !== "frames") {
|
|
6040
6060
|
if (existsSync8(path13.resolve(spec.root, spec.source))) {
|
|
6041
6061
|
state = "ok";
|
|
6042
6062
|
reason = `placed from ${spec.source}; not generated`;
|
|
@@ -8246,36 +8266,54 @@ function handEditPath(loaded, spec) {
|
|
|
8246
8266
|
const file = inside ? relative : path20.basename(spec.outFile);
|
|
8247
8267
|
return path20.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
|
|
8248
8268
|
}
|
|
8249
|
-
function
|
|
8269
|
+
function handEditBases(spec, lock) {
|
|
8250
8270
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8251
8271
|
if (entry) {
|
|
8252
|
-
if (entry.outputs.length !== 1) return null;
|
|
8253
|
-
const
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8272
|
+
if (entry.outputs.length !== 1 && !isFrameSetEntry(entry)) return null;
|
|
8273
|
+
const members = entry.outputs.map((output, index) => ({
|
|
8274
|
+
role: entry.outputs.length === 1 ? null : output.role ?? null,
|
|
8275
|
+
path: currentEntryOutputPath(entry, spec, index),
|
|
8276
|
+
mediaType: output.mediaType
|
|
8277
|
+
}));
|
|
8278
|
+
if (members.some((member) => member.mediaType && member.mediaType !== "image/png")) return null;
|
|
8279
|
+
if (entry.outputs.length === 1 && !primaryOutput(entry)) return null;
|
|
8280
|
+
if (!members.every((member) => existsSync16(member.path))) return null;
|
|
8281
|
+
return members.map(({ role, path: file }) => ({ role, path: file }));
|
|
8282
|
+
}
|
|
8283
|
+
return existsSync16(spec.outFile) ? [{ role: null, path: spec.outFile }] : null;
|
|
8284
|
+
}
|
|
8285
|
+
function handEditMembers(loaded, spec, lock, editPath) {
|
|
8286
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8287
|
+
const bases = handEditBases(spec, lock);
|
|
8288
|
+
if (entry && isFrameSetEntry(entry)) {
|
|
8289
|
+
return entry.outputs.map((output, index) => ({
|
|
8290
|
+
role: output.role ?? null,
|
|
8291
|
+
path: memberPath(editPath, output.role, index, entry.outputs.length, output.mediaType),
|
|
8292
|
+
base: bases?.[index]?.path ?? null
|
|
8293
|
+
}));
|
|
8257
8294
|
}
|
|
8258
|
-
return
|
|
8295
|
+
return [{ role: null, path: editPath, base: bases?.[0]?.path ?? null }];
|
|
8259
8296
|
}
|
|
8260
8297
|
async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
8261
8298
|
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8262
|
-
if (entry && entry.outputs.length > 1) {
|
|
8299
|
+
if (entry && entry.outputs.length > 1 && !isFrameSetEntry(entry)) {
|
|
8263
8300
|
throw new ManifestEditError(
|
|
8264
|
-
`${spec.styleId}/${spec.assetId}
|
|
8301
|
+
`${spec.styleId}/${spec.assetId} is a structural set of ${entry.outputs.length} outputs; hand edits cover single images and frame sets`
|
|
8265
8302
|
);
|
|
8266
8303
|
}
|
|
8267
8304
|
const existing = spec.source ? path20.resolve(loaded.root, spec.source) : null;
|
|
8268
8305
|
const editPath = existing ?? handEditPath(loaded, spec);
|
|
8306
|
+
const members = handEditMembers(loaded, spec, lock, editPath);
|
|
8269
8307
|
let created = false;
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
if (!base) {
|
|
8308
|
+
for (const member of members) {
|
|
8309
|
+
if (existsSync16(member.path)) continue;
|
|
8310
|
+
if (!member.base) {
|
|
8273
8311
|
throw new ManifestEditError(
|
|
8274
8312
|
`${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
|
|
8275
8313
|
);
|
|
8276
8314
|
}
|
|
8277
|
-
await mkdir6(path20.dirname(
|
|
8278
|
-
await copyFile(base,
|
|
8315
|
+
await mkdir6(path20.dirname(member.path), { recursive: true });
|
|
8316
|
+
await copyFile(member.base, member.path);
|
|
8279
8317
|
created = true;
|
|
8280
8318
|
}
|
|
8281
8319
|
const source = portableOutputPath(editPath, loaded.root);
|
|
@@ -8292,7 +8330,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
|
|
|
8292
8330
|
declared = result.changed;
|
|
8293
8331
|
manifestSha256 = result.sha256;
|
|
8294
8332
|
}
|
|
8295
|
-
return { editPath, source, created, declared, manifestSha256 };
|
|
8333
|
+
return { editPath, members, source, created, declared, manifestSha256 };
|
|
8296
8334
|
}
|
|
8297
8335
|
async function detachHandEdit(loaded, spec, opts = {}) {
|
|
8298
8336
|
if (!spec.source) {
|
|
@@ -8306,19 +8344,44 @@ async function detachHandEdit(loaded, spec, opts = {}) {
|
|
|
8306
8344
|
});
|
|
8307
8345
|
return { manifestSha256: result.sha256, changed: result.changed };
|
|
8308
8346
|
}
|
|
8309
|
-
var
|
|
8310
|
-
|
|
8311
|
-
|
|
8347
|
+
var CompanionOutputSchema = z6.object({
|
|
8348
|
+
/** Output role for a set member; null for a single image. */
|
|
8349
|
+
role: z6.string().nullable(),
|
|
8350
|
+
/** sha256 of the generated file this member started from; null when it started from untracked art. */
|
|
8312
8351
|
basedOn: z6.string().regex(/^[0-9a-f]{64}$/).nullable(),
|
|
8313
|
-
/** sha256 of the
|
|
8314
|
-
sha256: z6.string().regex(/^[0-9a-f]{64}$/)
|
|
8352
|
+
/** sha256 of the member as saved, so a later change by another tool is visible. */
|
|
8353
|
+
sha256: z6.string().regex(/^[0-9a-f]{64}$/)
|
|
8354
|
+
}).strict();
|
|
8355
|
+
var CompanionV2Schema = z6.object({
|
|
8356
|
+
version: z6.literal(2),
|
|
8315
8357
|
/** Editor identity the bridge reported, e.g. `pixelorama@v1.2.2-stable`. */
|
|
8316
8358
|
editor: z6.string().min(1),
|
|
8317
8359
|
protocol: z6.number().int().positive(),
|
|
8318
8360
|
savedAt: z6.string().datetime(),
|
|
8319
8361
|
/** Basename of the layered project file beside the edit, when one was kept. */
|
|
8362
|
+
project: z6.string().min(1).nullable(),
|
|
8363
|
+
/** One entry per file, in output order. */
|
|
8364
|
+
outputs: z6.array(CompanionOutputSchema).min(1)
|
|
8365
|
+
}).strict();
|
|
8366
|
+
var CompanionV1Schema = z6.object({
|
|
8367
|
+
version: z6.literal(1),
|
|
8368
|
+
basedOn: z6.string().regex(/^[0-9a-f]{64}$/).nullable(),
|
|
8369
|
+
sha256: z6.string().regex(/^[0-9a-f]{64}$/),
|
|
8370
|
+
editor: z6.string().min(1),
|
|
8371
|
+
protocol: z6.number().int().positive(),
|
|
8372
|
+
savedAt: z6.string().datetime(),
|
|
8320
8373
|
project: z6.string().min(1).nullable()
|
|
8321
8374
|
}).strict();
|
|
8375
|
+
var HandEditCompanionSchema = z6.union([CompanionV2Schema, CompanionV1Schema]).transform(
|
|
8376
|
+
(value) => value.version === 2 ? value : {
|
|
8377
|
+
version: 2,
|
|
8378
|
+
editor: value.editor,
|
|
8379
|
+
protocol: value.protocol,
|
|
8380
|
+
savedAt: value.savedAt,
|
|
8381
|
+
project: value.project,
|
|
8382
|
+
outputs: [{ role: null, basedOn: value.basedOn, sha256: value.sha256 }]
|
|
8383
|
+
}
|
|
8384
|
+
);
|
|
8322
8385
|
function handEditCompanionPath(editPath) {
|
|
8323
8386
|
return editPath.replace(/\.png$/i, "") + ".edit.json";
|
|
8324
8387
|
}
|
|
@@ -8340,45 +8403,79 @@ async function readHandEditCompanion(editPath) {
|
|
|
8340
8403
|
}
|
|
8341
8404
|
var MAX_HAND_EDIT_BYTES = 16 * 1024 * 1024;
|
|
8342
8405
|
async function saveHandEdit(loaded, lock, spec, input) {
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
}
|
|
8406
|
+
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8407
|
+
const frameSet = entry ? isFrameSetEntry(entry) : false;
|
|
8408
|
+
const incoming = input.frames ?? (input.png ? [{ role: null, png: input.png }] : []);
|
|
8409
|
+
if (!incoming.length) throw new ManifestEditError("the save carried no image");
|
|
8346
8410
|
if (input.project && input.project.length > MAX_HAND_EDIT_BYTES) {
|
|
8347
8411
|
throw new ManifestEditError(`the project file is ${input.project.length} bytes; the limit is ${MAX_HAND_EDIT_BYTES}`);
|
|
8348
8412
|
}
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8413
|
+
if (frameSet) {
|
|
8414
|
+
const expected = entry.outputs.map((output) => output.role ?? null);
|
|
8415
|
+
const got = incoming.map((frame) => frame.role);
|
|
8416
|
+
if (got.length !== expected.length || expected.some((role, index) => got[index] !== role)) {
|
|
8417
|
+
throw new ManifestEditError(
|
|
8418
|
+
`${spec.styleId}/${spec.assetId} is a set of ${expected.length} frames (${expected.join(", ")}); the editor returned ${got.length} (${got.map((role) => role ?? "new").join(", ")}) \u2014 keep the frames it opened with`
|
|
8419
|
+
);
|
|
8420
|
+
}
|
|
8421
|
+
} else if (incoming.length !== 1) {
|
|
8422
|
+
throw new ManifestEditError(`${spec.styleId}/${spec.assetId} is one image; the editor returned ${incoming.length} frames \u2014 keep a single frame`);
|
|
8355
8423
|
}
|
|
8356
|
-
const
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8424
|
+
const decoded = incoming.map((frame, index) => {
|
|
8425
|
+
if (frame.png.length > MAX_HAND_EDIT_BYTES) {
|
|
8426
|
+
throw new ManifestEditError(`the edit is ${frame.png.length} bytes; the limit is ${MAX_HAND_EDIT_BYTES}`);
|
|
8427
|
+
}
|
|
8428
|
+
try {
|
|
8429
|
+
validateMedia(frame.png, MediaType.PNG);
|
|
8430
|
+
return decodePng(frame.png);
|
|
8431
|
+
} catch (error) {
|
|
8432
|
+
const which = frameSet ? `frame ${frame.role ?? index}` : "the edit";
|
|
8433
|
+
throw new ManifestEditError(`${which} is not a valid PNG: ${error instanceof Error ? error.message : String(error)}`);
|
|
8434
|
+
}
|
|
8435
|
+
});
|
|
8436
|
+
const bases = handEditBases(spec, lock);
|
|
8437
|
+
const expectedSize = bases?.[0] ? decodePng(await readFile15(bases[0].path)) : { width: spec.width, height: spec.height };
|
|
8438
|
+
for (const [index, image] of decoded.entries()) {
|
|
8439
|
+
if (image.width !== expectedSize.width || image.height !== expectedSize.height) {
|
|
8440
|
+
const which = frameSet ? `frame ${incoming[index].role ?? index}` : "the edit";
|
|
8441
|
+
throw new ManifestEditError(
|
|
8442
|
+
`${which} is ${image.width}\xD7${image.height}; ${spec.styleId}/${spec.assetId} is ${expectedSize.width}\xD7${expectedSize.height}`
|
|
8443
|
+
);
|
|
8444
|
+
}
|
|
8362
8445
|
}
|
|
8363
8446
|
const started = await startHandEdit(loaded, lock, spec, { expectedSha256: input.expectedSha256 });
|
|
8364
|
-
const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
|
|
8365
|
-
const basedOn = entry ? primaryOutput(entry)?.sha256 ?? null : null;
|
|
8366
|
-
const digest2 = sha256(input.png);
|
|
8367
8447
|
const projectPath = input.project ? handEditProjectPath(started.editPath) : null;
|
|
8368
8448
|
const companion = {
|
|
8369
|
-
version:
|
|
8370
|
-
basedOn,
|
|
8371
|
-
sha256: digest2,
|
|
8449
|
+
version: 2,
|
|
8372
8450
|
editor: input.editor,
|
|
8373
8451
|
protocol: input.protocol,
|
|
8374
8452
|
savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
8375
|
-
project: projectPath ? path20.basename(projectPath) : null
|
|
8453
|
+
project: projectPath ? path20.basename(projectPath) : null,
|
|
8454
|
+
outputs: incoming.map((frame, index) => ({
|
|
8455
|
+
role: frame.role,
|
|
8456
|
+
basedOn: basedOnHash(entry, index),
|
|
8457
|
+
sha256: sha256(frame.png)
|
|
8458
|
+
}))
|
|
8376
8459
|
};
|
|
8377
|
-
|
|
8460
|
+
for (const [index, member] of started.members.entries()) {
|
|
8461
|
+
await replaceFile(member.path, incoming[index].png);
|
|
8462
|
+
}
|
|
8378
8463
|
if (projectPath && input.project) await replaceFile(projectPath, input.project);
|
|
8379
8464
|
const companionPath = handEditCompanionPath(started.editPath);
|
|
8380
8465
|
await replaceFile(companionPath, JSON.stringify(companion, null, 2) + "\n");
|
|
8381
|
-
return {
|
|
8466
|
+
return {
|
|
8467
|
+
...started,
|
|
8468
|
+
sha256: companion.outputs[0].sha256,
|
|
8469
|
+
basedOn: companion.outputs[0].basedOn,
|
|
8470
|
+
companion,
|
|
8471
|
+
companionPath,
|
|
8472
|
+
projectPath
|
|
8473
|
+
};
|
|
8474
|
+
}
|
|
8475
|
+
function basedOnHash(entry, index) {
|
|
8476
|
+
if (!entry) return null;
|
|
8477
|
+
if (entry.outputs.length === 1) return primaryOutput(entry)?.sha256 ?? null;
|
|
8478
|
+
return entry.outputs[index]?.sha256 ?? null;
|
|
8382
8479
|
}
|
|
8383
8480
|
async function replaceFile(file, bytes) {
|
|
8384
8481
|
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
@@ -8425,7 +8522,10 @@ var HandEditRequestSchema = z7.discriminatedUnion("action", [
|
|
|
8425
8522
|
assetId: z7.string().min(1),
|
|
8426
8523
|
styleId: z7.string().min(1),
|
|
8427
8524
|
expectedSha256: z7.string().regex(/^[0-9a-f]{64}$/),
|
|
8428
|
-
|
|
8525
|
+
/** The flattened image; for a frame set, `frames` instead. */
|
|
8526
|
+
png: Base64.min(1).optional(),
|
|
8527
|
+
/** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
|
|
8528
|
+
frames: z7.array(z7.object({ role: z7.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
|
|
8429
8529
|
/** The editor's layered project file, kept beside the edit. */
|
|
8430
8530
|
pxo: Base64.optional(),
|
|
8431
8531
|
/** Editor identity from the bridge's ready message. */
|
|
@@ -8448,18 +8548,23 @@ function createGalleryEditHandler(opts) {
|
|
|
8448
8548
|
const started = await startHandEdit(ctx.loaded, ctx.lock, spec, { expectedSha256: request.expectedSha256 });
|
|
8449
8549
|
log2(` hand edit ${started.created ? "created" : "found"}: ${started.source}${started.declared ? " (declared in the manifest)" : ""}`);
|
|
8450
8550
|
if (request.open) {
|
|
8451
|
-
const command = (opts.openEditor ?? openInEditor)(started.
|
|
8551
|
+
const command = (opts.openEditor ?? openInEditor)(started.members[0].path);
|
|
8452
8552
|
log2(` opened with: ${command}`);
|
|
8453
8553
|
}
|
|
8454
8554
|
} else if (request.action === "save-edit") {
|
|
8555
|
+
if (request.png !== void 0 === (request.frames !== void 0)) {
|
|
8556
|
+
throw new ManifestEditError("invalid edit: send png for one image or frames for a set, not both");
|
|
8557
|
+
}
|
|
8455
8558
|
const saved = await saveHandEdit(ctx.loaded, ctx.lock, spec, {
|
|
8456
|
-
png: Buffer.from(request.png, "base64"),
|
|
8559
|
+
...request.png !== void 0 ? { png: Buffer.from(request.png, "base64") } : {},
|
|
8560
|
+
...request.frames ? { frames: request.frames.map((frame) => ({ role: frame.role, png: Buffer.from(frame.png, "base64") })) } : {},
|
|
8457
8561
|
...request.pxo ? { project: Buffer.from(request.pxo, "base64") } : {},
|
|
8458
8562
|
editor: request.editor,
|
|
8459
8563
|
protocol: request.protocol,
|
|
8460
8564
|
expectedSha256: request.expectedSha256
|
|
8461
8565
|
});
|
|
8462
|
-
|
|
8566
|
+
const what = saved.members.length > 1 ? `${saved.source} (${saved.members.length} frames)` : saved.source;
|
|
8567
|
+
log2(` hand edit saved from ${request.editor}: ${what}${saved.declared ? " (declared in the manifest)" : ""}${saved.projectPath ? " + project file" : ""}`);
|
|
8463
8568
|
} else {
|
|
8464
8569
|
const result2 = await detachHandEdit(ctx.loaded, spec, { expectedSha256: request.expectedSha256 });
|
|
8465
8570
|
if (result2.changed) log2(` hand edit detached: ${request.styleId}/${request.assetId} (file kept)`);
|
|
@@ -8507,6 +8612,18 @@ function matchesFilter(filter, styleId, assetId) {
|
|
|
8507
8612
|
if (filter?.assets?.length && !filter.assets.includes(assetId)) return false;
|
|
8508
8613
|
return true;
|
|
8509
8614
|
}
|
|
8615
|
+
var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
|
|
8616
|
+
async function samePixels(a, b) {
|
|
8617
|
+
try {
|
|
8618
|
+
const [left, right] = await Promise.all([readFile16(a), readFile16(b)]);
|
|
8619
|
+
if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
|
|
8620
|
+
const x = decodePng(left);
|
|
8621
|
+
const y = decodePng(right);
|
|
8622
|
+
return x.width === y.width && x.height === y.height && x.pixels.equals(y.pixels);
|
|
8623
|
+
} catch {
|
|
8624
|
+
return false;
|
|
8625
|
+
}
|
|
8626
|
+
}
|
|
8510
8627
|
function metadataFps(entry) {
|
|
8511
8628
|
if (!entry) return null;
|
|
8512
8629
|
const namespace = entry.providerMetadata?.[entry.provider];
|
|
@@ -8624,6 +8741,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8624
8741
|
const asset = loaded.manifest.assets[spec.assetId] ?? null;
|
|
8625
8742
|
let outputs;
|
|
8626
8743
|
let edit = null;
|
|
8744
|
+
let edits = [];
|
|
8745
|
+
let editChanged = [];
|
|
8627
8746
|
let editStatus = null;
|
|
8628
8747
|
let editMeta = null;
|
|
8629
8748
|
if (entry && entry.outputs.length) {
|
|
@@ -8634,9 +8753,15 @@ async function buildGallerySnapshot(opts) {
|
|
|
8634
8753
|
);
|
|
8635
8754
|
if (spec.source) {
|
|
8636
8755
|
const editPath = path22.resolve(root, spec.source);
|
|
8637
|
-
const
|
|
8638
|
-
|
|
8639
|
-
|
|
8756
|
+
const memberCount = isFrameSetEntry(entry) ? entry.outputs.length : 1;
|
|
8757
|
+
const editShas = await Promise.all(
|
|
8758
|
+
Array.from({ length: memberCount }, async (_, index) => {
|
|
8759
|
+
const file = sourceOutputPath(spec.source, entry, index, root);
|
|
8760
|
+
return { file, sha256: existsSync17(file) ? await sha256File(file) : null };
|
|
8761
|
+
})
|
|
8762
|
+
);
|
|
8763
|
+
edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
|
|
8764
|
+
edit = edits[0] ?? null;
|
|
8640
8765
|
const companion = await readHandEditCompanion(editPath);
|
|
8641
8766
|
if (companion) {
|
|
8642
8767
|
const projectPath = handEditProjectPath(editPath);
|
|
@@ -8650,14 +8775,30 @@ async function buildGallerySnapshot(opts) {
|
|
|
8650
8775
|
editMeta = {
|
|
8651
8776
|
editor: companion.editor,
|
|
8652
8777
|
savedAt: companion.savedAt,
|
|
8653
|
-
basedOn: companion.basedOn,
|
|
8654
|
-
changedSince:
|
|
8778
|
+
basedOn: companion.outputs[0]?.basedOn ?? null,
|
|
8779
|
+
changedSince: editShas.some(({ sha256: editSha }, index) => {
|
|
8780
|
+
const recorded = companion.outputs[index]?.sha256;
|
|
8781
|
+
return editSha !== null && recorded !== void 0 && editSha !== recorded;
|
|
8782
|
+
}),
|
|
8655
8783
|
project: project ? portableOutputPath(projectPath, root) : null,
|
|
8656
8784
|
projectUrl
|
|
8657
8785
|
};
|
|
8658
8786
|
}
|
|
8659
|
-
const regenerated =
|
|
8660
|
-
|
|
8787
|
+
const regenerated = edits.some((member, index) => {
|
|
8788
|
+
const generated = outputs[index];
|
|
8789
|
+
if (companion) {
|
|
8790
|
+
const recorded = companion.outputs[index];
|
|
8791
|
+
return recorded !== void 0 && generated.sha256 !== null && recorded.basedOn !== generated.sha256;
|
|
8792
|
+
}
|
|
8793
|
+
return Boolean(generated.modifiedAt && member.modifiedAt && generated.modifiedAt > member.modifiedAt && generated.exists);
|
|
8794
|
+
});
|
|
8795
|
+
editChanged = await Promise.all(edits.map(async (member, index) => {
|
|
8796
|
+
const generated = outputs[index];
|
|
8797
|
+
if (!member.exists || !generated.exists) return true;
|
|
8798
|
+
if (member.sha256 === generated.sha256) return false;
|
|
8799
|
+
return !await samePixels(member.absolutePath, generated.absolutePath);
|
|
8800
|
+
}));
|
|
8801
|
+
editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
|
|
8661
8802
|
}
|
|
8662
8803
|
} else if (spec.source) {
|
|
8663
8804
|
outputs = [await describeOutput(media, root, path22.resolve(root, spec.source))];
|
|
@@ -8706,6 +8847,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8706
8847
|
asset,
|
|
8707
8848
|
source: spec.source ?? null,
|
|
8708
8849
|
edit,
|
|
8850
|
+
edits,
|
|
8851
|
+
editChanged,
|
|
8709
8852
|
editStatus,
|
|
8710
8853
|
editMeta,
|
|
8711
8854
|
upstreamUrl: entry?.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
|
|
@@ -8763,6 +8906,8 @@ async function buildGallerySnapshot(opts) {
|
|
|
8763
8906
|
asset: null,
|
|
8764
8907
|
source: null,
|
|
8765
8908
|
edit: null,
|
|
8909
|
+
edits: [],
|
|
8910
|
+
editChanged: [],
|
|
8766
8911
|
editStatus: null,
|
|
8767
8912
|
editMeta: null,
|
|
8768
8913
|
upstreamUrl: entry.provider === "pixellab" ? pixelLabObjectUrl(entry.generator, entry.objectId) : null,
|
|
@@ -9697,10 +9842,15 @@ window.addEventListener('message', (e) => {
|
|
|
9697
9842
|
// declares the file the same way pixelkiln edit does.
|
|
9698
9843
|
|
|
9699
9844
|
let SHEET = null;
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
const
|
|
9703
|
-
|
|
9845
|
+
// What the editor gets: the edit files when they are all there, else the
|
|
9846
|
+
// generated outputs (one PNG, or every frame of a set).
|
|
9847
|
+
const editorSources = (item) => {
|
|
9848
|
+
if (item.edits.length && item.edits.every((e) => e.exists)) return { files: item.edits, fromEdit: true };
|
|
9849
|
+
if (editableOutputs(item)) return { files: item.outputs, fromEdit: false };
|
|
9850
|
+
return null;
|
|
9851
|
+
};
|
|
9852
|
+
const editorSource = (item) => { const s = editorSources(item); return s ? s.files[0] : null; };
|
|
9853
|
+
const canEditInBrowser = (item) => EDITOR && EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 && editorSources(item);
|
|
9704
9854
|
const toBase64 = (buf) => new Promise((resolve, reject) => {
|
|
9705
9855
|
const r = new FileReader();
|
|
9706
9856
|
r.onload = () => resolve(String(r.result).slice(String(r.result).indexOf(',') + 1));
|
|
@@ -9729,7 +9879,7 @@ function openEditorSheet(item) {
|
|
|
9729
9879
|
const bar = el('div', 'rbar');
|
|
9730
9880
|
const close = el('button', null, 'Close'); close.type = 'button';
|
|
9731
9881
|
close.onclick = () => closeEditorSheet();
|
|
9732
|
-
const title = el('b', null, (item.project ? item.project + ':' : '') + item.styleId + '/' + item.assetId + ' \xB7 ' + item.width + '\xD7' + item.height);
|
|
9882
|
+
const title = el('b', null, (item.project ? item.project + ':' : '') + item.styleId + '/' + item.assetId + ' \xB7 ' + item.width + '\xD7' + item.height + (item.outputs.length > 1 ? ' \xB7 ' + item.outputs.length + ' frames' : ''));
|
|
9733
9883
|
const status = el('span', 'st');
|
|
9734
9884
|
const acts = el('div', 'acts');
|
|
9735
9885
|
const msg = el('span', 'msg');
|
|
@@ -9750,23 +9900,34 @@ function openEditorSheet(item) {
|
|
|
9750
9900
|
stage.append(frame, loading);
|
|
9751
9901
|
panel.append(bar, stage);
|
|
9752
9902
|
host.append(scrim, panel);
|
|
9753
|
-
SHEET = { item, frame, loading, status, msg, save, saveClose, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false };
|
|
9903
|
+
SHEET = { item, frame, loading, status, msg, save, saveClose, dirty: false, opened: false, ready: null, pending: null, requests: 0, source: src, sentProject: false, roles: null };
|
|
9754
9904
|
sheetStatus('cool', 'loading');
|
|
9755
9905
|
sheetButtons();
|
|
9756
9906
|
close.focus({ preventScroll: true });
|
|
9757
9907
|
}
|
|
9758
|
-
// The edit
|
|
9759
|
-
// project beside
|
|
9760
|
-
// falling back to the flattened
|
|
9908
|
+
// The edit files are what the editor gets; when a browser save kept the
|
|
9909
|
+
// layered project beside them, that goes along too and the editor restores
|
|
9910
|
+
// the layers, falling back to the flattened PNGs if the file cannot be read.
|
|
9911
|
+
// A frame set is sent as frames, one per member, and comes back the same way.
|
|
9761
9912
|
async function sendOpen() {
|
|
9762
|
-
const { item
|
|
9913
|
+
const { item } = SHEET;
|
|
9914
|
+
const sources = editorSources(item);
|
|
9763
9915
|
const style = snap.styles.find((s) => s.id === item.styleId && s.project === item.project);
|
|
9764
|
-
const projectUrl =
|
|
9765
|
-
|
|
9916
|
+
const projectUrl = sources.fromEdit && item.editMeta && item.editMeta.projectUrl && SHEET.ready.version >= 2 ? item.editMeta.projectUrl : null;
|
|
9917
|
+
const set = sources.files.length > 1;
|
|
9918
|
+
if (set && SHEET.ready.version < 3) {
|
|
9919
|
+
SHEET.msg.textContent = 'This editor build cannot open frame sets; reinstall it with pixelkiln tools install editor.';
|
|
9920
|
+
sheetStatus('bad', 'unsupported');
|
|
9921
|
+
return;
|
|
9922
|
+
}
|
|
9923
|
+
const pngs = [];
|
|
9924
|
+
let pxo = null;
|
|
9766
9925
|
try {
|
|
9767
|
-
const
|
|
9768
|
-
|
|
9769
|
-
|
|
9926
|
+
for (const file of sources.files) {
|
|
9927
|
+
const res = await fetch(file.url, { cache: 'no-store' });
|
|
9928
|
+
if (!res.ok) throw new Error(await res.text());
|
|
9929
|
+
pngs.push(await res.arrayBuffer());
|
|
9930
|
+
}
|
|
9770
9931
|
if (projectUrl) {
|
|
9771
9932
|
const p = await fetch(projectUrl, { cache: 'no-store' });
|
|
9772
9933
|
if (p.ok) pxo = await p.arrayBuffer();
|
|
@@ -9778,12 +9939,21 @@ async function sendOpen() {
|
|
|
9778
9939
|
if (!SHEET) return;
|
|
9779
9940
|
const request = 'open-' + (++SHEET.requests);
|
|
9780
9941
|
SHEET.sentProject = !!pxo;
|
|
9942
|
+
SHEET.roles = set ? item.outputs.map((o) => o.role || null) : null;
|
|
9781
9943
|
const message = {
|
|
9782
9944
|
type: 'pixelkiln:open', request,
|
|
9783
9945
|
asset: { key: item.styleId + '/' + item.assetId, id: item.id, name: item.assetId, width: item.width, height: item.height },
|
|
9784
|
-
png, palette: style ? style.palette : [],
|
|
9946
|
+
png: pngs[0], palette: style ? style.palette : [],
|
|
9785
9947
|
};
|
|
9786
|
-
const transfer = [
|
|
9948
|
+
const transfer = [pngs[0]];
|
|
9949
|
+
if (set) {
|
|
9950
|
+
message.frames = pngs.map((png, i) => ({ role: SHEET.roles[i], png }));
|
|
9951
|
+
message.fps = item.fps || 12;
|
|
9952
|
+
for (const png of pngs.slice(1)) transfer.push(png);
|
|
9953
|
+
// The first frame is both png (older editors) and frames[0]; copy it so both transfer.
|
|
9954
|
+
message.png = pngs[0].slice(0);
|
|
9955
|
+
transfer.push(message.png);
|
|
9956
|
+
}
|
|
9787
9957
|
if (pxo) { message.pxo = pxo; transfer.push(pxo); }
|
|
9788
9958
|
SHEET.frame.contentWindow.postMessage(message, location.origin, transfer);
|
|
9789
9959
|
sheetStatus('cool', 'opening');
|
|
@@ -9811,8 +9981,9 @@ async function onEditorMessage(m) {
|
|
|
9811
9981
|
case 'pixelkiln:opened':
|
|
9812
9982
|
SHEET.opened = true;
|
|
9813
9983
|
SHEET.loading.remove();
|
|
9814
|
-
if (m.source === 'pxo') sheetStatus('ok', 'editing the edit file
|
|
9815
|
-
else if (SHEET.sentProject) sheetStatus('warn', 'editing the flattened edit file;
|
|
9984
|
+
if (m.source === 'pxo') sheetStatus('ok', 'editing the edit file' + (m.frames > 1 ? 's' : '') + ' with ' + (m.layers === 1 ? 'the layer' : m.layers + ' layers') + (m.frames > 1 ? ' and ' + m.frames + ' frames' : '') + ' restored');
|
|
9985
|
+
else if (SHEET.sentProject) sheetStatus('warn', 'editing the flattened edit file' + (m.frames > 1 ? 's' : '') + '; the layer file could not be opened');
|
|
9986
|
+
else if (m.source === 'frames') sheetStatus('ok', (SHEET.item.edits.length ? 'editing the edit files' : 'editing a copy of the generated frames') + ' as ' + m.frames + ' frames');
|
|
9816
9987
|
else sheetStatus('ok', SHEET.item.edit && SHEET.item.edit.exists ? 'editing the edit file' : 'editing a copy of the generated art');
|
|
9817
9988
|
sheetButtons();
|
|
9818
9989
|
break;
|
|
@@ -9825,14 +9996,21 @@ async function onEditorMessage(m) {
|
|
|
9825
9996
|
const pending = SHEET.pending;
|
|
9826
9997
|
if (!pending || pending.request !== m.request) return;
|
|
9827
9998
|
try {
|
|
9828
|
-
const body = {
|
|
9999
|
+
const body = { editor: SHEET.ready.editor, protocol: SHEET.ready.version };
|
|
10000
|
+
if (SHEET.roles) {
|
|
10001
|
+
if (!Array.isArray(m.frames)) throw new Error('the editor returned no frames');
|
|
10002
|
+
body.frames = [];
|
|
10003
|
+
for (const f of m.frames) body.frames.push({ role: f.role === undefined ? null : f.role, png: await toBase64(f.png) });
|
|
10004
|
+
} else {
|
|
10005
|
+
body.png = await toBase64(m.png);
|
|
10006
|
+
}
|
|
9829
10007
|
if (m.pxo && m.pxo.byteLength) body.pxo = await toBase64(m.pxo);
|
|
9830
10008
|
await postHandEdit(SHEET.item, 'save-edit', body);
|
|
9831
10009
|
const saved = snap.items.find((i) => i.id === SHEET.item.id);
|
|
9832
10010
|
if (saved) SHEET.item = saved;
|
|
9833
10011
|
SHEET.dirty = false;
|
|
9834
10012
|
SHEET.pending = null;
|
|
9835
|
-
ui.notice = { id: SHEET.item.id, text: 'Saved ' + (saved && saved.edit ? saved.edit.path : 'the edit') + '. mount and pack place it in place of the generated art.' };
|
|
10013
|
+
ui.notice = { id: SHEET.item.id, text: 'Saved ' + (saved && saved.edits.length > 1 ? saved.edits.length + ' frames to ' + saved.source : saved && saved.edit ? saved.edit.path : 'the edit') + '. mount and pack place ' + (saved && saved.edits.length > 1 ? 'them' : 'it') + ' in place of the generated art.' };
|
|
9836
10014
|
render();
|
|
9837
10015
|
if (pending.close) { closeEditorSheet(true); return; }
|
|
9838
10016
|
sheetStatus('ok', 'saved');
|
|
@@ -9980,7 +10158,7 @@ function clearFilters() {
|
|
|
9980
10158
|
function thumb(item) {
|
|
9981
10159
|
const cell = el('div', 'cell');
|
|
9982
10160
|
// A hand edit is what ships, so it is what the card shows.
|
|
9983
|
-
const shown = item.edit && item.
|
|
10161
|
+
const shown = item.edit && item.editStatus === 'edited' && item.edits.every((e) => e.url) ? item.edits : item.outputs.filter((o) => o.url);
|
|
9984
10162
|
if (!shown.length) {
|
|
9985
10163
|
cell.classList.add('none');
|
|
9986
10164
|
cell.append(el('span', null, item.state === 'missing' ? 'not generated yet'
|
|
@@ -10394,7 +10572,7 @@ function openCompare() {
|
|
|
10394
10572
|
// ---- hand edits -------------------------------------------------------------
|
|
10395
10573
|
|
|
10396
10574
|
const EDIT_STATUS_TEXT = {
|
|
10397
|
-
same: '
|
|
10575
|
+
same: 'The generated art\u2019s pixels, not changed yet. Open it in your editor and save.',
|
|
10398
10576
|
edited: 'Differs from the generated art. mount and pack place this file; the generated file stays as the record.',
|
|
10399
10577
|
'regenerated-since': 'The generated art changed after this edit was saved \u2014 the edit is based on an older generation.',
|
|
10400
10578
|
missing: 'The declared file is not on disk.',
|
|
@@ -10405,12 +10583,16 @@ async function postHandEdit(item, action, extra) {
|
|
|
10405
10583
|
if (item.project) body.project = item.project;
|
|
10406
10584
|
snap = await postEdit(body);
|
|
10407
10585
|
}
|
|
10586
|
+
// A single PNG, or an ordered frame set: both are hand-editable, one file per
|
|
10587
|
+
// member. Structural sets (tiles, characters) are not.
|
|
10588
|
+
const editableOutputs = (item) => item.outputs.length && (item.outputs.length === 1 || isFrameSet(item)) &&
|
|
10589
|
+
item.outputs.every((o) => o.exists && o.mediaType === 'image/png');
|
|
10408
10590
|
function handEditSection(item) {
|
|
10409
|
-
const canStart = EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 &&
|
|
10410
|
-
item.outputs.length === 1 && item.outputs[0].exists && item.outputs[0].mediaType === 'image/png';
|
|
10591
|
+
const canStart = EDITABLE && item.declared && item.asset && projectOf(item)?.manifestSha256 && editableOutputs(item);
|
|
10411
10592
|
if (!item.edit && !canStart) return null;
|
|
10593
|
+
const set = item.edits.length > 1;
|
|
10412
10594
|
const s = el('section', 'meta');
|
|
10413
|
-
s.append(el('h3', null, 'Hand edit'));
|
|
10595
|
+
s.append(el('h3', null, set ? 'Hand edit (' + item.edits.length + ' frames)' : 'Hand edit'));
|
|
10414
10596
|
if (item.edit) {
|
|
10415
10597
|
const pair = el('div', 'pair');
|
|
10416
10598
|
const shared = displayScale(item.width, item.height, 200, 160);
|
|
@@ -10424,24 +10606,36 @@ function handEditSection(item) {
|
|
|
10424
10606
|
f.append(el('figcaption', null, label));
|
|
10425
10607
|
return f;
|
|
10426
10608
|
};
|
|
10427
|
-
|
|
10609
|
+
// For a set, the member the preview is on; the strip there steps through them.
|
|
10610
|
+
const at = set ? Math.min(ui.member, item.edits.length - 1) : 0;
|
|
10611
|
+
const which = set ? ' \xB7 ' + (item.outputs[at].role || 'frame ' + (at + 1)) : '';
|
|
10612
|
+
pair.append(fig(item.outputs[at], 'generated' + which), fig(item.edits[at], 'edit' + which));
|
|
10428
10613
|
s.append(pair);
|
|
10429
10614
|
const dl = el('dl');
|
|
10430
10615
|
const tone = item.editStatus === 'edited' ? 'ok' : item.editStatus === 'same' ? 'dim' : 'warn';
|
|
10431
10616
|
const st = el('span', 'state-' + tone); st.append(el('i', 'dot ' + tone), document.createTextNode(' ' + item.editStatus));
|
|
10432
10617
|
row(dl, 'status', st);
|
|
10433
10618
|
row(dl, 'why', EDIT_STATUS_TEXT[item.editStatus] || '');
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10619
|
+
if (set) {
|
|
10620
|
+
row(dl, 'files', item.source + ' \u2014 one per frame: ' + item.edits.map((e) => e.path.split('/').pop()).join(', '), { mono: true, copy: item.edits.map((e) => e.absolutePath).join('\\n') });
|
|
10621
|
+
const differing = item.editChanged.filter(Boolean).length;
|
|
10622
|
+
row(dl, 'changed', differing + ' of ' + item.edits.length + ' frames differ from the generated art' +
|
|
10623
|
+
(differing ? ': ' + item.edits.filter((e, i) => item.editChanged[i]).map((e) => item.outputs[item.edits.indexOf(e)].role || e.path.split('/').pop()).join(', ') : ''));
|
|
10624
|
+
} else {
|
|
10625
|
+
row(dl, 'file', item.edit.path, { mono: true, copy: item.edit.absolutePath });
|
|
10626
|
+
if (item.edit.sha256) row(dl, 'sha256', item.edit.sha256.slice(0, 16) + '\u2026', { mono: true, copy: item.edit.sha256 });
|
|
10627
|
+
}
|
|
10628
|
+
row(dl, 'saved', fmtWhen(item.edits.reduce((latest, e) => e.modifiedAt && (!latest || e.modifiedAt > latest) ? e.modifiedAt : latest, null)));
|
|
10437
10629
|
if (item.editMeta) {
|
|
10438
|
-
row(dl, 'editor', item.editMeta.editor + ', ' + fmtWhen(item.editMeta.savedAt) + (item.editMeta.changedSince ? ' \u2014
|
|
10439
|
-
if (item.editMeta.basedOn) row(dl, 'based on', item.editMeta.basedOn.slice(0, 16) + '\u2026', { mono: true, copy: item.editMeta.basedOn });
|
|
10630
|
+
row(dl, 'editor', item.editMeta.editor + ', ' + fmtWhen(item.editMeta.savedAt) + (item.editMeta.changedSince ? ' \u2014 a file changed since' : ''));
|
|
10631
|
+
if (item.editMeta.basedOn && !set) row(dl, 'based on', item.editMeta.basedOn.slice(0, 16) + '\u2026', { mono: true, copy: item.editMeta.basedOn });
|
|
10440
10632
|
if (item.editMeta.project) row(dl, 'layers', item.editMeta.project, { mono: true });
|
|
10441
10633
|
}
|
|
10442
10634
|
s.append(dl);
|
|
10443
10635
|
} else {
|
|
10444
|
-
s.append(el('div', 'state-dim',
|
|
10636
|
+
s.append(el('div', 'state-dim', isFrameSet(item)
|
|
10637
|
+
? 'Touch the frames up in your own editor or in the browser, where they open as one animation. The generated files stay untouched; the edit is one sibling file per frame that the manifest points at, and mount and pack place those instead.'
|
|
10638
|
+
: 'Touch it up in your own editor. The generated file stays untouched; the edit is a sibling file the manifest points at, and mount and pack place that instead.'));
|
|
10445
10639
|
}
|
|
10446
10640
|
if (EDITABLE && item.declared && projectOf(item)?.manifestSha256) {
|
|
10447
10641
|
const acts = el('div', 'hand-actions');
|
|
@@ -10469,10 +10663,10 @@ function handEditSection(item) {
|
|
|
10469
10663
|
}
|
|
10470
10664
|
if (item.edit) {
|
|
10471
10665
|
go('Open in desktop editor', 'start-edit', { open: true }, 'Opened ' + item.edit.path + ' in your editor. Save there, then Refresh.');
|
|
10472
|
-
go('Detach edit', 'detach-edit', {}, 'Detached. The file is still at ' + item.
|
|
10666
|
+
go('Detach edit', 'detach-edit', {}, 'Detached. The file' + (set ? 's are' : ' is') + ' still at ' + item.source + '; the generated art is placed again.');
|
|
10473
10667
|
} else if (canStart) {
|
|
10474
|
-
go('Edit by hand', 'start-edit', { open: true }, 'Created the edit file and opened it in your editor. Save there, then Refresh to see it here.');
|
|
10475
|
-
go('Create edit file only', 'start-edit', { open: false }, 'Created the edit file and declared it in the manifest.');
|
|
10668
|
+
go('Edit by hand', 'start-edit', { open: true }, 'Created the edit file' + (isFrameSet(item) ? 's and opened the first frame' : ' and opened it') + ' in your editor. Save there, then Refresh to see it here.');
|
|
10669
|
+
go('Create edit file' + (isFrameSet(item) ? 's' : '') + ' only', 'start-edit', { open: false }, 'Created the edit file' + (isFrameSet(item) ? 's' : '') + ' and declared ' + (isFrameSet(item) ? 'them' : 'it') + ' in the manifest.');
|
|
10476
10670
|
}
|
|
10477
10671
|
acts.append(msg);
|
|
10478
10672
|
s.append(acts);
|
|
@@ -11785,8 +11979,8 @@ var pin_default = {
|
|
|
11785
11979
|
pixelorama: "v1.2.2",
|
|
11786
11980
|
godot: "4.7.2",
|
|
11787
11981
|
extensionsApi: 9,
|
|
11788
|
-
protocol:
|
|
11789
|
-
release: "editor-pixelorama-v1.2.2-pk.
|
|
11982
|
+
protocol: 3,
|
|
11983
|
+
release: "editor-pixelorama-v1.2.2-pk.14",
|
|
11790
11984
|
files: {
|
|
11791
11985
|
"index.apple-touch-icon.png": {
|
|
11792
11986
|
sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
|
|
@@ -11801,7 +11995,7 @@ var pin_default = {
|
|
|
11801
11995
|
bytes: 7298
|
|
11802
11996
|
},
|
|
11803
11997
|
"index.html": {
|
|
11804
|
-
sha256: "
|
|
11998
|
+
sha256: "2974146c89f4df9f796e840947c334aaf3e4fa3dd18bcd7e08db2eabfd0f086e",
|
|
11805
11999
|
bytes: 5442
|
|
11806
12000
|
},
|
|
11807
12001
|
"index.icon.png": {
|
|
@@ -11813,8 +12007,8 @@ var pin_default = {
|
|
|
11813
12007
|
bytes: 279815
|
|
11814
12008
|
},
|
|
11815
12009
|
"index.pck": {
|
|
11816
|
-
sha256: "
|
|
11817
|
-
bytes:
|
|
12010
|
+
sha256: "736bd383c82fdadf31cc894caf08490b99c5df572aa20db683972c35c7a0465b",
|
|
12011
|
+
bytes: 6429628
|
|
11818
12012
|
},
|
|
11819
12013
|
"index.png": {
|
|
11820
12014
|
sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",
|
|
@@ -15121,12 +15315,14 @@ async function main() {
|
|
|
15121
15315
|
return;
|
|
15122
15316
|
}
|
|
15123
15317
|
const started = await startHandEdit(loaded, lock, spec);
|
|
15124
|
-
|
|
15318
|
+
const set = started.members.length > 1;
|
|
15319
|
+
log(` ${started.created ? "created" : "found"} ${started.source}${set ? ` (${started.members.length} frames)` : ""}${started.declared ? " and declared it as the asset's source" : ""}`);
|
|
15320
|
+
if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path30.relative(process.cwd(), member.path)}`);
|
|
15125
15321
|
if (args.noOpen) {
|
|
15126
|
-
log(` edit it with your own tool, then run pixelkiln plan; mount and pack place it in place of the generated art`);
|
|
15322
|
+
log(` edit ${set ? "them" : "it"} with your own tool, then run pixelkiln plan; mount and pack place ${set ? "them" : "it"} in place of the generated art`);
|
|
15127
15323
|
} else {
|
|
15128
|
-
const command = openInEditor(started.
|
|
15129
|
-
log(` opened with: ${command}${process.env.PIXELKILN_EDITOR ? "" : " (set PIXELKILN_EDITOR to choose the program)"}`);
|
|
15324
|
+
const command = openInEditor(started.members[0].path);
|
|
15325
|
+
log(` opened ${set ? "the first frame " : ""}with: ${command}${process.env.PIXELKILN_EDITOR ? "" : " (set PIXELKILN_EDITOR to choose the program)"}`);
|
|
15130
15326
|
}
|
|
15131
15327
|
return;
|
|
15132
15328
|
}
|