@kitschpatrol/tldraw-cli 4.3.2 → 4.4.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/bin/cli.js +236 -113
- package/dist/.DS_Store +0 -0
- package/dist/lib/index.js +222 -111
- package/dist/lib/tldraw-controller.d.ts +8 -8
- package/dist/lib/tldraw-to-image.d.ts +1 -0
- package/package.json +8 -8
- package/readme.md +37 -17
package/bin/cli.js
CHANGED
|
@@ -17247,81 +17247,13 @@ var TldrawController = class {
|
|
|
17247
17247
|
async close() {
|
|
17248
17248
|
if (!this.browser)
|
|
17249
17249
|
throw new Error("Controller not started");
|
|
17250
|
+
const pages = await this.browser.pages();
|
|
17251
|
+
await Promise.all(pages.map(async (page) => page.close()));
|
|
17250
17252
|
await this.browser.close();
|
|
17251
17253
|
log_default.info("Stopped controller");
|
|
17252
17254
|
}
|
|
17253
|
-
//
|
|
17255
|
+
// eslint-disable-next-line complexity
|
|
17254
17256
|
async download(options) {
|
|
17255
|
-
return this._download(void 0, options);
|
|
17256
|
-
}
|
|
17257
|
-
async downloadFrame(frameNameOrId, options) {
|
|
17258
|
-
return this.downloadFrames([frameNameOrId], options);
|
|
17259
|
-
}
|
|
17260
|
-
async downloadFrames(frameNamesOrIds, options) {
|
|
17261
|
-
const validPageFrames = [];
|
|
17262
|
-
for (const frame of frameNamesOrIds) {
|
|
17263
|
-
const pageFrame = await this.getPageFrameWithNameOrId(frame);
|
|
17264
|
-
if (pageFrame === void 0) {
|
|
17265
|
-
log_default.warn(`Frame "${frame}" not found, skipping`);
|
|
17266
|
-
} else {
|
|
17267
|
-
validPageFrames.push(pageFrame);
|
|
17268
|
-
}
|
|
17269
|
-
}
|
|
17270
|
-
if (validPageFrames.length === 0) {
|
|
17271
|
-
throw new Error("No valid frames found");
|
|
17272
|
-
}
|
|
17273
|
-
const validFrameNames = validPageFrames.map((frame) => slugify(frame.name));
|
|
17274
|
-
const isFrameNameCollision = validFrameNames.length !== new Set(validFrameNames).size;
|
|
17275
|
-
if (isFrameNameCollision) {
|
|
17276
|
-
log_default.warn(
|
|
17277
|
-
"Frame names are not unique, including frame IDs in the output filenames to avoid collisions"
|
|
17278
|
-
);
|
|
17279
|
-
}
|
|
17280
|
-
const outputAccumulator = [];
|
|
17281
|
-
for (const frame of validPageFrames) {
|
|
17282
|
-
const frameSuffix = isFrameNameCollision ? `-${frame.id.replace("shape:", "")}` : "";
|
|
17283
|
-
outputAccumulator.push(
|
|
17284
|
-
...await this._download(frame, {
|
|
17285
|
-
...options,
|
|
17286
|
-
name: `${options.name}${frameSuffix}`
|
|
17287
|
-
})
|
|
17288
|
-
);
|
|
17289
|
-
}
|
|
17290
|
-
return outputAccumulator;
|
|
17291
|
-
}
|
|
17292
|
-
async downloadAllFrames(options) {
|
|
17293
|
-
const pageFrames = await this.getPageFrames();
|
|
17294
|
-
const frameNamesOrIds = pageFrames.map((f) => f.id);
|
|
17295
|
-
return this.downloadFrames(frameNamesOrIds, options);
|
|
17296
|
-
}
|
|
17297
|
-
async loadFile(filePath) {
|
|
17298
|
-
if (!this.page)
|
|
17299
|
-
throw new Error("Controller not started");
|
|
17300
|
-
if (this.isLocal)
|
|
17301
|
-
throw new Error(
|
|
17302
|
-
"File loading is only supported for remote tldraw.com instances. See tldraw-open.ts for local file loading approach."
|
|
17303
|
-
);
|
|
17304
|
-
await this.closeMenus();
|
|
17305
|
-
await this.page.evaluate(`userPreferences.showFileOpenWarning.set(false);`);
|
|
17306
|
-
log_default.info(`Uploading local file to tldraw.com: ${filePath}`);
|
|
17307
|
-
const tldrFile = await fs.readFile(filePath, "utf8");
|
|
17308
|
-
await this.page.evaluate(set_tldr_iife_default);
|
|
17309
|
-
await this.page.evaluate(`window.setTldr(${tldrFile})`);
|
|
17310
|
-
}
|
|
17311
|
-
async getShareUrl() {
|
|
17312
|
-
if (!this.page)
|
|
17313
|
-
throw new Error("Controller not started");
|
|
17314
|
-
if (this.isLocal)
|
|
17315
|
-
throw new Error("Share URLs are only supported for remote tldraw.com instances.");
|
|
17316
|
-
await this.closeMenus();
|
|
17317
|
-
await this.clickButtonTitles(["Menu", "File", "Share this project"]);
|
|
17318
|
-
await this.page.waitForNavigation({ waitUntil: "networkidle0" });
|
|
17319
|
-
const shareUrl = new URL(this.page.url());
|
|
17320
|
-
shareUrl.search = "";
|
|
17321
|
-
return shareUrl.href;
|
|
17322
|
-
}
|
|
17323
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
17324
|
-
async _download(pageFrame, options) {
|
|
17325
17257
|
if (!this.page)
|
|
17326
17258
|
throw new Error("Controller not started");
|
|
17327
17259
|
if (this.isEmpty)
|
|
@@ -17329,10 +17261,14 @@ var TldrawController = class {
|
|
|
17329
17261
|
if (options.stripStyle && options.format !== "svg")
|
|
17330
17262
|
log_default.warn("--strip-style is only supported for SVG output");
|
|
17331
17263
|
if (options.format === "tldr") {
|
|
17332
|
-
if (
|
|
17264
|
+
if (options.frames !== void 0)
|
|
17333
17265
|
log_default.warn(
|
|
17334
17266
|
'--frames is not supported when exporting to "tldr", ignoring flag and exporting entire sketch file'
|
|
17335
17267
|
);
|
|
17268
|
+
if (options.pages !== void 0)
|
|
17269
|
+
log_default.warn(
|
|
17270
|
+
'--pages is not supported when exporting to "tldr", ignoring flag and exporting entire sketch file'
|
|
17271
|
+
);
|
|
17336
17272
|
if (options.dark !== false) {
|
|
17337
17273
|
log_default.warn(
|
|
17338
17274
|
'--dark is not supported when exporting to "tldr", ignoring flag and exporting sketch file'
|
|
@@ -17365,22 +17301,37 @@ var TldrawController = class {
|
|
|
17365
17301
|
stripStyle = false,
|
|
17366
17302
|
transparent = false
|
|
17367
17303
|
} = this.stripUndefined(options);
|
|
17368
|
-
let frameSuffix = "";
|
|
17369
|
-
let base64String = "";
|
|
17370
17304
|
if (format3 === "tldr") {
|
|
17371
17305
|
await this.page.evaluate(get_tldr_iife_default);
|
|
17372
|
-
base64String = await this.page.evaluate(async () => window.getTldr());
|
|
17373
|
-
|
|
17374
|
-
|
|
17306
|
+
const base64String = await this.page.evaluate(async () => window.getTldr());
|
|
17307
|
+
if (print) {
|
|
17308
|
+
const plainString = base64ToString(base64String).replaceAll("\n", "");
|
|
17309
|
+
return [plainString];
|
|
17310
|
+
}
|
|
17311
|
+
const outputPath = path2.resolve(untildify(path2.join(output, `${filename}.${format3}`)));
|
|
17312
|
+
await fs.writeFile(outputPath, base64ToUint8Array(base64String));
|
|
17313
|
+
return [outputPath];
|
|
17314
|
+
}
|
|
17315
|
+
await this.page.evaluate(get_image_iife_default);
|
|
17316
|
+
const sketchStructure = await this.getSketchStructure();
|
|
17317
|
+
const downloadPlan = this.getDownloadPlans(sketchStructure, options.pages, options.frames);
|
|
17318
|
+
const outputAccumulator = [];
|
|
17319
|
+
const initialPageId = await this.getCurrentPage();
|
|
17320
|
+
let pageChanged = false;
|
|
17321
|
+
for (const download of downloadPlan) {
|
|
17322
|
+
if (download.pageId !== initialPageId) {
|
|
17323
|
+
log_default.info(`Selecting sketch page "${download.pageId}"`);
|
|
17324
|
+
await this.setCurrentPage(download.pageId);
|
|
17325
|
+
pageChanged = true;
|
|
17326
|
+
}
|
|
17327
|
+
if (download.frameId === void 0) {
|
|
17375
17328
|
await this.page.evaluate("editor.selectAll()");
|
|
17376
17329
|
} else {
|
|
17377
|
-
log_default.info(`Selecting sketch frame "${
|
|
17378
|
-
frameSuffix = `-${slugify(pageFrame.name)}`;
|
|
17330
|
+
log_default.info(`Selecting sketch frame "${download.frameId}"`);
|
|
17379
17331
|
await this.page.evaluate("editor.selectNone()");
|
|
17380
|
-
await this.page.evaluate(`editor.select('${
|
|
17332
|
+
await this.page.evaluate(`editor.select('${download.frameId}')`);
|
|
17381
17333
|
}
|
|
17382
|
-
await this.page.evaluate(
|
|
17383
|
-
base64String = await this.page.evaluate(async (options2) => window.getImage(options2), {
|
|
17334
|
+
let base64String = await this.page.evaluate(async (options2) => window.getImage(options2), {
|
|
17384
17335
|
background: transparent === void 0 ? void 0 : !transparent,
|
|
17385
17336
|
darkMode: dark,
|
|
17386
17337
|
format: format3,
|
|
@@ -17390,19 +17341,51 @@ var TldrawController = class {
|
|
|
17390
17341
|
if (stripStyle && format3 === "svg") {
|
|
17391
17342
|
base64String = stringToBase64(this.stripStyleElement(base64ToString(base64String)));
|
|
17392
17343
|
}
|
|
17393
|
-
|
|
17394
|
-
|
|
17395
|
-
|
|
17396
|
-
|
|
17397
|
-
|
|
17398
|
-
|
|
17399
|
-
|
|
17344
|
+
if (print) {
|
|
17345
|
+
if (format3 === "png") {
|
|
17346
|
+
outputAccumulator.push(base64String);
|
|
17347
|
+
} else {
|
|
17348
|
+
const plainString = base64ToString(base64String).replaceAll("\n", "");
|
|
17349
|
+
outputAccumulator.push(plainString);
|
|
17350
|
+
}
|
|
17351
|
+
} else {
|
|
17352
|
+
const outputPath = path2.resolve(
|
|
17353
|
+
untildify(path2.join(output, `${filename}${download.fileSuffix}.${format3}`))
|
|
17354
|
+
);
|
|
17355
|
+
await fs.writeFile(outputPath, base64ToUint8Array(base64String));
|
|
17356
|
+
outputAccumulator.push(outputPath);
|
|
17400
17357
|
}
|
|
17401
|
-
const plainString = base64ToString(base64String).replaceAll("\n", "");
|
|
17402
|
-
return [plainString];
|
|
17403
17358
|
}
|
|
17404
|
-
|
|
17405
|
-
|
|
17359
|
+
if (pageChanged) {
|
|
17360
|
+
await this.setCurrentPage(initialPageId);
|
|
17361
|
+
}
|
|
17362
|
+
return outputAccumulator;
|
|
17363
|
+
}
|
|
17364
|
+
async loadFile(filePath) {
|
|
17365
|
+
if (!this.page)
|
|
17366
|
+
throw new Error("Controller not started");
|
|
17367
|
+
if (this.isLocal)
|
|
17368
|
+
throw new Error(
|
|
17369
|
+
"File loading is only supported for remote tldraw.com instances. See tldraw-open.ts for local file loading approach."
|
|
17370
|
+
);
|
|
17371
|
+
await this.closeMenus();
|
|
17372
|
+
await this.page.evaluate(`userPreferences.showFileOpenWarning.set(false);`);
|
|
17373
|
+
log_default.info(`Uploading local file to tldraw.com: ${filePath}`);
|
|
17374
|
+
const tldrFile = await fs.readFile(filePath, "utf8");
|
|
17375
|
+
await this.page.evaluate(set_tldr_iife_default);
|
|
17376
|
+
await this.page.evaluate(`window.setTldr(${tldrFile})`);
|
|
17377
|
+
}
|
|
17378
|
+
async getShareUrl() {
|
|
17379
|
+
if (!this.page)
|
|
17380
|
+
throw new Error("Controller not started");
|
|
17381
|
+
if (this.isLocal)
|
|
17382
|
+
throw new Error("Share URLs are only supported for remote tldraw.com instances.");
|
|
17383
|
+
await this.closeMenus();
|
|
17384
|
+
await this.clickButtonTitles(["Menu", "File", "Share this project"]);
|
|
17385
|
+
await this.page.waitForNavigation({ waitUntil: "networkidle0" });
|
|
17386
|
+
const shareUrl = new URL(this.page.url());
|
|
17387
|
+
shareUrl.search = "";
|
|
17388
|
+
return shareUrl.href;
|
|
17406
17389
|
}
|
|
17407
17390
|
async closeMenus() {
|
|
17408
17391
|
if (!this.page)
|
|
@@ -17419,28 +17402,163 @@ var TldrawController = class {
|
|
|
17419
17402
|
await this.page.click(`button[title="${title}"]`);
|
|
17420
17403
|
}
|
|
17421
17404
|
}
|
|
17422
|
-
//
|
|
17423
|
-
|
|
17424
|
-
|
|
17425
|
-
|
|
17426
|
-
|
|
17405
|
+
// Deduplicates, verifies existence, and normalizes to ids of the style 'page:xxxx'
|
|
17406
|
+
validatePages(sketch, pages) {
|
|
17407
|
+
if (Array.isArray(pages)) {
|
|
17408
|
+
const validPages = [];
|
|
17409
|
+
for (const p of pages) {
|
|
17410
|
+
const matchingPage = typeof p === "number" ? sketch[p] : sketch.find(
|
|
17411
|
+
(page) => slugify(p) === slugify(page.name) || `page:${p.replace(/^page:/, "")}` === page.id
|
|
17412
|
+
);
|
|
17413
|
+
if (matchingPage) {
|
|
17414
|
+
if (!validPages.includes(matchingPage.id)) {
|
|
17415
|
+
validPages.push(matchingPage.id);
|
|
17416
|
+
}
|
|
17417
|
+
} else {
|
|
17418
|
+
log_default.warn(`Page "${p}" not found in sketch`);
|
|
17419
|
+
}
|
|
17420
|
+
}
|
|
17421
|
+
if (validPages.length === 0) {
|
|
17422
|
+
log_default.warn("None of the requested pages were found in sketch, ignoring pages option");
|
|
17423
|
+
return void 0;
|
|
17424
|
+
}
|
|
17425
|
+
return validPages;
|
|
17426
|
+
}
|
|
17427
|
+
return pages;
|
|
17428
|
+
}
|
|
17429
|
+
validateFrames(sketch, pages, frames) {
|
|
17430
|
+
if (Array.isArray(frames)) {
|
|
17431
|
+
const validSketch = sketch.filter(
|
|
17432
|
+
(page, index2) => pages === void 0 || typeof pages === "boolean" && !pages && index2 === 0 || typeof pages === "boolean" && pages || Array.isArray(pages) && pages.some(
|
|
17433
|
+
(p, index3) => typeof p === "number" ? index3 === p : slugify(p) === slugify(page.name) || p.replace(/^page:/, "") === page.id.replace(/^page:/, "")
|
|
17434
|
+
)
|
|
17435
|
+
);
|
|
17436
|
+
const validFrames = [];
|
|
17437
|
+
for (const f of frames) {
|
|
17438
|
+
const matchingFrames = [];
|
|
17439
|
+
for (const page of validSketch) {
|
|
17440
|
+
const match = page.frames.find(
|
|
17441
|
+
(frame) => slugify(f) === slugify(frame.name) || `shape:${f.replace(/^shape:/, "")}` === frame.id
|
|
17442
|
+
);
|
|
17443
|
+
if (match && !matchingFrames.includes(match.id)) {
|
|
17444
|
+
matchingFrames.push(match.id);
|
|
17445
|
+
}
|
|
17446
|
+
}
|
|
17447
|
+
if (matchingFrames.length === 0) {
|
|
17448
|
+
log_default.warn(`Frame "${f}" not found in sketch`);
|
|
17449
|
+
} else {
|
|
17450
|
+
validFrames.push(...matchingFrames);
|
|
17451
|
+
}
|
|
17452
|
+
if (validFrames.length === 0) {
|
|
17453
|
+
log_default.warn("None of the requested frames were found in sketch, ignoring frames option");
|
|
17454
|
+
return void 0;
|
|
17455
|
+
}
|
|
17456
|
+
}
|
|
17457
|
+
return validFrames;
|
|
17427
17458
|
}
|
|
17428
|
-
return
|
|
17429
|
-
|
|
17459
|
+
return frames;
|
|
17460
|
+
}
|
|
17461
|
+
// eslint-disable-next-line complexity
|
|
17462
|
+
getDownloadPlans(sketch, pages, frames) {
|
|
17463
|
+
const validPages = this.validatePages(sketch, pages);
|
|
17464
|
+
const validFrames = this.validateFrames(sketch, validPages, frames);
|
|
17465
|
+
const filteredSketch = validPages === void 0 ? [sketch[0]] : typeof validPages === "boolean" && !validPages ? [sketch[0]] : sketch.filter(
|
|
17466
|
+
(sketchPage) => typeof validPages === "boolean" && validPages ? true : validPages.some(
|
|
17467
|
+
(p) => slugify(p) === slugify(sketchPage.name) || p.replace("page:", "") === sketchPage.id.replace("page:", "")
|
|
17468
|
+
)
|
|
17430
17469
|
);
|
|
17470
|
+
for (const page of filteredSketch) {
|
|
17471
|
+
page.frames = validFrames === void 0 || typeof validFrames === "boolean" && !validFrames ? [] : page.frames.filter(
|
|
17472
|
+
(frame) => typeof validFrames === "boolean" && validFrames ? true : validFrames.some(
|
|
17473
|
+
(f) => slugify(f) === slugify(frame.name) || f.replace("shape:", "") === frame.id.replace("shape:", "")
|
|
17474
|
+
)
|
|
17475
|
+
);
|
|
17476
|
+
}
|
|
17477
|
+
const downloadPlans = [];
|
|
17478
|
+
const isPageNameCollision = new Set(filteredSketch.map((page) => slugify(page.name))).size !== filteredSketch.length;
|
|
17479
|
+
if (isPageNameCollision) {
|
|
17480
|
+
log_default.warn(
|
|
17481
|
+
"Page names are not unique, including page IDs in the output filenames to avoid collisions"
|
|
17482
|
+
);
|
|
17483
|
+
}
|
|
17484
|
+
for (const page of filteredSketch) {
|
|
17485
|
+
const pageSuffix = validPages === void 0 || typeof validPages === "boolean" && !validPages ? void 0 : isPageNameCollision ? `${slugify(page.name)}-${page.id.replace("page:", "")}` : slugify(page.name);
|
|
17486
|
+
if (page.frames.length === 0) {
|
|
17487
|
+
downloadPlans.push({
|
|
17488
|
+
fileSuffix: pageSuffix ?? "",
|
|
17489
|
+
frameId: void 0,
|
|
17490
|
+
pageId: page.id
|
|
17491
|
+
});
|
|
17492
|
+
} else {
|
|
17493
|
+
const isFrameNameCollision = new Set(page.frames.map((frame) => slugify(frame.name))).size !== page.frames.length;
|
|
17494
|
+
if (isFrameNameCollision) {
|
|
17495
|
+
log_default.warn(
|
|
17496
|
+
"Frame names are not unique, including frame IDs in the output filenames to avoid collisions"
|
|
17497
|
+
);
|
|
17498
|
+
}
|
|
17499
|
+
for (const frame of page.frames) {
|
|
17500
|
+
const frameSuffix = isFrameNameCollision ? [slugify(frame.name), frame.id.replace("shape:", "")].filter((value) => value !== "").join("-") : slugify(frame.name);
|
|
17501
|
+
downloadPlans.push({
|
|
17502
|
+
fileSuffix: [pageSuffix, frameSuffix].filter((value) => value !== void 0).join("-"),
|
|
17503
|
+
frameId: frame.id,
|
|
17504
|
+
pageId: page.id
|
|
17505
|
+
});
|
|
17506
|
+
}
|
|
17507
|
+
}
|
|
17508
|
+
}
|
|
17509
|
+
for (const plan of downloadPlans) {
|
|
17510
|
+
if (plan.fileSuffix !== "") {
|
|
17511
|
+
plan.fileSuffix = `-${plan.fileSuffix}`;
|
|
17512
|
+
}
|
|
17513
|
+
}
|
|
17514
|
+
return downloadPlans;
|
|
17431
17515
|
}
|
|
17432
|
-
//
|
|
17433
|
-
async
|
|
17516
|
+
// Structure
|
|
17517
|
+
async getSketchStructure() {
|
|
17518
|
+
if (!this.page)
|
|
17519
|
+
throw new Error("Controller not started");
|
|
17520
|
+
const document = await this.getPages();
|
|
17521
|
+
for (const page of document) {
|
|
17522
|
+
page.frames = await this.getPageFrames(page.id);
|
|
17523
|
+
}
|
|
17524
|
+
return document;
|
|
17525
|
+
}
|
|
17526
|
+
async getPages() {
|
|
17434
17527
|
if (!this.page)
|
|
17435
17528
|
throw new Error("Controller not started");
|
|
17436
17529
|
return await this.page.evaluate(
|
|
17530
|
+
`editor.getPages().map((page) => ({ id: page.id, name: page.name, frames: []}))`
|
|
17531
|
+
);
|
|
17532
|
+
}
|
|
17533
|
+
async getPageFrames(pageId) {
|
|
17534
|
+
if (!this.page)
|
|
17535
|
+
throw new Error("Controller not started");
|
|
17536
|
+
const initialPageId = await this.getCurrentPage();
|
|
17537
|
+
if (pageId !== initialPageId) {
|
|
17538
|
+
await this.setCurrentPage(pageId);
|
|
17539
|
+
}
|
|
17540
|
+
const frames = await this.page.evaluate(
|
|
17437
17541
|
`editor.getCurrentPageShapes().reduce((accumulator, shape) => {
|
|
17438
17542
|
if (shape.type === 'frame') {
|
|
17439
|
-
accumulator.push({ id: shape.id, name: shape.props.name })
|
|
17543
|
+
accumulator.push({ id: shape.id, name: shape.props.name === '' ? 'Frame' : shape.props.name })
|
|
17440
17544
|
}
|
|
17441
17545
|
return accumulator
|
|
17442
17546
|
}, [])`
|
|
17443
17547
|
);
|
|
17548
|
+
if (pageId !== initialPageId) {
|
|
17549
|
+
await this.setCurrentPage(initialPageId);
|
|
17550
|
+
}
|
|
17551
|
+
return frames;
|
|
17552
|
+
}
|
|
17553
|
+
async getCurrentPage() {
|
|
17554
|
+
if (!this.page)
|
|
17555
|
+
throw new Error("Controller not started");
|
|
17556
|
+
return await this.page.evaluate(`editor.getCurrentPageId()`);
|
|
17557
|
+
}
|
|
17558
|
+
async setCurrentPage(pageId) {
|
|
17559
|
+
if (!this.page)
|
|
17560
|
+
throw new Error("Controller not started");
|
|
17561
|
+
await this.page.evaluate(`editor.setCurrentPage("${pageId}")`);
|
|
17444
17562
|
}
|
|
17445
17563
|
// Helpers
|
|
17446
17564
|
stripStyleElement(svg) {
|
|
@@ -17676,14 +17794,7 @@ async function tldrawToImage(tldrPathOrUrl, options = {}) {
|
|
|
17676
17794
|
const tldrawUrl = isLocal ? tldrawServer.href : validatedPathOrUrl.href;
|
|
17677
17795
|
const tldrawController = new TldrawController(tldrawUrl);
|
|
17678
17796
|
await tldrawController.start();
|
|
17679
|
-
|
|
17680
|
-
if (options.frames && typeof options.frames === "boolean") {
|
|
17681
|
-
exportReport = await tldrawController.downloadAllFrames(options);
|
|
17682
|
-
} else if (Array.isArray(options.frames) && options.frames.length > 0) {
|
|
17683
|
-
exportReport = await tldrawController.downloadFrames(options.frames, options);
|
|
17684
|
-
} else {
|
|
17685
|
-
exportReport = await tldrawController.download(options);
|
|
17686
|
-
}
|
|
17797
|
+
const exportReport = await tldrawController.download(options);
|
|
17687
17798
|
await tldrawController.close();
|
|
17688
17799
|
if (isLocal)
|
|
17689
17800
|
tldrawServer.close();
|
|
@@ -23183,7 +23294,17 @@ await yargsInstance.scriptName("tldraw-cli").command("$0 <command>", "CLI tools
|
|
|
23183
23294
|
// value to true, and still be able to extract meaning from the
|
|
23184
23295
|
// absence of the option
|
|
23185
23296
|
defaultDescription: "false",
|
|
23186
|
-
describe: 'Export each sketch "frame" as a separate image. Pass one or more frame names or IDs to export specific frames, or
|
|
23297
|
+
describe: 'Export each sketch "frame" as a separate image. Pass one or more frame names or IDs to export specific frames, or pass the flag without the arguments to export all frames. By default, the entire first page is exported with all frames.',
|
|
23298
|
+
type: "array"
|
|
23299
|
+
}).option("pages", {
|
|
23300
|
+
coerce(args) {
|
|
23301
|
+
return args.length === 0 ? true : args;
|
|
23302
|
+
},
|
|
23303
|
+
// Do not set a default value, so we can coerce --pages without a
|
|
23304
|
+
// value to true, and still be able to extract meaning from the
|
|
23305
|
+
// absence of the option
|
|
23306
|
+
defaultDescription: "false",
|
|
23307
|
+
describe: 'Export each sketch "page" as a separate image. Pass one or more page names or IDs to export specific page, or pass one or more page index numbers (from 0), or pass the flag without the arguments to export all pages. By default, only the first page is exported.',
|
|
23187
23308
|
type: "array"
|
|
23188
23309
|
}).option("transparent", {
|
|
23189
23310
|
alias: "t",
|
|
@@ -23233,6 +23354,7 @@ await yargsInstance.scriptName("tldraw-cli").command("$0 <command>", "CLI tools
|
|
|
23233
23354
|
name,
|
|
23234
23355
|
output,
|
|
23235
23356
|
padding,
|
|
23357
|
+
pages,
|
|
23236
23358
|
print,
|
|
23237
23359
|
scale,
|
|
23238
23360
|
stripStyle,
|
|
@@ -23254,6 +23376,7 @@ await yargsInstance.scriptName("tldraw-cli").command("$0 <command>", "CLI tools
|
|
|
23254
23376
|
name: resolvedName,
|
|
23255
23377
|
output,
|
|
23256
23378
|
padding,
|
|
23379
|
+
pages,
|
|
23257
23380
|
print,
|
|
23258
23381
|
scale,
|
|
23259
23382
|
stripStyle,
|
|
@@ -23342,7 +23465,7 @@ await yargsInstance.scriptName("tldraw-cli").command("$0 <command>", "CLI tools
|
|
|
23342
23465
|
process.exit(1);
|
|
23343
23466
|
}
|
|
23344
23467
|
}
|
|
23345
|
-
).demandCommand(1).alias("h", "help").version(
|
|
23468
|
+
).demandCommand(1).alias("h", "help").version().alias("v", "version").help().wrap(process.stdout.isTTY ? Math.min(120, yargsInstance.terminalWidth()) : 0).parse();
|
|
23346
23469
|
/*! Bundled license information:
|
|
23347
23470
|
|
|
23348
23471
|
yargs-parser/build/lib/string-utils.js:
|
package/dist/.DS_Store
CHANGED
|
Binary file
|
package/dist/lib/index.js
CHANGED
|
@@ -17049,81 +17049,13 @@ var TldrawController = class {
|
|
|
17049
17049
|
async close() {
|
|
17050
17050
|
if (!this.browser)
|
|
17051
17051
|
throw new Error("Controller not started");
|
|
17052
|
+
const pages = await this.browser.pages();
|
|
17053
|
+
await Promise.all(pages.map(async (page) => page.close()));
|
|
17052
17054
|
await this.browser.close();
|
|
17053
17055
|
log_default.info("Stopped controller");
|
|
17054
17056
|
}
|
|
17055
|
-
//
|
|
17057
|
+
// eslint-disable-next-line complexity
|
|
17056
17058
|
async download(options) {
|
|
17057
|
-
return this._download(void 0, options);
|
|
17058
|
-
}
|
|
17059
|
-
async downloadFrame(frameNameOrId, options) {
|
|
17060
|
-
return this.downloadFrames([frameNameOrId], options);
|
|
17061
|
-
}
|
|
17062
|
-
async downloadFrames(frameNamesOrIds, options) {
|
|
17063
|
-
const validPageFrames = [];
|
|
17064
|
-
for (const frame of frameNamesOrIds) {
|
|
17065
|
-
const pageFrame = await this.getPageFrameWithNameOrId(frame);
|
|
17066
|
-
if (pageFrame === void 0) {
|
|
17067
|
-
log_default.warn(`Frame "${frame}" not found, skipping`);
|
|
17068
|
-
} else {
|
|
17069
|
-
validPageFrames.push(pageFrame);
|
|
17070
|
-
}
|
|
17071
|
-
}
|
|
17072
|
-
if (validPageFrames.length === 0) {
|
|
17073
|
-
throw new Error("No valid frames found");
|
|
17074
|
-
}
|
|
17075
|
-
const validFrameNames = validPageFrames.map((frame) => slugify(frame.name));
|
|
17076
|
-
const isFrameNameCollision = validFrameNames.length !== new Set(validFrameNames).size;
|
|
17077
|
-
if (isFrameNameCollision) {
|
|
17078
|
-
log_default.warn(
|
|
17079
|
-
"Frame names are not unique, including frame IDs in the output filenames to avoid collisions"
|
|
17080
|
-
);
|
|
17081
|
-
}
|
|
17082
|
-
const outputAccumulator = [];
|
|
17083
|
-
for (const frame of validPageFrames) {
|
|
17084
|
-
const frameSuffix = isFrameNameCollision ? `-${frame.id.replace("shape:", "")}` : "";
|
|
17085
|
-
outputAccumulator.push(
|
|
17086
|
-
...await this._download(frame, {
|
|
17087
|
-
...options,
|
|
17088
|
-
name: `${options.name}${frameSuffix}`
|
|
17089
|
-
})
|
|
17090
|
-
);
|
|
17091
|
-
}
|
|
17092
|
-
return outputAccumulator;
|
|
17093
|
-
}
|
|
17094
|
-
async downloadAllFrames(options) {
|
|
17095
|
-
const pageFrames = await this.getPageFrames();
|
|
17096
|
-
const frameNamesOrIds = pageFrames.map((f) => f.id);
|
|
17097
|
-
return this.downloadFrames(frameNamesOrIds, options);
|
|
17098
|
-
}
|
|
17099
|
-
async loadFile(filePath) {
|
|
17100
|
-
if (!this.page)
|
|
17101
|
-
throw new Error("Controller not started");
|
|
17102
|
-
if (this.isLocal)
|
|
17103
|
-
throw new Error(
|
|
17104
|
-
"File loading is only supported for remote tldraw.com instances. See tldraw-open.ts for local file loading approach."
|
|
17105
|
-
);
|
|
17106
|
-
await this.closeMenus();
|
|
17107
|
-
await this.page.evaluate(`userPreferences.showFileOpenWarning.set(false);`);
|
|
17108
|
-
log_default.info(`Uploading local file to tldraw.com: ${filePath}`);
|
|
17109
|
-
const tldrFile = await fs.readFile(filePath, "utf8");
|
|
17110
|
-
await this.page.evaluate(set_tldr_iife_default);
|
|
17111
|
-
await this.page.evaluate(`window.setTldr(${tldrFile})`);
|
|
17112
|
-
}
|
|
17113
|
-
async getShareUrl() {
|
|
17114
|
-
if (!this.page)
|
|
17115
|
-
throw new Error("Controller not started");
|
|
17116
|
-
if (this.isLocal)
|
|
17117
|
-
throw new Error("Share URLs are only supported for remote tldraw.com instances.");
|
|
17118
|
-
await this.closeMenus();
|
|
17119
|
-
await this.clickButtonTitles(["Menu", "File", "Share this project"]);
|
|
17120
|
-
await this.page.waitForNavigation({ waitUntil: "networkidle0" });
|
|
17121
|
-
const shareUrl = new URL(this.page.url());
|
|
17122
|
-
shareUrl.search = "";
|
|
17123
|
-
return shareUrl.href;
|
|
17124
|
-
}
|
|
17125
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
17126
|
-
async _download(pageFrame, options) {
|
|
17127
17059
|
if (!this.page)
|
|
17128
17060
|
throw new Error("Controller not started");
|
|
17129
17061
|
if (this.isEmpty)
|
|
@@ -17131,10 +17063,14 @@ var TldrawController = class {
|
|
|
17131
17063
|
if (options.stripStyle && options.format !== "svg")
|
|
17132
17064
|
log_default.warn("--strip-style is only supported for SVG output");
|
|
17133
17065
|
if (options.format === "tldr") {
|
|
17134
|
-
if (
|
|
17066
|
+
if (options.frames !== void 0)
|
|
17135
17067
|
log_default.warn(
|
|
17136
17068
|
'--frames is not supported when exporting to "tldr", ignoring flag and exporting entire sketch file'
|
|
17137
17069
|
);
|
|
17070
|
+
if (options.pages !== void 0)
|
|
17071
|
+
log_default.warn(
|
|
17072
|
+
'--pages is not supported when exporting to "tldr", ignoring flag and exporting entire sketch file'
|
|
17073
|
+
);
|
|
17138
17074
|
if (options.dark !== false) {
|
|
17139
17075
|
log_default.warn(
|
|
17140
17076
|
'--dark is not supported when exporting to "tldr", ignoring flag and exporting sketch file'
|
|
@@ -17167,22 +17103,37 @@ var TldrawController = class {
|
|
|
17167
17103
|
stripStyle = false,
|
|
17168
17104
|
transparent = false
|
|
17169
17105
|
} = this.stripUndefined(options);
|
|
17170
|
-
let frameSuffix = "";
|
|
17171
|
-
let base64String = "";
|
|
17172
17106
|
if (format === "tldr") {
|
|
17173
17107
|
await this.page.evaluate(get_tldr_iife_default);
|
|
17174
|
-
base64String = await this.page.evaluate(async () => window.getTldr());
|
|
17175
|
-
|
|
17176
|
-
|
|
17108
|
+
const base64String = await this.page.evaluate(async () => window.getTldr());
|
|
17109
|
+
if (print) {
|
|
17110
|
+
const plainString = base64ToString(base64String).replaceAll("\n", "");
|
|
17111
|
+
return [plainString];
|
|
17112
|
+
}
|
|
17113
|
+
const outputPath = path2.resolve(untildify(path2.join(output, `${filename}.${format}`)));
|
|
17114
|
+
await fs.writeFile(outputPath, base64ToUint8Array(base64String));
|
|
17115
|
+
return [outputPath];
|
|
17116
|
+
}
|
|
17117
|
+
await this.page.evaluate(get_image_iife_default);
|
|
17118
|
+
const sketchStructure = await this.getSketchStructure();
|
|
17119
|
+
const downloadPlan = this.getDownloadPlans(sketchStructure, options.pages, options.frames);
|
|
17120
|
+
const outputAccumulator = [];
|
|
17121
|
+
const initialPageId = await this.getCurrentPage();
|
|
17122
|
+
let pageChanged = false;
|
|
17123
|
+
for (const download of downloadPlan) {
|
|
17124
|
+
if (download.pageId !== initialPageId) {
|
|
17125
|
+
log_default.info(`Selecting sketch page "${download.pageId}"`);
|
|
17126
|
+
await this.setCurrentPage(download.pageId);
|
|
17127
|
+
pageChanged = true;
|
|
17128
|
+
}
|
|
17129
|
+
if (download.frameId === void 0) {
|
|
17177
17130
|
await this.page.evaluate("editor.selectAll()");
|
|
17178
17131
|
} else {
|
|
17179
|
-
log_default.info(`Selecting sketch frame "${
|
|
17180
|
-
frameSuffix = `-${slugify(pageFrame.name)}`;
|
|
17132
|
+
log_default.info(`Selecting sketch frame "${download.frameId}"`);
|
|
17181
17133
|
await this.page.evaluate("editor.selectNone()");
|
|
17182
|
-
await this.page.evaluate(`editor.select('${
|
|
17134
|
+
await this.page.evaluate(`editor.select('${download.frameId}')`);
|
|
17183
17135
|
}
|
|
17184
|
-
await this.page.evaluate(
|
|
17185
|
-
base64String = await this.page.evaluate(async (options2) => window.getImage(options2), {
|
|
17136
|
+
let base64String = await this.page.evaluate(async (options2) => window.getImage(options2), {
|
|
17186
17137
|
background: transparent === void 0 ? void 0 : !transparent,
|
|
17187
17138
|
darkMode: dark,
|
|
17188
17139
|
format,
|
|
@@ -17192,19 +17143,51 @@ var TldrawController = class {
|
|
|
17192
17143
|
if (stripStyle && format === "svg") {
|
|
17193
17144
|
base64String = stringToBase64(this.stripStyleElement(base64ToString(base64String)));
|
|
17194
17145
|
}
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17146
|
+
if (print) {
|
|
17147
|
+
if (format === "png") {
|
|
17148
|
+
outputAccumulator.push(base64String);
|
|
17149
|
+
} else {
|
|
17150
|
+
const plainString = base64ToString(base64String).replaceAll("\n", "");
|
|
17151
|
+
outputAccumulator.push(plainString);
|
|
17152
|
+
}
|
|
17153
|
+
} else {
|
|
17154
|
+
const outputPath = path2.resolve(
|
|
17155
|
+
untildify(path2.join(output, `${filename}${download.fileSuffix}.${format}`))
|
|
17156
|
+
);
|
|
17157
|
+
await fs.writeFile(outputPath, base64ToUint8Array(base64String));
|
|
17158
|
+
outputAccumulator.push(outputPath);
|
|
17202
17159
|
}
|
|
17203
|
-
const plainString = base64ToString(base64String).replaceAll("\n", "");
|
|
17204
|
-
return [plainString];
|
|
17205
17160
|
}
|
|
17206
|
-
|
|
17207
|
-
|
|
17161
|
+
if (pageChanged) {
|
|
17162
|
+
await this.setCurrentPage(initialPageId);
|
|
17163
|
+
}
|
|
17164
|
+
return outputAccumulator;
|
|
17165
|
+
}
|
|
17166
|
+
async loadFile(filePath) {
|
|
17167
|
+
if (!this.page)
|
|
17168
|
+
throw new Error("Controller not started");
|
|
17169
|
+
if (this.isLocal)
|
|
17170
|
+
throw new Error(
|
|
17171
|
+
"File loading is only supported for remote tldraw.com instances. See tldraw-open.ts for local file loading approach."
|
|
17172
|
+
);
|
|
17173
|
+
await this.closeMenus();
|
|
17174
|
+
await this.page.evaluate(`userPreferences.showFileOpenWarning.set(false);`);
|
|
17175
|
+
log_default.info(`Uploading local file to tldraw.com: ${filePath}`);
|
|
17176
|
+
const tldrFile = await fs.readFile(filePath, "utf8");
|
|
17177
|
+
await this.page.evaluate(set_tldr_iife_default);
|
|
17178
|
+
await this.page.evaluate(`window.setTldr(${tldrFile})`);
|
|
17179
|
+
}
|
|
17180
|
+
async getShareUrl() {
|
|
17181
|
+
if (!this.page)
|
|
17182
|
+
throw new Error("Controller not started");
|
|
17183
|
+
if (this.isLocal)
|
|
17184
|
+
throw new Error("Share URLs are only supported for remote tldraw.com instances.");
|
|
17185
|
+
await this.closeMenus();
|
|
17186
|
+
await this.clickButtonTitles(["Menu", "File", "Share this project"]);
|
|
17187
|
+
await this.page.waitForNavigation({ waitUntil: "networkidle0" });
|
|
17188
|
+
const shareUrl = new URL(this.page.url());
|
|
17189
|
+
shareUrl.search = "";
|
|
17190
|
+
return shareUrl.href;
|
|
17208
17191
|
}
|
|
17209
17192
|
async closeMenus() {
|
|
17210
17193
|
if (!this.page)
|
|
@@ -17221,28 +17204,163 @@ var TldrawController = class {
|
|
|
17221
17204
|
await this.page.click(`button[title="${title}"]`);
|
|
17222
17205
|
}
|
|
17223
17206
|
}
|
|
17224
|
-
//
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
17207
|
+
// Deduplicates, verifies existence, and normalizes to ids of the style 'page:xxxx'
|
|
17208
|
+
validatePages(sketch, pages) {
|
|
17209
|
+
if (Array.isArray(pages)) {
|
|
17210
|
+
const validPages = [];
|
|
17211
|
+
for (const p of pages) {
|
|
17212
|
+
const matchingPage = typeof p === "number" ? sketch[p] : sketch.find(
|
|
17213
|
+
(page) => slugify(p) === slugify(page.name) || `page:${p.replace(/^page:/, "")}` === page.id
|
|
17214
|
+
);
|
|
17215
|
+
if (matchingPage) {
|
|
17216
|
+
if (!validPages.includes(matchingPage.id)) {
|
|
17217
|
+
validPages.push(matchingPage.id);
|
|
17218
|
+
}
|
|
17219
|
+
} else {
|
|
17220
|
+
log_default.warn(`Page "${p}" not found in sketch`);
|
|
17221
|
+
}
|
|
17222
|
+
}
|
|
17223
|
+
if (validPages.length === 0) {
|
|
17224
|
+
log_default.warn("None of the requested pages were found in sketch, ignoring pages option");
|
|
17225
|
+
return void 0;
|
|
17226
|
+
}
|
|
17227
|
+
return validPages;
|
|
17228
|
+
}
|
|
17229
|
+
return pages;
|
|
17230
|
+
}
|
|
17231
|
+
validateFrames(sketch, pages, frames) {
|
|
17232
|
+
if (Array.isArray(frames)) {
|
|
17233
|
+
const validSketch = sketch.filter(
|
|
17234
|
+
(page, index2) => pages === void 0 || typeof pages === "boolean" && !pages && index2 === 0 || typeof pages === "boolean" && pages || Array.isArray(pages) && pages.some(
|
|
17235
|
+
(p, index3) => typeof p === "number" ? index3 === p : slugify(p) === slugify(page.name) || p.replace(/^page:/, "") === page.id.replace(/^page:/, "")
|
|
17236
|
+
)
|
|
17237
|
+
);
|
|
17238
|
+
const validFrames = [];
|
|
17239
|
+
for (const f of frames) {
|
|
17240
|
+
const matchingFrames = [];
|
|
17241
|
+
for (const page of validSketch) {
|
|
17242
|
+
const match = page.frames.find(
|
|
17243
|
+
(frame) => slugify(f) === slugify(frame.name) || `shape:${f.replace(/^shape:/, "")}` === frame.id
|
|
17244
|
+
);
|
|
17245
|
+
if (match && !matchingFrames.includes(match.id)) {
|
|
17246
|
+
matchingFrames.push(match.id);
|
|
17247
|
+
}
|
|
17248
|
+
}
|
|
17249
|
+
if (matchingFrames.length === 0) {
|
|
17250
|
+
log_default.warn(`Frame "${f}" not found in sketch`);
|
|
17251
|
+
} else {
|
|
17252
|
+
validFrames.push(...matchingFrames);
|
|
17253
|
+
}
|
|
17254
|
+
if (validFrames.length === 0) {
|
|
17255
|
+
log_default.warn("None of the requested frames were found in sketch, ignoring frames option");
|
|
17256
|
+
return void 0;
|
|
17257
|
+
}
|
|
17258
|
+
}
|
|
17259
|
+
return validFrames;
|
|
17229
17260
|
}
|
|
17230
|
-
return
|
|
17231
|
-
|
|
17261
|
+
return frames;
|
|
17262
|
+
}
|
|
17263
|
+
// eslint-disable-next-line complexity
|
|
17264
|
+
getDownloadPlans(sketch, pages, frames) {
|
|
17265
|
+
const validPages = this.validatePages(sketch, pages);
|
|
17266
|
+
const validFrames = this.validateFrames(sketch, validPages, frames);
|
|
17267
|
+
const filteredSketch = validPages === void 0 ? [sketch[0]] : typeof validPages === "boolean" && !validPages ? [sketch[0]] : sketch.filter(
|
|
17268
|
+
(sketchPage) => typeof validPages === "boolean" && validPages ? true : validPages.some(
|
|
17269
|
+
(p) => slugify(p) === slugify(sketchPage.name) || p.replace("page:", "") === sketchPage.id.replace("page:", "")
|
|
17270
|
+
)
|
|
17232
17271
|
);
|
|
17272
|
+
for (const page of filteredSketch) {
|
|
17273
|
+
page.frames = validFrames === void 0 || typeof validFrames === "boolean" && !validFrames ? [] : page.frames.filter(
|
|
17274
|
+
(frame) => typeof validFrames === "boolean" && validFrames ? true : validFrames.some(
|
|
17275
|
+
(f) => slugify(f) === slugify(frame.name) || f.replace("shape:", "") === frame.id.replace("shape:", "")
|
|
17276
|
+
)
|
|
17277
|
+
);
|
|
17278
|
+
}
|
|
17279
|
+
const downloadPlans = [];
|
|
17280
|
+
const isPageNameCollision = new Set(filteredSketch.map((page) => slugify(page.name))).size !== filteredSketch.length;
|
|
17281
|
+
if (isPageNameCollision) {
|
|
17282
|
+
log_default.warn(
|
|
17283
|
+
"Page names are not unique, including page IDs in the output filenames to avoid collisions"
|
|
17284
|
+
);
|
|
17285
|
+
}
|
|
17286
|
+
for (const page of filteredSketch) {
|
|
17287
|
+
const pageSuffix = validPages === void 0 || typeof validPages === "boolean" && !validPages ? void 0 : isPageNameCollision ? `${slugify(page.name)}-${page.id.replace("page:", "")}` : slugify(page.name);
|
|
17288
|
+
if (page.frames.length === 0) {
|
|
17289
|
+
downloadPlans.push({
|
|
17290
|
+
fileSuffix: pageSuffix ?? "",
|
|
17291
|
+
frameId: void 0,
|
|
17292
|
+
pageId: page.id
|
|
17293
|
+
});
|
|
17294
|
+
} else {
|
|
17295
|
+
const isFrameNameCollision = new Set(page.frames.map((frame) => slugify(frame.name))).size !== page.frames.length;
|
|
17296
|
+
if (isFrameNameCollision) {
|
|
17297
|
+
log_default.warn(
|
|
17298
|
+
"Frame names are not unique, including frame IDs in the output filenames to avoid collisions"
|
|
17299
|
+
);
|
|
17300
|
+
}
|
|
17301
|
+
for (const frame of page.frames) {
|
|
17302
|
+
const frameSuffix = isFrameNameCollision ? [slugify(frame.name), frame.id.replace("shape:", "")].filter((value) => value !== "").join("-") : slugify(frame.name);
|
|
17303
|
+
downloadPlans.push({
|
|
17304
|
+
fileSuffix: [pageSuffix, frameSuffix].filter((value) => value !== void 0).join("-"),
|
|
17305
|
+
frameId: frame.id,
|
|
17306
|
+
pageId: page.id
|
|
17307
|
+
});
|
|
17308
|
+
}
|
|
17309
|
+
}
|
|
17310
|
+
}
|
|
17311
|
+
for (const plan of downloadPlans) {
|
|
17312
|
+
if (plan.fileSuffix !== "") {
|
|
17313
|
+
plan.fileSuffix = `-${plan.fileSuffix}`;
|
|
17314
|
+
}
|
|
17315
|
+
}
|
|
17316
|
+
return downloadPlans;
|
|
17233
17317
|
}
|
|
17234
|
-
//
|
|
17235
|
-
async
|
|
17318
|
+
// Structure
|
|
17319
|
+
async getSketchStructure() {
|
|
17320
|
+
if (!this.page)
|
|
17321
|
+
throw new Error("Controller not started");
|
|
17322
|
+
const document = await this.getPages();
|
|
17323
|
+
for (const page of document) {
|
|
17324
|
+
page.frames = await this.getPageFrames(page.id);
|
|
17325
|
+
}
|
|
17326
|
+
return document;
|
|
17327
|
+
}
|
|
17328
|
+
async getPages() {
|
|
17236
17329
|
if (!this.page)
|
|
17237
17330
|
throw new Error("Controller not started");
|
|
17238
17331
|
return await this.page.evaluate(
|
|
17332
|
+
`editor.getPages().map((page) => ({ id: page.id, name: page.name, frames: []}))`
|
|
17333
|
+
);
|
|
17334
|
+
}
|
|
17335
|
+
async getPageFrames(pageId) {
|
|
17336
|
+
if (!this.page)
|
|
17337
|
+
throw new Error("Controller not started");
|
|
17338
|
+
const initialPageId = await this.getCurrentPage();
|
|
17339
|
+
if (pageId !== initialPageId) {
|
|
17340
|
+
await this.setCurrentPage(pageId);
|
|
17341
|
+
}
|
|
17342
|
+
const frames = await this.page.evaluate(
|
|
17239
17343
|
`editor.getCurrentPageShapes().reduce((accumulator, shape) => {
|
|
17240
17344
|
if (shape.type === 'frame') {
|
|
17241
|
-
accumulator.push({ id: shape.id, name: shape.props.name })
|
|
17345
|
+
accumulator.push({ id: shape.id, name: shape.props.name === '' ? 'Frame' : shape.props.name })
|
|
17242
17346
|
}
|
|
17243
17347
|
return accumulator
|
|
17244
17348
|
}, [])`
|
|
17245
17349
|
);
|
|
17350
|
+
if (pageId !== initialPageId) {
|
|
17351
|
+
await this.setCurrentPage(initialPageId);
|
|
17352
|
+
}
|
|
17353
|
+
return frames;
|
|
17354
|
+
}
|
|
17355
|
+
async getCurrentPage() {
|
|
17356
|
+
if (!this.page)
|
|
17357
|
+
throw new Error("Controller not started");
|
|
17358
|
+
return await this.page.evaluate(`editor.getCurrentPageId()`);
|
|
17359
|
+
}
|
|
17360
|
+
async setCurrentPage(pageId) {
|
|
17361
|
+
if (!this.page)
|
|
17362
|
+
throw new Error("Controller not started");
|
|
17363
|
+
await this.page.evaluate(`editor.setCurrentPage("${pageId}")`);
|
|
17246
17364
|
}
|
|
17247
17365
|
// Helpers
|
|
17248
17366
|
stripStyleElement(svg) {
|
|
@@ -17478,14 +17596,7 @@ async function tldrawToImage(tldrPathOrUrl, options = {}) {
|
|
|
17478
17596
|
const tldrawUrl = isLocal ? tldrawServer.href : validatedPathOrUrl.href;
|
|
17479
17597
|
const tldrawController = new TldrawController(tldrawUrl);
|
|
17480
17598
|
await tldrawController.start();
|
|
17481
|
-
|
|
17482
|
-
if (options.frames && typeof options.frames === "boolean") {
|
|
17483
|
-
exportReport = await tldrawController.downloadAllFrames(options);
|
|
17484
|
-
} else if (Array.isArray(options.frames) && options.frames.length > 0) {
|
|
17485
|
-
exportReport = await tldrawController.downloadFrames(options.frames, options);
|
|
17486
|
-
} else {
|
|
17487
|
-
exportReport = await tldrawController.download(options);
|
|
17488
|
-
}
|
|
17599
|
+
const exportReport = await tldrawController.download(options);
|
|
17489
17600
|
await tldrawController.close();
|
|
17490
17601
|
if (isLocal)
|
|
17491
17602
|
tldrawServer.close();
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { TldrawToImageOptions } from './tldraw-to-image';
|
|
2
|
-
type DownloadOptions = Omit<TldrawToImageOptions, 'frames'>;
|
|
3
2
|
export default class TldrawController {
|
|
4
3
|
private readonly href;
|
|
5
4
|
private page?;
|
|
@@ -9,18 +8,19 @@ export default class TldrawController {
|
|
|
9
8
|
private get isLocal();
|
|
10
9
|
start(): Promise<void>;
|
|
11
10
|
close(): Promise<void>;
|
|
12
|
-
download(options:
|
|
13
|
-
downloadFrame(frameNameOrId: string, options: DownloadOptions): Promise<string[]>;
|
|
14
|
-
downloadFrames(frameNamesOrIds: string[], options: DownloadOptions): Promise<string[]>;
|
|
15
|
-
downloadAllFrames(options: DownloadOptions): Promise<string[]>;
|
|
11
|
+
download(options: TldrawToImageOptions): Promise<string[]>;
|
|
16
12
|
loadFile(filePath: string): Promise<void>;
|
|
17
13
|
getShareUrl(): Promise<string>;
|
|
18
|
-
private _download;
|
|
19
14
|
private closeMenus;
|
|
20
15
|
private clickButtonTitles;
|
|
21
|
-
private
|
|
16
|
+
private validatePages;
|
|
17
|
+
private validateFrames;
|
|
18
|
+
private getDownloadPlans;
|
|
19
|
+
private getSketchStructure;
|
|
20
|
+
private getPages;
|
|
22
21
|
private getPageFrames;
|
|
22
|
+
private getCurrentPage;
|
|
23
|
+
private setCurrentPage;
|
|
23
24
|
private stripStyleElement;
|
|
24
25
|
private stripUndefined;
|
|
25
26
|
}
|
|
26
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitschpatrol/tldraw-cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A CLI tool for exporting tldraw sketch URLs and local .tldr files to SVG or PNG images.",
|
|
6
6
|
"repository": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"url": "https://ericmika.com"
|
|
19
19
|
},
|
|
20
20
|
"license": "MIT",
|
|
21
|
-
"packageManager": "pnpm@8.15.
|
|
21
|
+
"packageManager": "pnpm@8.15.6",
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=18.0.0",
|
|
24
24
|
"pnpm": ">=8.0.0"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@fontsource/inter": "^5.0.17",
|
|
48
48
|
"express": "^4.19.2",
|
|
49
|
-
"puppeteer": "^22.6.
|
|
49
|
+
"puppeteer": "^22.6.3",
|
|
50
50
|
"uint8array-extras": "^1.1.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"@sindresorhus/slugify": "^2.2.1",
|
|
55
55
|
"@tldraw/assets": "^2.0.2",
|
|
56
56
|
"@types/express": "^4.17.21",
|
|
57
|
-
"@types/react": "^18.2.
|
|
58
|
-
"@types/react-dom": "^18.2.
|
|
57
|
+
"@types/react": "^18.2.74",
|
|
58
|
+
"@types/react-dom": "^18.2.24",
|
|
59
59
|
"@types/yargs": "^17.0.32",
|
|
60
60
|
"@vitejs/plugin-react-swc": "^3.6.0",
|
|
61
61
|
"bumpp": "^9.4.0",
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
"react": "^18.2.0",
|
|
74
74
|
"react-dom": "^18.2.0",
|
|
75
75
|
"tldraw": "^2.0.2",
|
|
76
|
-
"tsx": "^4.7.
|
|
77
|
-
"typescript": "^5.4.
|
|
76
|
+
"tsx": "^4.7.2",
|
|
77
|
+
"typescript": "^5.4.4",
|
|
78
78
|
"untildify": "^5.0.0",
|
|
79
|
-
"vite": "^5.
|
|
79
|
+
"vite": "^5.2.8",
|
|
80
80
|
"vitest": "^1.4.0",
|
|
81
81
|
"yargs": "^17.7.2"
|
|
82
82
|
},
|
package/readme.md
CHANGED
|
@@ -109,21 +109,22 @@ tldraw-cli export <files-or-urls..>
|
|
|
109
109
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
|
110
110
|
| `files-or-urls` | The tldraw sketch to export. May be one or more paths to local `.tldr` files, or tldraw\.com sketch URLs. Accepts a mix of both file paths and URLs, and supports glob matching via your shell. Prints the absolute path(s) to the exported image(s) to `stdout`. _(Required.)_ | `array` |
|
|
111
111
|
|
|
112
|
-
| Option | Alias | Argument | Description
|
|
113
|
-
| --------------- | ----- | ---------- |
|
|
114
|
-
| `--format` | `-f` | | Output image format.
|
|
115
|
-
| `--output` | `-o` | | Output image directory.
|
|
116
|
-
| `--name` | `-n` | | Output image name (without extension).
|
|
117
|
-
| `--frames` | | | Export each sketch "frame" as a separate image. Pass one or more frame names or IDs to export specific frames, or
|
|
118
|
-
| `--
|
|
119
|
-
| `--
|
|
120
|
-
| `--
|
|
121
|
-
| `--
|
|
122
|
-
| `--
|
|
123
|
-
| `--
|
|
124
|
-
| `--
|
|
125
|
-
| `--
|
|
126
|
-
| `--
|
|
112
|
+
| Option | Alias | Argument | Description | Type | Default |
|
|
113
|
+
| --------------- | ----- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----------------------------------------- |
|
|
114
|
+
| `--format` | `-f` | | Output image format. | `string` | `"svg"` |
|
|
115
|
+
| `--output` | `-o` | | Output image directory. | `string` | `"./"` |
|
|
116
|
+
| `--name` | `-n` | | Output image name (without extension). | `string` | The original file name or URL id is used. |
|
|
117
|
+
| `--frames` | | | Export each sketch "frame" as a separate image. Pass one or more frame names or IDs to export specific frames, or pass the flag without the arguments to export all frames. By default, the entire first page is exported with all frames. | `array` | `false` |
|
|
118
|
+
| `--pages` | | | Export each sketch "page" as a separate image. Pass one or more page names or IDs to export specific page, or pass one or more page index numbers (from 0), or pass the flag without the arguments to export all pages. By default, only the first page is exported. | `array` | `false` |
|
|
119
|
+
| `--transparent` | `-t` | | Export an image with a transparent background. | `boolean` | `false` |
|
|
120
|
+
| `--dark` | `-d` | | Export a dark theme version of the image. | `boolean` | `false` |
|
|
121
|
+
| `--padding` | | `[number]` | Set a specific padding amount around the exported image. | | `32` |
|
|
122
|
+
| `--scale` | | `[number]` | Set a sampling factor for raster image exports. | | `1` |
|
|
123
|
+
| `--strip-style` | | | Remove `<style>` elements from SVG output, useful to lighten the load of embedded fonts if you intend to provide your own stylesheets. Applies to SVG output only. | `boolean` | `false` |
|
|
124
|
+
| `--print` | `-p` | | Print the exported image(s) to stdout instead of saving to a file. Incompatible with `--output`, and disregards `--name`. PNGs are printed as base64-encoded strings. | `boolean` | `false` |
|
|
125
|
+
| `--verbose` | | | Enable verbose logging. All verbose logs and prefixed with their log level and are printed to `stderr` for ease of redirection. | `boolean` | `false` |
|
|
126
|
+
| `--help` | `-h` | | Show help | `boolean` | |
|
|
127
|
+
| `--version` | `-v` | | Show version number | `boolean` | |
|
|
127
128
|
|
|
128
129
|
#### Subcommand: `tldraw-cli open`
|
|
129
130
|
|
|
@@ -240,6 +241,24 @@ tldraw-cli export https://www.tldraw.com/s/v2_c_FI5RYWbdpAtjsy4OIKrKw --frames "
|
|
|
240
241
|
tldraw-cli export https://www.tldraw.com/s/v2_c_FI5RYWbdpAtjsy4OIKrKw --frames "Frame 1" "Frame 3"
|
|
241
242
|
```
|
|
242
243
|
|
|
244
|
+
##### Export a specific page by name from a tldraw\.com URL
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
tldraw-cli export https://www.tldraw.com/s/v2_c_L_RFQ3mJA_BWHejdH2hlD --pages "Page 3"
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
##### Export a specific pages by index from a tldraw\.com URL
|
|
251
|
+
|
|
252
|
+
```sh
|
|
253
|
+
tldraw-cli export https://www.tldraw.com/s/v2_c_L_RFQ3mJA_BWHejdH2hlD --pages 0 2
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
##### Export each pages as its own SVG from a tldraw\.com URL
|
|
257
|
+
|
|
258
|
+
```sh
|
|
259
|
+
tldraw-cli export https://www.tldraw.com/s/v2_c_L_RFQ3mJA_BWHejdH2hlD --pages
|
|
260
|
+
```
|
|
261
|
+
|
|
243
262
|
##### Export to JSON
|
|
244
263
|
|
|
245
264
|
```sh
|
|
@@ -280,6 +299,7 @@ The library exports a single async function, `tldrawToImage`, which takes an opt
|
|
|
280
299
|
name?: string
|
|
281
300
|
output?: string
|
|
282
301
|
padding?: number
|
|
302
|
+
pages?: boolean | string[] | number[]
|
|
283
303
|
print?: boolean
|
|
284
304
|
scale?: number
|
|
285
305
|
stripStyle?: boolean
|
|
@@ -362,9 +382,9 @@ On Discord:
|
|
|
362
382
|
|
|
363
383
|
## Implementation notes
|
|
364
384
|
|
|
365
|
-
This tool is not a part of the official tldraw project
|
|
385
|
+
This tool is not a part of the official tldraw project.
|
|
366
386
|
|
|
367
|
-
Due to the
|
|
387
|
+
Due to the architecture of tldraw, export depends on functionality provided by a web browser. So, behind the scenes, this app serves a local instance of tldraw, then loads a `.tldr` and invokes the export download via the [Puppeteer](https://pptr.dev) headless browser automation tool.
|
|
368
388
|
|
|
369
389
|
This can be a bit slow, (exporting seems to take a second or two), but in the context of a statically-generated content pipeline it's not the end of the world.
|
|
370
390
|
|