From a69b85061805f8526c80a5997455d87c1cbdfc49 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Mon, 20 Jan 2020 10:18:38 +0100 Subject: [PATCH 01/46] Transpile to ES2017 to avoid regenerator-runtime for now --- tsconfig.module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.module.json b/tsconfig.module.json index 6159976f..795ba395 100644 --- a/tsconfig.module.json +++ b/tsconfig.module.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig", "compilerOptions": { - "target": "es5", + "target": "es2017", "outDir": "lib/module", "module": "esnext" }, From 042fdaefd5966d33a2880a48f746b38567d39a84 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Mon, 20 Jan 2020 10:21:32 +0100 Subject: [PATCH 02/46] Use weakRequire instead of ad-hoc evals --- src/jsonUtils/nodeImpl/createStringMeasurer.ts | 7 ++++--- src/jsonUtils/nodeImpl/findFontName.ts | 6 ++++-- src/jsonUtils/nodeImpl/makeImageDataFromUrl.ts | 5 ----- src/jsonUtils/nodeImpl/makeSvgLayer.ts | 4 ++-- src/jsonUtils/nodeImpl/requireObjCBridge.ts | 11 ----------- src/jsonUtils/nodeImpl/requireSvgModel.ts | 4 ---- src/utils/weakRequire.ts | 9 +++++++++ 7 files changed, 19 insertions(+), 27 deletions(-) delete mode 100644 src/jsonUtils/nodeImpl/makeImageDataFromUrl.ts delete mode 100644 src/jsonUtils/nodeImpl/requireObjCBridge.ts delete mode 100644 src/jsonUtils/nodeImpl/requireSvgModel.ts create mode 100644 src/utils/weakRequire.ts diff --git a/src/jsonUtils/nodeImpl/createStringMeasurer.ts b/src/jsonUtils/nodeImpl/createStringMeasurer.ts index 9b8037a0..b6eb9407 100644 --- a/src/jsonUtils/nodeImpl/createStringMeasurer.ts +++ b/src/jsonUtils/nodeImpl/createStringMeasurer.ts @@ -1,7 +1,8 @@ -import requireObjCBridge from './requireObjCBridge'; - +import weakRequire from '../../utils/weakRequire'; import { TextNode, Size } from '../../types'; +const bridge = weakRequire(module, 'node-sketch-bridge'); + export default function createStringMeasurer(textNodes: TextNode[], width: number): Size { - return requireObjCBridge().createStringMeasurer(textNodes, width); + return bridge.createStringMeasurer(textNodes, width); } diff --git a/src/jsonUtils/nodeImpl/findFontName.ts b/src/jsonUtils/nodeImpl/findFontName.ts index f68952b5..e6a811f5 100644 --- a/src/jsonUtils/nodeImpl/findFontName.ts +++ b/src/jsonUtils/nodeImpl/findFontName.ts @@ -1,6 +1,8 @@ -import requireObjCBridge from './requireObjCBridge'; import { TextStyle } from '../../types'; +import weakRequire from '../../utils/weakRequire'; + +const bridge = weakRequire(module, 'node-sketch-bridge'); export default function findFontName(style: TextStyle): string { - return requireObjCBridge().findFontName(style); + return bridge.findFontName(style); } diff --git a/src/jsonUtils/nodeImpl/makeImageDataFromUrl.ts b/src/jsonUtils/nodeImpl/makeImageDataFromUrl.ts deleted file mode 100644 index 7cfeb6ea..00000000 --- a/src/jsonUtils/nodeImpl/makeImageDataFromUrl.ts +++ /dev/null @@ -1,5 +0,0 @@ -import requireObjCBridge from './requireObjCBridge'; - -export default function makeImageDataFromUrl(url?: string): string { - return requireObjCBridge().makeImageDataFromUrl(url); -} diff --git a/src/jsonUtils/nodeImpl/makeSvgLayer.ts b/src/jsonUtils/nodeImpl/makeSvgLayer.ts index c9a30bf4..38d160b0 100644 --- a/src/jsonUtils/nodeImpl/makeSvgLayer.ts +++ b/src/jsonUtils/nodeImpl/makeSvgLayer.ts @@ -6,7 +6,7 @@ import { createUniformBorder } from '../borders'; import layerGroup from '../layerGroup'; import { makePathsFromCommands, makeLineCapStyle } from './graphics/path'; import { unionRects, scaleRect, makeBoundingRectFromCommands, resize } from './graphics/rect'; -import requireSvgModel from './requireSvgModel'; +import weakRequire from '../../utils/weakRequire'; function makeLayerFromPathElement(pathElement, _parentFrame: FileFormat.Rect, scale: number) { const { @@ -79,7 +79,7 @@ function makeLayerGroup( } export default function makeSvgLayer(layout: LayoutInfo, name: string, svg: string) { - const svgModel = requireSvgModel(); + const svgModel = weakRequire(module, '@lona/svg-model'); const { data: { params, children }, diff --git a/src/jsonUtils/nodeImpl/requireObjCBridge.ts b/src/jsonUtils/nodeImpl/requireObjCBridge.ts deleted file mode 100644 index 74aaaea9..00000000 --- a/src/jsonUtils/nodeImpl/requireObjCBridge.ts +++ /dev/null @@ -1,11 +0,0 @@ -// This is the ugliest but it's kind of the only way to avoid bundling -// this module when using skpm (the other solution would be to add an `ignore` option -// in every client webpack config...) -let cached$: any; // cache nodobjc instance -export default function requireObjCBridge() { - if (cached$) { - return cached$; - } - cached$ = eval("require('node-sketch-bridge')"); // eslint-disable-line - return cached$; -} diff --git a/src/jsonUtils/nodeImpl/requireSvgModel.ts b/src/jsonUtils/nodeImpl/requireSvgModel.ts deleted file mode 100644 index c585b5ea..00000000 --- a/src/jsonUtils/nodeImpl/requireSvgModel.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Hack to avoid bundling the node implementation/dependencies unless needed -export default function requireSvgModel() { - return eval("require('@lona/svg-model')"); // eslint-disable-line -} diff --git a/src/utils/weakRequire.ts b/src/utils/weakRequire.ts new file mode 100644 index 00000000..6d0b20dd --- /dev/null +++ b/src/utils/weakRequire.ts @@ -0,0 +1,9 @@ +/** + * Requires a module without having webpack including it in the bundle. + * + * @param sourceModule reference to the `module` instance from where this request originated. + * @param request name of the module to require. + */ +export default function weakRequire(sourceModule: NodeModule, request: string): any { + return 'require' in sourceModule ? sourceModule.require(request) : {}; +} From 22b7f60676a6deb7ca53457cecf5d6e296f3b176 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Mon, 20 Jan 2020 10:40:37 +0100 Subject: [PATCH 03/46] Make everything async --- src/flexToSketchJSON.ts | 15 ++++--- .../sketchImpl/makeImageDataFromUrl.ts | 43 ------------------- src/render.tsx | 32 +++++++++----- src/renderers/ArtboardRenderer.ts | 2 +- src/renderers/ImageRenderer.ts | 4 +- src/renderers/SketchRenderer.ts | 41 ++++++++++-------- src/renderers/SvgRenderer.ts | 4 +- src/renderers/SymbolInstanceRenderer.ts | 18 ++++---- src/renderers/SymbolMasterRenderer.ts | 2 +- src/renderers/TextRenderer.ts | 2 +- src/renderers/ViewRenderer.ts | 34 ++++++++------- src/symbol.tsx | 6 +-- src/utils/getImageDataFromURL.ts | 16 +++---- 13 files changed, 98 insertions(+), 121 deletions(-) delete mode 100644 src/jsonUtils/sketchImpl/makeImageDataFromUrl.ts diff --git a/src/flexToSketchJSON.ts b/src/flexToSketchJSON.ts index 2c31bd6f..cdf74c86 100644 --- a/src/flexToSketchJSON.ts +++ b/src/flexToSketchJSON.ts @@ -9,14 +9,15 @@ function missingRendererError(type: string, annotations?: string) { ); } -const flexToSketchJSON = ( +const flexToSketchJSON = async ( node: TreeNode | string, -): +): Promise< | FileFormat.SymbolMaster | FileFormat.Artboard | FileFormat.Group | FileFormat.ShapeGroup - | FileFormat.SymbolInstance => { + | FileFormat.SymbolInstance +> => { if (typeof node === 'string') { throw missingRendererError('string'); } @@ -45,17 +46,19 @@ const flexToSketchJSON = ( } const renderer = new Renderer(); - const groupLayer = renderer.renderGroupLayer(node); + const groupLayer = await renderer.renderGroupLayer(node); if (groupLayer._class === 'symbolInstance') { return groupLayer; } - const backingLayers = renderer.renderBackingLayers(node); + const backingLayers = await renderer.renderBackingLayers(node); // stopping the walk down the tree if we have an svg const sublayers = - children && type !== 'sketch_svg' ? children.map(child => flexToSketchJSON(child)) : []; + children && type !== 'sketch_svg' + ? await Promise.all(children.map(child => flexToSketchJSON(child))) + : []; // Filter out anything null, undefined const layers = [...backingLayers, ...sublayers].filter(l => l); diff --git a/src/jsonUtils/sketchImpl/makeImageDataFromUrl.ts b/src/jsonUtils/sketchImpl/makeImageDataFromUrl.ts deleted file mode 100644 index fb360f38..00000000 --- a/src/jsonUtils/sketchImpl/makeImageDataFromUrl.ts +++ /dev/null @@ -1,43 +0,0 @@ -export default function makeImageDataFromUrl(url?: string) { - let fetchedData = url ? NSData.dataWithContentsOfURL(NSURL.URLWithString(url)) : undefined; - - if (fetchedData) { - const firstByte = String( - NSString.alloc().initWithData_encoding(fetchedData, NSISOLatin1StringEncoding), - ).charCodeAt(0); - - // Check for first byte to see if we have an image. - // 0xFF = JPEG, 0x89 = PNG, 0x47 = GIF, 0x49 = TIFF, 0x4D = TIFF - if ( - firstByte !== 0xff && - firstByte !== 0x89 && - firstByte !== 0x47 && - firstByte !== 0x49 && - firstByte !== 0x4d - ) { - fetchedData = null; - } - } - - let image: any; - - if (!fetchedData) { - const errorUrl = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8w8DwHwAEOQHNmnaaOAAAAABJRU5ErkJggg=='; - image = NSImage.alloc().initWithContentsOfURL(NSURL.URLWithString(errorUrl)); - } else { - image = NSImage.alloc().initWithData(fetchedData); - } - - let imageData: any; - - if (MSImageData.alloc().initWithImage_convertColorSpace !== undefined) { - imageData = MSImageData.alloc().initWithImage_convertColorSpace(image, false); - } else { - imageData = MSImageData.alloc().initWithImage(image); - } - - return String( - imageData.data().base64EncodedStringWithOptions(NSDataBase64EncodingEndLineWithCarriageReturn), - ); -} diff --git a/src/render.tsx b/src/render.tsx index b40ce492..29e265e4 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { fromSJSON } from './jsonUtils/sketchImpl/json-to-sketch'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; +import { fromSJSON } from './jsonUtils/sketchImpl/json-to-sketch'; import buildTree from './buildTree'; import flexToSketchJSON from './flexToSketchJSON'; import { resetLayer, resetDocument } from './resets'; @@ -13,7 +13,7 @@ import isNativePage from './utils/isNativePage'; import isNativeSymbolsPage from './utils/isNativeSymbolsPage'; import { getSketchVersion } from './utils/getSketchVersion'; -export const renderToJSON = (element: React.ReactElement): FileFormat.AnyLayer => { +export const renderToJSON = async (element: React.ReactElement): Promise => { const tree = buildTree(element); return flexToSketchJSON(tree); }; @@ -36,14 +36,17 @@ const getDefaultPage = (): SketchLayer => { return isNativeSymbolsPage(currentPage) ? doc.addBlankPage() : currentPage; }; -const renderContents = (tree: TreeNode | string, container: SketchLayer): SketchLayer => { - const json = flexToSketchJSON(tree); +const renderContents = async ( + tree: TreeNode | string, + container: SketchLayer, +): Promise => { + const json = await flexToSketchJSON(tree); const layer = fromSJSON(json, '119'); return renderLayers([layer], container); }; -const renderPage = (tree: TreeNode, page: SketchPage): Array => { +const renderPage = async (tree: TreeNode, page: SketchPage): Promise> => { const children = tree.children || []; // assume if name is set on this nested page, the intent is to overwrite @@ -52,10 +55,13 @@ const renderPage = (tree: TreeNode, page: SketchPage): Array => { page.setName(tree.props.name); } - return children.map(child => renderContents(child, page)); + return Promise.all(children.map(child => renderContents(child, page))); }; -const renderDocument = (tree: TreeNode, documentData: SketchDocumentData): Array => { +const renderDocument = async ( + tree: TreeNode, + documentData: SketchDocumentData, +): Promise> => { if (!isNativeDocument(documentData)) { throw new Error('Cannot render a Document into a child of Document'); } @@ -74,7 +80,10 @@ const renderDocument = (tree: TreeNode, documentData: SketchDocumentData): Array }); }; -const renderTree = (tree: TreeNode, _container?: SketchLayer): SketchLayer | Array => { +const renderTree = async ( + tree: TreeNode, + _container?: SketchLayer, +): Promise> => { if (isNativeDocument(_container) && tree.type !== 'sketch_document') { throw new Error('You need to render a Document into Document'); } @@ -98,10 +107,10 @@ const renderTree = (tree: TreeNode, _container?: SketchLayer): SketchLayer | Arr : renderContents(tree, container); }; -export const render = ( +export const render = async ( element: React.ReactElement, container?: SketchLayer | WrappedSketchLayer, -): SketchLayer | Array => { +): Promise> => { if (getSketchVersion() === 'NodeJS') { return renderToJSON(element); } @@ -124,7 +133,8 @@ export const render = ( injectSymbols(getDocumentDataFromContainer(nativeContainer)); - return renderTree(tree, nativeContainer); + const layer = await renderTree(tree, nativeContainer); + return layer; } catch (err) { // eslint-disable-next-line no-console console.error(err); diff --git a/src/renderers/ArtboardRenderer.ts b/src/renderers/ArtboardRenderer.ts index 0455c4d7..76328201 100644 --- a/src/renderers/ArtboardRenderer.ts +++ b/src/renderers/ArtboardRenderer.ts @@ -6,7 +6,7 @@ import { TreeNode } from '../types'; import { Props } from '../components/Artboard'; export default class ArtboardRenderer extends SketchRenderer { - renderGroupLayer({ layout, style, props }: TreeNode): FileFormat.Artboard { + async renderGroupLayer({ layout, style, props }: TreeNode): Promise { let color: FileFormat.Color; if (style.backgroundColor !== undefined) { color = makeColorFromCSS(style.backgroundColor); diff --git a/src/renderers/ImageRenderer.ts b/src/renderers/ImageRenderer.ts index b4194f5f..ce68e10e 100644 --- a/src/renderers/ImageRenderer.ts +++ b/src/renderers/ImageRenderer.ts @@ -16,7 +16,7 @@ function extractURLFromSource(source?: string | { uri?: string }): string | unde } export default class ImageRenderer extends SketchRenderer { - renderBackingLayers({ layout, style, props }: TreeNode) { + async renderBackingLayers({ layout, style, props }: TreeNode) { let layers: FileFormat.ShapeGroup[] = []; const { @@ -28,7 +28,7 @@ export default class ImageRenderer extends SketchRenderer { const url = extractURLFromSource(props.source); - const image = getImageDataFromURL(url); + const image = await getImageDataFromURL(url); const fillImage = makeJSONDataReference(image); diff --git a/src/renderers/SketchRenderer.ts b/src/renderers/SketchRenderer.ts index 5f1ef373..0691a706 100644 --- a/src/renderers/SketchRenderer.ts +++ b/src/renderers/SketchRenderer.ts @@ -11,16 +11,17 @@ export default class SketchRenderer { return 'Group'; } - renderGroupLayer({ + async renderGroupLayer({ layout, style, props, - }: TreeNode): + }: TreeNode): Promise< | FileFormat.SymbolMaster | FileFormat.Artboard | FileFormat.Group | FileFormat.ShapeGroup - | FileFormat.SymbolInstance { + | FileFormat.SymbolInstance + > { // Default SketchRenderer just renders an empty group const transform = processTransform(layout, style); @@ -42,23 +43,25 @@ export default class SketchRenderer { }; } - renderBackingLayers( + async renderBackingLayers( _node: TreeNode, - ): ( - | FileFormat.ShapePath - | FileFormat.Rectangle - | FileFormat.SymbolMaster - | FileFormat.Group - | FileFormat.Polygon - | FileFormat.Star - | FileFormat.Triangle - | FileFormat.ShapeGroup - | FileFormat.Text - | FileFormat.SymbolInstance - | FileFormat.Slice - | FileFormat.Hotspot - | FileFormat.Bitmap - )[] { + ): Promise< + ( + | FileFormat.ShapePath + | FileFormat.Rectangle + | FileFormat.SymbolMaster + | FileFormat.Group + | FileFormat.Polygon + | FileFormat.Star + | FileFormat.Triangle + | FileFormat.ShapeGroup + | FileFormat.Text + | FileFormat.SymbolInstance + | FileFormat.Slice + | FileFormat.Hotspot + | FileFormat.Bitmap + )[] + > { return []; } } diff --git a/src/renderers/SvgRenderer.ts b/src/renderers/SvgRenderer.ts index 4e6366f1..b91d9d5d 100644 --- a/src/renderers/SvgRenderer.ts +++ b/src/renderers/SvgRenderer.ts @@ -65,8 +65,8 @@ export default class SvgRenderer extends ViewRenderer { return props.name || 'Svg'; } - renderBackingLayers(node: TreeNode) { - const layers = super.renderBackingLayers(node); + async renderBackingLayers(node: TreeNode) { + const layers = await super.renderBackingLayers(node); const { layout, props, children } = node; diff --git a/src/renderers/SymbolInstanceRenderer.ts b/src/renderers/SymbolInstanceRenderer.ts index ca1d6908..fed11d32 100644 --- a/src/renderers/SymbolInstanceRenderer.ts +++ b/src/renderers/SymbolInstanceRenderer.ts @@ -98,7 +98,7 @@ const extractOverrides = (layers: FileFormat.AnyLayer[] = [], path?: string): Ov }; export default class SymbolInstanceRenderer extends SketchRenderer { - renderGroupLayer({ layout, props }: TreeNode) { + async renderGroupLayer({ layout, props }: TreeNode) { const masterTree = getSymbolMasterById(props.symbolID); const symbolInstance = makeSymbolInstance( @@ -114,8 +114,8 @@ export default class SymbolInstanceRenderer extends SketchRenderer { const overridableLayers = extractOverrides(masterTree.layers); - const overrides = overridableLayers.reduce(function inject( - memo: FileFormat.OverrideValue[], + const overrides = await overridableLayers.reduce(async function inject( + memo: Promise, reference: Override, ) { if (reference.type === 'symbolID') { @@ -141,7 +141,9 @@ export default class SymbolInstanceRenderer extends SketchRenderer { ); } - memo.push(makeOverride(reference.path, reference.type, replacementMaster.symbolID)); + (await memo).push( + makeOverride(reference.path, reference.type, replacementMaster.symbolID), + ); extractOverrides(replacementMaster.layers, newPath).reduce(inject, memo); @@ -165,7 +167,7 @@ export default class SymbolInstanceRenderer extends SketchRenderer { `The override value of a Text must be a string.\n\nIn Symbol Instance: "${props.name}"\nFor Override: "${reference.name}"`, ); } - memo.push(makeOverride(reference.path, reference.type, overrideValue)); + (await memo).push(makeOverride(reference.path, reference.type, overrideValue)); } if (reference.type === 'image') { @@ -174,18 +176,18 @@ export default class SymbolInstanceRenderer extends SketchRenderer { `The override value of an Image must be a url.\n\nIn Symbol Instance: "${props.name}"\nFor Override: "${reference.name}"`, ); } - memo.push( + (await memo).push( makeOverride( reference.path, reference.type, - makeJSONDataReference(getImageDataFromURL(overrideValue)), + makeJSONDataReference(await getImageDataFromURL(overrideValue)), ), ); } return memo; }, - []); + Promise.resolve([])); symbolInstance.overrideValues = overrides; diff --git a/src/renderers/SymbolMasterRenderer.ts b/src/renderers/SymbolMasterRenderer.ts index c71f7e6a..586329da 100644 --- a/src/renderers/SymbolMasterRenderer.ts +++ b/src/renderers/SymbolMasterRenderer.ts @@ -4,7 +4,7 @@ import { TreeNode } from '../types'; import { SymbolMasterProps } from '../symbol'; export default class SymbolMasterRenderer extends SketchRenderer { - renderGroupLayer({ layout, props }: TreeNode) { + async renderGroupLayer({ layout, props }: TreeNode) { return makeSymbolMaster( makeRect(layout.left, layout.top, layout.width, layout.height), props.symbolID, diff --git a/src/renderers/TextRenderer.ts b/src/renderers/TextRenderer.ts index 3f0cd222..e8e282da 100644 --- a/src/renderers/TextRenderer.ts +++ b/src/renderers/TextRenderer.ts @@ -10,7 +10,7 @@ export default class TextRenderer extends SketchRenderer { return props.name || 'Text'; } - renderBackingLayers({ layout, style, textStyle, props }: TreeNode) { + async renderBackingLayers({ layout, style, textStyle, props }: TreeNode) { let { name } = props; // Append all text nodes's content into one string diff --git a/src/renderers/ViewRenderer.ts b/src/renderers/ViewRenderer.ts index e7ea6183..b901d51c 100644 --- a/src/renderers/ViewRenderer.ts +++ b/src/renderers/ViewRenderer.ts @@ -38,25 +38,27 @@ export default class ViewRenderer extends SketchRenderer { return 'View'; } - renderBackingLayers({ + async renderBackingLayers({ layout, style, props, - }: TreeNode): ( - | FileFormat.ShapePath - | FileFormat.Rectangle - | FileFormat.SymbolMaster - | FileFormat.Group - | FileFormat.Polygon - | FileFormat.Star - | FileFormat.Triangle - | FileFormat.ShapeGroup - | FileFormat.Text - | FileFormat.SymbolInstance - | FileFormat.Slice - | FileFormat.Hotspot - | FileFormat.Bitmap - )[] { + }: TreeNode): Promise< + ( + | FileFormat.ShapePath + | FileFormat.Rectangle + | FileFormat.SymbolMaster + | FileFormat.Group + | FileFormat.Polygon + | FileFormat.Star + | FileFormat.Triangle + | FileFormat.ShapeGroup + | FileFormat.Text + | FileFormat.SymbolInstance + | FileFormat.Slice + | FileFormat.Hotspot + | FileFormat.Bitmap + )[] + > { let layers: FileFormat.ShapeGroup[] = []; // NOTE(lmr): the group handles the position, so we just care about width/height here const { diff --git a/src/symbol.tsx b/src/symbol.tsx index f7bc65a6..66c94809 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -150,7 +150,7 @@ const SymbolMasterPropTypes = { export type SymbolMasterProps = PropTypes.InferProps; -export const makeSymbol = ( +export const makeSymbol = async ( Component: React.ComponentType, symbolProps: string | SymbolMasterProps, document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, @@ -167,7 +167,7 @@ export const makeSymbol = ( ? existingSymbol.symbolID : generateID(`symbolID:${masterName}`, !!masterName); - const symbolMaster = flexToSketchJSON( + const symbolMaster = (await flexToSketchJSON( buildTree( , ), - ) as FileFormat.SymbolMaster; + )) as FileFormat.SymbolMaster; symbolsRegistry[symbolID] = symbolMaster; return createSymbolInstanceClass(symbolMaster); diff --git a/src/utils/getImageDataFromURL.ts b/src/utils/getImageDataFromURL.ts index e7b19312..793aeae9 100644 --- a/src/utils/getImageDataFromURL.ts +++ b/src/utils/getImageDataFromURL.ts @@ -1,15 +1,15 @@ import sha1 from 'js-sha1'; -import { getSketchVersion } from './getSketchVersion'; -import sketchMethod from '../jsonUtils/sketchImpl/makeImageDataFromUrl'; -import nodeMethod from '../jsonUtils/nodeImpl/makeImageDataFromUrl'; -const makeImageDataFromUrl = (url?: string): { data: string; sha1: string } => { - const data = getSketchVersion() === 'NodeJS' ? nodeMethod(url) : sketchMethod(url); +export default async function getImageDataFromURL( + url?: string, +): Promise<{ data: string; sha1: string }> { + // FIXME: Mock + console.log(url); + const data = + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8w8DwHwAEOQHNmnaaOAAAAABJRU5ErkJggg=='; return { data, sha1: sha1(data), }; -}; - -export default makeImageDataFromUrl; +} From 2968a647151859735e3f55cffe8bcf9e6f1491fc Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Mon, 20 Jan 2020 12:29:03 +0100 Subject: [PATCH 04/46] Add getImageDataFromURL() implementation using fetch --- src/utils/getImageDataFromURL.ts | 54 +++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/src/utils/getImageDataFromURL.ts b/src/utils/getImageDataFromURL.ts index 793aeae9..f6769fdc 100644 --- a/src/utils/getImageDataFromURL.ts +++ b/src/utils/getImageDataFromURL.ts @@ -1,12 +1,56 @@ +/* global fetch */ +// TODO: Read data:// URLs +// TODO: Load node polyfill +// TODO: Read from FS import sha1 from 'js-sha1'; +const ERROR_IMAGE = + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8w8DwHwAEOQHNmnaaOAAAAABJRU5ErkJggg=='; + +const ERROR_RESULT = { + data: ERROR_IMAGE, + sha1: sha1(ERROR_IMAGE), +}; + +const readBufferFromResponse = async (response: Response): Promise => { + const arrayBuffer = await response.arrayBuffer(); + if (Buffer.isBuffer(arrayBuffer)) { + // skpm polyfill returns a Buffer instead of an ArrayBuffer + return arrayBuffer; + } + return Buffer.from(arrayBuffer); +}; + +const isImage = (buffer: Buffer): boolean => { + const firstByte = buffer[0]; + + // Check for first byte to see if we have an image. + // 0xFF = JPEG, 0x89 = PNG, 0x47 = GIF, 0x49 = TIFF, 0x4D = TIFF + return ( + firstByte === 0xff || + firstByte === 0x89 || + firstByte === 0x47 || + firstByte === 0x49 || + firstByte === 0x4d + ); +}; + export default async function getImageDataFromURL( url?: string, -): Promise<{ data: string; sha1: string }> { - // FIXME: Mock - console.log(url); - const data = - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8w8DwHwAEOQHNmnaaOAAAAABJRU5ErkJggg=='; +): Promise> { + if (!url) { + return ERROR_RESULT; + } + + const response = await fetch(url); + if (!response.ok) { + return ERROR_RESULT; + } + + const buffer = await readBufferFromResponse(response); + if (!isImage(buffer)) return ERROR_RESULT; + + const data = buffer.toString('base64'); return { data, From 1ad2d1723285b608972958ec383c47b5bd546b47 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Tue, 21 Jan 2020 14:00:15 +0100 Subject: [PATCH 05/46] Split the bridges in their own objects --- .../nodeImpl/createStringMeasurer.ts | 8 -------- src/jsonUtils/nodeImpl/findFontName.ts | 8 -------- src/platformBridges/NodeMacOSBridge.ts | 19 +++++++++++++++++++ .../SketchBridge}/createStringMeasurer.ts | 4 ++-- .../SketchBridge}/findFontName.ts | 2 +- src/platformBridges/SketchBridge/index.ts | 13 +++++++++++++ src/platformBridges/SketchBridge/readFile.ts | 5 +++++ src/types/index.ts | 7 +++++++ 8 files changed, 47 insertions(+), 19 deletions(-) delete mode 100644 src/jsonUtils/nodeImpl/createStringMeasurer.ts delete mode 100644 src/jsonUtils/nodeImpl/findFontName.ts create mode 100644 src/platformBridges/NodeMacOSBridge.ts rename src/{jsonUtils/sketchImpl => platformBridges/SketchBridge}/createStringMeasurer.ts (96%) rename src/{jsonUtils/sketchImpl => platformBridges/SketchBridge}/findFontName.ts (98%) create mode 100644 src/platformBridges/SketchBridge/index.ts create mode 100644 src/platformBridges/SketchBridge/readFile.ts diff --git a/src/jsonUtils/nodeImpl/createStringMeasurer.ts b/src/jsonUtils/nodeImpl/createStringMeasurer.ts deleted file mode 100644 index b6eb9407..00000000 --- a/src/jsonUtils/nodeImpl/createStringMeasurer.ts +++ /dev/null @@ -1,8 +0,0 @@ -import weakRequire from '../../utils/weakRequire'; -import { TextNode, Size } from '../../types'; - -const bridge = weakRequire(module, 'node-sketch-bridge'); - -export default function createStringMeasurer(textNodes: TextNode[], width: number): Size { - return bridge.createStringMeasurer(textNodes, width); -} diff --git a/src/jsonUtils/nodeImpl/findFontName.ts b/src/jsonUtils/nodeImpl/findFontName.ts deleted file mode 100644 index e6a811f5..00000000 --- a/src/jsonUtils/nodeImpl/findFontName.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TextStyle } from '../../types'; -import weakRequire from '../../utils/weakRequire'; - -const bridge = weakRequire(module, 'node-sketch-bridge'); - -export default function findFontName(style: TextStyle): string { - return bridge.findFontName(style); -} diff --git a/src/platformBridges/NodeMacOSBridge.ts b/src/platformBridges/NodeMacOSBridge.ts new file mode 100644 index 00000000..1d84d5a5 --- /dev/null +++ b/src/platformBridges/NodeMacOSBridge.ts @@ -0,0 +1,19 @@ +import { PlatformBridge } from '../types'; +import weakRequire from '../utils/weakRequire'; + +const { createStringMeasurer, findFontName } = weakRequire(module, 'node-sketch-bridge'); +const fetch = weakRequire(module, 'node-fetch'); +const { readFile: nodeReadFile } = weakRequire(module, 'fs'); + +const NodeMacOSBridge: PlatformBridge = { + createStringMeasurer, + findFontName, + fetch, + async readFile(path: string): Promise { + return new Promise((resolve, reject) => { + nodeReadFile(path, (err, data) => (err ? reject(err) : resolve(data))); + }); + }, +}; + +export default NodeMacOSBridge; diff --git a/src/jsonUtils/sketchImpl/createStringMeasurer.ts b/src/platformBridges/SketchBridge/createStringMeasurer.ts similarity index 96% rename from src/jsonUtils/sketchImpl/createStringMeasurer.ts rename to src/platformBridges/SketchBridge/createStringMeasurer.ts index d67800ad..f3aacfaf 100644 --- a/src/jsonUtils/sketchImpl/createStringMeasurer.ts +++ b/src/platformBridges/SketchBridge/createStringMeasurer.ts @@ -4,9 +4,9 @@ import { TEXT_DECORATION_LINETHROUGH, TEXT_ALIGN, TEXT_TRANSFORM, -} from '../textLayers'; +} from '../../jsonUtils/textLayers'; import { findFont } from './findFontName'; -import { makeColorFromCSS } from '../models'; +import { makeColorFromCSS } from '../../jsonUtils/models'; // TODO(lmr): do something more sensible here const FLOAT_MAX = 999999; diff --git a/src/jsonUtils/sketchImpl/findFontName.ts b/src/platformBridges/SketchBridge/findFontName.ts similarity index 98% rename from src/jsonUtils/sketchImpl/findFontName.ts rename to src/platformBridges/SketchBridge/findFontName.ts index 3417c114..2453fbb9 100644 --- a/src/jsonUtils/sketchImpl/findFontName.ts +++ b/src/platformBridges/SketchBridge/findFontName.ts @@ -2,7 +2,7 @@ import hashStyle from '../../utils/hashStyle'; import { TextStyle } from '../../types'; -import { FONT_STYLES } from '../textLayers'; +import { FONT_STYLES } from '../../jsonUtils/textLayers'; // this borrows heavily from react-native's RCTFont class // thanks y'all diff --git a/src/platformBridges/SketchBridge/index.ts b/src/platformBridges/SketchBridge/index.ts new file mode 100644 index 00000000..bc15e5db --- /dev/null +++ b/src/platformBridges/SketchBridge/index.ts @@ -0,0 +1,13 @@ +import { PlatformBridge } from '../../types'; +import createStringMeasurer from './createStringMeasurer'; +import findFontName from './findFontName'; +import readFile from './readFile'; + +const SketchBridge: PlatformBridge = { + createStringMeasurer, + findFontName, + fetch, + readFile, +}; + +export default SketchBridge; diff --git a/src/platformBridges/SketchBridge/readFile.ts b/src/platformBridges/SketchBridge/readFile.ts new file mode 100644 index 00000000..3f6bc55b --- /dev/null +++ b/src/platformBridges/SketchBridge/readFile.ts @@ -0,0 +1,5 @@ +import { readFileSync } from '@skpm/fs'; + +export default async function readFile(path: string): Promise { + return Promise.resolve(readFileSync(path)); +} diff --git a/src/types/index.ts b/src/types/index.ts index 153d47e7..63d36415 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -215,3 +215,10 @@ export type ResizeConstraints = { fixedHeight?: boolean; fixedWidth?: boolean; }; + +export type PlatformBridge = { + createStringMeasurer(textNodes: TextNode[], maxWidth: number): Size; + findFontName(style: TextStyle): string; + fetch(input: RequestInfo, init?: RequestInit): Promise; + readFile(path: string): Promise; +}; From dfd88c9ffc2776e8c39e290d0566f0c564130f9d Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Tue, 21 Jan 2020 14:29:25 +0100 Subject: [PATCH 06/46] Change the interface of getSketchVersion() --- src/Platform.ts | 4 +++- src/jsonUtils/svgLayer.ts | 7 ++----- src/sharedStyles/TextStyles.ts | 7 +++---- src/utils/getSketchVersion.ts | 9 ++++----- src/utils/isRunningInSketch.ts | 5 +++++ src/utils/sharedTextStyles.ts | 4 ++-- 6 files changed, 19 insertions(+), 17 deletions(-) create mode 100644 src/utils/isRunningInSketch.ts diff --git a/src/Platform.ts b/src/Platform.ts index a5f529ec..f2824ca2 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -1,6 +1,8 @@ +import getSketchVersion from './utils/getSketchVersion'; + const Platform = { OS: 'sketch', - Version: 1, + Version: getSketchVersion(), select: (obj: { sketch: any }) => obj.sketch, }; diff --git a/src/jsonUtils/svgLayer.ts b/src/jsonUtils/svgLayer.ts index 489dc567..01289cf1 100644 --- a/src/jsonUtils/svgLayer.ts +++ b/src/jsonUtils/svgLayer.ts @@ -1,14 +1,11 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { LayoutInfo } from '../types'; -import { getSketchVersion } from '../utils/getSketchVersion'; +import isRunningInSketch from '../utils/isRunningInSketch'; import sketchMethod from './sketchImpl/makeSvgLayer'; import nodeMethod from './nodeImpl/makeSvgLayer'; const makeSvgLayer = (layout: LayoutInfo, name: string, svg: string): FileFormat.Group => { - if (getSketchVersion() === 'NodeJS') { - return nodeMethod(layout, name, svg); - } - return sketchMethod(layout, name, svg); + return isRunningInSketch() ? sketchMethod(layout, name, svg) : nodeMethod(layout, name, svg); }; export default makeSvgLayer; diff --git a/src/sharedStyles/TextStyles.ts b/src/sharedStyles/TextStyles.ts index 2f8fcda8..211368fe 100644 --- a/src/sharedStyles/TextStyles.ts +++ b/src/sharedStyles/TextStyles.ts @@ -1,6 +1,7 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { SketchDocumentData, SketchDocument, WrappedSketchDocument, TextStyle } from '../types'; -import { getSketchVersion } from '../utils/getSketchVersion'; +import getSketchVersion from '../utils/getSketchVersion'; +import isRunningInSketch from '../utils/isRunningInSketch'; import hashStyle from '../utils/hashStyle'; import { getDocument } from '../utils/getDocument'; import sharedTextStyles from '../utils/sharedTextStyles'; @@ -22,8 +23,6 @@ type StyleHash = { [key: string]: RegisteredStyle }; let _styles: StyleHash = {}; const _byName: { [key: string]: MurmurHash } = {}; -const sketchVersion = getSketchVersion(); - const registerStyle = (name: string, style: TextStyle): void => { const safeStyle = pick(style, INHERITABLE_FONT_STYLES); const hash = hashStyle(safeStyle); @@ -51,7 +50,7 @@ const create = (styles: { [key: string]: TextStyle }, options: Options = {}): St const doc = getDocument(document); - if (sketchVersion !== 'NodeJS' && sketchVersion < 50) { + if (isRunningInSketch() && parseInt(getSketchVersion()) < 50) { doc.showMessage('💎 Requires Sketch 50+ 💎'); return {}; } diff --git a/src/utils/getSketchVersion.ts b/src/utils/getSketchVersion.ts index e754f282..d38095d5 100644 --- a/src/utils/getSketchVersion.ts +++ b/src/utils/getSketchVersion.ts @@ -1,6 +1,5 @@ -export function getSketchVersion(): number | 'NodeJS' { - if (typeof NSBundle !== 'undefined') { - return parseFloat(NSBundle.mainBundle().infoDictionary().CFBundleShortVersionString); - } - return 'NodeJS'; +export default function getSketchVersion(): string { + return typeof NSBundle !== 'undefined' + ? NSBundle.mainBundle().infoDictionary().CFBundleShortVersionString + : ''; } diff --git a/src/utils/isRunningInSketch.ts b/src/utils/isRunningInSketch.ts new file mode 100644 index 00000000..6703c9da --- /dev/null +++ b/src/utils/isRunningInSketch.ts @@ -0,0 +1,5 @@ +import getSketchVersion from './getSketchVersion'; + +export default function isRunningInSketch() { + return getSketchVersion() !== ''; +} diff --git a/src/utils/sharedTextStyles.ts b/src/utils/sharedTextStyles.ts index 117bee6e..92c4b20b 100644 --- a/src/utils/sharedTextStyles.ts +++ b/src/utils/sharedTextStyles.ts @@ -1,5 +1,5 @@ -import { getSketchVersion } from './getSketchVersion'; import SketchStyles from '../jsonUtils/sketchImpl/sharedTextStyles'; import NodeStyles from '../jsonUtils/nodeImpl/sharedTextStyles'; +import isRunningInSketch from './isRunningInSketch'; -export default getSketchVersion() === 'NodeJS' ? new NodeStyles() : new SketchStyles(); +export default isRunningInSketch() ? new NodeStyles() : new SketchStyles(); From 08d1a2ef308f543db280e355fcbb84ef8a41e535 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Tue, 21 Jan 2020 15:52:24 +0100 Subject: [PATCH 07/46] Use strong imports in NodeMacOSBridge --- src/platformBridges/NodeMacOSBridge.ts | 7 +++---- src/platformBridges/getDefaultBridge.ts | 8 ++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 src/platformBridges/getDefaultBridge.ts diff --git a/src/platformBridges/NodeMacOSBridge.ts b/src/platformBridges/NodeMacOSBridge.ts index 1d84d5a5..cfa31aca 100644 --- a/src/platformBridges/NodeMacOSBridge.ts +++ b/src/platformBridges/NodeMacOSBridge.ts @@ -1,9 +1,8 @@ import { PlatformBridge } from '../types'; -import weakRequire from '../utils/weakRequire'; -const { createStringMeasurer, findFontName } = weakRequire(module, 'node-sketch-bridge'); -const fetch = weakRequire(module, 'node-fetch'); -const { readFile: nodeReadFile } = weakRequire(module, 'fs'); +import { createStringMeasurer, findFontName } from 'node-sketch-bridge'; +import fetch from 'node-fetch'; +import { readFile as nodeReadFile } from 'fs'; const NodeMacOSBridge: PlatformBridge = { createStringMeasurer, diff --git a/src/platformBridges/getDefaultBridge.ts b/src/platformBridges/getDefaultBridge.ts new file mode 100644 index 00000000..c1fce7f4 --- /dev/null +++ b/src/platformBridges/getDefaultBridge.ts @@ -0,0 +1,8 @@ +import isRunningInSketch from '../utils/isRunningInSketch'; +import { PlatformBridge } from '../types'; +import SketchBridge from './SketchBridge'; +import weakRequire from '../utils/weakRequire'; + +export default function getDefaultBridge(): PlatformBridge { + return isRunningInSketch() ? SketchBridge : weakRequire(module, './NodeMacOSBridge'); +} From f9c83a96e9df0eaeecef54e6c9b280efd2632a4b Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Tue, 21 Jan 2020 22:12:26 +0100 Subject: [PATCH 08/46] Add PlatformBridge injection everywhere --- src/buildTree.ts | 6 +-- src/flexToSketchJSON.ts | 7 +-- src/index.ts | 67 ++++++------------------- src/jsonUtils/computeYogaNode.ts | 7 +-- src/jsonUtils/computeYogaTree.ts | 14 +++--- src/jsonUtils/textLayers.ts | 47 ++++++++++------- src/render.tsx | 59 ++++++++++++---------- src/renderToJSON.ts | 14 ++++++ src/renderers/ImageRenderer.ts | 2 +- src/renderers/SketchRenderer.ts | 8 ++- src/renderers/SymbolInstanceRenderer.ts | 2 +- src/renderers/TextRenderer.ts | 1 + src/sharedStyles/TextStyles.ts | 25 +++++++-- src/symbol.tsx | 18 ++++--- src/utils/createStringMeasurer.ts | 22 -------- src/utils/findFont.ts | 14 ------ src/utils/getImageDataFromURL.ts | 4 +- src/utils/isNaN.ts | 6 +++ src/utils/measureString.ts | 14 ++++++ 19 files changed, 174 insertions(+), 163 deletions(-) create mode 100644 src/renderToJSON.ts delete mode 100644 src/utils/createStringMeasurer.ts delete mode 100644 src/utils/findFont.ts create mode 100644 src/utils/isNaN.ts create mode 100644 src/utils/measureString.ts diff --git a/src/buildTree.ts b/src/buildTree.ts index 98d80066..10a666b0 100644 --- a/src/buildTree.ts +++ b/src/buildTree.ts @@ -1,7 +1,7 @@ import * as TestRenderer from 'react-test-renderer'; import yoga from 'yoga-layout-prebuilt'; import Context from './utils/Context'; -import { TreeNode, TextNode } from './types'; +import { TreeNode, TextNode, PlatformBridge } from './types'; import hasAnyDefined from './utils/hasAnyDefined'; import pick from './utils/pick'; import computeYogaTree from './jsonUtils/computeYogaTree'; @@ -87,7 +87,7 @@ export const reactTreeToFlexTree = ( }; }; -const buildTree = (element: React.ReactElement): TreeNode => { +const buildTree = (element: React.ReactElement, bridge: PlatformBridge): TreeNode => { let renderer: TestRenderer.ReactTestRenderer; if (typeof TestRenderer.act !== 'undefined') { @@ -100,7 +100,7 @@ const buildTree = (element: React.ReactElement): TreeNode => { } const json: TreeNode = renderer.toJSON(); - const yogaNode = computeYogaTree(json, new Context()); + const yogaNode = computeYogaTree(json, new Context(), bridge); yogaNode.calculateLayout(undefined, undefined, yoga.DIRECTION_LTR); const tree = reactTreeToFlexTree(json, yogaNode, new Context()); diff --git a/src/flexToSketchJSON.ts b/src/flexToSketchJSON.ts index cdf74c86..95c53435 100644 --- a/src/flexToSketchJSON.ts +++ b/src/flexToSketchJSON.ts @@ -1,7 +1,7 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import * as renderers from './renderers'; import SketchRenderer from './renderers/SketchRenderer'; -import { TreeNode } from './types'; +import { TreeNode, PlatformBridge } from './types'; function missingRendererError(type: string, annotations?: string) { return new Error( @@ -11,6 +11,7 @@ function missingRendererError(type: string, annotations?: string) { const flexToSketchJSON = async ( node: TreeNode | string, + bridge: PlatformBridge, ): Promise< | FileFormat.SymbolMaster | FileFormat.Artboard @@ -45,7 +46,7 @@ const flexToSketchJSON = async ( throw missingRendererError(type); } - const renderer = new Renderer(); + const renderer = new Renderer(bridge); const groupLayer = await renderer.renderGroupLayer(node); if (groupLayer._class === 'symbolInstance') { @@ -57,7 +58,7 @@ const flexToSketchJSON = async ( // stopping the walk down the tree if we have an svg const sublayers = children && type !== 'sketch_svg' - ? await Promise.all(children.map(child => flexToSketchJSON(child))) + ? await Promise.all(children.map(child => flexToSketchJSON(child, bridge))) : []; // Filter out anything null, undefined diff --git a/src/index.ts b/src/index.ts index cb95b801..91ba7abf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,56 +1,19 @@ -import { render as _render, renderToJSON as _renderToJSON } from './render'; -import _Platform from './Platform'; -import _StyleSheet from './stylesheet'; -import _Document from './components/Document'; -import _Page from './components/Page'; -import _Artboard from './components/Artboard'; -import _Image from './components/Image'; -import _RedBox from './components/RedBox'; -import _Svg from './components/Svg'; -import _View from './components/View'; -import _Text from './components/Text'; -import _TextStyles from './sharedStyles/TextStyles'; -import { - makeSymbol as _makeSymbol, - getSymbolComponentByName as _getSymbolComponentByName, - getSymbolMasterByName as _getSymbolMasterByName, - injectSymbols as _injectSymbols, -} from './symbol'; - -export const render = _render; -export const renderToJSON = _renderToJSON; -export const StyleSheet = _StyleSheet; -export const Document = _Document; -export const Page = _Page; -export const Artboard = _Artboard; -export const Image = _Image; -export const RedBox = _RedBox; -export const Svg = _Svg; -export const Text = _Text; -export const TextStyles = _TextStyles; -export const View = _View; -export const Platform = _Platform; -export const makeSymbol = _makeSymbol; -export const getSymbolComponentByName = _getSymbolComponentByName; -export const getSymbolMasterByName = _getSymbolMasterByName; -export const injectSymbols = _injectSymbols; - -export default { - render, - renderToJSON, - StyleSheet, - Document, - Page, - Artboard, - Image, - RedBox, - Svg, - Text, - TextStyles, - View, - Platform, +export { default as render } from './render'; +export { default as renderToJSON } from './renderToJSON'; +export { default as Platform } from './Platform'; +export { default as StyleSheet } from './stylesheet'; +export { default as Document } from './components/Document'; +export { default as Page } from './components/Page'; +export { default as Artboard } from './components/Artboard'; +export { default as Image } from './components/Image'; +export { default as RedBox } from './components/RedBox'; +export { default as Svg } from './components/Svg'; +export { default as View } from './components/View'; +export { default as Text } from './components/Text'; +export { default as TextStyles } from './sharedStyles/TextStyles'; +export { makeSymbol, getSymbolComponentByName, getSymbolMasterByName, injectSymbols, -}; +} from './symbol'; diff --git a/src/jsonUtils/computeYogaNode.ts b/src/jsonUtils/computeYogaNode.ts index c3000920..bb1d8b62 100644 --- a/src/jsonUtils/computeYogaNode.ts +++ b/src/jsonUtils/computeYogaNode.ts @@ -1,7 +1,7 @@ import yoga from 'yoga-layout-prebuilt'; -import { TreeNode, ViewStyle } from '../types'; +import { TreeNode, ViewStyle, PlatformBridge } from '../types'; import Context from '../utils/Context'; -import createStringMeasurer from '../utils/createStringMeasurer'; +import measureString from '../utils/measureString'; import hasAnyDefined from '../utils/hasAnyDefined'; import pick from '../utils/pick'; import computeTextTree from './computeTextTree'; @@ -30,6 +30,7 @@ export const getStyles = (node: TreeNode | string): ViewStyle => { const computeYogaNode = ( node: TreeNode | string, context: Context, + bridge: PlatformBridge, ): { node: yoga.YogaNode; stop?: boolean } => { const yogaNode = yoga.Node.create(); const hasStyle = typeof node !== 'string' && node.props && node.props.style; @@ -341,7 +342,7 @@ const computeYogaNode = ( // Handle Text Children const textNodes = computeTextTree(node, context); - yogaNode.setMeasureFunc(createStringMeasurer(textNodes)); + yogaNode.setMeasureFunc((width: number = 0) => measureString(bridge, textNodes, width)); return { node: yogaNode, stop: true }; } diff --git a/src/jsonUtils/computeYogaTree.ts b/src/jsonUtils/computeYogaTree.ts index 012f847e..8b145dc8 100644 --- a/src/jsonUtils/computeYogaTree.ts +++ b/src/jsonUtils/computeYogaTree.ts @@ -1,11 +1,11 @@ import yoga from 'yoga-layout-prebuilt'; import computeYogaNode from './computeYogaNode'; -import { TreeNode } from '../types'; +import { TreeNode, PlatformBridge } from '../types'; import Context from '../utils/Context'; import zIndex from '../utils/zIndex'; -const walkTree = (tree: TreeNode | string, context: Context) => { - const { node, stop } = computeYogaNode(tree, context); +const walkTree = (tree: TreeNode | string, context: Context, bridge: PlatformBridge) => { + const { node, stop } = computeYogaNode(tree, context, bridge); if (typeof tree === 'string' || tree.type === 'sketch_svg') { // handle svg node, eg: stop here, we will handle the children in the renderer @@ -20,7 +20,7 @@ const walkTree = (tree: TreeNode | string, context: Context) => { const childComponent = children[index]; // Avoid going into node's children if (!stop) { - const childNode = walkTree(childComponent, context.forChildren()); + const childNode = walkTree(childComponent, context.forChildren(), bridge); node.insertChild(childNode, index); } } @@ -28,6 +28,8 @@ const walkTree = (tree: TreeNode | string, context: Context) => { return node; }; -const treeToNodes = (root: TreeNode, context: Context): yoga.YogaNode => walkTree(root, context); -export default treeToNodes; +const computeYogaTree = (root: TreeNode, context: Context, bridge: PlatformBridge): yoga.YogaNode => + walkTree(root, context, bridge); + +export default computeYogaTree; diff --git a/src/jsonUtils/textLayers.ts b/src/jsonUtils/textLayers.ts index 2abe0129..3c824734 100644 --- a/src/jsonUtils/textLayers.ts +++ b/src/jsonUtils/textLayers.ts @@ -1,11 +1,9 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import makeResizeConstraint from './resizeConstraint'; -import { TextNode, ResizeConstraints, TextStyle, ViewStyle } from '../types'; +import { TextNode, ResizeConstraints, TextStyle, ViewStyle, PlatformBridge } from '../types'; import { generateID, makeColorFromCSS } from './models'; import { makeStyle, parseStyle } from './style'; -import findFontName from '../utils/findFont'; - export const TEXT_DECORATION_UNDERLINE = { none: FileFormat.UnderlineStyle.None, underline: FileFormat.UnderlineStyle.Underlined, @@ -50,15 +48,18 @@ export const FONT_STYLES = { oblique: true, }; -const makeFontDescriptor = (style: TextStyle): FileFormat.FontDescriptor => ({ +const makeFontDescriptor = ( + style: TextStyle, + bridge: PlatformBridge, +): FileFormat.FontDescriptor => ({ _class: 'fontDescriptor', attributes: { - name: String(findFontName(style)), // will default to the system font + name: String(bridge.findFontName(style)), // will default to the system font size: style.fontSize || 14, }, }); -const makeTextStyleAttributes = (style: TextStyle) => +const makeTextStyleAttributes = (style: TextStyle, bridge: PlatformBridge) => ({ underlineStyle: style.textDecoration ? TEXT_DECORATION_UNDERLINE[style.textDecoration] || 0 : 0, strikethroughStyle: style.textDecoration @@ -86,19 +87,26 @@ const makeTextStyleAttributes = (style: TextStyle) => MSAttributedStringTextTransformAttribute: TEXT_TRANSFORM[style.textTransform] * 1, } : {}), - MSAttributedStringFontAttribute: makeFontDescriptor(style), + MSAttributedStringFontAttribute: makeFontDescriptor(style, bridge), textStyleVerticalAlignmentKey: 0, MSAttributedStringColorAttribute: makeColorFromCSS(style.color || 'black'), } as const); -const makeAttribute = (node: TextNode, location: number): FileFormat.StringAttribute => ({ +const makeAttribute = ( + node: TextNode, + location: number, + bridge: PlatformBridge, +): FileFormat.StringAttribute => ({ _class: 'stringAttribute', location, length: node.content.length, - attributes: makeTextStyleAttributes(node.textStyles), + attributes: makeTextStyleAttributes(node.textStyles, bridge), }); -const makeAttributedString = (textNodes: TextNode[]): FileFormat.AttributedString => { +const makeAttributedString = ( + textNodes: TextNode[], + bridge: PlatformBridge, +): FileFormat.AttributedString => { const json: FileFormat.AttributedString = { _class: 'attributedString', string: '', @@ -108,7 +116,7 @@ const makeAttributedString = (textNodes: TextNode[]): FileFormat.AttributedStrin let location = 0; textNodes.forEach(node => { - json.attributes.push(makeAttribute(node, location)); + json.attributes.push(makeAttribute(node, location, bridge)); json.string += node.content; location += node.content.length; }); @@ -116,11 +124,15 @@ const makeAttributedString = (textNodes: TextNode[]): FileFormat.AttributedStrin return json; }; -export const makeTextStyle = (style: TextStyle, shadows?: Array): FileFormat.Style => { +export const makeTextStyle = ( + style: TextStyle, + shadows: Array | null, + bridge: PlatformBridge, +): FileFormat.Style => { const json = makeStyle(style, undefined, shadows); json.textStyle = { _class: 'textStyle', - encodedAttributes: makeTextStyleAttributes(style), + encodedAttributes: makeTextStyleAttributes(style, bridge), verticalAlignment: FileFormat.TextVerticalAlignment.Top, }; return json; @@ -195,8 +207,9 @@ const makeTextLayer = ( name: string, textNodes: TextNode[], _style: ViewStyle, - resizingConstraint?: ResizeConstraints, - shadows?: ViewStyle[], + resizingConstraint: ResizeConstraints | null, + shadows: ViewStyle[] | null, + bridge: PlatformBridge, ): FileFormat.Text => ({ _class: 'text', do_objectID: generateID(`text:${name}-${textNodes.map(node => node.content).join('')}`), @@ -212,8 +225,8 @@ const makeTextLayer = ( resizingType: FileFormat.ResizeType.Stretch, rotation: 0, shouldBreakMaskChain: false, - attributedString: makeAttributedString(textNodes), - style: makeTextStyle((textNodes[0] || { textStyles: {} }).textStyles, shadows), + attributedString: makeAttributedString(textNodes, bridge), + style: makeTextStyle((textNodes[0] || { textStyles: {} }).textStyles, shadows, bridge), automaticallyDrawOnUnderlyingPath: false, dontSynchroniseWithSymbol: false, // NOTE(akp): I haven't fully figured out the meaning of glyphBounds diff --git a/src/render.tsx b/src/render.tsx index 29e265e4..90b3211d 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -1,22 +1,23 @@ import * as React from 'react'; -import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { fromSJSON } from './jsonUtils/sketchImpl/json-to-sketch'; import buildTree from './buildTree'; import flexToSketchJSON from './flexToSketchJSON'; import { resetLayer, resetDocument } from './resets'; import { injectSymbols } from './symbol'; -import { SketchDocumentData, SketchLayer, SketchPage, TreeNode, WrappedSketchLayer } from './types'; +import { + SketchDocumentData, + SketchLayer, + SketchPage, + TreeNode, + WrappedSketchLayer, + PlatformBridge, +} from './types'; import RedBox from './components/RedBox'; import { getDocumentDataFromContainer, getDocumentDataFromContext } from './utils/getDocument'; import isNativeDocument from './utils/isNativeDocument'; import isNativePage from './utils/isNativePage'; import isNativeSymbolsPage from './utils/isNativeSymbolsPage'; -import { getSketchVersion } from './utils/getSketchVersion'; - -export const renderToJSON = async (element: React.ReactElement): Promise => { - const tree = buildTree(element); - return flexToSketchJSON(tree); -}; +import getDefaultBridge from './platformBridges/getDefaultBridge'; export const renderLayers = (layers: Array, container: SketchLayer): SketchLayer => { if (container.addLayers === undefined) { @@ -39,14 +40,19 @@ const getDefaultPage = (): SketchLayer => { const renderContents = async ( tree: TreeNode | string, container: SketchLayer, + bridge: PlatformBridge, ): Promise => { - const json = await flexToSketchJSON(tree); + const json = await flexToSketchJSON(tree, bridge); const layer = fromSJSON(json, '119'); return renderLayers([layer], container); }; -const renderPage = async (tree: TreeNode, page: SketchPage): Promise> => { +const renderPage = async ( + tree: TreeNode, + page: SketchPage, + bridge: PlatformBridge, +): Promise> => { const children = tree.children || []; // assume if name is set on this nested page, the intent is to overwrite @@ -55,12 +61,13 @@ const renderPage = async (tree: TreeNode, page: SketchPage): Promise renderContents(child, page))); + return Promise.all(children.map(child => renderContents(child, page, bridge))); }; const renderDocument = async ( tree: TreeNode, documentData: SketchDocumentData, + bridge: PlatformBridge, ): Promise> => { if (!isNativeDocument(documentData)) { throw new Error('Cannot render a Document into a child of Document'); @@ -76,13 +83,14 @@ const renderDocument = async ( } const page = i === 0 && shouldRenderInitialPage ? initialPage : documentData.addBlankPage(); - return renderPage(child, page); + return renderPage(child, page, bridge); }); }; const renderTree = async ( tree: TreeNode, - _container?: SketchLayer, + _container: SketchLayer | null | undefined, + bridge: PlatformBridge, ): Promise> => { if (isNativeDocument(_container) && tree.type !== 'sketch_document') { throw new Error('You need to render a Document into Document'); @@ -96,25 +104,22 @@ const renderTree = async ( const doc = _container || getDocumentDataFromContext(context); resetDocument(doc); - return renderDocument(tree, doc); + return renderDocument(tree, doc, bridge); } const container = _container || getDefaultPage(); resetLayer(container); return tree.type === 'sketch_page' - ? renderPage(tree, container) - : renderContents(tree, container); + ? renderPage(tree, container, bridge) + : renderContents(tree, container, bridge); }; -export const render = async ( +export default async function render( element: React.ReactElement, container?: SketchLayer | WrappedSketchLayer, -): Promise> => { - if (getSketchVersion() === 'NodeJS') { - return renderToJSON(element); - } - + platformBridge: PlatformBridge = getDefaultBridge(), +): Promise> { let nativeContainer: SketchLayer | void; if (container && container.sketchObject) { nativeContainer = container.sketchObject; @@ -129,16 +134,16 @@ export const render = async ( } try { - const tree = buildTree(element); + const tree = buildTree(element, platformBridge); injectSymbols(getDocumentDataFromContainer(nativeContainer)); - const layer = await renderTree(tree, nativeContainer); + const layer = await renderTree(tree, nativeContainer, platformBridge); return layer; } catch (err) { // eslint-disable-next-line no-console console.error(err); - const tree = buildTree(); - return renderContents(tree, nativeContainer); + const tree = buildTree(, platformBridge); + return renderContents(tree, nativeContainer, platformBridge); } -}; +} diff --git a/src/renderToJSON.ts b/src/renderToJSON.ts new file mode 100644 index 00000000..23cda552 --- /dev/null +++ b/src/renderToJSON.ts @@ -0,0 +1,14 @@ +import getDefaultBridge from './platformBridges/getDefaultBridge'; +import { PlatformBridge } from './types'; +import buildTree from './buildTree'; +import flexToSketchJSON from './flexToSketchJSON'; +import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; +import * as React from 'react'; + +export default async function renderToJSON( + element: React.ReactElement, + platformBridge: PlatformBridge = getDefaultBridge(), +): Promise { + const tree = buildTree(element, platformBridge); + return flexToSketchJSON(tree, platformBridge); +} diff --git a/src/renderers/ImageRenderer.ts b/src/renderers/ImageRenderer.ts index ce68e10e..737d68e3 100644 --- a/src/renderers/ImageRenderer.ts +++ b/src/renderers/ImageRenderer.ts @@ -28,7 +28,7 @@ export default class ImageRenderer extends SketchRenderer { const url = extractURLFromSource(props.source); - const image = await getImageDataFromURL(url); + const image = await getImageDataFromURL(this.platformBridge, url); const fillImage = makeJSONDataReference(image); diff --git a/src/renderers/SketchRenderer.ts b/src/renderers/SketchRenderer.ts index 0691a706..c98f47aa 100644 --- a/src/renderers/SketchRenderer.ts +++ b/src/renderers/SketchRenderer.ts @@ -1,12 +1,18 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import layerGroup from '../jsonUtils/layerGroup'; import hotspotLayer from '../jsonUtils/hotspotLayer'; -import { TreeNode } from '../types'; +import { TreeNode, PlatformBridge } from '../types'; import processTransform from '../utils/processTransform'; const DEFAULT_OPACITY = 1.0; export default class SketchRenderer { + protected readonly platformBridge: PlatformBridge; + + constructor(bridge: PlatformBridge) { + this.platformBridge = bridge; + } + getDefaultGroupName(_props: any) { return 'Group'; } diff --git a/src/renderers/SymbolInstanceRenderer.ts b/src/renderers/SymbolInstanceRenderer.ts index fed11d32..ce38e2c5 100644 --- a/src/renderers/SymbolInstanceRenderer.ts +++ b/src/renderers/SymbolInstanceRenderer.ts @@ -180,7 +180,7 @@ export default class SymbolInstanceRenderer extends SketchRenderer { makeOverride( reference.path, reference.type, - makeJSONDataReference(await getImageDataFromURL(overrideValue)), + makeJSONDataReference(await getImageDataFromURL(this.platformBridge, overrideValue)), ), ); } diff --git a/src/renderers/TextRenderer.ts b/src/renderers/TextRenderer.ts index e8e282da..176bb2db 100644 --- a/src/renderers/TextRenderer.ts +++ b/src/renderers/TextRenderer.ts @@ -29,6 +29,7 @@ export default class TextRenderer extends SketchRenderer { style, props.resizingConstraint, props.shadows, + this.platformBridge, ); const resolvedTextStyle = TextStyles.resolve(textStyle); diff --git a/src/sharedStyles/TextStyles.ts b/src/sharedStyles/TextStyles.ts index 211368fe..66c3d8b5 100644 --- a/src/sharedStyles/TextStyles.ts +++ b/src/sharedStyles/TextStyles.ts @@ -1,5 +1,11 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import { SketchDocumentData, SketchDocument, WrappedSketchDocument, TextStyle } from '../types'; +import { + SketchDocumentData, + SketchDocument, + WrappedSketchDocument, + TextStyle, + PlatformBridge, +} from '../types'; import getSketchVersion from '../utils/getSketchVersion'; import isRunningInSketch from '../utils/isRunningInSketch'; import hashStyle from '../utils/hashStyle'; @@ -8,6 +14,7 @@ import sharedTextStyles from '../utils/sharedTextStyles'; import { makeTextStyle } from '../jsonUtils/textLayers'; import pick from '../utils/pick'; import { INHERITABLE_FONT_STYLES } from '../utils/constants'; +import getDefaultBridge from '../platformBridges/getDefaultBridge'; type MurmurHash = string; @@ -23,10 +30,14 @@ type StyleHash = { [key: string]: RegisteredStyle }; let _styles: StyleHash = {}; const _byName: { [key: string]: MurmurHash } = {}; -const registerStyle = (name: string, style: TextStyle): void => { +const registerStyle = ( + name: string, + style: TextStyle, + platformBridge: PlatformBridge = getDefaultBridge(), +): void => { const safeStyle = pick(style, INHERITABLE_FONT_STYLES); const hash = hashStyle(safeStyle); - const sketchStyle = makeTextStyle(safeStyle); + const sketchStyle = makeTextStyle(safeStyle, null, platformBridge); const sharedObjectID = sharedTextStyles.addStyle(name, sketchStyle); // FIXME(gold): side effect :'( @@ -45,7 +56,11 @@ type Options = { document?: SketchDocumentData | SketchDocument | WrappedSketchDocument; }; -const create = (styles: { [key: string]: TextStyle }, options: Options = {}): StyleHash => { +const create = ( + styles: { [key: string]: TextStyle }, + options: Options = {}, + platformBridge: PlatformBridge = getDefaultBridge(), +): StyleHash => { const { clearExistingStyles, document } = options; const doc = getDocument(document); @@ -62,7 +77,7 @@ const create = (styles: { [key: string]: TextStyle }, options: Options = {}): St sharedTextStyles.setStyles([]); } - Object.keys(styles).forEach(name => registerStyle(name, styles[name])); + Object.keys(styles).forEach(name => registerStyle(name, styles[name], platformBridge)); return _styles; }; diff --git a/src/symbol.tsx b/src/symbol.tsx index 66c94809..cf85cad6 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -12,8 +12,9 @@ import flexToSketchJSON from './flexToSketchJSON'; import { renderLayers } from './render'; import { resetLayer } from './resets'; import { getDocumentData } from './utils/getDocument'; -import { SketchDocumentData, SketchDocument, WrappedSketchDocument } from './types'; -import { getSketchVersion } from './utils/getSketchVersion'; +import { SketchDocumentData, SketchDocument, WrappedSketchDocument, PlatformBridge } from './types'; +import getDefaultBridge from './platformBridges/getDefaultBridge'; +import isRunningInSketch from './utils/isRunningInSketch'; let id = 0; const nextId = () => ++id; // eslint-disable-line @@ -67,8 +68,8 @@ const getExistingSymbols = (documentData: SketchDocumentData) => { export const injectSymbols = ( document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, ) => { - if (getSketchVersion() === 'NodeJS') { - console.error('Cannot inject symbols in NodeJS'); + if (!isRunningInSketch()) { + console.error('Cannot inject symbols while Sketch is not running'); return; } @@ -154,8 +155,9 @@ export const makeSymbol = async ( Component: React.ComponentType, symbolProps: string | SymbolMasterProps, document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, + bridge: PlatformBridge = getDefaultBridge(), ) => { - if (!hasInitialized && getSketchVersion() !== 'NodeJS') { + if (!hasInitialized && isRunningInSketch()) { getExistingSymbols(getDocumentData(document)); } @@ -176,7 +178,9 @@ export const makeSymbol = async ( > , + bridge, ), + bridge, )) as FileFormat.SymbolMaster; symbolsRegistry[symbolID] = symbolMaster; @@ -222,7 +226,7 @@ export const getSymbolMasterByName = ( ? Object.keys(symbolsRegistry).find(key => String(symbolsRegistry[key].name) === name) : ''; - if (typeof symbolID === 'undefined' && name && getSketchVersion() !== 'NodeJS') { + if (typeof symbolID === 'undefined' && name && isRunningInSketch()) { return tryGettingSymbolMasterInDocumentByName(name, document); } @@ -239,7 +243,7 @@ export const getSymbolMasterById = ( ): FileFormat.SymbolMaster => { let symbolMaster = symbolID ? symbolsRegistry[symbolID] : undefined; - if (typeof symbolMaster === 'undefined' && symbolID && getSketchVersion() !== 'NodeJS') { + if (typeof symbolMaster === 'undefined' && symbolID && isRunningInSketch()) { symbolMaster = tryGettingSymbolMasterInDocumentById(symbolID, document); } diff --git a/src/utils/createStringMeasurer.ts b/src/utils/createStringMeasurer.ts deleted file mode 100644 index 8420c02f..00000000 --- a/src/utils/createStringMeasurer.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { TextNode, Size } from '../types'; -import { getSketchVersion } from './getSketchVersion'; -import sketchMethod from '../jsonUtils/sketchImpl/createStringMeasurer'; -import nodeMethod from '../jsonUtils/nodeImpl/createStringMeasurer'; - -const createStringMeasurer = (textNodes: TextNode[]) => (width: number = 0): Size => { - // width would be obj-c NaN and the only way to check for it is by comparing - // width to itself (because NaN !== NaN) - // eslint-disable-next-line no-self-compare - const _width = width !== width ? 0 : width; - - if (textNodes.length > 0) { - if (getSketchVersion() === 'NodeJS') { - return nodeMethod(textNodes, _width); - } - return sketchMethod(textNodes, _width); - } - - return { width: _width, height: 0 }; -}; - -export default createStringMeasurer; diff --git a/src/utils/findFont.ts b/src/utils/findFont.ts deleted file mode 100644 index 394a22c9..00000000 --- a/src/utils/findFont.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { TextStyle } from '../types'; - -import { getSketchVersion } from './getSketchVersion'; -import sketchMethod from '../jsonUtils/sketchImpl/findFontName'; -import nodeMethod from '../jsonUtils/nodeImpl/findFontName'; - -const findFontName = (style: TextStyle) => { - if (getSketchVersion() === 'NodeJS') { - return nodeMethod(style); - } - return sketchMethod(style); -}; - -export default findFontName; diff --git a/src/utils/getImageDataFromURL.ts b/src/utils/getImageDataFromURL.ts index f6769fdc..90f1ac61 100644 --- a/src/utils/getImageDataFromURL.ts +++ b/src/utils/getImageDataFromURL.ts @@ -3,6 +3,7 @@ // TODO: Load node polyfill // TODO: Read from FS import sha1 from 'js-sha1'; +import { PlatformBridge } from '../types'; const ERROR_IMAGE = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8w8DwHwAEOQHNmnaaOAAAAABJRU5ErkJggg=='; @@ -36,13 +37,14 @@ const isImage = (buffer: Buffer): boolean => { }; export default async function getImageDataFromURL( + bridge: PlatformBridge, url?: string, ): Promise> { if (!url) { return ERROR_RESULT; } - const response = await fetch(url); + const response = await bridge.fetch(url); if (!response.ok) { return ERROR_RESULT; } diff --git a/src/utils/isNaN.ts b/src/utils/isNaN.ts new file mode 100644 index 00000000..e11383ec --- /dev/null +++ b/src/utils/isNaN.ts @@ -0,0 +1,6 @@ +export default function isNaN(num: number): boolean { + // If the value is obj-c NaN, the only way to check for it is by comparing + // width to itself (because NaN !== NaN) + // eslint-disable-next-line no-self-compare + return Number.isNaN(num) || num !== num; +} diff --git a/src/utils/measureString.ts b/src/utils/measureString.ts new file mode 100644 index 00000000..3847c1e4 --- /dev/null +++ b/src/utils/measureString.ts @@ -0,0 +1,14 @@ +import { TextNode, Size, PlatformBridge } from '../types'; +import isNaN from './isNaN'; + +export default function measureString( + bridge: PlatformBridge, + textNodes: TextNode[], + width: number, +): Size { + const sanitizedWidth = isNaN(width) ? 0 : width; + + return textNodes.length > 0 + ? bridge.createStringMeasurer(textNodes, sanitizedWidth) + : { width: sanitizedWidth, height: 0 }; +} From 67fc4cefaf11952e236fa4fddba8411eb41b0a5d Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Tue, 21 Jan 2020 22:40:42 +0100 Subject: [PATCH 09/46] Group together files by method instead than by implementation --- .../{nodeImpl => makeSvgLayer}/graphics/curvePoint.ts | 0 .../{nodeImpl => makeSvgLayer}/graphics/path.ts | 0 .../{nodeImpl => makeSvgLayer}/graphics/point.ts | 0 .../{nodeImpl => makeSvgLayer}/graphics/rect.ts | 0 .../{nodeImpl => makeSvgLayer}/graphics/types.ts | 0 src/jsonUtils/makeSvgLayer/index.ts | 5 +++++ .../makeSvgLayer.sketch.ts} | 2 +- .../{nodeImpl => makeSvgLayer}/makeSvgLayer.ts | 0 src/jsonUtils/svgLayer.ts | 11 ----------- src/renderers/SvgRenderer.ts | 2 +- src/utils/sharedTextStyles.ts | 5 ----- .../sharedTextStyles/TextStyles.sketch.ts} | 8 ++++---- .../sharedTextStyles/TextStyles.ts} | 2 +- src/utils/sharedTextStyles/index.ts | 5 +++++ 14 files changed, 17 insertions(+), 23 deletions(-) rename src/jsonUtils/{nodeImpl => makeSvgLayer}/graphics/curvePoint.ts (100%) rename src/jsonUtils/{nodeImpl => makeSvgLayer}/graphics/path.ts (100%) rename src/jsonUtils/{nodeImpl => makeSvgLayer}/graphics/point.ts (100%) rename src/jsonUtils/{nodeImpl => makeSvgLayer}/graphics/rect.ts (100%) rename src/jsonUtils/{nodeImpl => makeSvgLayer}/graphics/types.ts (100%) create mode 100644 src/jsonUtils/makeSvgLayer/index.ts rename src/jsonUtils/{sketchImpl/makeSvgLayer.ts => makeSvgLayer/makeSvgLayer.sketch.ts} (93%) rename src/jsonUtils/{nodeImpl => makeSvgLayer}/makeSvgLayer.ts (100%) delete mode 100644 src/jsonUtils/svgLayer.ts delete mode 100644 src/utils/sharedTextStyles.ts rename src/{jsonUtils/sketchImpl/sharedTextStyles.ts => utils/sharedTextStyles/TextStyles.sketch.ts} (90%) rename src/{jsonUtils/nodeImpl/sharedTextStyles.ts => utils/sharedTextStyles/TextStyles.ts} (90%) create mode 100644 src/utils/sharedTextStyles/index.ts diff --git a/src/jsonUtils/nodeImpl/graphics/curvePoint.ts b/src/jsonUtils/makeSvgLayer/graphics/curvePoint.ts similarity index 100% rename from src/jsonUtils/nodeImpl/graphics/curvePoint.ts rename to src/jsonUtils/makeSvgLayer/graphics/curvePoint.ts diff --git a/src/jsonUtils/nodeImpl/graphics/path.ts b/src/jsonUtils/makeSvgLayer/graphics/path.ts similarity index 100% rename from src/jsonUtils/nodeImpl/graphics/path.ts rename to src/jsonUtils/makeSvgLayer/graphics/path.ts diff --git a/src/jsonUtils/nodeImpl/graphics/point.ts b/src/jsonUtils/makeSvgLayer/graphics/point.ts similarity index 100% rename from src/jsonUtils/nodeImpl/graphics/point.ts rename to src/jsonUtils/makeSvgLayer/graphics/point.ts diff --git a/src/jsonUtils/nodeImpl/graphics/rect.ts b/src/jsonUtils/makeSvgLayer/graphics/rect.ts similarity index 100% rename from src/jsonUtils/nodeImpl/graphics/rect.ts rename to src/jsonUtils/makeSvgLayer/graphics/rect.ts diff --git a/src/jsonUtils/nodeImpl/graphics/types.ts b/src/jsonUtils/makeSvgLayer/graphics/types.ts similarity index 100% rename from src/jsonUtils/nodeImpl/graphics/types.ts rename to src/jsonUtils/makeSvgLayer/graphics/types.ts diff --git a/src/jsonUtils/makeSvgLayer/index.ts b/src/jsonUtils/makeSvgLayer/index.ts new file mode 100644 index 00000000..31f97846 --- /dev/null +++ b/src/jsonUtils/makeSvgLayer/index.ts @@ -0,0 +1,5 @@ +import isRunningInSketch from '../../utils/isRunningInSketch'; +import sketchMakeSvgLayer from './makeSvgLayer.sketch'; +import pureJsSketchMakeSvgLayer from './makeSvgLayer'; + +export default isRunningInSketch() ? pureJsSketchMakeSvgLayer : sketchMakeSvgLayer; diff --git a/src/jsonUtils/sketchImpl/makeSvgLayer.ts b/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts similarity index 93% rename from src/jsonUtils/sketchImpl/makeSvgLayer.ts rename to src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts index 91fa3f4a..6457130d 100644 --- a/src/jsonUtils/sketchImpl/makeSvgLayer.ts +++ b/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts @@ -1,5 +1,5 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import { toSJSON } from './sketch-to-json'; +import { toSJSON } from '../sketchImpl/sketch-to-json'; import { LayoutInfo } from '../../types'; diff --git a/src/jsonUtils/nodeImpl/makeSvgLayer.ts b/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts similarity index 100% rename from src/jsonUtils/nodeImpl/makeSvgLayer.ts rename to src/jsonUtils/makeSvgLayer/makeSvgLayer.ts diff --git a/src/jsonUtils/svgLayer.ts b/src/jsonUtils/svgLayer.ts deleted file mode 100644 index 01289cf1..00000000 --- a/src/jsonUtils/svgLayer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import { LayoutInfo } from '../types'; -import isRunningInSketch from '../utils/isRunningInSketch'; -import sketchMethod from './sketchImpl/makeSvgLayer'; -import nodeMethod from './nodeImpl/makeSvgLayer'; - -const makeSvgLayer = (layout: LayoutInfo, name: string, svg: string): FileFormat.Group => { - return isRunningInSketch() ? sketchMethod(layout, name, svg) : nodeMethod(layout, name, svg); -}; - -export default makeSvgLayer; diff --git a/src/renderers/SvgRenderer.ts b/src/renderers/SvgRenderer.ts index b91d9d5d..12fb830d 100644 --- a/src/renderers/SvgRenderer.ts +++ b/src/renderers/SvgRenderer.ts @@ -1,6 +1,6 @@ import ViewRenderer from './ViewRenderer'; import { TreeNode } from '../types'; -import makeSvgLayer from '../jsonUtils/svgLayer'; +import makeSvgLayer from '../jsonUtils/makeSvgLayer'; import { Props } from '../components/Svg/Svg'; const snakeExceptions = [ diff --git a/src/utils/sharedTextStyles.ts b/src/utils/sharedTextStyles.ts deleted file mode 100644 index 92c4b20b..00000000 --- a/src/utils/sharedTextStyles.ts +++ /dev/null @@ -1,5 +0,0 @@ -import SketchStyles from '../jsonUtils/sketchImpl/sharedTextStyles'; -import NodeStyles from '../jsonUtils/nodeImpl/sharedTextStyles'; -import isRunningInSketch from './isRunningInSketch'; - -export default isRunningInSketch() ? new NodeStyles() : new SketchStyles(); diff --git a/src/jsonUtils/sketchImpl/sharedTextStyles.ts b/src/utils/sharedTextStyles/TextStyles.sketch.ts similarity index 90% rename from src/jsonUtils/sketchImpl/sharedTextStyles.ts rename to src/utils/sharedTextStyles/TextStyles.sketch.ts index fc48690a..2754086d 100644 --- a/src/jsonUtils/sketchImpl/sharedTextStyles.ts +++ b/src/utils/sharedTextStyles/TextStyles.sketch.ts @@ -1,10 +1,10 @@ import invariant from 'invariant'; -import { fromSJSON } from './json-to-sketch'; -import { toSJSON } from './sketch-to-json'; +import { fromSJSON } from '../../jsonUtils/sketchImpl/json-to-sketch'; +import { toSJSON } from '../../jsonUtils/sketchImpl/sketch-to-json'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { SketchDocument, TextStyle } from '../../types'; -import { generateID } from '../models'; -import { parseTextStyle } from '../textLayers'; +import { generateID } from '../../jsonUtils/models'; +import { parseTextStyle } from '../../jsonUtils/textLayers'; class TextStyles { _document?: SketchDocument; diff --git a/src/jsonUtils/nodeImpl/sharedTextStyles.ts b/src/utils/sharedTextStyles/TextStyles.ts similarity index 90% rename from src/jsonUtils/nodeImpl/sharedTextStyles.ts rename to src/utils/sharedTextStyles/TextStyles.ts index 46beb066..cbd3e60c 100644 --- a/src/jsonUtils/nodeImpl/sharedTextStyles.ts +++ b/src/utils/sharedTextStyles/TextStyles.ts @@ -1,6 +1,6 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { SketchDocument, TextStyle } from '../../types'; -import { generateID } from '../models'; +import { generateID } from '../../jsonUtils/models'; class TextStyles { setDocument(_doc: SketchDocument) { diff --git a/src/utils/sharedTextStyles/index.ts b/src/utils/sharedTextStyles/index.ts new file mode 100644 index 00000000..0a89f5cb --- /dev/null +++ b/src/utils/sharedTextStyles/index.ts @@ -0,0 +1,5 @@ +import SketchTextStyles from './TextStyles.sketch'; +import PureJsTextStyles from './TextStyles'; +import isRunningInSketch from '../isRunningInSketch'; + +export default isRunningInSketch() ? new PureJsTextStyles() : new SketchTextStyles(); From 6d6c801a522690dbe78cba7eb174b62d2f868cfa Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Tue, 21 Jan 2020 22:50:31 +0100 Subject: [PATCH 10/46] Rename the *SJSON functions --- src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts | 4 ++-- .../convertJsonToSketch.ts} | 2 +- .../convertSketchToJson.ts} | 4 +++- src/render.tsx | 4 ++-- src/symbol.tsx | 12 ++++++------ src/utils/sharedTextStyles/TextStyles.sketch.ts | 8 ++++---- 6 files changed, 18 insertions(+), 16 deletions(-) rename src/jsonUtils/{sketchImpl/json-to-sketch.ts => sketchJson/convertJsonToSketch.ts} (95%) rename src/jsonUtils/{sketchImpl/sketch-to-json.ts => sketchJson/convertSketchToJson.ts} (79%) diff --git a/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts b/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts index 6457130d..db93bbd6 100644 --- a/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts +++ b/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts @@ -1,5 +1,5 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import { toSJSON } from '../sketchImpl/sketch-to-json'; +import convertSketchToJson from '../sketchJson/convertSketchToJson'; import { LayoutInfo } from '../../types'; @@ -21,5 +21,5 @@ export default function makeSvgLayer( root.ungroupSingleChildDescendentGroups(); svgImporter.scale_rootGroup(svgImporter.importer().scaleValue(), root); - return toSJSON(root) as FileFormat.Group; + return convertSketchToJson(root) as FileFormat.Group; } diff --git a/src/jsonUtils/sketchImpl/json-to-sketch.ts b/src/jsonUtils/sketchJson/convertJsonToSketch.ts similarity index 95% rename from src/jsonUtils/sketchImpl/json-to-sketch.ts rename to src/jsonUtils/sketchJson/convertJsonToSketch.ts index abdf656c..838e1f7d 100644 --- a/src/jsonUtils/sketchImpl/json-to-sketch.ts +++ b/src/jsonUtils/sketchJson/convertJsonToSketch.ts @@ -10,7 +10,7 @@ const SKETCH_HIGHEST_COMPATIBLE_VERSION = '95'; /** * Takes a Sketch JSON tree and turns it into a native object. May throw on invalid data */ -export function fromSJSON( +export default function convertJsonToSketch( jsonTree: FileFormat.AnyLayer | FileFormat.AnyObject, version = SKETCH_HIGHEST_COMPATIBLE_VERSION, ): SketchLayer { diff --git a/src/jsonUtils/sketchImpl/sketch-to-json.ts b/src/jsonUtils/sketchJson/convertSketchToJson.ts similarity index 79% rename from src/jsonUtils/sketchImpl/sketch-to-json.ts rename to src/jsonUtils/sketchJson/convertSketchToJson.ts index 43059bb6..17ba03bb 100644 --- a/src/jsonUtils/sketchImpl/sketch-to-json.ts +++ b/src/jsonUtils/sketchJson/convertSketchToJson.ts @@ -1,7 +1,9 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { SketchLayer } from '../../types'; -export function toSJSON(sketchObject: SketchLayer): FileFormat.AnyObject | FileFormat.AnyLayer { +export default function convertSketchToJson( + sketchObject: SketchLayer, +): FileFormat.AnyObject | FileFormat.AnyLayer { if (!sketchObject) { return null; } diff --git a/src/render.tsx b/src/render.tsx index 90b3211d..edaac7d5 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { fromSJSON } from './jsonUtils/sketchImpl/json-to-sketch'; +import convertJsonToSketch from './jsonUtils/sketchJson/convertJsonToSketch'; import buildTree from './buildTree'; import flexToSketchJSON from './flexToSketchJSON'; import { resetLayer, resetDocument } from './resets'; @@ -43,7 +43,7 @@ const renderContents = async ( bridge: PlatformBridge, ): Promise => { const json = await flexToSketchJSON(tree, bridge); - const layer = fromSJSON(json, '119'); + const layer = convertJsonToSketch(json, '119'); return renderLayers([layer], container); }; diff --git a/src/symbol.tsx b/src/symbol.tsx index cf85cad6..e5394393 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import { fromSJSON } from './jsonUtils/sketchImpl/json-to-sketch'; -import { toSJSON } from './jsonUtils/sketchImpl/sketch-to-json'; +import convertJsonToSketch from './jsonUtils/sketchJson/convertJsonToSketch'; +import convertSketchToJson from './jsonUtils/sketchJson/convertSketchToJson'; import StyleSheet from './stylesheet'; import { generateID } from './jsonUtils/models'; import ViewStylePropTypes from './components/ViewStylePropTypes'; @@ -47,7 +47,7 @@ const getExistingSymbols = (documentData: SketchDocumentData) => { existingSymbols = msListToArray(symbolsPage.layers()) .map(x => { - const symbolJson = toSJSON(x); + const symbolJson = convertSketchToJson(x); if (symbolJson._class !== 'symbolMaster') { return undefined; } @@ -93,7 +93,7 @@ export const injectSymbols = ( symbolMaster.frame.x = left; left += symbolMaster.frame.width + 20; - const newLayer = fromSJSON(symbolMaster, '119'); + const newLayer = convertJsonToSketch(symbolMaster, '119'); layers[symbolMaster.symbolID] = newLayer; }); @@ -200,7 +200,7 @@ function tryGettingSymbolMasterInDocumentByName( return undefined; } - return toSJSON(symbol) as FileFormat.SymbolMaster; + return convertSketchToJson(symbol) as FileFormat.SymbolMaster; } function tryGettingSymbolMasterInDocumentById( @@ -215,7 +215,7 @@ function tryGettingSymbolMasterInDocumentById( return undefined; } - return toSJSON(symbol) as FileFormat.SymbolMaster; + return convertSketchToJson(symbol) as FileFormat.SymbolMaster; } export const getSymbolMasterByName = ( diff --git a/src/utils/sharedTextStyles/TextStyles.sketch.ts b/src/utils/sharedTextStyles/TextStyles.sketch.ts index 2754086d..0bc3b990 100644 --- a/src/utils/sharedTextStyles/TextStyles.sketch.ts +++ b/src/utils/sharedTextStyles/TextStyles.sketch.ts @@ -1,6 +1,6 @@ import invariant from 'invariant'; -import { fromSJSON } from '../../jsonUtils/sketchImpl/json-to-sketch'; -import { toSJSON } from '../../jsonUtils/sketchImpl/sketch-to-json'; +import convertJsonToSketch from '../../jsonUtils/sketchJson/convertJsonToSketch'; +import convertSketchToJson from '../../jsonUtils/sketchJson/convertSketchToJson'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { SketchDocument, TextStyle } from '../../types'; import { generateID } from '../../jsonUtils/models'; @@ -35,7 +35,7 @@ class TextStyles { const { _document } = this; invariant(_document, 'Please provide a sketch document reference'); - const nativeStyle = fromSJSON(style, '119'); + const nativeStyle = convertJsonToSketch(style, '119'); const container = _document.documentData().layerTextStyles(); @@ -86,7 +86,7 @@ class TextStyles { return undefined; } - const style = toSJSON(foundStyle) as FileFormat.Style; + const style = convertSketchToJson(foundStyle) as FileFormat.Style; return parseTextStyle(style); } From 5bae926c589e588405662cabc656c98dcbd1d444 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Tue, 21 Jan 2020 23:01:31 +0100 Subject: [PATCH 11/46] Don't use the console in prodution --- src/components/Svg/TextPath.tsx | 7 ++++--- src/components/Svg/Use.tsx | 9 +++++---- src/jsonUtils/sketchJson/convertJsonToSketch.ts | 2 +- src/jsonUtils/sketchJson/convertSketchToJson.ts | 2 +- src/render.tsx | 5 +++-- src/symbol.tsx | 3 ++- src/utils/processTransform/parseTransformProp.ts | 5 +++-- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/Svg/TextPath.tsx b/src/components/Svg/TextPath.tsx index 1816c975..0924bd03 100755 --- a/src/components/Svg/TextPath.tsx +++ b/src/components/Svg/TextPath.tsx @@ -12,9 +12,10 @@ export default class TextPath extends React.Component { render() { if (!this.props.href || !this.props.href.match(idExpReg)) { // eslint-disable-next-line no-console - console.warn( - `Invalid \`href\` prop for \`TextPath\` element, expected a href like \`"#id"\`, but got: "${this.props.href}"`, - ); + if (process.env.NODE_ENV !== 'production') + console.warn( + `Invalid \`href\` prop for \`TextPath\` element, expected a href like \`"#id"\`, but got: "${this.props.href}"`, + ); } const { children, ...rest } = this.props; diff --git a/src/components/Svg/Use.tsx b/src/components/Svg/Use.tsx index 2c4a97ac..c98124b6 100755 --- a/src/components/Svg/Use.tsx +++ b/src/components/Svg/Use.tsx @@ -22,10 +22,11 @@ export default class Use extends React.Component { const matched = href.match(idExpReg); if (!href || !matched) { - // eslint-disable-next-line no-console - console.warn( - `Invalid \`href\` prop for \`Use\` element, expected a href like \`"#id"\`, but got: "${href}"`, - ); + if (process.env.NODE_ENV !== 'production') + // eslint-disable-next-line no-console + console.warn( + `Invalid \`href\` prop for \`Use\` element, expected a href like \`"#id"\`, but got: "${href}"`, + ); } const { children, ...rest } = this.props; return {children}; diff --git a/src/jsonUtils/sketchJson/convertJsonToSketch.ts b/src/jsonUtils/sketchJson/convertJsonToSketch.ts index 838e1f7d..df1de098 100644 --- a/src/jsonUtils/sketchJson/convertJsonToSketch.ts +++ b/src/jsonUtils/sketchJson/convertJsonToSketch.ts @@ -23,7 +23,7 @@ export default function convertJsonToSketch( ); if (err.value() !== null) { - console.error(err.value()); + if (process.env.NODE_ENV !== 'production') console.error(err.value()); throw new Error(err.value()); } diff --git a/src/jsonUtils/sketchJson/convertSketchToJson.ts b/src/jsonUtils/sketchJson/convertSketchToJson.ts index 17ba03bb..70ecdc27 100644 --- a/src/jsonUtils/sketchJson/convertSketchToJson.ts +++ b/src/jsonUtils/sketchJson/convertSketchToJson.ts @@ -13,7 +13,7 @@ export default function convertSketchToJson( const data = MSJSONDataArchiver.archiveStringWithRootObject_error(imm, err); if (err.value() !== null) { - console.error(err.value()); + if (process.env.NODE_ENV !== 'production') console.error(err.value()); throw new Error(err.value()); } diff --git a/src/render.tsx b/src/render.tsx index edaac7d5..26e9d4f8 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -141,8 +141,9 @@ export default async function render( const layer = await renderTree(tree, nativeContainer, platformBridge); return layer; } catch (err) { - // eslint-disable-next-line no-console - console.error(err); + if (process.env.NODE_ENV !== 'production') + // eslint-disable-next-line no-console + console.error(err); const tree = buildTree(, platformBridge); return renderContents(tree, nativeContainer, platformBridge); } diff --git a/src/symbol.tsx b/src/symbol.tsx index e5394393..cb28da92 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -69,7 +69,8 @@ export const injectSymbols = ( document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, ) => { if (!isRunningInSketch()) { - console.error('Cannot inject symbols while Sketch is not running'); + if (process.env.NODE_ENV !== 'production') + console.error('Cannot inject symbols while Sketch is not running'); return; } diff --git a/src/utils/processTransform/parseTransformProp.ts b/src/utils/processTransform/parseTransformProp.ts index 69e88d89..34b4c5ff 100644 --- a/src/utils/processTransform/parseTransformProp.ts +++ b/src/utils/processTransform/parseTransformProp.ts @@ -168,8 +168,9 @@ function appendTransform(transform: string) { const [a, c, e, b, d, f] = transformParser.parse(transform); pooledMatrix.append(a, b, c, d, e, f); } catch (e) { - // eslint-disable-next-line no-console - console.error(e); + if (process.env.NODE_ENV !== 'production') + // eslint-disable-next-line no-console + console.error(e); } } From d849c9bf1f128d048fb249db2aa121f3e1a9dd37 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 09:27:55 +0100 Subject: [PATCH 12/46] Reorganize exports --- __tests__/jest/components/nodeImpl/Svg.tsx | 2 +- src/{stylesheet => StyleSheet}/expandStyle.ts | 0 src/{stylesheet => StyleSheet}/index.ts | 0 src/{stylesheet => StyleSheet}/types.ts | 0 src/components/Artboard.tsx | 2 +- src/components/Image.tsx | 2 +- src/components/Page.tsx | 2 +- src/components/Svg/Svg.tsx | 2 +- src/components/Text.tsx | 2 +- src/components/View.tsx | 2 +- src/components/index.ts | 8 ++++++++ src/index.ts | 11 ++--------- src/symbol.tsx | 2 +- 13 files changed, 18 insertions(+), 17 deletions(-) rename src/{stylesheet => StyleSheet}/expandStyle.ts (100%) rename src/{stylesheet => StyleSheet}/index.ts (100%) rename src/{stylesheet => StyleSheet}/types.ts (100%) create mode 100644 src/components/index.ts diff --git a/__tests__/jest/components/nodeImpl/Svg.tsx b/__tests__/jest/components/nodeImpl/Svg.tsx index 23ce49bd..57b32f84 100644 --- a/__tests__/jest/components/nodeImpl/Svg.tsx +++ b/__tests__/jest/components/nodeImpl/Svg.tsx @@ -1,7 +1,7 @@ /* eslint-disable global-require */ import * as React from 'react'; -import ReactSketch from '../../../../src'; +import * as ReactSketch from '../../../../src'; import Svg from '../../../../src/components/Svg'; jest.mock('../../../../src/jsonUtils/models', () => ({ diff --git a/src/stylesheet/expandStyle.ts b/src/StyleSheet/expandStyle.ts similarity index 100% rename from src/stylesheet/expandStyle.ts rename to src/StyleSheet/expandStyle.ts diff --git a/src/stylesheet/index.ts b/src/StyleSheet/index.ts similarity index 100% rename from src/stylesheet/index.ts rename to src/StyleSheet/index.ts diff --git a/src/stylesheet/types.ts b/src/StyleSheet/types.ts similarity index 100% rename from src/stylesheet/types.ts rename to src/StyleSheet/types.ts diff --git a/src/components/Artboard.tsx b/src/components/Artboard.tsx index 6e0bbbe1..df1c12ca 100644 --- a/src/components/Artboard.tsx +++ b/src/components/Artboard.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../stylesheet'; +import StyleSheet from '../StyleSheet'; import ViewStylePropTypes from './ViewStylePropTypes'; const ViewportPropTypes = { diff --git a/src/components/Image.tsx b/src/components/Image.tsx index ce8a281d..59513052 100644 --- a/src/components/Image.tsx +++ b/src/components/Image.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../stylesheet'; +import StyleSheet from '../StyleSheet'; import ResizeModePropTypes from './ResizeModePropTypes'; import ImageStylePropTypes from './ImageStylePropTypes'; import { ViewPropTypes } from './View'; diff --git a/src/components/Page.tsx b/src/components/Page.tsx index 606cb272..3dbb84cd 100644 --- a/src/components/Page.tsx +++ b/src/components/Page.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../stylesheet'; +import StyleSheet from '../StyleSheet'; import PageStylePropTypes from './PageStylePropTypes'; export const PagePropTypes = { diff --git a/src/components/Svg/Svg.tsx b/src/components/Svg/Svg.tsx index 6c8b6610..f455e9ef 100755 --- a/src/components/Svg/Svg.tsx +++ b/src/components/Svg/Svg.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { ViewPropTypes } from '../View'; -import StyleSheet from '../../stylesheet'; +import StyleSheet from '../../StyleSheet'; import Circle from './Circle'; import ClipPath from './ClipPath'; import Defs from './Defs'; diff --git a/src/components/Text.tsx b/src/components/Text.tsx index 0d20facd..0a73d693 100644 --- a/src/components/Text.tsx +++ b/src/components/Text.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../stylesheet'; +import StyleSheet from '../StyleSheet'; import TextStylePropTypes from './TextStylePropTypes'; import { ViewPropTypes } from './View'; diff --git a/src/components/View.tsx b/src/components/View.tsx index de4b3569..2b491caf 100644 --- a/src/components/View.tsx +++ b/src/components/View.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../stylesheet'; +import StyleSheet from '../StyleSheet'; import ViewStylePropTypes from './ViewStylePropTypes'; import ResizingConstraintPropTypes from './ResizingConstraintPropTypes'; import ShadowsPropTypes from './ShadowsPropTypes'; diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 00000000..ef6b5a34 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,8 @@ +export { default as Document } from './Document'; +export { default as Page } from './Page'; +export { default as Artboard } from './Artboard'; +export { default as Image } from './Image'; +export { default as RedBox } from './RedBox'; +export { default as Svg } from './Svg'; +export { default as View } from './View'; +export { default as Text } from './Text'; diff --git a/src/index.ts b/src/index.ts index 91ba7abf..8d1887e6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,8 @@ export { default as render } from './render'; export { default as renderToJSON } from './renderToJSON'; export { default as Platform } from './Platform'; -export { default as StyleSheet } from './stylesheet'; -export { default as Document } from './components/Document'; -export { default as Page } from './components/Page'; -export { default as Artboard } from './components/Artboard'; -export { default as Image } from './components/Image'; -export { default as RedBox } from './components/RedBox'; -export { default as Svg } from './components/Svg'; -export { default as View } from './components/View'; -export { default as Text } from './components/Text'; +export { default as StyleSheet } from './StyleSheet'; +export * from './components'; export { default as TextStyles } from './sharedStyles/TextStyles'; export { makeSymbol, diff --git a/src/symbol.tsx b/src/symbol.tsx index cb28da92..7d5cc78b 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -3,7 +3,7 @@ import * as PropTypes from 'prop-types'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import convertJsonToSketch from './jsonUtils/sketchJson/convertJsonToSketch'; import convertSketchToJson from './jsonUtils/sketchJson/convertSketchToJson'; -import StyleSheet from './stylesheet'; +import StyleSheet from './StyleSheet'; import { generateID } from './jsonUtils/models'; import ViewStylePropTypes from './components/ViewStylePropTypes'; import ResizingConstraintPropTypes from './components/ResizingConstraintPropTypes'; From b8bc1427841bf6f7cc13c9d792ca8aa5e2f1526c Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 10:15:52 +0100 Subject: [PATCH 13/46] Split entrypoints by platform --- src/index.common.ts | 5 +++++ src/index.node.ts | 31 +++++++++++++++++++++++++++++++ src/index.sketch.ts | 32 ++++++++++++++++++++++++++++++++ src/index.ts | 12 ------------ src/render.tsx | 4 ++-- src/renderToJSON.ts | 3 +-- src/symbol.tsx | 2 +- 7 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 src/index.common.ts create mode 100644 src/index.node.ts create mode 100644 src/index.sketch.ts delete mode 100644 src/index.ts diff --git a/src/index.common.ts b/src/index.common.ts new file mode 100644 index 00000000..a28ec7d0 --- /dev/null +++ b/src/index.common.ts @@ -0,0 +1,5 @@ +export { default as Platform } from './Platform'; +export { default as StyleSheet } from './StyleSheet'; +export * from './components'; +export { default as TextStyles } from './sharedStyles/TextStyles'; +export { getSymbolComponentByName, getSymbolMasterByName, injectSymbols } from './symbol'; diff --git a/src/index.node.ts b/src/index.node.ts new file mode 100644 index 00000000..a111f60b --- /dev/null +++ b/src/index.node.ts @@ -0,0 +1,31 @@ +import { default as _render } from './render'; +import { default as _renderToJSON } from './renderToJSON'; +import { makeSymbol as _makeSymbol } from './symbol'; +import { SketchLayer, WrappedSketchLayer, PlatformBridge } from './types'; +import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; + +export async function render( + element: React.ReactElement, + container?: SketchLayer | WrappedSketchLayer, + platformBridge: PlatformBridge = require('./platformBridges/NodeMacOSBridge'), +): Promise> { + return _render(element, container, platformBridge); +} + +export async function renderToJSON( + element: React.ReactElement, + platformBridge: PlatformBridge = require('./platformBridges/NodeMacOSBridge'), +): Promise { + return _renderToJSON(element, platformBridge); +} + +export async function makeSymbol( + Component: React.ComponentType, + symbolProps?: string | SymbolMasterProps, + document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, + bridge: PlatformBridge = require('./platformBridges/NodeMacOSBridge'), +) { + return _makeSymbol(Component, symbolProps, document, bridge); +} + +export * from './index.common'; diff --git a/src/index.sketch.ts b/src/index.sketch.ts new file mode 100644 index 00000000..c52a99f4 --- /dev/null +++ b/src/index.sketch.ts @@ -0,0 +1,32 @@ +import { default as _render } from './render'; +import { default as _renderToJSON } from './renderToJSON'; +import { makeSymbol as _makeSymbol } from './symbol'; +import { SketchLayer, WrappedSketchLayer, PlatformBridge } from './types'; +import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; +import SketchBridge from './platformBridges/SketchBridge'; + +export async function render( + element: React.ReactElement, + container?: SketchLayer | WrappedSketchLayer, + platformBridge: PlatformBridge = SketchBridge, +): Promise> { + return _render(element, container, platformBridge); +} + +export async function renderToJSON( + element: React.ReactElement, + platformBridge: PlatformBridge = SketchBridge, +): Promise { + return _renderToJSON(element, platformBridge); +} + +export async function makeSymbol( + Component: React.ComponentType, + symbolProps?: string | SymbolMasterProps, + document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, + bridge: PlatformBridge = SketchBridge, +) { + return _makeSymbol(Component, symbolProps, document, bridge); +} + +export * from './index.common'; diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 8d1887e6..00000000 --- a/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export { default as render } from './render'; -export { default as renderToJSON } from './renderToJSON'; -export { default as Platform } from './Platform'; -export { default as StyleSheet } from './StyleSheet'; -export * from './components'; -export { default as TextStyles } from './sharedStyles/TextStyles'; -export { - makeSymbol, - getSymbolComponentByName, - getSymbolMasterByName, - injectSymbols, -} from './symbol'; diff --git a/src/render.tsx b/src/render.tsx index 26e9d4f8..c85fe26b 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -117,8 +117,8 @@ const renderTree = async ( export default async function render( element: React.ReactElement, - container?: SketchLayer | WrappedSketchLayer, - platformBridge: PlatformBridge = getDefaultBridge(), + container: SketchLayer | WrappedSketchLayer | null, + platformBridge: PlatformBridge, ): Promise> { let nativeContainer: SketchLayer | void; if (container && container.sketchObject) { diff --git a/src/renderToJSON.ts b/src/renderToJSON.ts index 23cda552..a9535099 100644 --- a/src/renderToJSON.ts +++ b/src/renderToJSON.ts @@ -1,4 +1,3 @@ -import getDefaultBridge from './platformBridges/getDefaultBridge'; import { PlatformBridge } from './types'; import buildTree from './buildTree'; import flexToSketchJSON from './flexToSketchJSON'; @@ -7,7 +6,7 @@ import * as React from 'react'; export default async function renderToJSON( element: React.ReactElement, - platformBridge: PlatformBridge = getDefaultBridge(), + platformBridge: PlatformBridge, ): Promise { const tree = buildTree(element, platformBridge); return flexToSketchJSON(tree, platformBridge); diff --git a/src/symbol.tsx b/src/symbol.tsx index 7d5cc78b..3e720974 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -156,7 +156,7 @@ export const makeSymbol = async ( Component: React.ComponentType, symbolProps: string | SymbolMasterProps, document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, - bridge: PlatformBridge = getDefaultBridge(), + bridge: PlatformBridge, ) => { if (!hasInitialized && isRunningInSketch()) { getExistingSymbols(getDocumentData(document)); From 2d063e684066de5fa895ede51780f86d0dfa5b29 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 10:43:02 +0100 Subject: [PATCH 14/46] Change the bridge injection for TextStyles --- src/index.common.ts | 1 - src/index.node.ts | 13 +- src/index.sketch.ts | 3 + src/platformBridges/getDefaultBridge.ts | 8 -- src/render.tsx | 1 - src/sharedStyles/TextStyles.ts | 179 ++++++++++++------------ src/symbol.tsx | 1 - 7 files changed, 100 insertions(+), 106 deletions(-) delete mode 100644 src/platformBridges/getDefaultBridge.ts diff --git a/src/index.common.ts b/src/index.common.ts index a28ec7d0..2ce39c5c 100644 --- a/src/index.common.ts +++ b/src/index.common.ts @@ -1,5 +1,4 @@ export { default as Platform } from './Platform'; export { default as StyleSheet } from './StyleSheet'; export * from './components'; -export { default as TextStyles } from './sharedStyles/TextStyles'; export { getSymbolComponentByName, getSymbolMasterByName, injectSymbols } from './symbol'; diff --git a/src/index.node.ts b/src/index.node.ts index a111f60b..9784687d 100644 --- a/src/index.node.ts +++ b/src/index.node.ts @@ -3,18 +3,23 @@ import { default as _renderToJSON } from './renderToJSON'; import { makeSymbol as _makeSymbol } from './symbol'; import { SketchLayer, WrappedSketchLayer, PlatformBridge } from './types'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; +import { default as _TextStyles } from './sharedStyles/TextStyles'; + +function getDefaultPlatformBridge() { + return require('./platformBridges/NodeMacOSBridge'); +} export async function render( element: React.ReactElement, container?: SketchLayer | WrappedSketchLayer, - platformBridge: PlatformBridge = require('./platformBridges/NodeMacOSBridge'), + platformBridge: PlatformBridge = getDefaultPlatformBridge(), ): Promise> { return _render(element, container, platformBridge); } export async function renderToJSON( element: React.ReactElement, - platformBridge: PlatformBridge = require('./platformBridges/NodeMacOSBridge'), + platformBridge: PlatformBridge = getDefaultPlatformBridge(), ): Promise { return _renderToJSON(element, platformBridge); } @@ -23,9 +28,11 @@ export async function makeSymbol( Component: React.ComponentType, symbolProps?: string | SymbolMasterProps, document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, - bridge: PlatformBridge = require('./platformBridges/NodeMacOSBridge'), + bridge: PlatformBridge = getDefaultPlatformBridge(), ) { return _makeSymbol(Component, symbolProps, document, bridge); } +export const TextStyles = _TextStyles(getDefaultPlatformBridge); + export * from './index.common'; diff --git a/src/index.sketch.ts b/src/index.sketch.ts index c52a99f4..a744a2a0 100644 --- a/src/index.sketch.ts +++ b/src/index.sketch.ts @@ -3,6 +3,7 @@ import { default as _renderToJSON } from './renderToJSON'; import { makeSymbol as _makeSymbol } from './symbol'; import { SketchLayer, WrappedSketchLayer, PlatformBridge } from './types'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; +import { default as _TextStyles } from './sharedStyles/TextStyles'; import SketchBridge from './platformBridges/SketchBridge'; export async function render( @@ -29,4 +30,6 @@ export async function makeSymbol( return _makeSymbol(Component, symbolProps, document, bridge); } +export const TextStyles = _TextStyles(() => SketchBridge); + export * from './index.common'; diff --git a/src/platformBridges/getDefaultBridge.ts b/src/platformBridges/getDefaultBridge.ts deleted file mode 100644 index c1fce7f4..00000000 --- a/src/platformBridges/getDefaultBridge.ts +++ /dev/null @@ -1,8 +0,0 @@ -import isRunningInSketch from '../utils/isRunningInSketch'; -import { PlatformBridge } from '../types'; -import SketchBridge from './SketchBridge'; -import weakRequire from '../utils/weakRequire'; - -export default function getDefaultBridge(): PlatformBridge { - return isRunningInSketch() ? SketchBridge : weakRequire(module, './NodeMacOSBridge'); -} diff --git a/src/render.tsx b/src/render.tsx index c85fe26b..1ab5873e 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -17,7 +17,6 @@ import { getDocumentDataFromContainer, getDocumentDataFromContext } from './util import isNativeDocument from './utils/isNativeDocument'; import isNativePage from './utils/isNativePage'; import isNativeSymbolsPage from './utils/isNativeSymbolsPage'; -import getDefaultBridge from './platformBridges/getDefaultBridge'; export const renderLayers = (layers: Array, container: SketchLayer): SketchLayer => { if (container.addLayers === undefined) { diff --git a/src/sharedStyles/TextStyles.ts b/src/sharedStyles/TextStyles.ts index 66c3d8b5..44739c1b 100644 --- a/src/sharedStyles/TextStyles.ts +++ b/src/sharedStyles/TextStyles.ts @@ -14,7 +14,6 @@ import sharedTextStyles from '../utils/sharedTextStyles'; import { makeTextStyle } from '../jsonUtils/textLayers'; import pick from '../utils/pick'; import { INHERITABLE_FONT_STYLES } from '../utils/constants'; -import getDefaultBridge from '../platformBridges/getDefaultBridge'; type MurmurHash = string; @@ -27,104 +26,100 @@ type RegisteredStyle = { type StyleHash = { [key: string]: RegisteredStyle }; -let _styles: StyleHash = {}; -const _byName: { [key: string]: MurmurHash } = {}; - -const registerStyle = ( - name: string, - style: TextStyle, - platformBridge: PlatformBridge = getDefaultBridge(), -): void => { - const safeStyle = pick(style, INHERITABLE_FONT_STYLES); - const hash = hashStyle(safeStyle); - const sketchStyle = makeTextStyle(safeStyle, null, platformBridge); - const sharedObjectID = sharedTextStyles.addStyle(name, sketchStyle); - - // FIXME(gold): side effect :'( - _byName[name] = hash; - - _styles[hash] = { - cssStyle: safeStyle, - name, - sketchStyle, - sharedObjectID, - }; -}; - type Options = { clearExistingStyles?: boolean; document?: SketchDocumentData | SketchDocument | WrappedSketchDocument; }; -const create = ( - styles: { [key: string]: TextStyle }, - options: Options = {}, - platformBridge: PlatformBridge = getDefaultBridge(), -): StyleHash => { - const { clearExistingStyles, document } = options; - - const doc = getDocument(document); - - if (isRunningInSketch() && parseInt(getSketchVersion()) < 50) { - doc.showMessage('💎 Requires Sketch 50+ 💎'); - return {}; - } - - sharedTextStyles.setDocument(doc); +let _styles: StyleHash = {}; +const _byName: { [key: string]: MurmurHash } = {}; - if (clearExistingStyles) { +const TextStyles = (getDefaultBridge: () => PlatformBridge) => ({ + registerStyle( + name: string, + style: TextStyle, + platformBridge: PlatformBridge = getDefaultBridge(), + ) { + const safeStyle = pick(style, INHERITABLE_FONT_STYLES); + const hash = hashStyle(safeStyle); + const sketchStyle = makeTextStyle(safeStyle, null, platformBridge); + const sharedObjectID = sharedTextStyles.addStyle(name, sketchStyle); + + // FIXME(gold): side effect :'( + _byName[name] = hash; + + _styles[hash] = { + cssStyle: safeStyle, + name, + sketchStyle, + sharedObjectID, + }; + }, + + create( + styles: { [key: string]: TextStyle }, + options: Options = {}, + platformBridge: PlatformBridge = getDefaultBridge(), + ) { + const { clearExistingStyles, document } = options; + + const doc = getDocument(document); + + if (isRunningInSketch() && parseInt(getSketchVersion()) < 50) { + doc.showMessage('💎 Requires Sketch 50+ 💎'); + return {}; + } + + sharedTextStyles.setDocument(doc); + + if (clearExistingStyles) { + _styles = {}; + sharedTextStyles.setStyles([]); + } + + Object.keys(styles).forEach(name => this.registerStyle(name, styles[name], platformBridge)); + + return _styles; + }, + + resolve(style: TextStyle): RegisteredStyle | undefined { + const safeStyle = pick(style, INHERITABLE_FONT_STYLES); + const hash = hashStyle(safeStyle); + + return _styles[hash]; + }, + + get( + name: string, + document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, + ): TextStyle | undefined { + const hash = _byName[name]; + const style = _styles[hash]; + + if (style) { + return style.cssStyle; + } + + return sharedTextStyles.getStyle(name, document ? getDocument(document) : undefined); + }, + + clear(): void { _styles = {}; sharedTextStyles.setStyles([]); - } - - Object.keys(styles).forEach(name => registerStyle(name, styles[name], platformBridge)); - - return _styles; -}; - -const resolve = (style: TextStyle): RegisteredStyle | undefined => { - const safeStyle = pick(style, INHERITABLE_FONT_STYLES); - const hash = hashStyle(safeStyle); - - return _styles[hash]; -}; - -const get = ( - name: string, - document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, -): TextStyle | undefined => { - const hash = _byName[name]; - const style = _styles[hash]; - - if (style) { - return style.cssStyle; - } - - return sharedTextStyles.getStyle(name, document ? getDocument(document) : undefined); -}; - -const clear = () => { - _styles = {}; - sharedTextStyles.setStyles([]); -}; - -const toJSON = (): FileFormat.SharedStyle[] => - Object.keys(_styles).map(k => ({ - _class: 'sharedStyle', - do_objectID: _styles[k].sharedObjectID, - name: _styles[k].name, - value: _styles[k].sketchStyle, - })); - -const styles = () => _styles; - -const TextStyles = { - create, - resolve, - get, - styles, - clear, - toJSON, -}; + }, + + toJSON(): FileFormat.SharedStyle[] { + return Object.keys(_styles).map(k => ({ + _class: 'sharedStyle', + do_objectID: _styles[k].sharedObjectID, + name: _styles[k].name, + value: _styles[k].sketchStyle, + })); + }, + + styles() { + return _styles; + }, +}); export default TextStyles; diff --git a/src/symbol.tsx b/src/symbol.tsx index 3e720974..fd0b671b 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -13,7 +13,6 @@ import { renderLayers } from './render'; import { resetLayer } from './resets'; import { getDocumentData } from './utils/getDocument'; import { SketchDocumentData, SketchDocument, WrappedSketchDocument, PlatformBridge } from './types'; -import getDefaultBridge from './platformBridges/getDefaultBridge'; import isRunningInSketch from './utils/isRunningInSketch'; let id = 0; From 0dbd5b059cc5fb1516658d6737342c9bf284cdc7 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 10:48:39 +0100 Subject: [PATCH 15/46] Use a dynamic import instead of weakRequire --- src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts | 4 ++-- src/jsonUtils/makeSvgLayer/makeSvgLayer.ts | 10 +++++++--- src/renderers/SvgRenderer.ts | 2 +- src/utils/weakRequire.ts | 9 --------- 4 files changed, 10 insertions(+), 15 deletions(-) delete mode 100644 src/utils/weakRequire.ts diff --git a/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts b/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts index db93bbd6..8d1a0e17 100644 --- a/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts +++ b/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts @@ -3,11 +3,11 @@ import convertSketchToJson from '../sketchJson/convertSketchToJson'; import { LayoutInfo } from '../../types'; -export default function makeSvgLayer( +export default async function makeSvgLayer( _layout: LayoutInfo, name: string, svg: string, -): FileFormat.Group { +): Promise { const svgString = NSString.stringWithString(svg); const svgData = svgString.dataUsingEncoding(NSUTF8StringEncoding); const svgImporter = MSSVGImporter.svgImporter(); diff --git a/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts b/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts index 38d160b0..f6ea8a34 100644 --- a/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts +++ b/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts @@ -6,7 +6,6 @@ import { createUniformBorder } from '../borders'; import layerGroup from '../layerGroup'; import { makePathsFromCommands, makeLineCapStyle } from './graphics/path'; import { unionRects, scaleRect, makeBoundingRectFromCommands, resize } from './graphics/rect'; -import weakRequire from '../../utils/weakRequire'; function makeLayerFromPathElement(pathElement, _parentFrame: FileFormat.Rect, scale: number) { const { @@ -78,8 +77,13 @@ function makeLayerGroup( return group; } -export default function makeSvgLayer(layout: LayoutInfo, name: string, svg: string) { - const svgModel = weakRequire(module, '@lona/svg-model'); +export default async function makeSvgLayer( + layout: LayoutInfo, + name: string, + svg: string, +): Promise { + // Load the module only if it has been made available through another import. + const svgModel = await import(/* webpackMode: "weak" */ '@lona/svg-model'); const { data: { params, children }, diff --git a/src/renderers/SvgRenderer.ts b/src/renderers/SvgRenderer.ts index 12fb830d..38a7d1e1 100644 --- a/src/renderers/SvgRenderer.ts +++ b/src/renderers/SvgRenderer.ts @@ -80,7 +80,7 @@ export default class SvgRenderer extends ViewRenderer { children, }); - const svgLayer = makeSvgLayer(layout, 'Shape', svgString); + const svgLayer = await makeSvgLayer(layout, 'Shape', svgString); layers.push(svgLayer); diff --git a/src/utils/weakRequire.ts b/src/utils/weakRequire.ts deleted file mode 100644 index 6d0b20dd..00000000 --- a/src/utils/weakRequire.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Requires a module without having webpack including it in the bundle. - * - * @param sourceModule reference to the `module` instance from where this request originated. - * @param request name of the module to require. - */ -export default function weakRequire(sourceModule: NodeModule, request: string): any { - return 'require' in sourceModule ? sourceModule.require(request) : {}; -} From 12dd68c7af0e125e0cce9570d60b07812c05554e Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 11:22:55 +0100 Subject: [PATCH 16/46] Fix compilation errors --- src/index.node.ts | 11 +++++++++-- src/index.sketch.ts | 11 +++++++++-- src/renderers/TextRenderer.ts | 2 +- src/symbol.tsx | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/index.node.ts b/src/index.node.ts index 9784687d..3384af43 100644 --- a/src/index.node.ts +++ b/src/index.node.ts @@ -1,7 +1,14 @@ import { default as _render } from './render'; import { default as _renderToJSON } from './renderToJSON'; -import { makeSymbol as _makeSymbol } from './symbol'; -import { SketchLayer, WrappedSketchLayer, PlatformBridge } from './types'; +import { makeSymbol as _makeSymbol, SymbolMasterProps } from './symbol'; +import { + SketchLayer, + WrappedSketchLayer, + PlatformBridge, + SketchDocumentData, + WrappedSketchDocument, + SketchDocument, +} from './types'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { default as _TextStyles } from './sharedStyles/TextStyles'; diff --git a/src/index.sketch.ts b/src/index.sketch.ts index a744a2a0..389f5a91 100644 --- a/src/index.sketch.ts +++ b/src/index.sketch.ts @@ -1,7 +1,14 @@ import { default as _render } from './render'; import { default as _renderToJSON } from './renderToJSON'; -import { makeSymbol as _makeSymbol } from './symbol'; -import { SketchLayer, WrappedSketchLayer, PlatformBridge } from './types'; +import { makeSymbol as _makeSymbol, SymbolMasterProps } from './symbol'; +import { + SketchLayer, + WrappedSketchLayer, + PlatformBridge, + SketchDocumentData, + WrappedSketchDocument, + SketchDocument, +} from './types'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { default as _TextStyles } from './sharedStyles/TextStyles'; import SketchBridge from './platformBridges/SketchBridge'; diff --git a/src/renderers/TextRenderer.ts b/src/renderers/TextRenderer.ts index 176bb2db..c253f9b4 100644 --- a/src/renderers/TextRenderer.ts +++ b/src/renderers/TextRenderer.ts @@ -32,7 +32,7 @@ export default class TextRenderer extends SketchRenderer { this.platformBridge, ); - const resolvedTextStyle = TextStyles.resolve(textStyle); + const resolvedTextStyle = TextStyles(() => this.platformBridge).resolve(textStyle); if (resolvedTextStyle) { if (!layer.style) { layer.style = resolvedTextStyle.sketchStyle; diff --git a/src/symbol.tsx b/src/symbol.tsx index fd0b671b..3bc9c4ce 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -154,7 +154,7 @@ export type SymbolMasterProps = PropTypes.InferProps, symbolProps: string | SymbolMasterProps, - document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, + document: SketchDocumentData | SketchDocument | WrappedSketchDocument | null, bridge: PlatformBridge, ) => { if (!hasInitialized && isRunningInSketch()) { From 435bce2f85924c866015e90c47289667803907e6 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 12:12:10 +0100 Subject: [PATCH 17/46] Move back StyleSheet -> stylesheet --- src/components/Artboard.tsx | 2 +- src/components/Image.tsx | 2 +- src/components/Page.tsx | 2 +- src/components/Svg/Svg.tsx | 2 +- src/components/Text.tsx | 2 +- src/components/View.tsx | 2 +- src/index.common.ts | 2 +- src/{StyleSheet => stylesheet}/expandStyle.ts | 0 src/{StyleSheet => stylesheet}/index.ts | 0 src/{StyleSheet => stylesheet}/types.ts | 0 src/symbol.tsx | 2 +- 11 files changed, 8 insertions(+), 8 deletions(-) rename src/{StyleSheet => stylesheet}/expandStyle.ts (100%) rename src/{StyleSheet => stylesheet}/index.ts (100%) rename src/{StyleSheet => stylesheet}/types.ts (100%) diff --git a/src/components/Artboard.tsx b/src/components/Artboard.tsx index df1c12ca..6e0bbbe1 100644 --- a/src/components/Artboard.tsx +++ b/src/components/Artboard.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../StyleSheet'; +import StyleSheet from '../stylesheet'; import ViewStylePropTypes from './ViewStylePropTypes'; const ViewportPropTypes = { diff --git a/src/components/Image.tsx b/src/components/Image.tsx index 59513052..ce8a281d 100644 --- a/src/components/Image.tsx +++ b/src/components/Image.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../StyleSheet'; +import StyleSheet from '../stylesheet'; import ResizeModePropTypes from './ResizeModePropTypes'; import ImageStylePropTypes from './ImageStylePropTypes'; import { ViewPropTypes } from './View'; diff --git a/src/components/Page.tsx b/src/components/Page.tsx index 3dbb84cd..606cb272 100644 --- a/src/components/Page.tsx +++ b/src/components/Page.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../StyleSheet'; +import StyleSheet from '../stylesheet'; import PageStylePropTypes from './PageStylePropTypes'; export const PagePropTypes = { diff --git a/src/components/Svg/Svg.tsx b/src/components/Svg/Svg.tsx index f455e9ef..6c8b6610 100755 --- a/src/components/Svg/Svg.tsx +++ b/src/components/Svg/Svg.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { ViewPropTypes } from '../View'; -import StyleSheet from '../../StyleSheet'; +import StyleSheet from '../../stylesheet'; import Circle from './Circle'; import ClipPath from './ClipPath'; import Defs from './Defs'; diff --git a/src/components/Text.tsx b/src/components/Text.tsx index 0a73d693..0d20facd 100644 --- a/src/components/Text.tsx +++ b/src/components/Text.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../StyleSheet'; +import StyleSheet from '../stylesheet'; import TextStylePropTypes from './TextStylePropTypes'; import { ViewPropTypes } from './View'; diff --git a/src/components/View.tsx b/src/components/View.tsx index 2b491caf..de4b3569 100644 --- a/src/components/View.tsx +++ b/src/components/View.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { or } from 'airbnb-prop-types'; -import StyleSheet from '../StyleSheet'; +import StyleSheet from '../stylesheet'; import ViewStylePropTypes from './ViewStylePropTypes'; import ResizingConstraintPropTypes from './ResizingConstraintPropTypes'; import ShadowsPropTypes from './ShadowsPropTypes'; diff --git a/src/index.common.ts b/src/index.common.ts index 2ce39c5c..1f1b4534 100644 --- a/src/index.common.ts +++ b/src/index.common.ts @@ -1,4 +1,4 @@ export { default as Platform } from './Platform'; -export { default as StyleSheet } from './StyleSheet'; +export { default as StyleSheet } from './stylesheet'; export * from './components'; export { getSymbolComponentByName, getSymbolMasterByName, injectSymbols } from './symbol'; diff --git a/src/StyleSheet/expandStyle.ts b/src/stylesheet/expandStyle.ts similarity index 100% rename from src/StyleSheet/expandStyle.ts rename to src/stylesheet/expandStyle.ts diff --git a/src/StyleSheet/index.ts b/src/stylesheet/index.ts similarity index 100% rename from src/StyleSheet/index.ts rename to src/stylesheet/index.ts diff --git a/src/StyleSheet/types.ts b/src/stylesheet/types.ts similarity index 100% rename from src/StyleSheet/types.ts rename to src/stylesheet/types.ts diff --git a/src/symbol.tsx b/src/symbol.tsx index 3bc9c4ce..fe2faba4 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -3,7 +3,7 @@ import * as PropTypes from 'prop-types'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import convertJsonToSketch from './jsonUtils/sketchJson/convertJsonToSketch'; import convertSketchToJson from './jsonUtils/sketchJson/convertSketchToJson'; -import StyleSheet from './StyleSheet'; +import StyleSheet from './stylesheet'; import { generateID } from './jsonUtils/models'; import ViewStylePropTypes from './components/ViewStylePropTypes'; import ResizingConstraintPropTypes from './components/ResizingConstraintPropTypes'; From 65e011dbac70d7fbd8d0a01b450f320e3a4c9a55 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 13:43:56 +0100 Subject: [PATCH 18/46] Fix the test suite --- __tests__/jest/components/nodeImpl/Svg.tsx | 4 +- __tests__/jest/index.ts | 6 +- __tests__/jest/jsonUtils/computeYogaNode.ts | 3 +- __tests__/jest/jsonUtils/computeYogaTree.ts | 5 +- __tests__/jest/reactTreeToFlexTree.ts | 3 +- __tests__/jest/sharedStyles/TextStyles.ts | 61 +++++++++------------ src/{index.node.ts => index.ts} | 0 src/jsonUtils/makeSvgLayer/index.ts | 2 +- src/jsonUtils/makeSvgLayer/makeSvgLayer.ts | 2 +- 9 files changed, 37 insertions(+), 49 deletions(-) rename src/{index.node.ts => index.ts} (100%) diff --git a/__tests__/jest/components/nodeImpl/Svg.tsx b/__tests__/jest/components/nodeImpl/Svg.tsx index 57b32f84..4f9a7030 100644 --- a/__tests__/jest/components/nodeImpl/Svg.tsx +++ b/__tests__/jest/components/nodeImpl/Svg.tsx @@ -10,7 +10,7 @@ jest.mock('../../../../src/jsonUtils/models', () => ({ })); describe('node ', () => { - it('generates the json for an svg', () => { + it('generates the json for an svg', async () => { class SVGElement extends React.Component { render() { return ( @@ -26,6 +26,6 @@ describe('node ', () => { } } - expect(ReactSketch.renderToJSON()).toMatchSnapshot(); + expect(await ReactSketch.renderToJSON()).toMatchSnapshot(); }); }); diff --git a/__tests__/jest/index.ts b/__tests__/jest/index.ts index b85655e1..46167f68 100644 --- a/__tests__/jest/index.ts +++ b/__tests__/jest/index.ts @@ -1,9 +1,5 @@ /* eslint-disable global-require */ -import ReactSketch from '../../src'; -jest.mock('../../src/jsonUtils/sketchImpl/createStringMeasurer'); -jest.mock('../../src/jsonUtils/sketchImpl/findFontName'); -jest.mock('../../src/jsonUtils/sketchImpl/makeImageDataFromUrl'); -jest.mock('../../src/jsonUtils/sketchImpl/makeSvgLayer'); +import * as ReactSketch from '../../src'; describe('public API', () => { it('exports render', () => { diff --git a/__tests__/jest/jsonUtils/computeYogaNode.ts b/__tests__/jest/jsonUtils/computeYogaNode.ts index b6af0d83..7021f525 100644 --- a/__tests__/jest/jsonUtils/computeYogaNode.ts +++ b/__tests__/jest/jsonUtils/computeYogaNode.ts @@ -1,6 +1,7 @@ import yoga from 'yoga-layout-prebuilt'; import computeYogaNode from '../../../src/jsonUtils/computeYogaNode'; import Context from '../../../src/utils/Context'; +import NodeMacOSBridge from '../../../src/platformBridges/NodeMacOSBridge'; const widthAndHeightStylesStub = { width: 10, @@ -32,7 +33,7 @@ const createYogaNodes = ( styles.forEach(style => { const treeNode = createTreeNode(style); const ctx = new Context(); - const { node } = computeYogaNode(treeNode, ctx); + const { node } = computeYogaNode(treeNode, ctx, NodeMacOSBridge); node.calculateLayout( containerWidth || undefined, containerHeight || undefined, diff --git a/__tests__/jest/jsonUtils/computeYogaTree.ts b/__tests__/jest/jsonUtils/computeYogaTree.ts index 636ca903..6dbf91fa 100644 --- a/__tests__/jest/jsonUtils/computeYogaTree.ts +++ b/__tests__/jest/jsonUtils/computeYogaTree.ts @@ -1,6 +1,7 @@ import yoga from 'yoga-layout-prebuilt'; import computeYogaTree from '../../../src/jsonUtils/computeYogaTree'; import Context from '../../../src/utils/Context'; +import NodeMacOSBridge from '../../../src/platformBridges/NodeMacOSBridge'; const treeRootStub = { type: 'artboard', @@ -35,11 +36,11 @@ const treeRootStub = { ], }; -computeYogaTree(treeRootStub, new Context()); +computeYogaTree(treeRootStub, new Context(), NodeMacOSBridge); describe('Compute Yoga Tree', () => { it('correctly create yoga nodes into layout tree', () => { - const yogaTree = computeYogaTree(treeRootStub, new Context()); + const yogaTree = computeYogaTree(treeRootStub, new Context(), NodeMacOSBridge); yogaTree.calculateLayout(undefined, undefined, yoga.DIRECTION_LTR); expect(yogaTree.getComputedLayout()).toEqual({ bottom: 0, diff --git a/__tests__/jest/reactTreeToFlexTree.ts b/__tests__/jest/reactTreeToFlexTree.ts index 96654075..5e3b95ac 100644 --- a/__tests__/jest/reactTreeToFlexTree.ts +++ b/__tests__/jest/reactTreeToFlexTree.ts @@ -2,6 +2,7 @@ import yoga from 'yoga-layout-prebuilt'; import computeYogaTree from '../../src/jsonUtils/computeYogaTree'; import Context from '../../src/utils/Context'; import { reactTreeToFlexTree } from '../../src/buildTree'; +import NodeMacOSBridge from '../../src/platformBridges/NodeMacOSBridge'; const treeRootStub = { type: 'artboard', @@ -55,7 +56,7 @@ const treeRootStub = { describe('Compute Flex Tree', () => { it('correctly creates flex tree', () => { - const yogaNode = computeYogaTree(treeRootStub, new Context()); + const yogaNode = computeYogaTree(treeRootStub, new Context(), NodeMacOSBridge); yogaNode.calculateLayout(undefined, undefined, yoga.DIRECTION_LTR); const tree = reactTreeToFlexTree(treeRootStub, yogaNode, new Context()); diff --git a/__tests__/jest/sharedStyles/TextStyles.ts b/__tests__/jest/sharedStyles/TextStyles.ts index 5fae7c04..0f1b43b3 100644 --- a/__tests__/jest/sharedStyles/TextStyles.ts +++ b/__tests__/jest/sharedStyles/TextStyles.ts @@ -1,3 +1,5 @@ +import NodeMacOSBridge from '../../../src/platformBridges/NodeMacOSBridge'; + /* eslint-disable global-require */ let TextStyles; let doc; @@ -7,7 +9,8 @@ beforeEach(() => { jest.resetModules(); jest.mock('../../../src/utils/getSketchVersion', () => ({ - getSketchVersion: jest.fn(() => 51), + __esModule: true, + default: jest.fn(() => '51'), })); TextStyles = require('../../../src/sharedStyles/TextStyles'); @@ -16,12 +19,7 @@ beforeEach(() => { jest.mock('../../../src/utils/sharedTextStyles'); - jest.mock('../../../src/jsonUtils/sketchImpl/createStringMeasurer'); - jest.mock('../../../src/jsonUtils/sketchImpl/findFontName'); - jest.mock('../../../src/jsonUtils/sketchImpl/makeImageDataFromUrl'); - jest.mock('../../../src/jsonUtils/sketchImpl/makeSvgLayer'); - - TextStyles = TextStyles.default; + TextStyles = TextStyles.default(() => NodeMacOSBridge); sharedTextStyles = sharedTextStyles.default; sharedTextStyles.setDocument = jest.fn(doc => { @@ -115,41 +113,32 @@ describe('create', () => { }); it('only stores text attributes', () => { - const whitelist = [ - 'color', - 'fontFamily', - 'fontSize', - 'fontStyle', - 'fontWeight', - 'textShadowOffset', - 'textShadowRadius', - 'textShadowColor', - 'textTransform', - 'letterSpacing', - 'lineHeight', - 'textAlign', - 'writingDirection', - ]; - - const blacklist = ['foo', 'bar', 'baz']; - - const input = [...whitelist, ...blacklist].reduce( - (acc, key) => ({ - ...acc, - [key]: true, - }), - {}, - ); + const whitelist = { + color: 'red', + fontFamily: 'Helvetica', + fontSize: 14, + fontStyle: 'italic', + fontWeight: 'bold', + textShadowOffset: 2, + textShadowRadius: 1, + textShadowColor: 'black', + textTransform: 'uppercase', + letterSpacing: 1, + lineHeight: 18, + textAlign: 'left', + writingDirection: 'ltr', + }; - const res = TextStyles.create({ foo: input }, { document: doc }); + const blacklist = { foo: 1, bar: 2, baz: 3 }; + const res = TextStyles.create({ foo: { ...whitelist, ...blacklist } }, { document: doc }); const firstStoredStyle = res[Object.keys(res)[0]].cssStyle; - whitelist.forEach(key => { - expect(firstStoredStyle).toHaveProperty(key, true); + Object.keys(whitelist).forEach(key => { + expect(firstStoredStyle).toHaveProperty(key, whitelist[key]); }); - blacklist.forEach(key => { + Object.keys(blacklist).forEach(key => { expect(firstStoredStyle).not.toHaveProperty(key); }); }); diff --git a/src/index.node.ts b/src/index.ts similarity index 100% rename from src/index.node.ts rename to src/index.ts diff --git a/src/jsonUtils/makeSvgLayer/index.ts b/src/jsonUtils/makeSvgLayer/index.ts index 31f97846..d61cb20d 100644 --- a/src/jsonUtils/makeSvgLayer/index.ts +++ b/src/jsonUtils/makeSvgLayer/index.ts @@ -2,4 +2,4 @@ import isRunningInSketch from '../../utils/isRunningInSketch'; import sketchMakeSvgLayer from './makeSvgLayer.sketch'; import pureJsSketchMakeSvgLayer from './makeSvgLayer'; -export default isRunningInSketch() ? pureJsSketchMakeSvgLayer : sketchMakeSvgLayer; +export default isRunningInSketch() ? sketchMakeSvgLayer : pureJsSketchMakeSvgLayer; diff --git a/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts b/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts index f6ea8a34..3d22b18b 100644 --- a/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts +++ b/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts @@ -83,7 +83,7 @@ export default async function makeSvgLayer( svg: string, ): Promise { // Load the module only if it has been made available through another import. - const svgModel = await import(/* webpackMode: "weak" */ '@lona/svg-model'); + const svgModel = (await import(/* webpackMode: "weak" */ '@lona/svg-model')).default; const { data: { params, children }, From 9c8268743fa090ce1c7159c57017dae6559e2639 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 17:28:17 +0100 Subject: [PATCH 19/46] Add multiple entrypoints in package.json --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 7a2ee752..7d26978b 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "3.0.5", "description": "A React renderer for Sketch.app", "main": "lib/index.js", + "module": "lib/module/index.js", + "sketch": "lib/module/index.sketch.js", "types": "lib/index.d.ts", "license": "MIT", "author": "Jon Gold (http://jon.gold)", From b267fcd2c913f0fc890c0c5f2933f85020117865 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 20:47:59 +0100 Subject: [PATCH 20/46] Add the node-fetch dependency --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 7d26978b..bbbea254 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "invariant": "^2.2.2", "js-sha1": "^0.6.0", "murmur2js": "^1.0.0", + "node-fetch": "^2.6.0", "node-sketch-bridge": "^0.2.0", "normalize-css-color": "^1.0.1", "pegjs": "^0.10.0", From 8e6b4853345a6b109a247e3d0bc1819e03338c05 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 21:31:23 +0100 Subject: [PATCH 21/46] Revert targeting es5 --- tsconfig.module.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.module.json b/tsconfig.module.json index 795ba395..6159976f 100644 --- a/tsconfig.module.json +++ b/tsconfig.module.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig", "compilerOptions": { - "target": "es2017", + "target": "es5", "outDir": "lib/module", "module": "esnext" }, From 7bacf274b7ef4988745f0f269fdc92ce854f9359 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 22 Jan 2020 21:31:47 +0100 Subject: [PATCH 22/46] Update @skpm/builder everywhere --- examples/basic-setup-typescript/package.json | 2 +- examples/basic-setup/package.json | 2 +- examples/basic-svg/package.json | 2 +- examples/colors/package.json | 2 +- examples/emotion/package.json | 2 +- examples/form-validation/package.json | 2 +- examples/foursquare-maps/package.json | 2 +- examples/glamorous/package.json | 2 +- examples/profile-cards-graphql/package.json | 2 +- examples/profile-cards-primitives/package.json | 2 +- examples/profile-cards-react-with-styles/package.json | 2 +- examples/profile-cards/package.json | 6 ++++-- examples/styled-components/package.json | 2 +- examples/styleguide/package.json | 2 +- examples/symbols/package.json | 2 +- examples/timeline-airtable/package.json | 2 +- 16 files changed, 19 insertions(+), 17 deletions(-) diff --git a/examples/basic-setup-typescript/package.json b/examples/basic-setup-typescript/package.json index 09f092ed..ee5d4464 100644 --- a/examples/basic-setup-typescript/package.json +++ b/examples/basic-setup-typescript/package.json @@ -19,7 +19,7 @@ "author": "Jon Gold ", "license": "MIT", "devDependencies": { - "@skpm/builder": "^0.4.0", + "@skpm/builder": "^0.7.5", "@types/chroma-js": "^1.3.3", "typescript": "^3.7.2" }, diff --git a/examples/basic-setup/package.json b/examples/basic-setup/package.json index 05d61df6..50c10fc1 100644 --- a/examples/basic-setup/package.json +++ b/examples/basic-setup/package.json @@ -16,7 +16,7 @@ "author": "Jon Gold ", "license": "MIT", "devDependencies": { - "@skpm/builder": "^0.7.4" + "@skpm/builder": "^0.7.5" }, "dependencies": { "chroma-js": "^1.2.2", diff --git a/examples/basic-svg/package.json b/examples/basic-svg/package.json index f7dc18a2..7c4ed1a9 100644 --- a/examples/basic-svg/package.json +++ b/examples/basic-svg/package.json @@ -16,7 +16,7 @@ "author": "Jon Gold ", "license": "MIT", "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" }, "dependencies": { "prop-types": "^15.5.8", diff --git a/examples/colors/package.json b/examples/colors/package.json index 2fe1c2d2..f5f5070d 100644 --- a/examples/colors/package.json +++ b/examples/colors/package.json @@ -25,6 +25,6 @@ "webpack-shell-plugin": "^0.5.0" }, "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" } } diff --git a/examples/emotion/package.json b/examples/emotion/package.json index eab42181..ce3a79c7 100644 --- a/examples/emotion/package.json +++ b/examples/emotion/package.json @@ -15,7 +15,7 @@ "author": "Nitin Tulswani ", "license": "MIT", "devDependencies": { - "@skpm/builder": "^0.4.3" + "@skpm/builder": "^0.7.5" }, "dependencies": { "emotion-primitives": "^1.0.0-beta.6", diff --git a/examples/form-validation/package.json b/examples/form-validation/package.json index 3b11fc62..3c6d7473 100644 --- a/examples/form-validation/package.json +++ b/examples/form-validation/package.json @@ -28,6 +28,6 @@ "devDependencies": { "extract-text-webpack-plugin": "^2.1.0", "nwb": "^0.15.6", - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" } } diff --git a/examples/foursquare-maps/package.json b/examples/foursquare-maps/package.json index 86693d4f..2a299491 100644 --- a/examples/foursquare-maps/package.json +++ b/examples/foursquare-maps/package.json @@ -29,6 +29,6 @@ "react-test-renderer": "^16.3.2" }, "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" } } diff --git a/examples/glamorous/package.json b/examples/glamorous/package.json index e12a0519..c0f60094 100644 --- a/examples/glamorous/package.json +++ b/examples/glamorous/package.json @@ -16,7 +16,7 @@ "author": "Nitin Tulswani ", "license": "MIT", "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" }, "dependencies": { "chroma-js": "^1.3.4", diff --git a/examples/profile-cards-graphql/package.json b/examples/profile-cards-graphql/package.json index a5784216..157801d6 100644 --- a/examples/profile-cards-graphql/package.json +++ b/examples/profile-cards-graphql/package.json @@ -27,6 +27,6 @@ "react-test-renderer": "^16.3.2" }, "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" } } diff --git a/examples/profile-cards-primitives/package.json b/examples/profile-cards-primitives/package.json index 4f8d8b9f..19ea6f3e 100644 --- a/examples/profile-cards-primitives/package.json +++ b/examples/profile-cards-primitives/package.json @@ -25,6 +25,6 @@ "react-test-renderer": "^16.3.2" }, "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" } } diff --git a/examples/profile-cards-react-with-styles/package.json b/examples/profile-cards-react-with-styles/package.json index 4797f233..be130ad5 100644 --- a/examples/profile-cards-react-with-styles/package.json +++ b/examples/profile-cards-react-with-styles/package.json @@ -21,6 +21,6 @@ "react-with-styles": "^1.4.0" }, "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" } } diff --git a/examples/profile-cards/package.json b/examples/profile-cards/package.json index cb09b7de..b296f983 100644 --- a/examples/profile-cards/package.json +++ b/examples/profile-cards/package.json @@ -10,7 +10,9 @@ "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", - "postinstall": "npm run build && skpm-link" + "postinstall": "npm run build && skpm-link", + "link": "skpm-link", + "clean": "rm -rf profile-cards.sketchplugin" }, "author": "Jon Gold ", "license": "MIT", @@ -20,6 +22,6 @@ "react-test-renderer": "^16.3.2" }, "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" } } diff --git a/examples/styled-components/package.json b/examples/styled-components/package.json index 7e6dde16..f1133286 100644 --- a/examples/styled-components/package.json +++ b/examples/styled-components/package.json @@ -16,7 +16,7 @@ "author": "Mathieu Dutour ", "license": "MIT", "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" }, "dependencies": { "chroma-js": "^1.2.2", diff --git a/examples/styleguide/package.json b/examples/styleguide/package.json index 954f2c4f..fe83f881 100644 --- a/examples/styleguide/package.json +++ b/examples/styleguide/package.json @@ -21,6 +21,6 @@ "react-test-renderer": "^16.3.2" }, "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" } } diff --git a/examples/symbols/package.json b/examples/symbols/package.json index c5b0c60c..1f1a71ab 100644 --- a/examples/symbols/package.json +++ b/examples/symbols/package.json @@ -16,7 +16,7 @@ "author": "Jon Gold ", "license": "MIT", "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" }, "dependencies": { "react": "^16.3.2", diff --git a/examples/timeline-airtable/package.json b/examples/timeline-airtable/package.json index a93a6443..fec26628 100644 --- a/examples/timeline-airtable/package.json +++ b/examples/timeline-airtable/package.json @@ -21,6 +21,6 @@ "react-test-renderer": "^16.3.2" }, "devDependencies": { - "@skpm/builder": "^0.4.0" + "@skpm/builder": "^0.7.5" } } From 48766e4269d49bb6060f15f62ede2e495201b7e8 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 23 Jan 2020 14:14:57 +0100 Subject: [PATCH 23/46] Add implementation to read from file: protocol --- src/utils/getImageDataFromURL.ts | 56 ++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/utils/getImageDataFromURL.ts b/src/utils/getImageDataFromURL.ts index 90f1ac61..845913fd 100644 --- a/src/utils/getImageDataFromURL.ts +++ b/src/utils/getImageDataFromURL.ts @@ -1,7 +1,3 @@ -/* global fetch */ -// TODO: Read data:// URLs -// TODO: Load node polyfill -// TODO: Read from FS import sha1 from 'js-sha1'; import { PlatformBridge } from '../types'; @@ -13,15 +9,6 @@ const ERROR_RESULT = { sha1: sha1(ERROR_IMAGE), }; -const readBufferFromResponse = async (response: Response): Promise => { - const arrayBuffer = await response.arrayBuffer(); - if (Buffer.isBuffer(arrayBuffer)) { - // skpm polyfill returns a Buffer instead of an ArrayBuffer - return arrayBuffer; - } - return Buffer.from(arrayBuffer); -}; - const isImage = (buffer: Buffer): boolean => { const firstByte = buffer[0]; @@ -36,6 +23,20 @@ const isImage = (buffer: Buffer): boolean => { ); }; +const fetchRemoteImage = async (url: string, { fetch }: PlatformBridge): Promise => { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`${response.status}`); + } + + const arrayBuffer = await response.arrayBuffer(); + if (Buffer.isBuffer(arrayBuffer)) { + // skpm polyfill returns a Buffer instead of an ArrayBuffer + return arrayBuffer; + } + return Buffer.from(arrayBuffer); +}; + export default async function getImageDataFromURL( bridge: PlatformBridge, url?: string, @@ -44,18 +45,25 @@ export default async function getImageDataFromURL( return ERROR_RESULT; } - const response = await bridge.fetch(url); - if (!response.ok) { - return ERROR_RESULT; - } + try { + const parsedUrl = new URL(url); + + const buffer = await (parsedUrl.protocol === 'file:' + ? bridge.readFile(parsedUrl.pathname) + : fetchRemoteImage(url, bridge)); - const buffer = await readBufferFromResponse(response); - if (!isImage(buffer)) return ERROR_RESULT; + if (!isImage(buffer)) throw new Error('Unrecognized image format'); - const data = buffer.toString('base64'); + const data = buffer.toString('base64'); - return { - data, - sha1: sha1(data), - }; + return { + data, + sha1: sha1(data), + }; + } catch (error) { + if (process.env.NODE_ENV !== 'production') + console.error(`Error while fetching '${url}':`, error); + + return ERROR_RESULT; + } } From 8d6d1ff678f1379b8598f5564beb5119e5d478e7 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 23 Jan 2020 14:15:17 +0100 Subject: [PATCH 24/46] Add TODO about moving the bridge out --- src/platformBridges/NodeMacOSBridge.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/platformBridges/NodeMacOSBridge.ts b/src/platformBridges/NodeMacOSBridge.ts index cfa31aca..9de01150 100644 --- a/src/platformBridges/NodeMacOSBridge.ts +++ b/src/platformBridges/NodeMacOSBridge.ts @@ -1,3 +1,4 @@ +// TODO: It would be better to move everything over to node-sketch-bridge import { PlatformBridge } from '../types'; import { createStringMeasurer, findFontName } from 'node-sketch-bridge'; From a0a9012329c1821f809c7655b0891aef7c27c7c8 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 23 Jan 2020 21:50:33 +0100 Subject: [PATCH 25/46] Fix types to work with strict --- package.json | 1 + src/index.sketch.ts | 4 ++-- src/index.ts | 4 ++-- src/platformBridges/NodeMacOSBridge.ts | 2 +- src/renderers/SymbolInstanceRenderer.ts | 1 + src/sharedStyles/TextStyles.ts | 2 +- src/types/lona__svg-model.d.ts | 1 + src/types/node-sketch-bridge.d.ts | 1 + src/types/skpm__fs.d.ts | 1 + 9 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 src/types/lona__svg-model.d.ts create mode 100644 src/types/node-sketch-bridge.d.ts create mode 100644 src/types/skpm__fs.d.ts diff --git a/package.json b/package.json index 52027f84..ed0cf548 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "@types/invariant": "^2.2.31", "@types/jest": "^24.9.0", "@types/node": "^13.1.8", + "@types/node-fetch": "^2.5.4", "@types/pegjs": "^0.10.1", "@types/react": "^16.9.17", "@types/react-test-renderer": "^16.9.1", diff --git a/src/index.sketch.ts b/src/index.sketch.ts index 389f5a91..531473c7 100644 --- a/src/index.sketch.ts +++ b/src/index.sketch.ts @@ -30,8 +30,8 @@ export async function renderToJSON( export async function makeSymbol( Component: React.ComponentType, - symbolProps?: string | SymbolMasterProps, - document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, + symbolProps: string | SymbolMasterProps, + document: SketchDocumentData | SketchDocument | WrappedSketchDocument | undefined, bridge: PlatformBridge = SketchBridge, ) { return _makeSymbol(Component, symbolProps, document, bridge); diff --git a/src/index.ts b/src/index.ts index 3384af43..3aad0de0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,8 +33,8 @@ export async function renderToJSON( export async function makeSymbol( Component: React.ComponentType, - symbolProps?: string | SymbolMasterProps, - document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, + symbolProps: string | SymbolMasterProps, + document: SketchDocumentData | SketchDocument | WrappedSketchDocument | undefined, bridge: PlatformBridge = getDefaultPlatformBridge(), ) { return _makeSymbol(Component, symbolProps, document, bridge); diff --git a/src/platformBridges/NodeMacOSBridge.ts b/src/platformBridges/NodeMacOSBridge.ts index 9de01150..68b5cd44 100644 --- a/src/platformBridges/NodeMacOSBridge.ts +++ b/src/platformBridges/NodeMacOSBridge.ts @@ -8,7 +8,7 @@ import { readFile as nodeReadFile } from 'fs'; const NodeMacOSBridge: PlatformBridge = { createStringMeasurer, findFontName, - fetch, + fetch: fetch as any, // call signatures are not perfectly identical, but we'll make do async readFile(path: string): Promise { return new Promise((resolve, reject) => { nodeReadFile(path, (err, data) => (err ? reject(err) : resolve(data))); diff --git a/src/renderers/SymbolInstanceRenderer.ts b/src/renderers/SymbolInstanceRenderer.ts index 2971ef4b..98b5b305 100644 --- a/src/renderers/SymbolInstanceRenderer.ts +++ b/src/renderers/SymbolInstanceRenderer.ts @@ -126,6 +126,7 @@ export default class SymbolInstanceRenderer extends SketchRenderer { const overridableLayers = extractOverrides(masterTree.layers); const overrideValues = await overridableLayers.reduce(async function inject( + this: SymbolInstanceRenderer, memo: Promise, reference: Override, ) { diff --git a/src/sharedStyles/TextStyles.ts b/src/sharedStyles/TextStyles.ts index 14ddaa05..13df6043 100644 --- a/src/sharedStyles/TextStyles.ts +++ b/src/sharedStyles/TextStyles.ts @@ -65,7 +65,7 @@ const TextStyles = (getDefaultBridge: () => PlatformBridge) => ({ const doc = getDocument(document); - if (isRunningInSketch() && parseInt(getSketchVersion()) < 50) { + if (isRunningInSketch() && doc != null && parseInt(getSketchVersion()) < 50) { doc.showMessage('💎 Requires Sketch 50+ 💎'); return {}; } diff --git a/src/types/lona__svg-model.d.ts b/src/types/lona__svg-model.d.ts new file mode 100644 index 00000000..d554b917 --- /dev/null +++ b/src/types/lona__svg-model.d.ts @@ -0,0 +1 @@ +declare module '@lona/svg-model'; diff --git a/src/types/node-sketch-bridge.d.ts b/src/types/node-sketch-bridge.d.ts new file mode 100644 index 00000000..3508fda4 --- /dev/null +++ b/src/types/node-sketch-bridge.d.ts @@ -0,0 +1 @@ +declare module 'node-sketch-bridge'; diff --git a/src/types/skpm__fs.d.ts b/src/types/skpm__fs.d.ts new file mode 100644 index 00000000..45b4e8d9 --- /dev/null +++ b/src/types/skpm__fs.d.ts @@ -0,0 +1 @@ +declare module '@skpm/fs'; From 87b175a0882f972e2ca7fdbc6c17379c9d75cdf4 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 23 Jan 2020 21:58:23 +0100 Subject: [PATCH 26/46] Use static platform switching --- ...makeSvgLayer.sketch.ts => index.sketch.ts} | 4 +- src/jsonUtils/makeSvgLayer/index.ts | 124 +++++++++++++++++- src/jsonUtils/makeSvgLayer/makeSvgLayer.ts | 123 ----------------- src/renderers/SvgRenderer.ts | 2 +- src/utils/sharedTextStyles/TextStyles.ts | 23 ---- .../{TextStyles.sketch.ts => index.sketch.ts} | 2 +- src/utils/sharedTextStyles/index.ts | 26 +++- 7 files changed, 146 insertions(+), 158 deletions(-) rename src/jsonUtils/makeSvgLayer/{makeSvgLayer.sketch.ts => index.sketch.ts} (92%) delete mode 100644 src/jsonUtils/makeSvgLayer/makeSvgLayer.ts delete mode 100644 src/utils/sharedTextStyles/TextStyles.ts rename src/utils/sharedTextStyles/{TextStyles.sketch.ts => index.sketch.ts} (98%) diff --git a/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts b/src/jsonUtils/makeSvgLayer/index.sketch.ts similarity index 92% rename from src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts rename to src/jsonUtils/makeSvgLayer/index.sketch.ts index 8d1a0e17..db93bbd6 100644 --- a/src/jsonUtils/makeSvgLayer/makeSvgLayer.sketch.ts +++ b/src/jsonUtils/makeSvgLayer/index.sketch.ts @@ -3,11 +3,11 @@ import convertSketchToJson from '../sketchJson/convertSketchToJson'; import { LayoutInfo } from '../../types'; -export default async function makeSvgLayer( +export default function makeSvgLayer( _layout: LayoutInfo, name: string, svg: string, -): Promise { +): FileFormat.Group { const svgString = NSString.stringWithString(svg); const svgData = svgString.dataUsingEncoding(NSUTF8StringEncoding); const svgImporter = MSSVGImporter.svgImporter(); diff --git a/src/jsonUtils/makeSvgLayer/index.ts b/src/jsonUtils/makeSvgLayer/index.ts index d61cb20d..62e6e4f7 100644 --- a/src/jsonUtils/makeSvgLayer/index.ts +++ b/src/jsonUtils/makeSvgLayer/index.ts @@ -1,5 +1,121 @@ -import isRunningInSketch from '../../utils/isRunningInSketch'; -import sketchMakeSvgLayer from './makeSvgLayer.sketch'; -import pureJsSketchMakeSvgLayer from './makeSvgLayer'; +import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; +import svgModel from '@lona/svg-model'; +import { LayoutInfo, ViewStyle } from '../../types'; +import { makeShapeGroup, makeShapePath } from '../shapeLayers'; +import { makeRect } from '../models'; +import { createUniformBorder } from '../borders'; +import layerGroup from '../layerGroup'; +import { makePathsFromCommands, makeLineCapStyle } from './graphics/path'; +import { unionRects, scaleRect, makeBoundingRectFromCommands, resize } from './graphics/rect'; -export default isRunningInSketch() ? sketchMakeSvgLayer : pureJsSketchMakeSvgLayer; +function makeLayerFromPathElement(pathElement: any, _parentFrame: FileFormat.Rect, scale: number) { + const { + data: { + params: { commands, style }, + }, + } = pathElement; + + // Paths are created using the original frame + const pathFrame = makeBoundingRectFromCommands(commands); + const paths = makePathsFromCommands(commands, pathFrame); + + // Scale the frame to fill the viewBox + const shapeGroupFrame = scaleRect(pathFrame, scale); + + // Each shape path has an origin of {0, 0}, since the shapeGroup layer stores the real origin, + // and we don't want to apply the origin translation twice. + const shapePathFrame = makeRect(0, 0, shapeGroupFrame.width, shapeGroupFrame.height); + + const shapePaths = paths.map(path => makeShapePath(shapePathFrame, path)); + + const viewStyle: ViewStyle = {}; + + if (style.fill) { + viewStyle.backgroundColor = style.fill; + } + + const shapeGroup = makeShapeGroup(shapeGroupFrame, shapePaths, viewStyle); + + if (style.stroke && shapeGroup.style) { + const lineCap = makeLineCapStyle(style.strokeLineCap); + const borderStyle = createUniformBorder( + style.strokeWidth * scale, + style.stroke, + 'solid', + FileFormat.BorderPosition.Center, + lineCap, + lineCap, + ); + shapeGroup.style = { ...shapeGroup.style, ...borderStyle }; + } + + return shapeGroup; +} + +function makeLayerGroup( + frame: FileFormat.Rect, + layers: ( + | FileFormat.Group + | FileFormat.Oval + | FileFormat.Polygon + | FileFormat.Rectangle + | FileFormat.ShapePath + | FileFormat.Star + | FileFormat.Triangle + | FileFormat.ShapeGroup + | FileFormat.Text + | FileFormat.SymbolMaster + | FileFormat.SymbolInstance + | FileFormat.Slice + | FileFormat.Hotspot + | FileFormat.Bitmap + )[], + name: string, +) { + const group = layerGroup(frame.x, frame.y, frame.width, frame.height, 1); + group.name = name; + group.layers = layers; + return group; +} + +export default function makeSvgLayer( + layout: LayoutInfo, + name: string, + svg: string, +): FileFormat.Group { + const { + data: { params, children }, + } = svgModel(svg); + + const { + viewBox = { + x: layout.left, + y: layout.top, + width: layout.width, + height: layout.height, + }, + preserveAspectRatio = 'xMidYMid meet', + } = params; + + const meetOrSlice = preserveAspectRatio.split(' ')[1] || 'meet'; + const resizeMode = meetOrSlice === 'meet' ? 'contain' : 'cover'; + + // Determine the rect to generate layers within + const croppedRect = resize(viewBox, layout, resizeMode); + const scale = croppedRect.width / viewBox.width; + + // The top-level frame is the union of every path within + const frame = unionRects( + ...children.map((pathElement: any) => + makeBoundingRectFromCommands(pathElement.data.params.commands), + ), + ); + + // Scale the frame to fill the viewBox + const scaledFrame = scaleRect(frame, scale); + + const layers = children.map((element: any) => + makeLayerFromPathElement(element, scaledFrame, scale), + ); + return makeLayerGroup(croppedRect, layers, name); +} diff --git a/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts b/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts deleted file mode 100644 index 054d5cee..00000000 --- a/src/jsonUtils/makeSvgLayer/makeSvgLayer.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import { LayoutInfo, ViewStyle } from '../../types'; -import { makeShapeGroup, makeShapePath } from '../shapeLayers'; -import { makeRect } from '../models'; -import { createUniformBorder } from '../borders'; -import layerGroup from '../layerGroup'; -import { makePathsFromCommands, makeLineCapStyle } from './graphics/path'; -import { unionRects, scaleRect, makeBoundingRectFromCommands, resize } from './graphics/rect'; - -function makeLayerFromPathElement(pathElement: any, _parentFrame: FileFormat.Rect, scale: number) { - const { - data: { - params: { commands, style }, - }, - } = pathElement; - - // Paths are created using the original frame - const pathFrame = makeBoundingRectFromCommands(commands); - const paths = makePathsFromCommands(commands, pathFrame); - - // Scale the frame to fill the viewBox - const shapeGroupFrame = scaleRect(pathFrame, scale); - - // Each shape path has an origin of {0, 0}, since the shapeGroup layer stores the real origin, - // and we don't want to apply the origin translation twice. - const shapePathFrame = makeRect(0, 0, shapeGroupFrame.width, shapeGroupFrame.height); - - const shapePaths = paths.map(path => makeShapePath(shapePathFrame, path)); - - const viewStyle: ViewStyle = {}; - - if (style.fill) { - viewStyle.backgroundColor = style.fill; - } - - const shapeGroup = makeShapeGroup(shapeGroupFrame, shapePaths, viewStyle); - - if (style.stroke && shapeGroup.style) { - const lineCap = makeLineCapStyle(style.strokeLineCap); - const borderStyle = createUniformBorder( - style.strokeWidth * scale, - style.stroke, - 'solid', - FileFormat.BorderPosition.Center, - lineCap, - lineCap, - ); - shapeGroup.style = { ...shapeGroup.style, ...borderStyle }; - } - - return shapeGroup; -} - -function makeLayerGroup( - frame: FileFormat.Rect, - layers: ( - | FileFormat.Group - | FileFormat.Oval - | FileFormat.Polygon - | FileFormat.Rectangle - | FileFormat.ShapePath - | FileFormat.Star - | FileFormat.Triangle - | FileFormat.ShapeGroup - | FileFormat.Text - | FileFormat.SymbolMaster - | FileFormat.SymbolInstance - | FileFormat.Slice - | FileFormat.Hotspot - | FileFormat.Bitmap - )[], - name: string, -) { - const group = layerGroup(frame.x, frame.y, frame.width, frame.height, 1); - group.name = name; - group.layers = layers; - return group; -} - -export default async function makeSvgLayer( - layout: LayoutInfo, - name: string, - svg: string, -): Promise { - // Load the module only if it has been made available through another import. - const svgModel = (await import(/* webpackMode: "weak" */ '@lona/svg-model')).default; - - const { - data: { params, children }, - } = svgModel(svg); - - const { - viewBox = { - x: layout.left, - y: layout.top, - width: layout.width, - height: layout.height, - }, - preserveAspectRatio = 'xMidYMid meet', - } = params; - - const meetOrSlice = preserveAspectRatio.split(' ')[1] || 'meet'; - const resizeMode = meetOrSlice === 'meet' ? 'contain' : 'cover'; - - // Determine the rect to generate layers within - const croppedRect = resize(viewBox, layout, resizeMode); - const scale = croppedRect.width / viewBox.width; - - // The top-level frame is the union of every path within - const frame = unionRects( - ...children.map((pathElement: any) => - makeBoundingRectFromCommands(pathElement.data.params.commands), - ), - ); - - // Scale the frame to fill the viewBox - const scaledFrame = scaleRect(frame, scale); - - const layers = children.map((element: any) => - makeLayerFromPathElement(element, scaledFrame, scale), - ); - return makeLayerGroup(croppedRect, layers, name); -} diff --git a/src/renderers/SvgRenderer.ts b/src/renderers/SvgRenderer.ts index 1ab8c78a..f6ebe5c8 100644 --- a/src/renderers/SvgRenderer.ts +++ b/src/renderers/SvgRenderer.ts @@ -82,7 +82,7 @@ export default class SvgRenderer extends ViewRenderer { layout, }); - const svgLayer = await makeSvgLayer(layout, 'Shape', svgString); + const svgLayer = makeSvgLayer(layout, 'Shape', svgString); layers.push(svgLayer); diff --git a/src/utils/sharedTextStyles/TextStyles.ts b/src/utils/sharedTextStyles/TextStyles.ts deleted file mode 100644 index 3f859aa4..00000000 --- a/src/utils/sharedTextStyles/TextStyles.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import { SketchDocument, TextStyle } from '../../types'; -import { generateID } from '../../jsonUtils/models'; - -class TextStyles { - setDocument(_doc?: SketchDocument) { - return this; - } - - setStyles(_styles: Array) { - return this; - } - - addStyle(name: string, _style: FileFormat.Style): string { - return generateID(`sharedStyle:${name}`, !!name); - } - - getStyle(_name: string, _document?: SketchDocument): TextStyle | undefined { - return undefined; - } -} - -export default TextStyles; diff --git a/src/utils/sharedTextStyles/TextStyles.sketch.ts b/src/utils/sharedTextStyles/index.sketch.ts similarity index 98% rename from src/utils/sharedTextStyles/TextStyles.sketch.ts rename to src/utils/sharedTextStyles/index.sketch.ts index 47f7037a..d0f42398 100644 --- a/src/utils/sharedTextStyles/TextStyles.sketch.ts +++ b/src/utils/sharedTextStyles/index.sketch.ts @@ -92,4 +92,4 @@ class TextStyles { } } -export default TextStyles; +export default new TextStyles(); diff --git a/src/utils/sharedTextStyles/index.ts b/src/utils/sharedTextStyles/index.ts index 0a89f5cb..5a694632 100644 --- a/src/utils/sharedTextStyles/index.ts +++ b/src/utils/sharedTextStyles/index.ts @@ -1,5 +1,23 @@ -import SketchTextStyles from './TextStyles.sketch'; -import PureJsTextStyles from './TextStyles'; -import isRunningInSketch from '../isRunningInSketch'; +import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; +import { SketchDocument, TextStyle } from '../../types'; +import { generateID } from '../../jsonUtils/models'; -export default isRunningInSketch() ? new PureJsTextStyles() : new SketchTextStyles(); +class TextStyles { + setDocument(_doc?: SketchDocument) { + return this; + } + + setStyles(_styles: Array) { + return this; + } + + addStyle(name: string, _style: FileFormat.Style): string { + return generateID(`sharedStyle:${name}`, !!name); + } + + getStyle(_name: string, _document?: SketchDocument): TextStyle | undefined { + return undefined; + } +} + +export default new TextStyles(); From 13ee09dfad53c20a414bc0e69134faedab0ea472 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 23 Jan 2020 22:00:07 +0100 Subject: [PATCH 27/46] Don't account for skpm-fetch peculiarities --- src/utils/getImageDataFromURL.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/utils/getImageDataFromURL.ts b/src/utils/getImageDataFromURL.ts index 845913fd..e5d53bbf 100644 --- a/src/utils/getImageDataFromURL.ts +++ b/src/utils/getImageDataFromURL.ts @@ -29,12 +29,7 @@ const fetchRemoteImage = async (url: string, { fetch }: PlatformBridge): Promise throw new Error(`${response.status}`); } - const arrayBuffer = await response.arrayBuffer(); - if (Buffer.isBuffer(arrayBuffer)) { - // skpm polyfill returns a Buffer instead of an ArrayBuffer - return arrayBuffer; - } - return Buffer.from(arrayBuffer); + return Buffer.from(await response.arrayBuffer()); }; export default async function getImageDataFromURL( From f88bd563915caf5dc26c442483ab9f75ccbb2a78 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 23 Jan 2020 22:03:21 +0100 Subject: [PATCH 28/46] Add npm build to Travis to check whether the types work --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index cd2687e9..93b26a60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ node_js: before_script: - npm prune script: + - npm run build # smoke-test that everything transpiles correctly - npm run test:ci # - npm run test:e2e -- --app=/Applications/Sketch.app # after_script: From f1435626e142381b9ed6555b04facb6e9d280b07 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 23 Jan 2020 23:30:17 +0100 Subject: [PATCH 29/46] Add sideEffects flag to allow tree shaking --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ed0cf548..8b456f65 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "react-sketchapp", "version": "3.1.0", "description": "A React renderer for Sketch.app", + "sideEffects": false, "main": "lib/index.js", "module": "lib/module/index.js", "sketch": "lib/module/index.sketch.js", From 87ff62804122873a56c7c8c39ad32f18fca5a648 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Sat, 25 Jan 2020 11:55:42 +0100 Subject: [PATCH 30/46] Revert changes to getSketchVersion() --- __tests__/jest/sharedStyles/TextStyles.ts | 3 +-- src/Platform.ts | 4 +--- src/sharedStyles/TextStyles.ts | 4 ++-- src/utils/getSketchVersion.ts | 9 +++++---- src/utils/isRunningInSketch.ts | 4 ++-- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/__tests__/jest/sharedStyles/TextStyles.ts b/__tests__/jest/sharedStyles/TextStyles.ts index a3fad04a..9b967ed5 100644 --- a/__tests__/jest/sharedStyles/TextStyles.ts +++ b/__tests__/jest/sharedStyles/TextStyles.ts @@ -8,8 +8,7 @@ beforeEach(() => { jest.resetModules(); jest.mock('../../../src/utils/getSketchVersion', () => ({ - __esModule: true, - default: jest.fn(() => '51'), + getSketchVersion: jest.fn(() => 51), })); TextStyles = require('../../../src/sharedStyles/TextStyles'); diff --git a/src/Platform.ts b/src/Platform.ts index f2824ca2..a5f529ec 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -1,8 +1,6 @@ -import getSketchVersion from './utils/getSketchVersion'; - const Platform = { OS: 'sketch', - Version: getSketchVersion(), + Version: 1, select: (obj: { sketch: any }) => obj.sketch, }; diff --git a/src/sharedStyles/TextStyles.ts b/src/sharedStyles/TextStyles.ts index 13df6043..86bfcc9d 100644 --- a/src/sharedStyles/TextStyles.ts +++ b/src/sharedStyles/TextStyles.ts @@ -6,7 +6,7 @@ import { TextStyle, PlatformBridge, } from '../types'; -import getSketchVersion from '../utils/getSketchVersion'; +import { getSketchVersion } from '../utils/getSketchVersion'; import isRunningInSketch from '../utils/isRunningInSketch'; import hashStyle from '../utils/hashStyle'; import { getDocument } from '../utils/getDocument'; @@ -65,7 +65,7 @@ const TextStyles = (getDefaultBridge: () => PlatformBridge) => ({ const doc = getDocument(document); - if (isRunningInSketch() && doc != null && parseInt(getSketchVersion()) < 50) { + if (isRunningInSketch() && doc != null && getSketchVersion() < 50) { doc.showMessage('💎 Requires Sketch 50+ 💎'); return {}; } diff --git a/src/utils/getSketchVersion.ts b/src/utils/getSketchVersion.ts index d38095d5..e754f282 100644 --- a/src/utils/getSketchVersion.ts +++ b/src/utils/getSketchVersion.ts @@ -1,5 +1,6 @@ -export default function getSketchVersion(): string { - return typeof NSBundle !== 'undefined' - ? NSBundle.mainBundle().infoDictionary().CFBundleShortVersionString - : ''; +export function getSketchVersion(): number | 'NodeJS' { + if (typeof NSBundle !== 'undefined') { + return parseFloat(NSBundle.mainBundle().infoDictionary().CFBundleShortVersionString); + } + return 'NodeJS'; } diff --git a/src/utils/isRunningInSketch.ts b/src/utils/isRunningInSketch.ts index 6703c9da..0d6f4eef 100644 --- a/src/utils/isRunningInSketch.ts +++ b/src/utils/isRunningInSketch.ts @@ -1,5 +1,5 @@ -import getSketchVersion from './getSketchVersion'; +import { getSketchVersion } from './getSketchVersion'; export default function isRunningInSketch() { - return getSketchVersion() !== ''; + return getSketchVersion() !== 'NodeJS'; } From 5adf6b5caf13770f0fa037a74e0ac4553ec20dfe Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Sat, 25 Jan 2020 11:57:09 +0100 Subject: [PATCH 31/46] Revert changes to logging --- src/components/Svg/TextPath.tsx | 7 +++---- src/components/Svg/Use.tsx | 7 +++---- src/jsonUtils/sketchJson/convertJsonToSketch.ts | 2 +- src/jsonUtils/sketchJson/convertSketchToJson.ts | 2 +- src/render.tsx | 2 +- src/symbol.tsx | 3 +-- src/utils/getImageDataFromURL.ts | 1 - src/utils/processTransform/parseTransformProp.ts | 2 +- 8 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/components/Svg/TextPath.tsx b/src/components/Svg/TextPath.tsx index f54c6223..4f8c466e 100755 --- a/src/components/Svg/TextPath.tsx +++ b/src/components/Svg/TextPath.tsx @@ -11,10 +11,9 @@ export default class TextPath extends React.Component { render() { if (!this.props.href || !this.props.href.match(idExpReg)) { - if (process.env.NODE_ENV !== 'production') - console.warn( - `Invalid \`href\` prop for \`TextPath\` element, expected a href like \`"#id"\`, but got: "${this.props.href}"`, - ); + console.warn( + `Invalid \`href\` prop for \`TextPath\` element, expected a href like \`"#id"\`, but got: "${this.props.href}"`, + ); } const { children, ...rest } = this.props; diff --git a/src/components/Svg/Use.tsx b/src/components/Svg/Use.tsx index c5d09cde..6decb496 100755 --- a/src/components/Svg/Use.tsx +++ b/src/components/Svg/Use.tsx @@ -22,10 +22,9 @@ export default class Use extends React.Component { const matched = href.match(idExpReg); if (!href || !matched) { - if (process.env.NODE_ENV !== 'production') - console.warn( - `Invalid \`href\` prop for \`Use\` element, expected a href like \`"#id"\`, but got: "${href}"`, - ); + console.warn( + `Invalid \`href\` prop for \`Use\` element, expected a href like \`"#id"\`, but got: "${href}"`, + ); } const { children, ...rest } = this.props; return {children}; diff --git a/src/jsonUtils/sketchJson/convertJsonToSketch.ts b/src/jsonUtils/sketchJson/convertJsonToSketch.ts index df1de098..838e1f7d 100644 --- a/src/jsonUtils/sketchJson/convertJsonToSketch.ts +++ b/src/jsonUtils/sketchJson/convertJsonToSketch.ts @@ -23,7 +23,7 @@ export default function convertJsonToSketch( ); if (err.value() !== null) { - if (process.env.NODE_ENV !== 'production') console.error(err.value()); + console.error(err.value()); throw new Error(err.value()); } diff --git a/src/jsonUtils/sketchJson/convertSketchToJson.ts b/src/jsonUtils/sketchJson/convertSketchToJson.ts index 43a7911d..91d0699d 100644 --- a/src/jsonUtils/sketchJson/convertSketchToJson.ts +++ b/src/jsonUtils/sketchJson/convertSketchToJson.ts @@ -13,7 +13,7 @@ export default function convertSketchToJson( const data = MSJSONDataArchiver.archiveStringWithRootObject_error(imm, err); if (err.value() !== null) { - if (process.env.NODE_ENV !== 'production') console.error(err.value()); + console.error(err.value()); throw new Error(err.value()); } diff --git a/src/render.tsx b/src/render.tsx index f1730a00..34415931 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -140,7 +140,7 @@ export default async function render( const layer = await renderTree(tree, nativeContainer, platformBridge); return layer; } catch (err) { - if (process.env.NODE_ENV !== 'production') console.error(err); + console.error(err); const tree = buildTree(, platformBridge); return renderContents(tree, nativeContainer, platformBridge); } diff --git a/src/symbol.tsx b/src/symbol.tsx index d6b7fb67..492868d2 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -71,8 +71,7 @@ export const injectSymbols = ( document?: SketchDocumentData | SketchDocument | WrappedSketchDocument, ) => { if (!isRunningInSketch()) { - if (process.env.NODE_ENV !== 'production') - console.error('Cannot inject symbols while Sketch is not running'); + console.error('Cannot inject symbols while Sketch is not running'); return; } diff --git a/src/utils/getImageDataFromURL.ts b/src/utils/getImageDataFromURL.ts index e5d53bbf..ee46cf34 100644 --- a/src/utils/getImageDataFromURL.ts +++ b/src/utils/getImageDataFromURL.ts @@ -56,7 +56,6 @@ export default async function getImageDataFromURL( sha1: sha1(data), }; } catch (error) { - if (process.env.NODE_ENV !== 'production') console.error(`Error while fetching '${url}':`, error); return ERROR_RESULT; diff --git a/src/utils/processTransform/parseTransformProp.ts b/src/utils/processTransform/parseTransformProp.ts index 0b3bb59a..1eaf8b6a 100644 --- a/src/utils/processTransform/parseTransformProp.ts +++ b/src/utils/processTransform/parseTransformProp.ts @@ -168,7 +168,7 @@ function appendTransform(transform: string) { const [a, c, e, b, d, f] = transformParser.parse(transform); pooledMatrix.append(a, b, c, d, e, f); } catch (e) { - if (process.env.NODE_ENV !== 'production') console.error(e); + console.error(e); } } From a6ddb651f090bb5ebfed17812bc484f2afad059c Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Sat, 25 Jan 2020 12:10:21 +0100 Subject: [PATCH 32/46] Revert adding extra scripts to examples/profile-cards --- examples/profile-cards/package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/profile-cards/package.json b/examples/profile-cards/package.json index b296f983..148e5c74 100644 --- a/examples/profile-cards/package.json +++ b/examples/profile-cards/package.json @@ -10,9 +10,7 @@ "watch": "skpm-build --watch", "render": "skpm-build --watch --run", "render:once": "skpm-build --run", - "postinstall": "npm run build && skpm-link", - "link": "skpm-link", - "clean": "rm -rf profile-cards.sketchplugin" + "postinstall": "npm run build && skpm-link" }, "author": "Jon Gold ", "license": "MIT", From 7c41cf6a6f336e28bd53a17e84aac41299314738 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Sat, 25 Jan 2020 12:17:17 +0100 Subject: [PATCH 33/46] Revert renaming the *SJON functions --- src/jsonUtils/makeSvgLayer/index.sketch.ts | 4 ++-- .../{convertJsonToSketch.ts => fromSJSON.ts} | 2 +- .../{convertSketchToJson.ts => toSJSON.ts} | 2 +- src/render.tsx | 4 ++-- src/symbol.tsx | 12 ++++++------ src/utils/sharedTextStyles/index.sketch.ts | 8 ++++---- 6 files changed, 16 insertions(+), 16 deletions(-) rename src/jsonUtils/sketchJson/{convertJsonToSketch.ts => fromSJSON.ts} (95%) rename src/jsonUtils/sketchJson/{convertSketchToJson.ts => toSJSON.ts} (92%) diff --git a/src/jsonUtils/makeSvgLayer/index.sketch.ts b/src/jsonUtils/makeSvgLayer/index.sketch.ts index db93bbd6..1f1b71d4 100644 --- a/src/jsonUtils/makeSvgLayer/index.sketch.ts +++ b/src/jsonUtils/makeSvgLayer/index.sketch.ts @@ -1,5 +1,5 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import convertSketchToJson from '../sketchJson/convertSketchToJson'; +import toSJSON from '../sketchJson/toSJSON'; import { LayoutInfo } from '../../types'; @@ -21,5 +21,5 @@ export default function makeSvgLayer( root.ungroupSingleChildDescendentGroups(); svgImporter.scale_rootGroup(svgImporter.importer().scaleValue(), root); - return convertSketchToJson(root) as FileFormat.Group; + return toSJSON(root) as FileFormat.Group; } diff --git a/src/jsonUtils/sketchJson/convertJsonToSketch.ts b/src/jsonUtils/sketchJson/fromSJSON.ts similarity index 95% rename from src/jsonUtils/sketchJson/convertJsonToSketch.ts rename to src/jsonUtils/sketchJson/fromSJSON.ts index 838e1f7d..4e1caa6c 100644 --- a/src/jsonUtils/sketchJson/convertJsonToSketch.ts +++ b/src/jsonUtils/sketchJson/fromSJSON.ts @@ -10,7 +10,7 @@ const SKETCH_HIGHEST_COMPATIBLE_VERSION = '95'; /** * Takes a Sketch JSON tree and turns it into a native object. May throw on invalid data */ -export default function convertJsonToSketch( +export default function fromSJSON( jsonTree: FileFormat.AnyLayer | FileFormat.AnyObject, version = SKETCH_HIGHEST_COMPATIBLE_VERSION, ): SketchLayer { diff --git a/src/jsonUtils/sketchJson/convertSketchToJson.ts b/src/jsonUtils/sketchJson/toSJSON.ts similarity index 92% rename from src/jsonUtils/sketchJson/convertSketchToJson.ts rename to src/jsonUtils/sketchJson/toSJSON.ts index 91d0699d..85f3e1de 100644 --- a/src/jsonUtils/sketchJson/convertSketchToJson.ts +++ b/src/jsonUtils/sketchJson/toSJSON.ts @@ -1,7 +1,7 @@ import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { SketchLayer } from '../../types'; -export default function convertSketchToJson( +export default function toSJSON( sketchObject: SketchLayer, ): FileFormat.AnyObject | FileFormat.AnyLayer | null { if (!sketchObject) { diff --git a/src/render.tsx b/src/render.tsx index 34415931..dbb530be 100644 --- a/src/render.tsx +++ b/src/render.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import convertJsonToSketch from './jsonUtils/sketchJson/convertJsonToSketch'; +import fromSJSON from './jsonUtils/sketchJson/fromSJSON'; import buildTree from './buildTree'; import flexToSketchJSON from './flexToSketchJSON'; import { resetLayer, resetDocument } from './resets'; @@ -42,7 +42,7 @@ const renderContents = async ( bridge: PlatformBridge, ): Promise => { const json = await flexToSketchJSON(tree, bridge); - const layer = convertJsonToSketch(json, '119'); + const layer = fromSJSON(json, '119'); return renderLayers([layer], container); }; diff --git a/src/symbol.tsx b/src/symbol.tsx index 492868d2..d631da2e 100644 --- a/src/symbol.tsx +++ b/src/symbol.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import convertJsonToSketch from './jsonUtils/sketchJson/convertJsonToSketch'; -import convertSketchToJson from './jsonUtils/sketchJson/convertSketchToJson'; +import fromSJSON from './jsonUtils/sketchJson/fromSJSON'; +import toSJSON from './jsonUtils/sketchJson/toSJSON'; import StyleSheet from './stylesheet'; import { generateID } from './jsonUtils/models'; import ViewStylePropTypes from './components/ViewStylePropTypes'; @@ -49,7 +49,7 @@ const getExistingSymbols = (documentData: SketchDocumentData) => { existingSymbols = msListToArray(symbolsPage.layers()) .map(x => { - const symbolJson = convertSketchToJson(x); + const symbolJson = toSJSON(x); if (!symbolJson || symbolJson._class !== 'symbolMaster') { return undefined; } @@ -95,7 +95,7 @@ export const injectSymbols = ( symbolMaster.frame.x = left; left += symbolMaster.frame.width + 20; - const newLayer = convertJsonToSketch(symbolMaster, '119'); + const newLayer = fromSJSON(symbolMaster, '119'); layers[symbolMaster.symbolID] = newLayer; }); @@ -209,7 +209,7 @@ function tryGettingSymbolMasterInDocumentByName( return undefined; } - return convertSketchToJson(symbol) as FileFormat.SymbolMaster; + return toSJSON(symbol) as FileFormat.SymbolMaster; } function tryGettingSymbolMasterInDocumentById( @@ -228,7 +228,7 @@ function tryGettingSymbolMasterInDocumentById( return undefined; } - return convertSketchToJson(symbol) as FileFormat.SymbolMaster; + return toSJSON(symbol) as FileFormat.SymbolMaster; } export const getSymbolMasterByName = ( diff --git a/src/utils/sharedTextStyles/index.sketch.ts b/src/utils/sharedTextStyles/index.sketch.ts index d0f42398..ccaa41f1 100644 --- a/src/utils/sharedTextStyles/index.sketch.ts +++ b/src/utils/sharedTextStyles/index.sketch.ts @@ -1,6 +1,6 @@ import invariant from 'invariant'; -import convertJsonToSketch from '../../jsonUtils/sketchJson/convertJsonToSketch'; -import convertSketchToJson from '../../jsonUtils/sketchJson/convertSketchToJson'; +import fromSJSON from '../../jsonUtils/sketchJson/fromSJSON'; +import toSJSON from '../../jsonUtils/sketchJson/toSJSON'; import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; import { SketchDocument, TextStyle } from '../../types'; import { generateID } from '../../jsonUtils/models'; @@ -35,7 +35,7 @@ class TextStyles { const { _document } = this; invariant(_document, 'Please provide a sketch document reference'); - const nativeStyle = convertJsonToSketch(style, '119'); + const nativeStyle = fromSJSON(style, '119'); const container = _document.documentData().layerTextStyles(); @@ -86,7 +86,7 @@ class TextStyles { return undefined; } - const style = convertSketchToJson(foundStyle) as FileFormat.Style; + const style = toSJSON(foundStyle) as FileFormat.Style; return parseTextStyle(style); } From d5a3f2618b17a2346e276d9dceca7a21a041e5ce Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Sat, 11 Jul 2020 09:38:57 +0200 Subject: [PATCH 34/46] Start documenting changes --- docs/API.md | 19 ++++++++++++++----- docs/PlatformBridges.md | 3 +++ 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 docs/PlatformBridges.md diff --git a/docs/API.md b/docs/API.md index 98bbe0c2..d3bbb115 100644 --- a/docs/API.md +++ b/docs/API.md @@ -27,7 +27,7 @@ - [`Symbols`](#symbols) - [`makeSymbol`](#makesymbolnode-name) -### `render(element, container)` +### `async render(element, container?, bridge?)` Returns the top-level rendered Sketch object or an array of Sketch objects if you use `` components. @@ -57,6 +57,10 @@ The element to render into - will be replaced. Should either be a Sketch [Docume Example: `sketch.getSelectedDocument().selectedPage`. +##### `bridge` (optional) + +An object implementing the [Platform Bridge API](/docs/PlatformBridges.md). When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. + #### Returns The top-most rendered native Sketch layer. @@ -73,12 +77,12 @@ const Document = props => ( ); -export default () => { - render(, sketch.getSelectedDocument().selectedPage); +export default async () => { + await render(, sketch.getSelectedDocument().selectedPage); }; ``` -### `renderToJSON(element)` +### `async renderToJSON(element, bridge?)` Returns a Sketch JSON object for further consumption - doesn't add to the page. @@ -88,6 +92,10 @@ Returns a Sketch JSON object for further consumption - doesn't add to the page. Top-level React component that defines your Sketch document. +##### `bridge` (optional) + +An object implementing the [Platform Bridge API](/docs/PlatformBridges.md). When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. + #### Returns The top-most Sketch layer as JSON. @@ -625,7 +633,7 @@ Reset the registered styles. An interface to Sketch's symbols. Create symbols and optionally inject them into the symbols page. -### `makeSymbol(node, props, document)` +### `makeSymbol(node, props, document, bridge?)` Creates a new symbol and injects it into the `Symbols` page. The name of the symbol can be optionally provided and will default to the display name of the component. @@ -640,6 +648,7 @@ Returns a react component which is an can be used to render instances of the sym | `props.name` | `String` | The node name | Optional name for the symbol, string can include backslashes to organize these symbols with Sketch. For example `squares/blue` | | `props.style` | [`Style`](/docs/styling.md) | | | | `document` | `Object` | The current document | The Sketch document to make the symbol in | +| `bridge` | `Object` | _platform-dependent_ | An object implementing the [Platform Bridge API](/docs/PlatformBridges.md). | ### `getSymbolComponentByName(name)` diff --git a/docs/PlatformBridges.md b/docs/PlatformBridges.md new file mode 100644 index 00000000..0ee36ae6 --- /dev/null +++ b/docs/PlatformBridges.md @@ -0,0 +1,3 @@ +# Platform Bridge API + +_TODO_ From 215adec6ad212a1c56c4ab9b8471cf7dca129cc5 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Sat, 11 Jul 2020 09:39:12 +0200 Subject: [PATCH 35/46] Update TypeScript version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b456f65..b6b7a12f 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "sketchapp-json-flow-types": "^0.3.6", "trash-cli": "^3.0.0", "ts-jest": "^24.3.0", - "typescript": "^3.7.5" + "typescript": "^3.8.3" }, "skpm": { "test": { From cadae333526fdd16a131f7fbbb58472eed9d0bc6 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 6 Jan 2021 19:33:13 +0100 Subject: [PATCH 36/46] Fix the TypeScript errors --- package.json | 1 - src/buildTree.ts | 7 + src/index.common.ts | 4 - src/index.sketch.ts | 42 ---- src/jsonUtils/computeYogaNode.ts | 1 - src/jsonUtils/textLayers.ts | 1 - src/platformBridges/NodeMacOSBridge.ts | 19 -- .../SketchBridge/createStringMeasurer.ts | 89 -------- .../SketchBridge/findFontName.ts | 196 ------------------ src/platformBridges/SketchBridge/index.ts | 13 -- src/platformBridges/macos.ts | 12 +- src/platformBridges/sketch/index.ts | 5 +- .../sketch/makeImageDataFromUrl.ts | 43 ---- .../{SketchBridge => sketch}/readFile.ts | 0 src/renderToJSON.ts | 2 +- src/renderers/ImageRenderer.ts | 4 +- src/renderers/SymbolInstanceRenderer.ts | 3 +- src/sharedStyles/TextStyles.ts | 2 +- src/types/index.ts | 12 +- 19 files changed, 31 insertions(+), 425 deletions(-) delete mode 100644 src/index.common.ts delete mode 100644 src/index.sketch.ts delete mode 100644 src/platformBridges/NodeMacOSBridge.ts delete mode 100644 src/platformBridges/SketchBridge/createStringMeasurer.ts delete mode 100644 src/platformBridges/SketchBridge/findFontName.ts delete mode 100644 src/platformBridges/SketchBridge/index.ts delete mode 100644 src/platformBridges/sketch/makeImageDataFromUrl.ts rename src/platformBridges/{SketchBridge => sketch}/readFile.ts (100%) diff --git a/package.json b/package.json index 2209962e..947aa214 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "sideEffects": false, "main": "lib/index.js", "module": "lib/module/index.js", - "sketch": "lib/module/index.sketch.js", "types": "lib/index.d.ts", "license": "MIT", "author": "Jon Gold (http://jon.gold)", diff --git a/src/buildTree.ts b/src/buildTree.ts index 246f9b9d..00e3d46c 100644 --- a/src/buildTree.ts +++ b/src/buildTree.ts @@ -109,6 +109,13 @@ export const buildTree = (bridge: PlatformBridge) => (element: React.ReactElemen if (!json) { throw new Error('Cannot render react element'); } + + if (Array.isArray(json)) { + // TODO: It should be investigated in which cases the renderer can actually return + // an array and possibly handle this better instead of bailing out + throw new Error('Unexpected array in render result'); + } + const yogaNode = computeYogaTree(bridge)(json, new Context()); yogaNode.calculateLayout(undefined, undefined, yoga.DIRECTION_LTR); const tree = reactTreeToFlexTree(json, yogaNode, new Context()); diff --git a/src/index.common.ts b/src/index.common.ts deleted file mode 100644 index 1f1b4534..00000000 --- a/src/index.common.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { default as Platform } from './Platform'; -export { default as StyleSheet } from './stylesheet'; -export * from './components'; -export { getSymbolComponentByName, getSymbolMasterByName, injectSymbols } from './symbol'; diff --git a/src/index.sketch.ts b/src/index.sketch.ts deleted file mode 100644 index 531473c7..00000000 --- a/src/index.sketch.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { default as _render } from './render'; -import { default as _renderToJSON } from './renderToJSON'; -import { makeSymbol as _makeSymbol, SymbolMasterProps } from './symbol'; -import { - SketchLayer, - WrappedSketchLayer, - PlatformBridge, - SketchDocumentData, - WrappedSketchDocument, - SketchDocument, -} from './types'; -import { FileFormat1 as FileFormat } from '@sketch-hq/sketch-file-format-ts'; -import { default as _TextStyles } from './sharedStyles/TextStyles'; -import SketchBridge from './platformBridges/SketchBridge'; - -export async function render( - element: React.ReactElement, - container?: SketchLayer | WrappedSketchLayer, - platformBridge: PlatformBridge = SketchBridge, -): Promise> { - return _render(element, container, platformBridge); -} - -export async function renderToJSON( - element: React.ReactElement, - platformBridge: PlatformBridge = SketchBridge, -): Promise { - return _renderToJSON(element, platformBridge); -} - -export async function makeSymbol( - Component: React.ComponentType, - symbolProps: string | SymbolMasterProps, - document: SketchDocumentData | SketchDocument | WrappedSketchDocument | undefined, - bridge: PlatformBridge = SketchBridge, -) { - return _makeSymbol(Component, symbolProps, document, bridge); -} - -export const TextStyles = _TextStyles(() => SketchBridge); - -export * from './index.common'; diff --git a/src/jsonUtils/computeYogaNode.ts b/src/jsonUtils/computeYogaNode.ts index 335693ef..84ad17ac 100644 --- a/src/jsonUtils/computeYogaNode.ts +++ b/src/jsonUtils/computeYogaNode.ts @@ -31,7 +31,6 @@ export const getStyles = (node: ReactTestRendererNode): ViewStyle => { export const computeYogaNode = (bridge: PlatformBridge) => ( node: ReactTestRendererNode, context: Context, - bridge: PlatformBridge, ): { node: yoga.YogaNode; stop?: boolean } => { const yogaNode = yoga.Node.create(); const hasStyle = typeof node !== 'string' && node.props && node.props.style; diff --git a/src/jsonUtils/textLayers.ts b/src/jsonUtils/textLayers.ts index 28991cda..79e7ce23 100644 --- a/src/jsonUtils/textLayers.ts +++ b/src/jsonUtils/textLayers.ts @@ -128,7 +128,6 @@ const makeAttributedString = (bridge: PlatformBridge) => ( export const makeTextStyle = (bridge: PlatformBridge) => ( style: TextStyle, shadows: (ViewStyle | undefined | null)[] | undefined | null, - bridge: PlatformBridge, ): FileFormat.Style => { const json = makeStyle(style, undefined, shadows); json.textStyle = { diff --git a/src/platformBridges/NodeMacOSBridge.ts b/src/platformBridges/NodeMacOSBridge.ts deleted file mode 100644 index 68b5cd44..00000000 --- a/src/platformBridges/NodeMacOSBridge.ts +++ /dev/null @@ -1,19 +0,0 @@ -// TODO: It would be better to move everything over to node-sketch-bridge -import { PlatformBridge } from '../types'; - -import { createStringMeasurer, findFontName } from 'node-sketch-bridge'; -import fetch from 'node-fetch'; -import { readFile as nodeReadFile } from 'fs'; - -const NodeMacOSBridge: PlatformBridge = { - createStringMeasurer, - findFontName, - fetch: fetch as any, // call signatures are not perfectly identical, but we'll make do - async readFile(path: string): Promise { - return new Promise((resolve, reject) => { - nodeReadFile(path, (err, data) => (err ? reject(err) : resolve(data))); - }); - }, -}; - -export default NodeMacOSBridge; diff --git a/src/platformBridges/SketchBridge/createStringMeasurer.ts b/src/platformBridges/SketchBridge/createStringMeasurer.ts deleted file mode 100644 index cc7519e1..00000000 --- a/src/platformBridges/SketchBridge/createStringMeasurer.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Size, TextNode, TextStyle } from '../../types'; -import { - TEXT_DECORATION_UNDERLINE, - TEXT_DECORATION_LINETHROUGH, - TEXT_ALIGN, - TEXT_TRANSFORM, -} from '../../jsonUtils/textLayers'; -import { makeColorFromCSS } from '../../jsonUtils/models'; -import { findFont } from './findFontName'; - -// TODO(lmr): do something more sensible here -const FLOAT_MAX = 999999; - -function makeParagraphStyle(textStyle: TextStyle) { - const pStyle = NSMutableParagraphStyle.alloc().init(); - if (textStyle.lineHeight !== undefined) { - pStyle.minimumLineHeight = textStyle.lineHeight; - pStyle.lineHeightMultiple = 1.0; - pStyle.maximumLineHeight = textStyle.lineHeight; - } - - if (textStyle.textAlign) { - pStyle.alignment = TEXT_ALIGN[textStyle.textAlign]; - } - - // TODO: check against only positive spacing values? - if (textStyle.paragraphSpacing !== undefined) { - pStyle.paragraphSpacing = textStyle.paragraphSpacing; - } - - return pStyle; -} - -// This shouldn't need to call into Sketch, but it does currently, which is bad for perf :( -function createStringAttributes(textStyles: TextStyle): Object { - const font = findFont(textStyles); - const { textDecoration } = textStyles; - - const underline = textDecoration && TEXT_DECORATION_UNDERLINE[textDecoration]; - const strikethrough = textDecoration && TEXT_DECORATION_LINETHROUGH[textDecoration]; - - const attribs: any = { - MSAttributedStringFontAttribute: font.fontDescriptor(), - NSFont: font, - NSParagraphStyle: makeParagraphStyle(textStyles), - NSUnderline: underline || 0, - NSStrikethrough: strikethrough || 0, - }; - - const color = makeColorFromCSS(textStyles.color || 'black'); - attribs.MSAttributedStringColorAttribute = color; - - if (textStyles.letterSpacing !== undefined && textStyles.letterSpacing !== null) { - attribs.NSKern = textStyles.letterSpacing; - } - - if (textStyles.textTransform !== undefined && textStyles.textTransform !== null) { - attribs.MSAttributedStringTextTransformAttribute = TEXT_TRANSFORM[textStyles.textTransform] * 1; - } - - return attribs; -} - -type NSAttributedString = any; - -function createAttributedString(textNode: TextNode): NSAttributedString { - const { content, textStyles } = textNode; - - const attribs = createStringAttributes(textStyles); - - return NSAttributedString.attributedStringWithString_attributes_(content, attribs); -} - -export function createStringMeasurer(textNodes: TextNode[], width: number): Size { - const fullStr = NSMutableAttributedString.alloc().init(); - textNodes.forEach((textNode) => { - const newString = createAttributedString(textNode); - fullStr.appendAttributedString(newString); - }); - const { - height: measureHeight, - width: measureWidth, - } = fullStr.boundingRectWithSize_options_context( - CGSizeMake(width, FLOAT_MAX), - NSStringDrawingUsesLineFragmentOrigin, - null, - ).size; - return { width: measureWidth, height: measureHeight }; -} diff --git a/src/platformBridges/SketchBridge/findFontName.ts b/src/platformBridges/SketchBridge/findFontName.ts deleted file mode 100644 index f8ef078a..00000000 --- a/src/platformBridges/SketchBridge/findFontName.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { hashStyle } from '../../utils/hashStyle'; -import { TextStyle } from '../../types'; -import { FONT_STYLES } from '../../jsonUtils/textLayers'; - -// this borrows heavily from react-native's RCTFont class -// thanks y'all -// https://github.com/facebook/react-native/blob/master/React/Views/RCTFont.mm - -const FONT_WEIGHTS: { [key: string]: number } = { - ultralight: -0.8, - '100': -0.8, - thin: -0.6, - '200': -0.6, - light: -0.4, - '300': -0.4, - normal: 0, - regular: 0, - '400': 0, - semibold: 0.23, - demibold: 0.23, - '500': 0.23, - '600': 0.3, - bold: 0.4, - '700': 0.4, - extrabold: 0.56, - ultrabold: 0.56, - heavy: 0.56, - '800': 0.56, - black: 0.62, - '900': 0.62, -}; - -type NSFont = any; - -const isItalicFont = (font: NSFont): boolean => { - const traits = font.fontDescriptor().objectForKey(NSFontTraitsAttribute); - const symbolicTraits = traits[NSFontSymbolicTrait].unsignedIntValue(); - - return (symbolicTraits & NSFontItalicTrait) !== 0; -}; - -const isCondensedFont = (font: NSFont): boolean => { - const traits = font.fontDescriptor().objectForKey(NSFontTraitsAttribute); - const symbolicTraits = traits[NSFontSymbolicTrait].unsignedIntValue(); - - return (symbolicTraits & NSFontCondensedTrait) !== 0; -}; - -const weightOfFont = (font: NSFont): number => { - const traits = font.fontDescriptor().objectForKey(NSFontTraitsAttribute); - - const weight = traits[NSFontWeightTrait].doubleValue(); - if (weight === 0.0) { - const weights = Object.keys(FONT_WEIGHTS); - const fontName = String(font.fontName()).toLowerCase(); - const matchingWeight = weights.find((w) => fontName.endsWith(w)); - if (matchingWeight) { - return FONT_WEIGHTS[matchingWeight]; - } - } - - return weight; -}; - -const fontNamesForFamilyName = (familyName: string): Array => { - const manager = NSFontManager.sharedFontManager(); - const members = NSArray.arrayWithArray(manager.availableMembersOfFontFamily(familyName)); - - const results = []; - for (let i = 0; i < members.length; i += 1) { - results.push(members[i][0]); - } - - return results; -}; - -const useCache = true; -const _cache: Map = new Map(); - -const getCached = (key: string): NSFont => { - if (!useCache) return undefined; - return _cache.get(key); -}; - -export const findFont = (style: TextStyle): NSFont => { - const cacheKey = hashStyle(style); - - let font = getCached(cacheKey); - if (font) { - return font; - } - const defaultFontFamily = NSFont.systemFontOfSize(14).familyName(); - const defaultFontWeight = NSFontWeightRegular; - const defaultFontSize = 14; - - const fontSize = style.fontSize ? style.fontSize : defaultFontSize; - let fontWeight = FONT_WEIGHTS[String(style.fontWeight).toLowerCase()] || defaultFontWeight; - - let familyName = defaultFontFamily; - let isItalic = false; - let isCondensed = false; - - if (style.fontFamily) { - familyName = style.fontFamily; - } - - if (style.fontStyle) { - isItalic = FONT_STYLES[style.fontStyle] || false; - } - - let didFindFont = false; - - // Handle system font as special case. This ensures that we preserve - // the specific metrics of the standard system font as closely as possible. - if (familyName === defaultFontFamily || familyName === 'System') { - font = NSFont.systemFontOfSize_weight(fontSize, fontWeight); - - if (font) { - didFindFont = true; - - if (isItalic || isCondensed) { - let fontDescriptor = font.fontDescriptor(); - let symbolicTraits = fontDescriptor.symbolicTraits(); - if (isItalic) { - symbolicTraits |= NSFontItalicTrait; - } - - if (isCondensed) { - symbolicTraits |= NSFontCondensedTrait; - } - - fontDescriptor = fontDescriptor.fontDescriptorWithSymbolicTraits(symbolicTraits); - font = NSFont.fontWithDescriptor_size(fontDescriptor, fontSize); - } - } - } - - const fontNames = fontNamesForFamilyName(familyName); - - // Gracefully handle being given a font name rather than font family, for - // example: "Helvetica Light Oblique" rather than just "Helvetica". - if (!didFindFont && fontNames.length === 0) { - font = NSFont.fontWithName_size(familyName, fontSize); - if (font) { - // It's actually a font name, not a font family name, - // but we'll do what was meant, not what was said. - familyName = font.familyName(); - fontWeight = style.fontWeight ? fontWeight : weightOfFont(font); - isItalic = style.fontStyle ? isItalic : isItalicFont(font); - isCondensed = isCondensedFont(font); - } else { - font = NSFont.systemFontOfSize_weight(fontSize, fontWeight); - } - - didFindFont = true; - } - - if (!didFindFont) { - // Get the closest font that matches the given weight for the fontFamily - let closestWeight = Infinity; - for (let i = 0; i < fontNames.length; i += 1) { - const match = NSFont.fontWithName_size(fontNames[i], fontSize); - - if (isItalic === isItalicFont(match) && isCondensed === isCondensedFont(match)) { - const testWeight = weightOfFont(match); - - if (Math.abs(testWeight - fontWeight) < Math.abs(closestWeight - fontWeight)) { - font = match; - - closestWeight = testWeight; - } - } - } - } - - // If we still don't have a match at least return the first font in the fontFamily - // This is to support built-in font Zapfino and other custom single font families like Impact - if (!font) { - if (fontNames.length > 0) { - font = NSFont.fontWithName_size(fontNames[0], fontSize); - } - } - - // TODO(gold): support opentype features: small-caps & number types - - if (font) { - _cache.set(cacheKey, font); - } - - return font; -}; - -export function findFontName(style: TextStyle): string { - const font = findFont(style); - return String(font.fontDescriptor().postscriptName()); -} diff --git a/src/platformBridges/SketchBridge/index.ts b/src/platformBridges/SketchBridge/index.ts deleted file mode 100644 index bc15e5db..00000000 --- a/src/platformBridges/SketchBridge/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PlatformBridge } from '../../types'; -import createStringMeasurer from './createStringMeasurer'; -import findFontName from './findFontName'; -import readFile from './readFile'; - -const SketchBridge: PlatformBridge = { - createStringMeasurer, - findFontName, - fetch, - readFile, -}; - -export default SketchBridge; diff --git a/src/platformBridges/macos.ts b/src/platformBridges/macos.ts index 6cdb85e2..68b5cd44 100644 --- a/src/platformBridges/macos.ts +++ b/src/platformBridges/macos.ts @@ -1,11 +1,19 @@ +// TODO: It would be better to move everything over to node-sketch-bridge import { PlatformBridge } from '../types'; -import { createStringMeasurer, findFontName, makeImageDataFromUrl } from 'node-sketch-bridge'; +import { createStringMeasurer, findFontName } from 'node-sketch-bridge'; +import fetch from 'node-fetch'; +import { readFile as nodeReadFile } from 'fs'; const NodeMacOSBridge: PlatformBridge = { createStringMeasurer, findFontName, - makeImageDataFromUrl, + fetch: fetch as any, // call signatures are not perfectly identical, but we'll make do + async readFile(path: string): Promise { + return new Promise((resolve, reject) => { + nodeReadFile(path, (err, data) => (err ? reject(err) : resolve(data))); + }); + }, }; export default NodeMacOSBridge; diff --git a/src/platformBridges/sketch/index.ts b/src/platformBridges/sketch/index.ts index 54135f13..4824b2e3 100644 --- a/src/platformBridges/sketch/index.ts +++ b/src/platformBridges/sketch/index.ts @@ -1,12 +1,13 @@ import { PlatformBridge } from '../../types'; import { createStringMeasurer } from './createStringMeasurer'; import { findFontName } from './findFontName'; -import { makeImageDataFromUrl } from './makeImageDataFromUrl'; +import readFile from './readFile'; const SketchBridge: PlatformBridge = { createStringMeasurer, findFontName, - makeImageDataFromUrl, + fetch, + readFile, }; export default SketchBridge; diff --git a/src/platformBridges/sketch/makeImageDataFromUrl.ts b/src/platformBridges/sketch/makeImageDataFromUrl.ts deleted file mode 100644 index 440bdeef..00000000 --- a/src/platformBridges/sketch/makeImageDataFromUrl.ts +++ /dev/null @@ -1,43 +0,0 @@ -export function makeImageDataFromUrl(url?: string) { - let fetchedData = url ? NSData.dataWithContentsOfURL(NSURL.URLWithString(url)) : undefined; - - if (fetchedData) { - const firstByte = String( - NSString.alloc().initWithData_encoding(fetchedData, NSISOLatin1StringEncoding), - ).charCodeAt(0); - - // Check for first byte to see if we have an image. - // 0xFF = JPEG, 0x89 = PNG, 0x47 = GIF, 0x49 = TIFF, 0x4D = TIFF - if ( - firstByte !== 0xff && - firstByte !== 0x89 && - firstByte !== 0x47 && - firstByte !== 0x49 && - firstByte !== 0x4d - ) { - fetchedData = null; - } - } - - let image: any; - - if (!fetchedData) { - const errorUrl = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8w8DwHwAEOQHNmnaaOAAAAABJRU5ErkJggg=='; - image = NSImage.alloc().initWithContentsOfURL(NSURL.URLWithString(errorUrl)); - } else { - image = NSImage.alloc().initWithData(fetchedData); - } - - let imageData: any; - - if (MSImageData.alloc().initWithImage_convertColorSpace !== undefined) { - imageData = MSImageData.alloc().initWithImage_convertColorSpace(image, false); - } else { - imageData = MSImageData.alloc().initWithImage(image); - } - - return String( - imageData.data().base64EncodedStringWithOptions(NSDataBase64EncodingEndLineWithCarriageReturn), - ); -} diff --git a/src/platformBridges/SketchBridge/readFile.ts b/src/platformBridges/sketch/readFile.ts similarity index 100% rename from src/platformBridges/SketchBridge/readFile.ts rename to src/platformBridges/sketch/readFile.ts diff --git a/src/renderToJSON.ts b/src/renderToJSON.ts index 3a0d44c4..56ae454e 100644 --- a/src/renderToJSON.ts +++ b/src/renderToJSON.ts @@ -4,7 +4,7 @@ import { buildTree } from './buildTree'; import { flexToSketchJSON } from './flexToSketchJSON'; import * as React from 'react'; -export const renderToJSON = async (platformBridge: PlatformBridge) => ( +export const renderToJSON = (platformBridge: PlatformBridge) => async ( element: React.ReactElement, ): Promise => { const tree = buildTree(platformBridge)(element); diff --git a/src/renderers/ImageRenderer.ts b/src/renderers/ImageRenderer.ts index 8667313b..d45779ee 100644 --- a/src/renderers/ImageRenderer.ts +++ b/src/renderers/ImageRenderer.ts @@ -15,7 +15,7 @@ function extractURLFromSource(source?: string | { uri?: string } | null): string return (source || {}).uri; } -export default class ImageRenderer extends SketchRenderer { +export class ImageRenderer extends SketchRenderer { async renderBackingLayers({ layout, style, @@ -32,7 +32,7 @@ export default class ImageRenderer extends SketchRenderer { const url = extractURLFromSource(props.source); - const image = await getImageDataFromURL(this.platformBridge, url); + const image = await getImageDataFromURL(this.platformBridge)(url); const fillImage = makeJSONDataReference(image); diff --git a/src/renderers/SymbolInstanceRenderer.ts b/src/renderers/SymbolInstanceRenderer.ts index e773fe68..882aa30b 100644 --- a/src/renderers/SymbolInstanceRenderer.ts +++ b/src/renderers/SymbolInstanceRenderer.ts @@ -102,7 +102,6 @@ export class SymbolInstanceRenderer extends SketchRenderer { layout, props, }: TreeNode) { - const bridge = this.platformBridge; const masterTree = getSymbolMasterById(props.symbolID); if (!masterTree) { @@ -203,7 +202,7 @@ export class SymbolInstanceRenderer extends SketchRenderer { makeOverride( reference.path, reference.type, - makeJSONDataReference(await getImageDataFromURL(this.platformBridge, overrideValue)), + makeJSONDataReference(await getImageDataFromURL(this.platformBridge)(overrideValue)), ), ); } diff --git a/src/sharedStyles/TextStyles.ts b/src/sharedStyles/TextStyles.ts index a3c952ef..93b1966c 100644 --- a/src/sharedStyles/TextStyles.ts +++ b/src/sharedStyles/TextStyles.ts @@ -42,7 +42,7 @@ export const TextStyles = (getDefaultBridge: () => PlatformBridge) => ({ ) { const safeStyle = pick(style, INHERITABLE_FONT_STYLES); const hash = hashStyle(safeStyle); - const sketchStyle = makeTextStyle(safeStyle, undefined, platformBridge); + const sketchStyle = makeTextStyle(platformBridge)(safeStyle, undefined); const sharedObjectID = sharedTextStyles.addStyle(name, sketchStyle); // FIXME(gold): side effect :'( diff --git a/src/types/index.ts b/src/types/index.ts index f3f84e53..97ad6d6f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -113,12 +113,12 @@ export type TreeNode = { }; export type ResizeConstraints = { - top?: boolean; - right?: boolean; - bottom?: boolean; - left?: boolean; - fixedHeight?: boolean; - fixedWidth?: boolean; + top?: boolean | null; + right?: boolean | null; + bottom?: boolean | null; + left?: boolean | null; + fixedHeight?: boolean | null; + fixedWidth?: boolean | null; }; export type PlatformBridge = { From bc20d9d3d21193156f3ef8919edeb8f17b473f8b Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 6 Jan 2021 19:47:34 +0100 Subject: [PATCH 37/46] Update API docs --- docs/API.md | 8 +++++--- docs/PlatformBridges.md | 3 --- 2 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 docs/PlatformBridges.md diff --git a/docs/API.md b/docs/API.md index f05fdf3d..78602f6d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -61,7 +61,7 @@ Example: `sketch.getSelectedDocument().selectedPage`. ##### `bridge` (optional) -An object implementing the [Platform Bridge API](/docs/PlatformBridges.md). When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. +An object implementing the [Platform Bridge API][platform-bridge-api]. When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. #### Returns @@ -96,7 +96,7 @@ Top-level React component that defines your Sketch document. ##### `bridge` (optional) -An object implementing the [Platform Bridge API](/docs/PlatformBridges.md). When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. +An object implementing the [Platform Bridge API][platform-bridge-api]. When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. #### Returns @@ -707,7 +707,7 @@ Returns a react component which is an can be used to render instances of the sym | `props.name` | `String` | The node name | Optional name for the symbol, string can include backslashes to organize these symbols with Sketch. For example `squares/blue` | | `props.style` | [`Style`](/docs/styling.md) | | | | `document` | `Object` | The current document | The Sketch document to make the symbol in | -| `bridge` | `Object` | _platform-dependent_ | An object implementing the [Platform Bridge API](/docs/PlatformBridges.md). | +| `bridge` | `Object` | _platform-dependent_ | An object implementing the [Platform Bridge API][platform-bridge-api]. | ### `getSymbolComponentByName(name)` @@ -861,3 +861,5 @@ export default () => { render(, sketch.getSelectedDocument().selectedPage); }; ``` + +[platform-bridge-api]: ./src/types/index.ts#L124-130 diff --git a/docs/PlatformBridges.md b/docs/PlatformBridges.md deleted file mode 100644 index 0ee36ae6..00000000 --- a/docs/PlatformBridges.md +++ /dev/null @@ -1,3 +0,0 @@ -# Platform Bridge API - -_TODO_ From c0b681dc95c2c6f100f8135ba54c9a5ea740f59a Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 6 Jan 2021 20:00:15 +0100 Subject: [PATCH 38/46] Clean up left-overs of refactors already implemented --- src/jsonUtils/textLayers.ts | 1 - src/renderers/TextRenderer.ts | 1 - src/types/lona__svg-model.d.ts | 1 - src/utils/isNaN.ts | 6 ------ src/utils/measureString.ts | 14 -------------- tsconfig.json | 4 ++-- 6 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 src/types/lona__svg-model.d.ts delete mode 100644 src/utils/isNaN.ts delete mode 100644 src/utils/measureString.ts diff --git a/src/jsonUtils/textLayers.ts b/src/jsonUtils/textLayers.ts index 79e7ce23..0e83a4a9 100644 --- a/src/jsonUtils/textLayers.ts +++ b/src/jsonUtils/textLayers.ts @@ -208,7 +208,6 @@ export const makeTextLayer = (bridge: PlatformBridge) => ( _style: ViewStyle, resizingConstraint: ResizeConstraints | undefined | null, shadows: (ViewStyle | undefined | null)[] | undefined | null, - bridge: PlatformBridge, ): FileFormat.Text => ({ _class: 'text', do_objectID: generateID(`text:${name}-${textNodes.map((node) => node.content).join('')}`), diff --git a/src/renderers/TextRenderer.ts b/src/renderers/TextRenderer.ts index a15b477c..954e702a 100644 --- a/src/renderers/TextRenderer.ts +++ b/src/renderers/TextRenderer.ts @@ -24,7 +24,6 @@ export class TextRenderer extends SketchRenderer { style, props.resizingConstraint, props.shadows, - this.platformBridge, ); const resolvedTextStyle = TextStyles(() => this.platformBridge).resolve(textStyle); diff --git a/src/types/lona__svg-model.d.ts b/src/types/lona__svg-model.d.ts deleted file mode 100644 index d554b917..00000000 --- a/src/types/lona__svg-model.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module '@lona/svg-model'; diff --git a/src/utils/isNaN.ts b/src/utils/isNaN.ts deleted file mode 100644 index e11383ec..00000000 --- a/src/utils/isNaN.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default function isNaN(num: number): boolean { - // If the value is obj-c NaN, the only way to check for it is by comparing - // width to itself (because NaN !== NaN) - // eslint-disable-next-line no-self-compare - return Number.isNaN(num) || num !== num; -} diff --git a/src/utils/measureString.ts b/src/utils/measureString.ts deleted file mode 100644 index 3847c1e4..00000000 --- a/src/utils/measureString.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { TextNode, Size, PlatformBridge } from '../types'; -import isNaN from './isNaN'; - -export default function measureString( - bridge: PlatformBridge, - textNodes: TextNode[], - width: number, -): Size { - const sanitizedWidth = isNaN(width) ? 0 : width; - - return textNodes.length > 0 - ? bridge.createStringMeasurer(textNodes, sanitizedWidth) - : { width: sanitizedWidth, height: 0 }; -} diff --git a/tsconfig.json b/tsconfig.json index 7a12bc88..99dd0b5e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,9 +8,9 @@ "declaration": true, "inlineSourceMap": false, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, - "strict": true /* Enable all strict type-checking options. */, - + "noUnusedLocals": true, + "noUnusedParameters": true, "lib": ["es5", "dom"], "types": ["jest", "node"], "typeRoots": ["node_modules/@types", "src/types"], From 8708fc2776fdd964745c4f81c9e0f97903501350 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 6 Jan 2021 20:11:12 +0100 Subject: [PATCH 39/46] Correct the link to the `PlatformBridge` type --- docs/API.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API.md b/docs/API.md index 78602f6d..17918efc 100644 --- a/docs/API.md +++ b/docs/API.md @@ -862,4 +862,4 @@ export default () => { }; ``` -[platform-bridge-api]: ./src/types/index.ts#L124-130 +[platform-bridge-api]: ../src/types/index.ts#L124-130 From 55b2c3d4cecb4091cc3af28ce11e2878f2b3bebb Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 6 Jan 2021 20:26:59 +0100 Subject: [PATCH 40/46] Disable node_modules cache --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 93b26a60..87adee70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ os: osx language: node_js -cache: - directories: - - node_modules +# cache: +# directories: +# - node_modules # - $HOME/Library/Caches/Homebrew notifications: email: false From 9cbae7b733a6296a5f28c3b58e697483bd759332 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 6 Jan 2021 20:32:13 +0100 Subject: [PATCH 41/46] Forcibly clean the cached node_modules --- .travis.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 87adee70..32ec9a58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,15 @@ os: osx language: node_js -# cache: -# directories: -# - node_modules +cache: + directories: + - node_modules # - $HOME/Library/Caches/Homebrew notifications: email: false node_js: - 'lts/*' -# before_install: +before_install: + - rm -rf node_modules # - brew update # - brew cask install sketch # install Sketch # - mkdir -p "~/Library/Application Support/com.bohemiancoding.sketch3/Plugins" # create plugins folder From 8b02a46169709514624da54dddba95e11612a7ca Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Wed, 6 Jan 2021 20:37:04 +0100 Subject: [PATCH 42/46] Remove footnote in `API.md` --- .travis.yml | 3 +-- docs/API.md | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 32ec9a58..93b26a60 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,7 @@ notifications: email: false node_js: - 'lts/*' -before_install: - - rm -rf node_modules +# before_install: # - brew update # - brew cask install sketch # install Sketch # - mkdir -p "~/Library/Application Support/com.bohemiancoding.sketch3/Plugins" # create plugins folder diff --git a/docs/API.md b/docs/API.md index 17918efc..779bcce6 100644 --- a/docs/API.md +++ b/docs/API.md @@ -61,7 +61,7 @@ Example: `sketch.getSelectedDocument().selectedPage`. ##### `bridge` (optional) -An object implementing the [Platform Bridge API][platform-bridge-api]. When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. +An object implementing the Platform Bridge API. When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. #### Returns @@ -96,7 +96,7 @@ Top-level React component that defines your Sketch document. ##### `bridge` (optional) -An object implementing the [Platform Bridge API][platform-bridge-api]. When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. +An object implementing the Platform Bridge API. When not specified, it will attempt to load the most suitable one for the platform in use among the ones bundled with the package. #### Returns @@ -707,7 +707,7 @@ Returns a react component which is an can be used to render instances of the sym | `props.name` | `String` | The node name | Optional name for the symbol, string can include backslashes to organize these symbols with Sketch. For example `squares/blue` | | `props.style` | [`Style`](/docs/styling.md) | | | | `document` | `Object` | The current document | The Sketch document to make the symbol in | -| `bridge` | `Object` | _platform-dependent_ | An object implementing the [Platform Bridge API][platform-bridge-api]. | +| `bridge` | `Object` | _platform-dependent_ | An object implementing the Platform Bridge API. | ### `getSymbolComponentByName(name)` @@ -861,5 +861,3 @@ export default () => { render(, sketch.getSelectedDocument().selectedPage); }; ``` - -[platform-bridge-api]: ../src/types/index.ts#L124-130 From 21e52ee0b33bfcd177547234c0baeff96448c215 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 8 Apr 2021 06:58:00 +0200 Subject: [PATCH 43/46] Update Travis config and npm --- .travis.yml | 10 ++++------ package.json | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 93b26a60..ec7c835c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,12 @@ os: osx language: node_js -cache: - directories: - - node_modules -# - $HOME/Library/Caches/Homebrew notifications: email: false node_js: - - 'lts/*' -# before_install: + - 12 + - 14 +before_install: + - npm install -g npm # - brew update # - brew cask install sketch # install Sketch # - mkdir -p "~/Library/Application Support/com.bohemiancoding.sketch3/Plugins" # create plugins folder diff --git a/package.json b/package.json index 947aa214..2c32c39c 100644 --- a/package.json +++ b/package.json @@ -84,9 +84,9 @@ "@skpm/test-runner": "^0.4.1", "@types/airbnb-prop-types": "^2.13.1", "@types/invariant": "^2.2.31", - "@types/node-fetch": "^2.5.4", "@types/jest": "^25.2.1", "@types/node": "^13.13.2", + "@types/node-fetch": "^2.5.4", "@types/pegjs": "^0.10.1", "@types/react": "^16.9.34", "@types/react-test-renderer": "^16.9.2", From 7c6da8e8dd54dbba1a84d4097b66dbe4ff34a317 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 8 Apr 2021 07:19:12 +0200 Subject: [PATCH 44/46] Change the compile target to `es2017` in tsconfig.json --- tsconfig.module.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tsconfig.module.json b/tsconfig.module.json index 6159976f..aa9b881d 100644 --- a/tsconfig.module.json +++ b/tsconfig.module.json @@ -1,7 +1,6 @@ { "extends": "./tsconfig", "compilerOptions": { - "target": "es5", "outDir": "lib/module", "module": "esnext" }, From abffbba897d76134b95e36f1dd2f21dcaaa7ef88 Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 8 Apr 2021 07:23:24 +0200 Subject: [PATCH 45/46] Don't use the npm cache --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index ec7c835c..c86671fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ os: osx language: node_js notifications: email: false +cache: + npm: false node_js: - 12 - 14 From 2bc1bf64bc211c67391b264be421c9265200984a Mon Sep 17 00:00:00 2001 From: Michele Piccirillo Date: Thu, 8 Apr 2021 08:23:17 +0200 Subject: [PATCH 46/46] Add libraries for retrocompatibility --- package.json | 3 +++ src/utils/getImageDataFromURL.ts | 1 + tsconfig.module.json | 1 + 3 files changed, 5 insertions(+) diff --git a/package.json b/package.json index 2c32c39c..3f32fafa 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,9 @@ "normalize-css-color": "^1.0.1", "pegjs": "^0.10.0", "prop-types": "^15.7.2", + "regenerator-runtime": "^0.13.7", "seedrandom": "^3.0.5", + "whatwg-url": "^7.1.0", "yoga-layout-prebuilt": "^1.9.5" }, "peerDependencies": { @@ -91,6 +93,7 @@ "@types/react": "^16.9.34", "@types/react-test-renderer": "^16.9.2", "@types/seedrandom": "^2.4.28", + "@types/whatwg-url": "^8.2.0", "gitbook-cli": "^2.3.0", "gitbook-plugin-anchorjs": "^2.1.0", "gitbook-plugin-codeblock-disable-glossary": "0.0.1", diff --git a/src/utils/getImageDataFromURL.ts b/src/utils/getImageDataFromURL.ts index 6554c361..38b416cc 100644 --- a/src/utils/getImageDataFromURL.ts +++ b/src/utils/getImageDataFromURL.ts @@ -1,5 +1,6 @@ import sha1 from 'js-sha1'; import { PlatformBridge } from '../types'; +import { URL } from 'whatwg-url'; const ERROR_IMAGE = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8w8DwHwAEOQHNmnaaOAAAAABJRU5ErkJggg=='; diff --git a/tsconfig.module.json b/tsconfig.module.json index aa9b881d..6159976f 100644 --- a/tsconfig.module.json +++ b/tsconfig.module.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig", "compilerOptions": { + "target": "es5", "outDir": "lib/module", "module": "esnext" },