New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code Review: Add support for slices in SketchAPI (#16184) #280

Merged
merged 13 commits into from Nov 20, 2018
Copy path View file
@@ -110,6 +110,9 @@ globals:
MSImmutableStarShape: false
MSImmutableTriangleShape: false
MSImmutableRectangleShape: false
MSSliceLayer: false
MSImmutableSliceLayer: false
MSExportFormat: false

rules:
###########
Copy path View file
@@ -1,6 +1,8 @@
{
"unreleased": [
"[New] Add `verticalAlignment` property to Text",
"[New] Add support for Slices",
"[New] Add `exportFormats` property on Layer",
"[Improved] No need to specify the type when there is no choice (like Document.pages can only contain Pages, Layer.exportFormats can only contain ExportFormats, etc.)",
"[New] Add UI.getInputFromUser method and deprecate the other input methods",
"[New] Add some `getParent*` methods on Layer"
],
Copy path View file
@@ -63,6 +63,9 @@ export class WrappedObject {
* @param {Object} object - The Sketch model object to wrap.
*/
static fromNative(sketchObject) {
if (!sketchObject) {
return sketchObject
}
return new this({
sketchObject,
})
Copy path View file
@@ -25,4 +25,6 @@ export const Types = {
SharedStyle: 'SharedStyle',
DataOverride: 'DataOverride',
ShapePath: 'ShapePath',
Slice: 'Slice',
ExportFormat: 'ExportFormat',
}
Copy path View file
@@ -20,6 +20,7 @@ export { Page } from './layers/Page'
export { SymbolMaster } from './layers/SymbolMaster'
export { SymbolInstance } from './layers/SymbolInstance'
export { HotSpot } from './layers/HotSpot'
export { Slice } from './layers/Slice'

export { Types } from './enums'
export { wrapObject as fromNative } from './wrapNativeObject'
Copy path View file
@@ -1,9 +1,9 @@
import { toArray } from 'util'
import { DefinedPropertiesKey } from '../WrappedObject'
import { StyledLayer } from './StyledLayer'
import { Rectangle } from '../models/Rectangle'
import { Types } from '../enums'
import { Factory } from '../Factory'
import { toArray } from '../utils'
import { wrapNativeObject, wrapObject } from '../wrapNativeObject'

/**
@@ -45,7 +45,7 @@ export class Group extends StyledLayer {
if (this.isImmutable()) {
return this
}
this._object.resizeToFitChildrenWithOption_(0)
this._object.fixGeometryWithOptions(0)
return this
}
}
Copy path View file
@@ -45,3 +45,5 @@ HotSpot.type = Types.HotSpot
HotSpot[DefinedPropertiesKey] = { ...Layer[DefinedPropertiesKey] }
Factory.registerClass(HotSpot, MSHotspotLayer)
Factory.registerClass(HotSpot, MSImmutableHotspotLayer)

delete HotSpot[DefinedPropertiesKey].exportFormats
Copy path View file
@@ -4,7 +4,6 @@ import { ImageData } from '../models/ImageData'
import { Rectangle } from '../models/Rectangle'
import { Types } from '../enums'
import { Factory } from '../Factory'
import { wrapObject } from '../wrapNativeObject'

/**
* Represents an image layer.
@@ -36,7 +35,7 @@ Factory.registerClass(Image, MSImmutableBitmapLayer)

Image.define('image', {
get() {
return wrapObject(this._object.image())
return ImageData.fromNative(this._object.image())
},
set(image) {
if (this.isImmutable()) {
Copy path View file
@@ -1,8 +1,11 @@
import { toArray } from 'util'
import { WrappedObject, DefinedPropertiesKey } from '../WrappedObject'
import { Factory } from '../Factory'
import { Rectangle } from '../models/Rectangle'
import { wrapObject, wrapNativeObject } from '../wrapNativeObject'
import { Flow } from '../models/Flow'
import { ExportFormat } from '../models/ExportFormat'
import { Types } from '../enums'

/**
* Abstract class that represents a Sketch layer.
@@ -283,3 +286,24 @@ Layer.define('locked', {
this._object.setIsLocked(locked)
},
})

Layer.define('exportFormats', {
get() {
return toArray(this._object.exportOptions().exportFormats() || []).map(
ExportFormat.fromNative.bind(ExportFormat)
)
},
set(exportFormats) {
if (this.isImmutable()) {
return
}

this._object
.exportOptions()
.setExportFormats(
toArray(exportFormats).map(
e => wrapObject(e, Types.ExportFormat).sketchObject
)
)
},
})
Copy path View file
@@ -85,6 +85,7 @@ delete Page[DefinedPropertiesKey].flow
delete Page[DefinedPropertiesKey].style
delete Page[DefinedPropertiesKey].locked
delete Page[DefinedPropertiesKey].hidden
delete Page[DefinedPropertiesKey].exportFormats

// override setting up the parent as it's needs to a be a Document
Page.define('parent', {
Copy path View file
@@ -0,0 +1,28 @@
import { DefinedPropertiesKey } from '../WrappedObject'
import { Layer } from './Layer'
import { Rectangle } from '../models/Rectangle'
import { Types } from '../enums'
import { Factory } from '../Factory'

/**
* Represents a slice.
*/
export class Slice extends Layer {
constructor(group = {}) {
if (!group.sketchObject) {
// eslint-disable-next-line no-param-reassign
group.sketchObject = Factory.createNative(Slice)
.alloc()
.initWithFrame(new Rectangle(0, 0, 100, 100).asCGRect())
}

super(group)
}
}

Slice.type = Types.Slice
Slice[DefinedPropertiesKey] = { ...Layer[DefinedPropertiesKey] }
Factory.registerClass(Slice, MSSliceLayer)
Factory.registerClass(Slice, MSImmutableSliceLayer)

delete Slice[DefinedPropertiesKey].flow
@@ -1,8 +1,8 @@
import { isNativeObject } from 'util'
import { DefinedPropertiesKey } from '../WrappedObject'
import { Factory } from '../Factory'
import { Layer } from './Layer'
import { Style } from '../style/Style'
import { isNativeObject } from '../utils'
import { SharedStyle } from '../models/SharedStyle'
import { wrapObject } from '../wrapNativeObject'

@@ -1,10 +1,10 @@
import { toArray } from 'util'
import { DefinedPropertiesKey } from '../WrappedObject'
import { StyledLayer } from './StyledLayer'
import { Rectangle } from '../models/Rectangle'
import { Types } from '../enums'
import { Factory } from '../Factory'
import { wrapObject } from '../wrapNativeObject'
import { toArray } from '../utils'
import { Override } from '../models/Override'
import { ImageData } from '../models/ImageData'

@@ -1,10 +1,10 @@
import { toArray } from 'util'
import { DefinedPropertiesKey } from '../WrappedObject'
import { Artboard } from './Artboard'
import { Rectangle } from '../models/Rectangle'
import { Types } from '../enums'
import { Factory } from '../Factory'
import { wrapObject } from '../wrapNativeObject'
import { toArray } from '../utils'

/**
* A Sketch symbol master.
@@ -10,5 +10,5 @@ test('should create an empty image', (context, document) => {
log(image)
expect(image.type).toBe('Image')
expect(image.parent).toEqual(page)
expect(image.image).toBe(undefined)
expect(image.image).toBe(null)
})
@@ -183,6 +183,27 @@ test('should lock the layer', () => {
expect(group.locked).toBe(true)
})

test('should change the exportFormats', () => {
const group = new Group()
expect(group.exportFormats).toEqual([])

group.exportFormats = [
{
size: '2x',
suffix: '@2x',
},
]
expect(group.exportFormats.map(e => e.toJSON())).toEqual([
{
type: 'ExportFormat',
fileFormat: 'png',
prefix: undefined,
suffix: '@2x',
size: '2x',
},
])
})

test('should get the different parents', (context, document) => {
const page = document.selectedPage
expect(page.parent).toEqual(document)
@@ -0,0 +1,10 @@
/* globals expect, test */

import { Slice } from '../..'

test('should create an slice', () => {
const slice = new Slice({ name: 'Test' })
// check that an artboard can be logged
log(slice)
expect(slice.type).toBe('Slice')
})
Copy path View file
@@ -1,7 +1,8 @@
import { toArray } from 'util'
import { WrappedObject, DefinedPropertiesKey } from '../WrappedObject'
import { Page } from '../layers/Page'
import { Selection } from './Selection'
import { toArray, getURLFromPath } from '../utils'
import { getURLFromPath } from '../utils'
import { wrapObject } from '../wrapNativeObject'
import { Types } from '../enums'
import { Factory } from '../Factory'
@@ -388,7 +389,7 @@ Document.define('pages', {
this._object.removePages_detachInstances(this._object.pages(), true)

toArray(pages)
.map(wrapObject)
.map(p => wrapObject(p, Types.Page))
.forEach(page => {
page.parent = this // eslint-disable-line
})
@@ -0,0 +1,109 @@
import { DefinedPropertiesKey, WrappedObject } from '../WrappedObject'
import { Types } from '../enums'
import { Factory } from '../Factory'

/**
* An MSExportFormat. This is not exposed, only used by the exportOptions of the Layer
*/
export class ExportFormat extends WrappedObject {
constructor(exportFormat = {}) {
if (!exportFormat.sketchObject) {
// eslint-disable-next-line no-param-reassign
exportFormat.sketchObject = MSExportFormat.formatWithScale_name_fileFormat(

This comment has been minimized.

@bomberstudios

bomberstudios Nov 9, 2018

Collaborator

Maybe we can use [MSExportOptions nextMostAppropriateExportFormatToAdd:] here, so as not to hardcode the values for the initial exportFormat?

This comment has been minimized.

@mathieudutour

mathieudutour Nov 12, 2018

Contributor

Hum I'm not sure. I get why we want that in the UI but IMO it would be weird to have a different fileformat depending on which one was added before

1,
'',
'png'
)
}

super(exportFormat)
}
}

ExportFormat.type = Types.ExportFormat
ExportFormat[DefinedPropertiesKey] = { ...WrappedObject[DefinedPropertiesKey] }
Factory.registerClass(ExportFormat, MSExportFormat)

delete ExportFormat[DefinedPropertiesKey].id

ExportFormat.define('fileFormat', {
get() {
return String(this._object.fileFormat())
},
set(fileFormat) {
if (!MSExportFormat.validFormats().containsObject(fileFormat)) {
throw new Error('File format not supported')
}

this._object.setFileFormat(fileFormat)
},
})

ExportFormat.define('prefix', {
get() {
if (this._object.namingScheme() == 0) {
// we have a suffix
return undefined
}
return String(this._object.name())
},
set(name) {
this._object.setNamingScheme(1)
this._object.setName(name)
},
})

ExportFormat.define('suffix', {
get() {
if (this._object.namingScheme() == 1) {
// we have a prefix
return undefined
}
return String(this._object.name())
},
set(name) {
this._object.setNamingScheme(0)
this._object.setName(name)
},
})

ExportFormat.define('size', {
get() {
switch (this._object.visibleScaleType()) {
case 0:
return `${this._object.scale()}x`
case 1:
return `${this._object.absoluteSize()}w`
case 2:
return `${this._object.absoluteSize()}h`
default:
throw new Error('unknown visibleScaleType')
}
},
set(_size) {
const size = String(_size)
if (
size.endsWith('w') ||
size.endsWith('width') ||
(size.endsWith('px') && this._object.visibleScaleType() == 1)
) {
this._object.setVisibleScaleType(1)
this._object.setScale(0)
this._object.setAbsoluteSize(parseInt(size, 10))
} else if (
size.endsWith('h') ||
size.endsWith('height') ||
(size.endsWith('px') && this._object.visibleScaleType() == 2)
) {
this._object.setVisibleScaleType(2)
this._object.setScale(0)
this._object.setAbsoluteSize(parseInt(size, 10))
} else if (size.endsWith('x')) {
this._object.setVisibleScaleType(0)
this._object.setScale(parseInt(size, 10))
this._object.setAbsoluteSize(0)
} else {
throw new Error('could not parse the size')
}
},
})
Copy path View file
@@ -1,7 +1,8 @@
import { isNativeObject } from 'util'
import { DefinedPropertiesKey, WrappedObject } from '../WrappedObject'
import { Types } from '../enums'
import { Factory } from '../Factory'
import { isWrappedObject, isNativeObject } from '../utils'
import { isWrappedObject } from '../utils'
import { wrapObject } from '../wrapNativeObject'

// Mapping between animation type names and values.
@@ -1,7 +1,8 @@
import { isNativeObject } from 'util'
import { DefinedPropertiesKey, WrappedObject } from '../WrappedObject'
import { Types } from '../enums'
import { Factory } from '../Factory'
import { isWrappedObject, isNativeObject } from '../utils'
import { isWrappedObject } from '../utils'

/**
* An MSImageData. This is not exposed, only used by Image
Copy path View file
@@ -1,6 +1,7 @@
import { toArray } from 'util'
import { WrappedObject, DefinedPropertiesKey } from '../WrappedObject'
import { Document } from './Document'
import { toArray, getURLFromPath, getDocumentData } from '../utils'
import { getURLFromPath, getDocumentData } from '../utils'
import { Types } from '../enums'
import { Factory } from '../Factory'
import { wrapObject } from '../wrapNativeObject'
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.