├── .appcast.xml
├── .eslintrc.yml
├── .gitignore
├── .vscode
└── settings.json
├── README.md
├── assets
└── icon.png
├── package-lock.json
├── package.json
├── prettier.config.js
├── src
├── __tests__
│ ├── init.test.js
│ └── selection-changed.test.js
├── constants.js
├── init.js
├── manifest.json
├── open-preferences.js
├── selection-changed.js
├── test-utils.js
└── utils.js
└── xib-views
├── Info.plist
├── LayerStylePreferences.xib
├── TextStylePreferences.xib
├── xib-views.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcuserdata
│ │ └── mathieudutour.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
└── xcuserdata
│ └── mathieudutour.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
└── xib_views.h
/.appcast.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | root: true
3 | extends:
4 | - airbnb
5 | - plugin:import/errors
6 | - prettier
7 | plugins:
8 | - no-not-accumulator-reassign
9 | - prettier
10 | env:
11 | node: true
12 | rules:
13 | import/no-unresolved:
14 | - error
15 | - commonjs: true
16 | caseSensitive: true
17 | ignore:
18 | - '^sketch(\/|$)'
19 | no-param-reassign: 0
20 | import/no-extraneous-dependencies: 0
21 | jsx-a11y/accessible-emoji: 0
22 | no-underscore-dangle: 0
23 | react/no-array-index-key: 0
24 | react/require-default-props: 0
25 | import/prefer-default-export: 0
26 | no-plusplus: 0
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # build artifacts
2 | sketch-styles-hierarchy.sketchplugin
3 |
4 | # npm
5 | node_modules
6 | .npm
7 | npm-debug.log
8 |
9 | # mac
10 | .DS_Store
11 |
12 | # WebStorm
13 | .idea
14 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shared Styles Hierarchy
2 |
3 | _Organize your shared styles using the layers list, create a hierarchy where children inherit properties from their parent._
4 |
5 | ## Installation
6 |
7 | _Requires Sketch >= 53_
8 |
9 | - [Download](https://github.com/mathieudutour/sketch-styles-hierarchy/releases/latest) the latest release of the plugin
10 | - Un-zip
11 | - Double-click on sketch-styles-hierarchy.sketchplugin
12 |
13 | ## Usage
14 |
15 | - You run the `setup` command which creates 2 new pages: `Text Styles` and `Layer Styles`.
16 | - Then you use the layer list and the inspector to manage the shared styles.
17 |
18 | Let's say you have 4 text styles: `Button/Primary`, `Button/Secondary`, `Header/H1` and `Header/H2`.
19 | You should have 2 groups: `Button` and `Header` with 3 text layers in each: `Primary` (or `H1`), `Secondary` (or `H2`) and `__default`.
20 |
21 | You now have multiple possible actions (each triggered after changing the selection):
22 |
23 | - rename a group to rename all the children (eg. renaming the `Header` group to `Heading` will change `Header/H1` to `Heading/H1` and `Header/H2` to `Heading/H2`)
24 | - rename a layer to rename the shared style
25 | - create a new Group (eg. creating a new Group called `Inverted` from `Button/Secondary` will rename `Button/Secondary` to `Button/Inverted/Secondary` and create a new `__default` layer)
26 | - drag and drop layers in the hierarchy to rename them
27 | - delete groups or layers to delete the Shared Styles
28 | - create new layers to create new shared styles
29 |
30 | Now you might wonder what the `__default` layers are for, and that's where the magic comes in :sparkles:
31 |
32 | - select a `__default` layer and press `cmd + shift + ,` to be able to select which style properties its children should inherit (the `__default` is linked to a group, so the children are the children of the parent group of the `__default` layer)
33 | - then, every time you change the `__default` style, the children will change as well
34 |
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mathieudutour/sketch-styles-hierarchy/720a815b7ad2b64102333d5ca8d51a25bfd8179f/assets/icon.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sketch-styles-hierarchy",
3 | "description": "Organize your shared styles using the layers list, create a hierarchy where children inherit properties from their parent.",
4 | "version": "0.1.1",
5 | "engines": {
6 | "sketch": ">=3.0"
7 | },
8 | "skpm": {
9 | "name": "Shared Styles Hierarchy",
10 | "manifest": "src/manifest.json",
11 | "main": "sketch-styles-hierarchy.sketchplugin",
12 | "assets": [
13 | "assets/**/*"
14 | ]
15 | },
16 | "scripts": {
17 | "build": "skpm-build",
18 | "watch": "skpm-build --watch",
19 | "start": "skpm-build --watch --run",
20 | "postinstall": "npm run build && skpm-link",
21 | "lint-staged": "lint-staged",
22 | "lint": "eslint --ignore-path=.gitignore .",
23 | "prettier:base": "prettier --write",
24 | "test": "skpm-test"
25 | },
26 | "devDependencies": {
27 | "@skpm/builder": "^0.5.11",
28 | "@skpm/test-runner": "^0.3.9",
29 | "eslint": "^5.13.0",
30 | "eslint-config-airbnb": "^17.1.0",
31 | "eslint-config-prettier": "^4.0.0",
32 | "eslint-plugin-import": "^2.16.0",
33 | "eslint-plugin-jsx-a11y": "^6.2.1",
34 | "eslint-plugin-no-not-accumulator-reassign": "^0.1.0",
35 | "eslint-plugin-prettier": "^3.0.1",
36 | "eslint-plugin-react": "^7.12.4",
37 | "lint-staged": "^8.1.3",
38 | "pre-commit": "^1.2.2",
39 | "prettier": "^1.16.4"
40 | },
41 | "author": "Mathieu Dutour ",
42 | "dependencies": {
43 | "sketch-module-web-view": "^2.1.2"
44 | },
45 | "pre-commit": [
46 | "lint-staged"
47 | ],
48 | "lint-staged": {
49 | "*.js": [
50 | "npm run prettier:base",
51 | "eslint --quiet --rule 'prettier/prettier: [\"error\", {\"trailingComma\": \"es5\", \"singleQuote\": true}]'",
52 | "git add"
53 | ]
54 | },
55 | "repository": {
56 | "type": "git",
57 | "url": "git+https://github.com/mathieudutour/sketch-styles-hierarchy.git"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | trailingComma: 'es5',
4 | semi: false,
5 | }
6 |
--------------------------------------------------------------------------------
/src/__tests__/init.test.js:
--------------------------------------------------------------------------------
1 | /* globals test, expect */
2 | import { initDummyDocument } from '../test-utils'
3 |
4 | test('should create 2 pages', (_, document) => {
5 | expect(document.pages.length).toBe(1)
6 | initDummyDocument(document)
7 | expect(document.pages.length).toBe(3)
8 | })
9 |
--------------------------------------------------------------------------------
/src/__tests__/selection-changed.test.js:
--------------------------------------------------------------------------------
1 | /* globals test, expect */
2 | import { initDummyDocument } from '../test-utils'
3 | import { onSelectionChanged } from '../selection-changed'
4 |
5 | test('should not do anything if there was no selection', (_, document) => {
6 | initDummyDocument(document)
7 | const context = {
8 | actionContext: {
9 | document: document.sketchObject,
10 | oldSelection: [],
11 | },
12 | }
13 | expect(onSelectionChanged(context)).toBe('no old selection')
14 | })
15 |
16 | test('should not do anything if there are more than one layer selected', (_, document) => {
17 | initDummyDocument(document)
18 | const context = {
19 | actionContext: {
20 | document: document.sketchObject,
21 | oldSelection: [
22 | document.pages[1].layers[0].layers[0].sketchObject,
23 | document.pages[1].layers[0].layers[1].sketchObject,
24 | ],
25 | },
26 | }
27 | expect(onSelectionChanged(context)).toBe('too big selection')
28 | })
29 |
30 | test('should not do anything if the selection is from another page', (_, document) => {
31 | initDummyDocument(document)
32 | const context = {
33 | actionContext: {
34 | document: document.sketchObject,
35 | oldSelection: [document.pages[0].sketchObject],
36 | },
37 | }
38 | expect(onSelectionChanged(context)).toBe('ignoring because wrong page')
39 | })
40 |
41 | test('should not do anything if the selection is not the right type', (_, document) => {
42 | initDummyDocument(document)
43 | const context = {
44 | actionContext: {
45 | document: document.sketchObject,
46 | oldSelection: [document.pages[2].layers[0].sketchObject],
47 | },
48 | }
49 | expect(onSelectionChanged(context)).toBe(
50 | 'ignoring because wrong type Artboard'
51 | )
52 | })
53 |
54 | test('should delete the shared style when deleting a layer', (_, document) => {
55 | initDummyDocument(document)
56 | let sharedStyles = (
57 | document.sharedTextStyles || document.getSharedTextStyles()
58 | ).filter(s => !s.getLibrary())
59 | expect(sharedStyles.length).toBe(4)
60 | document.pages[1].selected = true
61 | const layer = document.pages[1].layers[0].layers[1].layers[1]
62 | const sharedStyleName = layer.sharedStyle.name
63 | layer.parent = undefined
64 | const context = {
65 | actionContext: {
66 | document: document.sketchObject,
67 | oldSelection: [layer.sketchObject],
68 | },
69 | }
70 | expect(onSelectionChanged(context)).toBe('handled deleted')
71 | sharedStyles = (
72 | document.sharedTextStyles || document.getSharedTextStyles()
73 | ).filter(s => !s.getLibrary())
74 | expect(sharedStyles.length).toBe(3)
75 | expect(sharedStyles.find(s => s.name === sharedStyleName)).toBe(undefined)
76 | })
77 |
78 | test('should delete the shared styles when deleting a group', (_, document) => {
79 | initDummyDocument(document)
80 | let sharedStyles = (
81 | document.sharedTextStyles || document.getSharedTextStyles()
82 | ).filter(s => !s.getLibrary())
83 | expect(sharedStyles.length).toBe(4)
84 | document.pages[1].selected = true
85 | const group = document.pages[1].layers[0].layers[1]
86 | group.parent = undefined
87 | const context = {
88 | actionContext: {
89 | document: document.sketchObject,
90 | oldSelection: [group.sketchObject],
91 | },
92 | }
93 | expect(onSelectionChanged(context)).toBe('handled deleted')
94 | sharedStyles = (
95 | document.sharedTextStyles || document.getSharedTextStyles()
96 | ).filter(s => !s.getLibrary())
97 | expect(sharedStyles.length).toBe(2)
98 | })
99 |
100 | test('should ignore a group without style inside', (_, document) => {
101 | initDummyDocument(document)
102 |
103 | document.pages[1].layers[0].layers.push({
104 | type: 'Group',
105 | })
106 |
107 | const group = document.pages[1].layers[0].layers[2]
108 | const context = {
109 | actionContext: {
110 | document: document.sketchObject,
111 | oldSelection: [group.sketchObject],
112 | },
113 | }
114 | expect(onSelectionChanged(context)).toBe(
115 | 'A group without styles inside is forbidden'
116 | )
117 | })
118 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const TEXT_STYLES_PAGE = 'Text styles'
2 | export const LAYER_STYLES_PAGE = 'Layer styles'
3 |
4 | export const ROOT_STYLE_NAME = '__default'
5 |
6 | // https://fonts.google.com
7 | export const TEXT_EXAMPLES = [
8 | 'All their equipment and\ninstruments are alive.',
9 | 'A red flare silhouetted\nthe jagged edge of a wing.',
10 | 'I watched the storm, so\nbeautiful yet terrific.',
11 | 'Almost before we knew it,\nwe had left the ground.',
12 | 'A shining crescent far\nbeneath the flying vessel.',
13 | 'It was going to be a\nlonely trip back.',
14 | 'Mist enveloped the ship\nthree hours out from port.',
15 | 'My two natures had\nmemory in common.',
16 | 'Silver mist suffused\nthe deck of the ship.',
17 | 'The face of the moon\nwas in shadow.',
18 | 'She stared through\nthe window at the stars.',
19 | 'The recorded voice\nscratched in the speaker.',
20 | 'The sky was cloudless\nand of a deep dark blue.',
21 | 'The spectacle before\nus was indeed sublime.',
22 | 'Then came the night of\nthe first falling star.',
23 | 'Waves flung themselves\nat the blue evening.',
24 | ]
25 |
--------------------------------------------------------------------------------
/src/init.js:
--------------------------------------------------------------------------------
1 | import sketch from 'sketch'
2 | import { TEXT_STYLES_PAGE, LAYER_STYLES_PAGE } from './constants'
3 | import { getHierarchy, updateLayerHierarchy } from './utils'
4 |
5 | const buildLayerHierarchy = (parent, name, hierarchy, isTextStyle) => {
6 | const artboard = new sketch.Artboard({
7 | parent,
8 | name,
9 | frame: {
10 | x: 0,
11 | y: 0,
12 | width: 1000,
13 | height: 1000,
14 | },
15 | })
16 |
17 | updateLayerHierarchy(artboard, hierarchy, isTextStyle)
18 | }
19 |
20 | export function main(document) {
21 | const textStylesPage =
22 | document.pages.find(p => p.name === TEXT_STYLES_PAGE) ||
23 | new sketch.Page({
24 | name: TEXT_STYLES_PAGE,
25 | parent: document,
26 | })
27 |
28 | const layerStylesPage =
29 | document.pages.find(p => p.name === LAYER_STYLES_PAGE) ||
30 | new sketch.Page({
31 | name: LAYER_STYLES_PAGE,
32 | parent: document,
33 | })
34 |
35 | textStylesPage.layers = []
36 | layerStylesPage.layers = []
37 |
38 | const textStylesHierachy = getHierarchy(document, true)
39 | const layerStylesHierachy = getHierarchy(document, false)
40 |
41 | buildLayerHierarchy(textStylesPage, 'Text Styles', textStylesHierachy, true)
42 | buildLayerHierarchy(
43 | layerStylesPage,
44 | 'Layer Styles',
45 | layerStylesHierachy,
46 | false
47 | )
48 | }
49 |
50 | export default function() {
51 | const document = sketch.getSelectedDocument()
52 |
53 | main(document)
54 | }
55 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "compatibleVersion": 3,
3 | "bundleVersion": 1,
4 | "icon": "icon.png",
5 | "commands": [
6 | {
7 | "name": "Setup (once per file)",
8 | "identifier": "sketch-styles-hierarchy.init",
9 | "script": "./init.js"
10 | },
11 | {
12 | "name": "Hierarchy preferences",
13 | "identifier": "sketch-styles-hierarchy.preferences",
14 | "script": "./open-preferences.js",
15 | "shortcut": "cmd + shift + ,"
16 | },
17 | {
18 | "name": "Handle Selection Change",
19 | "identifier": "sketch-styles-hierarchy.selection-changed",
20 | "script": "./selection-changed.js",
21 | "handlers": {
22 | "actions": {
23 | "SelectionChanged.finish": "onSelectionChanged"
24 | }
25 | }
26 | }
27 | ],
28 | "menu": {
29 | "title": "sketch-styles-hierarchy",
30 | "items": [
31 | "sketch-styles-hierarchy.preferences",
32 | "-",
33 | "sketch-styles-hierarchy.init"
34 | ]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/open-preferences.js:
--------------------------------------------------------------------------------
1 | /* globals NSAlert, __command, NSAlertFirstButtonReturn, NSOnState, NSOffState */
2 | import * as sketch from 'sketch'
3 | import {
4 | TEXT_STYLES_PAGE,
5 | LAYER_STYLES_PAGE,
6 | ROOT_STYLE_NAME,
7 | } from './constants'
8 | import { updateChildren, updateLayout, getHierarchy } from './utils'
9 |
10 | const TextStylePreferencesUI = require('../xib-views/TextStylePreferences.xib')
11 | const LayerStylePreferencesUI = require('../xib-views/LayerStylePreferences.xib')
12 |
13 | export default function() {
14 | const document = sketch.getSelectedDocument()
15 |
16 | const selection = document.selectedLayers
17 |
18 | if (!selection.length || selection.length > 1) {
19 | sketch.UI.message('You need to select 1 layer')
20 | return
21 | }
22 |
23 | const layer = selection.layers[0]
24 | const parentPage = layer.getParentPage()
25 |
26 | if (
27 | !parentPage ||
28 | (parentPage.name !== TEXT_STYLES_PAGE &&
29 | parentPage.name !== LAYER_STYLES_PAGE)
30 | ) {
31 | sketch.UI.message('Only for layers in the right pages')
32 | return
33 | }
34 |
35 | const isTextStyle = parentPage.name === TEXT_STYLES_PAGE
36 |
37 | if (
38 | (isTextStyle || layer.type !== 'ShapePath') &&
39 | (!isTextStyle || layer.type !== 'Text')
40 | ) {
41 | sketch.UI.message('Only for layers of the right types')
42 | return
43 | }
44 |
45 | if (layer.name !== ROOT_STYLE_NAME) {
46 | sketch.UI.message('Only for default style layers')
47 | return
48 | }
49 |
50 | let preferencesUI
51 |
52 | if (isTextStyle) {
53 | preferencesUI = TextStylePreferencesUI()
54 | } else {
55 | preferencesUI = LayerStylePreferencesUI()
56 | }
57 |
58 | let inheritance =
59 | sketch.Settings.layerSettingForKey(layer, 'inheritance') || {}
60 |
61 | preferencesUI.opacity.state = inheritance.opacity ? NSOnState : NSOffState
62 | preferencesUI.blendingMode.state = inheritance.blendingMode
63 | ? NSOnState
64 | : NSOffState
65 | preferencesUI.borderOptions.state = inheritance.borderOptions
66 | ? NSOnState
67 | : NSOffState
68 | preferencesUI.blur.state = inheritance.blur ? NSOnState : NSOffState
69 | preferencesUI.fills.state = inheritance.fills ? NSOnState : NSOffState
70 | preferencesUI.borders.state = inheritance.borders ? NSOnState : NSOffState
71 | preferencesUI.shadows.state = inheritance.shadows ? NSOnState : NSOffState
72 | preferencesUI.innerShadows.state = inheritance.innerShadows
73 | ? NSOnState
74 | : NSOffState
75 |
76 | if (isTextStyle) {
77 | preferencesUI.alignment.state = inheritance.alignment
78 | ? NSOnState
79 | : NSOffState
80 | preferencesUI.verticalAlignment.state = inheritance.verticalAlignment
81 | ? NSOnState
82 | : NSOffState
83 | preferencesUI.kerning.state = inheritance.kerning ? NSOnState : NSOffState
84 | preferencesUI.lineHeight.state = inheritance.lineHeight
85 | ? NSOnState
86 | : NSOffState
87 | preferencesUI.paragraphSpacing.state = inheritance.paragraphSpacing
88 | ? NSOnState
89 | : NSOffState
90 | preferencesUI.textColor.state = inheritance.textColor
91 | ? NSOnState
92 | : NSOffState
93 | preferencesUI.fontSize.state = inheritance.fontSize ? NSOnState : NSOffState
94 | preferencesUI.textTransform.state = inheritance.textTransform
95 | ? NSOnState
96 | : NSOffState
97 | preferencesUI.fontFamily.state = inheritance.fontFamily
98 | ? NSOnState
99 | : NSOffState
100 | preferencesUI.fontWeight.state = inheritance.fontWeight
101 | ? NSOnState
102 | : NSOffState
103 | preferencesUI.fontStyle.state = inheritance.fontStyle
104 | ? NSOnState
105 | : NSOffState
106 | preferencesUI.fontVariant.state = inheritance.fontVariant
107 | ? NSOnState
108 | : NSOffState
109 | preferencesUI.fontStretch.state = inheritance.fontStretch
110 | ? NSOnState
111 | : NSOffState
112 | preferencesUI.textUnderline.state = inheritance.textUnderline
113 | ? NSOnState
114 | : NSOffState
115 | preferencesUI.textStrikethrough.state = inheritance.textStrikethrough
116 | ? NSOnState
117 | : NSOffState
118 | }
119 |
120 | const dialog = NSAlert.alloc().init()
121 |
122 | dialog.setMessageText('Style properties to pass down')
123 | dialog.setInformativeText(
124 | 'Select the properties of the style that the children will inherit.'
125 | )
126 | dialog.addButtonWithTitle('Save')
127 | dialog.addButtonWithTitle('Cancel')
128 | dialog.setAccessoryView(preferencesUI.getRoot())
129 | dialog.icon = __command.pluginBundle().alertIcon()
130 |
131 | const responseCode = dialog.runModal()
132 |
133 | if (responseCode !== NSAlertFirstButtonReturn) {
134 | return
135 | }
136 |
137 | inheritance = {
138 | opacity: preferencesUI.opacity.state() === NSOnState,
139 | blendingMode: preferencesUI.blendingMode.state() === NSOnState,
140 | borderOptions: preferencesUI.borderOptions.state() === NSOnState,
141 | blur: preferencesUI.blur.state() === NSOnState,
142 | fills: preferencesUI.fills.state() === NSOnState,
143 | borders: preferencesUI.borders.state() === NSOnState,
144 | shadows: preferencesUI.shadows.state() === NSOnState,
145 | innerShadows: preferencesUI.innerShadows.state() === NSOnState,
146 | }
147 |
148 | if (isTextStyle) {
149 | Object.assign(inheritance, {
150 | alignment: preferencesUI.alignment.state() === NSOnState,
151 | verticalAlignment: preferencesUI.verticalAlignment.state() === NSOnState,
152 | kerning: preferencesUI.kerning.state() === NSOnState,
153 | lineHeight: preferencesUI.lineHeight.state() === NSOnState,
154 | paragraphSpacing: preferencesUI.paragraphSpacing.state() === NSOnState,
155 | textColor: preferencesUI.textColor.state() === NSOnState,
156 | fontSize: preferencesUI.fontSize.state() === NSOnState,
157 | textTransform: preferencesUI.textTransform.state() === NSOnState,
158 | fontFamily: preferencesUI.fontFamily.state() === NSOnState,
159 | fontWeight: preferencesUI.fontWeight.state() === NSOnState,
160 | fontStyle: preferencesUI.fontStyle.state() === NSOnState,
161 | fontVariant: preferencesUI.fontVariant.state() === NSOnState,
162 | fontStretch: preferencesUI.fontStretch.state() === NSOnState,
163 | textUnderline: preferencesUI.textUnderline.state() === NSOnState,
164 | textStrikethrough: preferencesUI.textStrikethrough.state() === NSOnState,
165 | })
166 | }
167 |
168 | sketch.Settings.setLayerSettingForKey(layer, 'inheritance', inheritance)
169 |
170 | // update the children
171 | updateChildren(layer.parent)
172 |
173 | updateLayout(document, getHierarchy(document, isTextStyle), isTextStyle)
174 | }
175 |
--------------------------------------------------------------------------------
/src/selection-changed.js:
--------------------------------------------------------------------------------
1 | import * as sketch from 'sketch'
2 | import {
3 | TEXT_STYLES_PAGE,
4 | LAYER_STYLES_PAGE,
5 | ROOT_STYLE_NAME,
6 | } from './constants'
7 | import {
8 | getHierarchy,
9 | getParts,
10 | createSharedStyle,
11 | updateLayout,
12 | getTextExample,
13 | updateChildren,
14 | } from './utils'
15 |
16 | function updateStyleNames(hierarchy, newName) {
17 | if (hierarchy.style) {
18 | hierarchy.style.name = newName
19 | }
20 | hierarchy.children.forEach(c => {
21 | updateStyleNames(c, `${newName} / ${c.name}`)
22 | })
23 | }
24 |
25 | function getPreviousParts(group) {
26 | const { layers } = group
27 | const layerWithSharedStyle = layers.find(l => l.sharedStyle)
28 | if (!layerWithSharedStyle) {
29 | return undefined
30 | }
31 | const parts = getParts(layerWithSharedStyle.sharedStyle)
32 | if (layerWithSharedStyle.name === ROOT_STYLE_NAME) {
33 | return parts
34 | }
35 | parts.pop()
36 | return parts
37 | }
38 |
39 | function deleteSharedStyles(isTextStyle, group, document) {
40 | if (group.sharedStyle) {
41 | const documentData = document._getMSDocumentData()
42 | const container = documentData.sharedObjectContainerOfType(
43 | isTextStyle ? 2 : 1
44 | )
45 | container.removeSharedObject(group.sharedStyle.sketchObject)
46 | }
47 |
48 | if (group.layers) {
49 | group.layers.forEach(l => {
50 | deleteSharedStyles(isTextStyle, l, document)
51 | })
52 | }
53 | }
54 |
55 | export function onSelectionChanged(context) {
56 | if (!context.actionContext || !context.actionContext.oldSelection) {
57 | return 'no old selection'
58 | }
59 |
60 | if (!context.actionContext.oldSelection.length) {
61 | return 'no old selection'
62 | }
63 |
64 | if (context.actionContext.oldSelection.length > 1) {
65 | return 'too big selection'
66 | }
67 |
68 | const oldSelection = sketch.fromNative(context.actionContext.oldSelection[0])
69 | const wasDeleted = !oldSelection.getParentPage()
70 | const document = sketch.fromNative(context.actionContext.document)
71 | const parentPage = wasDeleted
72 | ? document.selectedPage
73 | : oldSelection.getParentPage()
74 |
75 | if (
76 | !parentPage ||
77 | (parentPage.name !== TEXT_STYLES_PAGE &&
78 | parentPage.name !== LAYER_STYLES_PAGE)
79 | ) {
80 | return 'ignoring because wrong page'
81 | }
82 |
83 | const isTextStyle = parentPage.name === TEXT_STYLES_PAGE
84 |
85 | if (
86 | oldSelection.type !== 'Group' &&
87 | (isTextStyle || oldSelection.type !== 'ShapePath') &&
88 | (!isTextStyle || oldSelection.type !== 'Text')
89 | ) {
90 | console.log(oldSelection.type)
91 | return `ignoring because wrong type ${oldSelection.type}`
92 | }
93 |
94 | if (wasDeleted) {
95 | deleteSharedStyles(isTextStyle, oldSelection, document)
96 |
97 | updateLayout(document, getHierarchy(document, isTextStyle), isTextStyle)
98 | return 'handled deleted'
99 | }
100 |
101 | const sharedStylesHierachy = getHierarchy(document, isTextStyle)
102 |
103 | const newParts = [oldSelection.name]
104 | let current = oldSelection.parent
105 | while (current && current.type === 'Group') {
106 | newParts.unshift(current.name)
107 | current = current.parent
108 | }
109 |
110 | if (oldSelection.type === 'Group') {
111 | const previousParts = getPreviousParts(oldSelection)
112 |
113 | if (!previousParts) {
114 | sketch.UI.message('A group without styles inside is forbidden')
115 | return 'A group without styles inside is forbidden'
116 | }
117 |
118 | if (!oldSelection.layers.find(l => l.name === ROOT_STYLE_NAME)) {
119 | // it's a new group. Let's create the default style
120 | oldSelection.layers.push({
121 | name: ROOT_STYLE_NAME,
122 | type: isTextStyle ? 'Text' : 'ShapePath',
123 | frame: {
124 | x: 0,
125 | y: 0,
126 | width: 50,
127 | height: 50,
128 | },
129 | })
130 |
131 | const defaultLayer = oldSelection.layers[oldSelection.layers.length - 1]
132 |
133 | if (isTextStyle) {
134 | defaultLayer.text = getTextExample()
135 | defaultLayer.name = ROOT_STYLE_NAME
136 | defaultLayer.adjustToFit()
137 | defaultLayer.frame.x = 0
138 | defaultLayer.frame.y = 0
139 | } else {
140 | defaultLayer.style = {
141 | fills: ['#000'],
142 | }
143 | }
144 |
145 | // set the inheritance setting like the parent
146 | const parentDefaultStyle = oldSelection.parent
147 | ? oldSelection.parent.layers.find(l => l.name === ROOT_STYLE_NAME)
148 | : undefined
149 | if (parentDefaultStyle) {
150 | sketch.Settings.setLayerSettingForKey(
151 | defaultLayer,
152 | 'inheritance',
153 | sketch.Settings.layerSettingForKey(
154 | parentDefaultStyle,
155 | 'inheritance'
156 | ) || {}
157 | )
158 | }
159 |
160 | // set the style depending on the parent
161 | updateChildren(oldSelection.parent)
162 |
163 | updateLayout(document, sharedStylesHierachy, isTextStyle)
164 | return 'handled new group'
165 | }
166 |
167 | if (
168 | previousParts.length === newParts.length &&
169 | previousParts.every((p, i) => p === newParts[i])
170 | ) {
171 | // nothing changed
172 | return 'nothing changed'
173 | }
174 |
175 | // we might have changed the name
176 | const foundParts = []
177 | let currentHierarchy = { children: sharedStylesHierachy }
178 | let index = currentHierarchy.children.findIndex(
179 | x => x.name === newParts[foundParts.length]
180 | )
181 | while (index !== -1) {
182 | currentHierarchy = currentHierarchy.children[index]
183 | foundParts.push(currentHierarchy.name)
184 | index = currentHierarchy.children.findIndex(
185 | x => x.name === newParts[foundParts.length]
186 | )
187 | }
188 |
189 | if (foundParts.length === newParts.length) {
190 | // nothing changed but should have
191 | sketch.UI.message(
192 | 'probably means that 2 groups are called the same, bad!'
193 | )
194 | return 'probably means that 2 groups are called the same, bad!'
195 | }
196 |
197 | // we need to find the group that changed
198 | const siblings = oldSelection.parent.layers.filter(
199 | l => l.name !== ROOT_STYLE_NAME && l.id !== oldSelection.id
200 | )
201 | const childLeft = currentHierarchy.children.filter(
202 | c => !siblings.find(s => s.name === c.name)
203 | )
204 | if (childLeft.length > 1) {
205 | sketch.UI.message('too many children left!')
206 | return 'too many children left!'
207 | }
208 | // it's been moved or renamed
209 | updateStyleNames(childLeft[0], newParts.join(' / '))
210 |
211 | updateLayout(document, getHierarchy(document, isTextStyle), isTextStyle)
212 | return 'handle renamed group'
213 | }
214 |
215 | // now we are dealing with a proper layer
216 |
217 | // a layer shared style should be up to date with its style at all time
218 | if (
219 | oldSelection.sharedStyle &&
220 | oldSelection.style.isOutOfSyncWithSharedStyle(oldSelection.sharedStyle)
221 | ) {
222 | oldSelection.sharedStyle.style = oldSelection.style
223 | }
224 |
225 | if (oldSelection.name === ROOT_STYLE_NAME) {
226 | newParts.pop()
227 | // update the children
228 | updateChildren(oldSelection.parent)
229 | } else if (!oldSelection.sharedStyle) {
230 | // we create a new layer so we need to transform it into a shared style
231 | const sharedStyle = createSharedStyle(
232 | isTextStyle,
233 | newParts.join(' / '),
234 | oldSelection.style,
235 | document
236 | )
237 | oldSelection.sharedStyle = sharedStyle
238 | }
239 |
240 | if (oldSelection.sharedStyle) {
241 | oldSelection.sharedStyle.name = newParts.join(' / ')
242 | }
243 |
244 | updateLayout(document, getHierarchy(document, isTextStyle), isTextStyle)
245 | return 'handle changed layer'
246 | }
247 |
--------------------------------------------------------------------------------
/src/test-utils.js:
--------------------------------------------------------------------------------
1 | import { createSharedStyle } from './utils'
2 | import { main as init } from './init'
3 |
4 | export function initDummyDocument(document) {
5 | document.pages[0].layers = [{ type: 'Artboard' }]
6 | createSharedStyle(true, 'Header/H1', { fontSize: 12 }, document)
7 | createSharedStyle(true, 'Header/H2', { fontSize: 12 }, document)
8 | createSharedStyle(true, 'Button/Primary', { fontSize: 12 }, document)
9 | createSharedStyle(true, 'Button/Secondary', { fontSize: 12 }, document)
10 | init(document)
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import * as sketch from 'sketch'
2 | import {
3 | TEXT_EXAMPLES,
4 | ROOT_STYLE_NAME,
5 | TEXT_STYLES_PAGE,
6 | LAYER_STYLES_PAGE,
7 | } from './constants'
8 |
9 | let i = 0
10 | export function getTextExample() {
11 | return TEXT_EXAMPLES[i++ % TEXT_EXAMPLES.length]
12 | }
13 |
14 | export function getParts(sharedStyle) {
15 | return sharedStyle.name.split('/').map(p => p.trim())
16 | }
17 |
18 | function buildHierarchy(sharedStyles) {
19 | return sharedStyles.reduce((prev, s) => {
20 | const parts = getParts(s)
21 |
22 | let currentParent = { children: prev }
23 |
24 | parts.forEach(p => {
25 | let index = currentParent.children.findIndex(x => x.name === p)
26 | if (index === -1) {
27 | index = currentParent.children.length
28 | currentParent.children.push({
29 | name: p,
30 | children: [],
31 | })
32 | }
33 | currentParent = currentParent.children[index]
34 | })
35 |
36 | currentParent.style = s
37 |
38 | return prev
39 | }, [])
40 | }
41 |
42 | export function getHierarchy(document, isTextStyle) {
43 | const sharedStyles = (isTextStyle
44 | ? document.sharedTextStyles || document.getSharedTextStyles()
45 | : document.sharedLayerStyles || document.getSharedLayerStyles()
46 | ).filter(s => !s.getLibrary())
47 |
48 | return buildHierarchy(sharedStyles)
49 | }
50 |
51 | export function createSharedStyle(isTextStyle, name, style, document) {
52 | if (document.sharedTextStyles) {
53 | const sharedStyles =
54 | document[isTextStyle ? 'sharedTextStyles' : 'sharedLayerStyles']
55 | const newLength = sharedStyles.push({
56 | name,
57 | style,
58 | })
59 | return sharedStyles[newLength - 1]
60 | }
61 |
62 | return sketch.SharedStyle.fromStyle({ name, style, document })
63 | }
64 |
65 | function _updateLayout({ name, style, children }, parent, isTextStyle, y) {
66 | if (!children.length && !style) {
67 | throw new Error('what')
68 | }
69 |
70 | const Primitive = isTextStyle ? sketch.Text : sketch.ShapePath
71 |
72 | if (!children.length) {
73 | let matchingLayer = parent.layers.find(l => l.name === name)
74 | if (!matchingLayer) {
75 | matchingLayer = new Primitive({
76 | parent,
77 | name,
78 | frame: {
79 | x: 20,
80 | y,
81 | width: 50,
82 | height: 50,
83 | },
84 | style: style.style,
85 | sharedStyle: style,
86 | })
87 |
88 | if (isTextStyle) {
89 | matchingLayer.text = getTextExample()
90 | matchingLayer.name = name
91 | matchingLayer.adjustToFit()
92 | }
93 | }
94 |
95 | // reposition the layer
96 | matchingLayer.frame.x = 20
97 | matchingLayer.frame.y = y
98 |
99 | return matchingLayer.frame.height
100 | }
101 |
102 | let matchingGroup = parent.layers.find(l => l.name === name)
103 | if (!matchingGroup) {
104 | matchingGroup = new sketch.Group({
105 | parent,
106 | name,
107 | frame: {
108 | x: 20,
109 | y,
110 | width: 1000,
111 | height: 1000,
112 | },
113 | })
114 | }
115 |
116 | // remove outdated layers
117 | matchingGroup.layers.forEach(l => {
118 | if (!children.find(c => c.name === l.name) && l.name !== ROOT_STYLE_NAME) {
119 | l.parent = undefined
120 | }
121 | })
122 |
123 | let matchingDefault = matchingGroup.layers.find(
124 | l => l.name === ROOT_STYLE_NAME
125 | )
126 | if (!matchingDefault) {
127 | matchingDefault = new Primitive({
128 | parent: matchingGroup,
129 | name: ROOT_STYLE_NAME,
130 | frame: {
131 | x: 0,
132 | y: 0,
133 | width: 50,
134 | height: 50,
135 | },
136 | })
137 | if (style) {
138 | matchingDefault.style = style.style
139 | matchingDefault.sharedStyle = style
140 | } else if (!isTextStyle) {
141 | // need to set a default style
142 | matchingDefault.style = {
143 | fills: ['#000'],
144 | }
145 | }
146 |
147 | if (isTextStyle) {
148 | matchingDefault.text = getTextExample()
149 | matchingDefault.name = ROOT_STYLE_NAME
150 | matchingDefault.adjustToFit()
151 | }
152 | }
153 |
154 | matchingDefault.frame.x = 0
155 | matchingDefault.frame.y = 0
156 |
157 | let childrenY = matchingDefault.frame.height + 20
158 |
159 | children.forEach(c => {
160 | childrenY += _updateLayout(c, matchingGroup, isTextStyle, childrenY) + 20
161 | })
162 |
163 | matchingGroup.adjustToFit()
164 | matchingGroup.frame.x = 20
165 | matchingGroup.frame.y = y
166 |
167 | return matchingGroup.frame.height
168 | }
169 |
170 | const adjustArtboard = artboard => {
171 | artboard.adjustToFit()
172 | artboard.frame.x = 0
173 | artboard.frame.y = 0
174 | artboard.frame.width += 40
175 | artboard.frame.height += 40
176 | artboard.layers.forEach(x => {
177 | x.frame.x += 20
178 | x.frame.y += 20
179 | })
180 | }
181 |
182 | export const updateLayerHierarchy = (artboard, hierarchy, isTextStyle) => {
183 | let y = 20
184 |
185 | hierarchy.forEach(c => {
186 | y += _updateLayout(c, artboard, isTextStyle, y) + 20
187 | })
188 |
189 | adjustArtboard(artboard)
190 | }
191 |
192 | export const getInheritedKeys = layer => {
193 | const inheritance = layer
194 | ? sketch.Settings.layerSettingForKey(layer, 'inheritance') || {}
195 | : {}
196 | return Object.keys(inheritance).filter(k => inheritance[k])
197 | }
198 |
199 | export const updateChildren = parent => {
200 | if (!parent || !parent.layers) {
201 | return
202 | }
203 | const defaultStyle = parent.layers.find(l => l.name === ROOT_STYLE_NAME)
204 | const inheritance = getInheritedKeys(defaultStyle)
205 | parent.layers.forEach(l => {
206 | if (l.name === ROOT_STYLE_NAME) {
207 | return
208 | }
209 | if (l.sharedStyle) {
210 | inheritance.forEach(k => {
211 | l.style[k] = defaultStyle.style[k]
212 | })
213 | l.sharedStyle.style = l.style
214 | }
215 | if (l.type === 'Group') {
216 | const child = l.layers.find(x => x.name === ROOT_STYLE_NAME)
217 | if (child) {
218 | inheritance.forEach(k => {
219 | child.style[k] = defaultStyle.style[k]
220 | })
221 | if (child.sharedStyle) {
222 | child.sharedStyle.style = child.style
223 | }
224 | }
225 | }
226 | updateChildren(l)
227 | })
228 | }
229 |
230 | export function updateLayout(document, hierarchy, isTextStyle) {
231 | const page = document.pages.find(
232 | p => p.name === (isTextStyle ? TEXT_STYLES_PAGE : LAYER_STYLES_PAGE)
233 | )
234 | updateLayerHierarchy(page.layers[0], hierarchy, isTextStyle)
235 | }
236 |
--------------------------------------------------------------------------------
/xib-views/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2019 Mathieu Dutour. All rights reserved.
23 |
24 |
25 |
--------------------------------------------------------------------------------
/xib-views/LayerStylePreferences.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
31 |
39 |
47 |
55 |
63 |
71 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/xib-views/TextStylePreferences.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
31 |
39 |
47 |
55 |
63 |
71 |
79 |
87 |
95 |
103 |
111 |
119 |
127 |
135 |
143 |
151 |
159 |
167 |
175 |
183 |
191 |
199 |
200 |
201 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/xib-views/xib-views.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0310D119220E1F13007B3546 /* xib_views.h in Headers */ = {isa = PBXBuildFile; fileRef = 0310D117220E1F13007B3546 /* xib_views.h */; settings = {ATTRIBUTES = (Public, ); }; };
11 | 0310D120220E2030007B3546 /* TextStylePreferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0310D11F220E2030007B3546 /* TextStylePreferences.xib */; };
12 | 0310D122220E2389007B3546 /* LayerStylePreferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0310D121220E2389007B3546 /* LayerStylePreferences.xib */; };
13 | /* End PBXBuildFile section */
14 |
15 | /* Begin PBXFileReference section */
16 | 0310D114220E1F13007B3546 /* xib_views.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = xib_views.framework; sourceTree = BUILT_PRODUCTS_DIR; };
17 | 0310D117220E1F13007B3546 /* xib_views.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = xib_views.h; sourceTree = ""; };
18 | 0310D118220E1F13007B3546 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
19 | 0310D11F220E2030007B3546 /* TextStylePreferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TextStylePreferences.xib; sourceTree = ""; };
20 | 0310D121220E2389007B3546 /* LayerStylePreferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LayerStylePreferences.xib; sourceTree = ""; };
21 | /* End PBXFileReference section */
22 |
23 | /* Begin PBXFrameworksBuildPhase section */
24 | 0310D111220E1F13007B3546 /* Frameworks */ = {
25 | isa = PBXFrameworksBuildPhase;
26 | buildActionMask = 2147483647;
27 | files = (
28 | );
29 | runOnlyForDeploymentPostprocessing = 0;
30 | };
31 | /* End PBXFrameworksBuildPhase section */
32 |
33 | /* Begin PBXGroup section */
34 | 0310D10A220E1F13007B3546 = {
35 | isa = PBXGroup;
36 | children = (
37 | 0310D11F220E2030007B3546 /* TextStylePreferences.xib */,
38 | 0310D121220E2389007B3546 /* LayerStylePreferences.xib */,
39 | 0310D117220E1F13007B3546 /* xib_views.h */,
40 | 0310D118220E1F13007B3546 /* Info.plist */,
41 | 0310D115220E1F13007B3546 /* Products */,
42 | );
43 | sourceTree = "";
44 | };
45 | 0310D115220E1F13007B3546 /* Products */ = {
46 | isa = PBXGroup;
47 | children = (
48 | 0310D114220E1F13007B3546 /* xib_views.framework */,
49 | );
50 | name = Products;
51 | sourceTree = "";
52 | };
53 | /* End PBXGroup section */
54 |
55 | /* Begin PBXHeadersBuildPhase section */
56 | 0310D10F220E1F13007B3546 /* Headers */ = {
57 | isa = PBXHeadersBuildPhase;
58 | buildActionMask = 2147483647;
59 | files = (
60 | 0310D119220E1F13007B3546 /* xib_views.h in Headers */,
61 | );
62 | runOnlyForDeploymentPostprocessing = 0;
63 | };
64 | /* End PBXHeadersBuildPhase section */
65 |
66 | /* Begin PBXNativeTarget section */
67 | 0310D113220E1F13007B3546 /* xib-views */ = {
68 | isa = PBXNativeTarget;
69 | buildConfigurationList = 0310D11C220E1F13007B3546 /* Build configuration list for PBXNativeTarget "xib-views" */;
70 | buildPhases = (
71 | 0310D10F220E1F13007B3546 /* Headers */,
72 | 0310D110220E1F13007B3546 /* Sources */,
73 | 0310D111220E1F13007B3546 /* Frameworks */,
74 | 0310D112220E1F13007B3546 /* Resources */,
75 | );
76 | buildRules = (
77 | );
78 | dependencies = (
79 | );
80 | name = "xib-views";
81 | productName = "xib-views";
82 | productReference = 0310D114220E1F13007B3546 /* xib_views.framework */;
83 | productType = "com.apple.product-type.framework";
84 | };
85 | /* End PBXNativeTarget section */
86 |
87 | /* Begin PBXProject section */
88 | 0310D10B220E1F13007B3546 /* Project object */ = {
89 | isa = PBXProject;
90 | attributes = {
91 | LastUpgradeCheck = 1010;
92 | ORGANIZATIONNAME = "Mathieu Dutour";
93 | TargetAttributes = {
94 | 0310D113220E1F13007B3546 = {
95 | CreatedOnToolsVersion = 10.1;
96 | };
97 | };
98 | };
99 | buildConfigurationList = 0310D10E220E1F13007B3546 /* Build configuration list for PBXProject "xib-views" */;
100 | compatibilityVersion = "Xcode 9.3";
101 | developmentRegion = en;
102 | hasScannedForEncodings = 0;
103 | knownRegions = (
104 | en,
105 | );
106 | mainGroup = 0310D10A220E1F13007B3546;
107 | productRefGroup = 0310D115220E1F13007B3546 /* Products */;
108 | projectDirPath = "";
109 | projectRoot = "";
110 | targets = (
111 | 0310D113220E1F13007B3546 /* xib-views */,
112 | );
113 | };
114 | /* End PBXProject section */
115 |
116 | /* Begin PBXResourcesBuildPhase section */
117 | 0310D112220E1F13007B3546 /* Resources */ = {
118 | isa = PBXResourcesBuildPhase;
119 | buildActionMask = 2147483647;
120 | files = (
121 | 0310D122220E2389007B3546 /* LayerStylePreferences.xib in Resources */,
122 | 0310D120220E2030007B3546 /* TextStylePreferences.xib in Resources */,
123 | );
124 | runOnlyForDeploymentPostprocessing = 0;
125 | };
126 | /* End PBXResourcesBuildPhase section */
127 |
128 | /* Begin PBXSourcesBuildPhase section */
129 | 0310D110220E1F13007B3546 /* Sources */ = {
130 | isa = PBXSourcesBuildPhase;
131 | buildActionMask = 2147483647;
132 | files = (
133 | );
134 | runOnlyForDeploymentPostprocessing = 0;
135 | };
136 | /* End PBXSourcesBuildPhase section */
137 |
138 | /* Begin XCBuildConfiguration section */
139 | 0310D11A220E1F13007B3546 /* Debug */ = {
140 | isa = XCBuildConfiguration;
141 | buildSettings = {
142 | ALWAYS_SEARCH_USER_PATHS = NO;
143 | CLANG_ANALYZER_NONNULL = YES;
144 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
145 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
146 | CLANG_CXX_LIBRARY = "libc++";
147 | CLANG_ENABLE_MODULES = YES;
148 | CLANG_ENABLE_OBJC_ARC = YES;
149 | CLANG_ENABLE_OBJC_WEAK = YES;
150 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
151 | CLANG_WARN_BOOL_CONVERSION = YES;
152 | CLANG_WARN_COMMA = YES;
153 | CLANG_WARN_CONSTANT_CONVERSION = YES;
154 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
155 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
156 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
157 | CLANG_WARN_EMPTY_BODY = YES;
158 | CLANG_WARN_ENUM_CONVERSION = YES;
159 | CLANG_WARN_INFINITE_RECURSION = YES;
160 | CLANG_WARN_INT_CONVERSION = YES;
161 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
162 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
163 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
164 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
165 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
166 | CLANG_WARN_STRICT_PROTOTYPES = YES;
167 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
168 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
169 | CLANG_WARN_UNREACHABLE_CODE = YES;
170 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
171 | CODE_SIGN_IDENTITY = "-";
172 | COPY_PHASE_STRIP = NO;
173 | CURRENT_PROJECT_VERSION = 1;
174 | DEBUG_INFORMATION_FORMAT = dwarf;
175 | ENABLE_STRICT_OBJC_MSGSEND = YES;
176 | ENABLE_TESTABILITY = YES;
177 | GCC_C_LANGUAGE_STANDARD = gnu11;
178 | GCC_DYNAMIC_NO_PIC = NO;
179 | GCC_NO_COMMON_BLOCKS = YES;
180 | GCC_OPTIMIZATION_LEVEL = 0;
181 | GCC_PREPROCESSOR_DEFINITIONS = (
182 | "DEBUG=1",
183 | "$(inherited)",
184 | );
185 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
186 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
187 | GCC_WARN_UNDECLARED_SELECTOR = YES;
188 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
189 | GCC_WARN_UNUSED_FUNCTION = YES;
190 | GCC_WARN_UNUSED_VARIABLE = YES;
191 | MACOSX_DEPLOYMENT_TARGET = 10.14;
192 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
193 | MTL_FAST_MATH = YES;
194 | ONLY_ACTIVE_ARCH = YES;
195 | SDKROOT = macosx;
196 | VERSIONING_SYSTEM = "apple-generic";
197 | VERSION_INFO_PREFIX = "";
198 | };
199 | name = Debug;
200 | };
201 | 0310D11B220E1F13007B3546 /* Release */ = {
202 | isa = XCBuildConfiguration;
203 | buildSettings = {
204 | ALWAYS_SEARCH_USER_PATHS = NO;
205 | CLANG_ANALYZER_NONNULL = YES;
206 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
207 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
208 | CLANG_CXX_LIBRARY = "libc++";
209 | CLANG_ENABLE_MODULES = YES;
210 | CLANG_ENABLE_OBJC_ARC = YES;
211 | CLANG_ENABLE_OBJC_WEAK = YES;
212 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
213 | CLANG_WARN_BOOL_CONVERSION = YES;
214 | CLANG_WARN_COMMA = YES;
215 | CLANG_WARN_CONSTANT_CONVERSION = YES;
216 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
217 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
218 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
219 | CLANG_WARN_EMPTY_BODY = YES;
220 | CLANG_WARN_ENUM_CONVERSION = YES;
221 | CLANG_WARN_INFINITE_RECURSION = YES;
222 | CLANG_WARN_INT_CONVERSION = YES;
223 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
224 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
225 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
226 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
227 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
228 | CLANG_WARN_STRICT_PROTOTYPES = YES;
229 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
230 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
231 | CLANG_WARN_UNREACHABLE_CODE = YES;
232 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
233 | CODE_SIGN_IDENTITY = "-";
234 | COPY_PHASE_STRIP = NO;
235 | CURRENT_PROJECT_VERSION = 1;
236 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
237 | ENABLE_NS_ASSERTIONS = NO;
238 | ENABLE_STRICT_OBJC_MSGSEND = YES;
239 | GCC_C_LANGUAGE_STANDARD = gnu11;
240 | GCC_NO_COMMON_BLOCKS = YES;
241 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
242 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
243 | GCC_WARN_UNDECLARED_SELECTOR = YES;
244 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
245 | GCC_WARN_UNUSED_FUNCTION = YES;
246 | GCC_WARN_UNUSED_VARIABLE = YES;
247 | MACOSX_DEPLOYMENT_TARGET = 10.14;
248 | MTL_ENABLE_DEBUG_INFO = NO;
249 | MTL_FAST_MATH = YES;
250 | SDKROOT = macosx;
251 | VERSIONING_SYSTEM = "apple-generic";
252 | VERSION_INFO_PREFIX = "";
253 | };
254 | name = Release;
255 | };
256 | 0310D11D220E1F13007B3546 /* Debug */ = {
257 | isa = XCBuildConfiguration;
258 | buildSettings = {
259 | CODE_SIGN_IDENTITY = "";
260 | CODE_SIGN_STYLE = Automatic;
261 | COMBINE_HIDPI_IMAGES = YES;
262 | DEFINES_MODULE = YES;
263 | DYLIB_COMPATIBILITY_VERSION = 1;
264 | DYLIB_CURRENT_VERSION = 1;
265 | DYLIB_INSTALL_NAME_BASE = "@rpath";
266 | FRAMEWORK_VERSION = A;
267 | INFOPLIST_FILE = "xib-views/Info.plist";
268 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
269 | LD_RUNPATH_SEARCH_PATHS = (
270 | "$(inherited)",
271 | "@executable_path/../Frameworks",
272 | "@loader_path/Frameworks",
273 | );
274 | PRODUCT_BUNDLE_IDENTIFIER = "me.dutour.mathieu.xib-views";
275 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
276 | SKIP_INSTALL = YES;
277 | };
278 | name = Debug;
279 | };
280 | 0310D11E220E1F13007B3546 /* Release */ = {
281 | isa = XCBuildConfiguration;
282 | buildSettings = {
283 | CODE_SIGN_IDENTITY = "";
284 | CODE_SIGN_STYLE = Automatic;
285 | COMBINE_HIDPI_IMAGES = YES;
286 | DEFINES_MODULE = YES;
287 | DYLIB_COMPATIBILITY_VERSION = 1;
288 | DYLIB_CURRENT_VERSION = 1;
289 | DYLIB_INSTALL_NAME_BASE = "@rpath";
290 | FRAMEWORK_VERSION = A;
291 | INFOPLIST_FILE = "xib-views/Info.plist";
292 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
293 | LD_RUNPATH_SEARCH_PATHS = (
294 | "$(inherited)",
295 | "@executable_path/../Frameworks",
296 | "@loader_path/Frameworks",
297 | );
298 | PRODUCT_BUNDLE_IDENTIFIER = "me.dutour.mathieu.xib-views";
299 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
300 | SKIP_INSTALL = YES;
301 | };
302 | name = Release;
303 | };
304 | /* End XCBuildConfiguration section */
305 |
306 | /* Begin XCConfigurationList section */
307 | 0310D10E220E1F13007B3546 /* Build configuration list for PBXProject "xib-views" */ = {
308 | isa = XCConfigurationList;
309 | buildConfigurations = (
310 | 0310D11A220E1F13007B3546 /* Debug */,
311 | 0310D11B220E1F13007B3546 /* Release */,
312 | );
313 | defaultConfigurationIsVisible = 0;
314 | defaultConfigurationName = Release;
315 | };
316 | 0310D11C220E1F13007B3546 /* Build configuration list for PBXNativeTarget "xib-views" */ = {
317 | isa = XCConfigurationList;
318 | buildConfigurations = (
319 | 0310D11D220E1F13007B3546 /* Debug */,
320 | 0310D11E220E1F13007B3546 /* Release */,
321 | );
322 | defaultConfigurationIsVisible = 0;
323 | defaultConfigurationName = Release;
324 | };
325 | /* End XCConfigurationList section */
326 | };
327 | rootObject = 0310D10B220E1F13007B3546 /* Project object */;
328 | }
329 |
--------------------------------------------------------------------------------
/xib-views/xib-views.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/xib-views/xib-views.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/xib-views/xib-views.xcodeproj/project.xcworkspace/xcuserdata/mathieudutour.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mathieudutour/sketch-styles-hierarchy/720a815b7ad2b64102333d5ca8d51a25bfd8179f/xib-views/xib-views.xcodeproj/project.xcworkspace/xcuserdata/mathieudutour.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/xib-views/xib-views.xcodeproj/xcuserdata/mathieudutour.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | xib-views.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/xib-views/xib_views.h:
--------------------------------------------------------------------------------
1 | //
2 | // xib_views.h
3 | // xib-views
4 | //
5 | // Created by Mathieu Dutour on 08/02/2019.
6 | // Copyright © 2019 Mathieu Dutour. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for xib_views.
12 | FOUNDATION_EXPORT double xib_viewsVersionNumber;
13 |
14 | //! Project version string for xib_views.
15 | FOUNDATION_EXPORT const unsigned char xib_viewsVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------