UNPKG

sketch-constructor

Version:

Read/write/manipulate Sketch files in Node without Sketch plugins!

207 lines (179 loc) 6.29 kB
/* * 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;