sketch-constructor
Version:
Read/write/manipulate Sketch files in Node without Sketch plugins!
207 lines (179 loc) • 6.29 kB
JavaScript
/*
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/
const fs = require('fs-extra');
const JsonStreamStringify = require('json-stream-stringify');
const path = require('path');
const JSZip = require('jszip');
const Meta = require('../Meta');
const User = require('../User');
const Document = require('../Document');
const Page = require('../Page');
const Artboard = require('../Artboard');
const SharedStyle = require('../SharedStyle');
const { STORAGE_DIR, STORAGE_IMG_DIR, STORAGE_PREVIEW_DIR, STORAGE_PREVIEW_FILE } = require('../../utils/paths');
class Sketch {
static fromFile(filePathOrBuffer) {
const sketch = new Sketch();
const isBuffer = Buffer.isBuffer(filePathOrBuffer);
const fileEntry = isBuffer ? filePathOrBuffer : fs.readFileSync(filePathOrBuffer);
return JSZip.loadAsync(fileEntry).then((zip) =>
Promise.all([
zip.file('document.json').async('string'),
zip.file('meta.json').async('string'),
zip.file('user.json').async('string'),
])
.then(([document, meta, user]) => {
sketch.document = new Document(null, JSON.parse(document));
sketch.meta = new Meta(null, JSON.parse(meta));
sketch.user = new User(null, JSON.parse(user));
return Promise.all(
Object.keys(sketch.meta.pagesAndArtboards).map((pageID) => zip.file(`pages/${pageID}.json`).async('string'))
);
})
.then((args) => {
sketch.pages = args.map((str) => new Page(null, JSON.parse(str)));
return sketch;
})
);
}
static fromExtractedFile(filePath) {
const sketch = new Sketch();
return Promise.all([
fs.readJSON(path.resolve(filePath, 'document.json'), { encoding: 'utf8' }),
fs.readJSON(path.resolve(filePath, 'meta.json'), { encoding: 'utf8' }),
fs.readJSON(path.resolve(filePath, 'user.json'), { encoding: 'utf8' }),
])
.then(([document, meta, user]) => {
sketch.document = new Document(null, document);
sketch.meta = new Meta(null, meta);
sketch.user = new User(null, user);
return Promise.all(
Object.keys(sketch.meta.pagesAndArtboards).map((pageID) =>
fs.readJSON(path.resolve(filePath, `pages/${pageID}.json`), { encoding: 'utf8' })
)
);
})
.then((args) => {
sketch.pages = args.map((str) => new Page(null, str));
return sketch;
});
}
static addPreview(preview) {
if (!fs.existsSync(preview)) {
console.warn('Error occurred while adding preview image, please check the file path');
return;
}
fs.ensureDirSync(STORAGE_PREVIEW_DIR);
fs.copyFileSync(preview, STORAGE_PREVIEW_FILE);
}
constructor(args = {}) {
Object.assign(this, {
meta: new Meta(args.meta),
document: new Document(args.document),
user: new User(args.user),
pages: [],
zip: new JSZip(),
});
return this;
}
getPages(predicate) {
if (predicate) {
return this.pages.filter(predicate);
}
return this.pages;
}
getPage(name) {
return this.pages.find((page) => page.name === name);
}
getLayerStyles() {
return this.document.getLayerStyles();
}
getLayerStyle(name) {
return this.document.getLayerStyle(name);
}
addLayerStyle(style) {
if (!(style instanceof SharedStyle)) {
style = SharedStyle.LayerStyle(style);
}
this.document.addLayerStyle(style);
}
addTextStyle(style) {
if (!(style instanceof SharedStyle)) {
style = SharedStyle.TextStyle(style);
}
this.document.addTextStyle(style);
}
getTextStyles() {
return this.document.getTextStyles();
}
addSwatch(swatch) {
this.document.addSwatch(swatch);
}
getSwatches() {
return this.document.getSwatches();
}
addPage(page, args) {
if (!(page instanceof Page)) {
page = new Page(page);
}
this.document.addPage(page.getID());
this.meta.addPage(page);
this.user.addPage(page.getID(), args);
this.pages = this.pages.concat(page);
page.getArtboards().forEach((artboard) => {
this.meta.addArtboard(page.getID(), artboard);
});
}
addArtboard(pageID, artboard) {
if (!(artboard instanceof Artboard)) {
artboard = new Artboard(artboard);
}
const page = this.pages.find((p) => p.getID() === pageID);
page.addArtboard(artboard);
this.meta.addArtboard(pageID, artboard);
}
build(output, compressionLevel = 0) {
this.zip
.file('meta.json', new JsonStreamStringify(this.meta))
.file('user.json', new JsonStreamStringify(this.user))
.file('document.json', new JsonStreamStringify(this.document));
this.zip.folder('pages');
this.zip.folder('previews');
if (fs.existsSync(STORAGE_PREVIEW_FILE))
this.zip.folder('previews').file('preview.png', fs.createReadStream(STORAGE_PREVIEW_FILE));
if (fs.existsSync(STORAGE_IMG_DIR)) {
fs.readdirSync(STORAGE_IMG_DIR).forEach((file) => {
this.zip.folder('images').file(file, fs.createReadStream(`${STORAGE_IMG_DIR}/${file}`));
});
}
this.pages.forEach((page) => {
this.zip.file(`pages/${page.do_objectID}.json`, new JsonStreamStringify(page));
});
return new Promise((resolve, reject) =>
this.zip
.generateNodeStream({
type: 'nodebuffer',
streamFiles: true,
compression: compressionLevel === 0 ? 'STORE' : 'DEFLATE',
compressionOptions: { level: compressionLevel },
})
.pipe(fs.createWriteStream(output))
.on('error', reject)
.on('finish', () => {
fs.removeSync(STORAGE_DIR);
resolve(output);
})
);
}
}
module.exports = Sketch;