├── .gitignore ├── .gitmodules ├── CSSketch Helper ├── CSK Tests │ ├── CSK_Tests.m │ └── Info.plist ├── CSS Launcher │ └── main.m ├── CSSketch Helper-Bridging-Header.h ├── CSSketch Helper.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── CSSketch Helper.xcscmblueprint │ ├── xcshareddata │ │ └── xcschemes │ │ │ ├── CSK Tests.xcscheme │ │ │ ├── CSS Launcher.xcscheme │ │ │ ├── CSSketch Helper.xcscheme │ │ │ └── CSSketch-Installer.xcscheme │ └── xcuserdata │ │ └── macbook.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── CSSketch Helper │ └── Info.plist ├── CSSketch-Installer │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon_128x128.png │ │ │ ├── icon_128x128@2x.png │ │ │ ├── icon_16x16.png │ │ │ ├── icon_16x16@2x.png │ │ │ ├── icon_256x256.png │ │ │ ├── icon_256x256@2x.png │ │ │ ├── icon_32x32.png │ │ │ ├── icon_32x32@2x.png │ │ │ ├── icon_512x512.png │ │ │ └── icon_512x512@2x.png │ │ ├── Contents.json │ │ ├── InstallerBackground.imageset │ │ │ ├── Contents.json │ │ │ └── InstallerBackground.png │ │ ├── Sketch 3 Icon.imageset │ │ │ ├── Contents.json │ │ │ └── Sketch 3 Icon.png │ │ └── installButton.imageset │ │ │ ├── Contents.json │ │ │ └── install-button-bg.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── CSKInstallButton.h │ ├── CSKInstallButton.m │ ├── Info.plist │ └── main.m ├── External │ └── Regex │ │ ├── RegExCategories.h │ │ └── RegExCategories.m ├── Resources │ ├── embedded.css │ ├── external │ │ └── less-rhino-1.7.5.js │ ├── icon-layout.png │ └── icon-layout24.png └── src │ ├── Categories │ ├── NSError+CSSketch.h │ ├── NSError+CSSketch.m │ ├── NSString+Paths.h │ └── NSString+Paths.m │ ├── Controllers │ ├── CSKDocumentController.h │ ├── CSKDocumentController.m │ ├── CSKMainController.h │ ├── CSKMainController.m │ ├── CSKSVGEditorController.h │ ├── CSKSVGEditorController.m │ ├── CSKStylesheet.h │ └── CSKStylesheet.m │ ├── Headers │ ├── CSKSketchHeaders.h │ ├── CSKSketchHeaders.m │ └── PrefixHeader.pch │ ├── Models │ ├── DOM │ │ ├── CSKDOM.h │ │ └── CSKDOM.m │ ├── Files │ │ ├── CSKFileMonitor.h │ │ └── CSKFileMonitor.m │ ├── Layers │ │ ├── CSKLayers.h │ │ └── CSKLayers.m │ ├── Layout │ │ ├── CSKLayerCSS.h │ │ └── CSKLayerCSS.m │ ├── Less │ │ ├── CSKLess.h │ │ └── CSKLess.m │ └── Toolbar │ │ ├── CSKToolbarProxy.h │ │ └── CSKToolbarProxy.m │ └── Research │ ├── RS_MSTextLayer.h │ └── RS_MSTextLayer.m ├── CSSketch.sketchplugin ├── CSSketch Helper.bundle │ └── Contents │ │ ├── Frameworks │ │ └── SketchKit.framework │ │ │ ├── Resources │ │ │ ├── SketchKit │ │ │ └── Versions │ │ │ ├── A │ │ │ ├── Resources │ │ │ │ └── Info.plist │ │ │ └── SketchKit │ │ │ └── Current │ │ ├── Info.plist │ │ ├── MacOS │ │ └── CSSketch Helper │ │ └── Resources │ │ ├── embedded.css │ │ ├── external │ │ └── less-rhino-1.7.5.js │ │ ├── icon-layout.png │ │ └── icon-layout24.png └── Contents │ └── Sketch │ ├── manifest.json │ └── src │ ├── CSSketch.js │ └── CSSketch_cached.js ├── Documentation └── dribbbleFollow.png ├── Examples ├── CSSketch - Installer.sketch ├── CSSketch - Keyboard.less ├── CSSketch - Keyboard.sketch ├── CSSketch - Netflix Player Redesign.less ├── CSSketch - Netflix Player Redesign.sketch ├── CSSketch - flexBox.less ├── CSSketch - flexBox.sass ├── CSSketch - flexBox.sketch ├── CSSketch - layout.less ├── CSSketch - layout.sketch └── common.less ├── External ├── CocoaSass │ ├── CocoaSass.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── CocoaSass.xcscmblueprint │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── CocoaSass.xcscheme │ └── CocoaSass │ │ ├── CocoaSass.h │ │ └── CocoaSass.m └── MonolithOSX.framework │ ├── Headers │ ├── Modules │ ├── MonolithOSX │ ├── PrivateHeaders │ ├── Resources │ └── Versions │ ├── A │ ├── Headers │ │ ├── MONCallHandler.h │ │ └── MONHook.h │ ├── MonolithOSX │ └── Resources │ │ └── Info.plist │ └── Current ├── LICENSE ├── Readme.md ├── Scripts ├── CSSketch-remote.coscript ├── CenterFinder │ ├── canvasDraw.js │ ├── main.html │ ├── main.js │ ├── package.json │ ├── polygonManager.js │ └── webpack.config.js ├── CentroidFinder.js ├── Classes │ └── Xcode.rb ├── buildRelease.rb └── setupSymlinks.rb └── screencast.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | xcuserdata/ 3 | compile/ 4 | build/ 5 | DerivedData/ 6 | *.xccheckout 7 | release/ 8 | debug.plist 9 | Examples/debug.html 10 | node_modules -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "External/libsass"] 2 | path = External/libsass 3 | url = https://github.com/sass/libsass 4 | [submodule "External/SketchKit"] 5 | path = External/SketchKit 6 | url = https://github.com/JohnCoates/SketchKit 7 | -------------------------------------------------------------------------------- /CSSketch Helper/CSK Tests/CSK_Tests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSK_Tests.m 3 | // CSK Tests 4 | // 5 | // Created by John Coates on 10/4/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CSKLess.h" 11 | #import "CSKDOM.h" 12 | #import "CSKStylesheet.h" 13 | 14 | @interface CSK_Tests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation CSK_Tests 19 | 20 | - (void)setUp { 21 | [super setUp]; 22 | // Put setup code here. This method is called before the invocation of each test method in the class. 23 | } 24 | 25 | - (void)tearDown { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testCSSparsing { 31 | [CSKLess compileLessStylesheet:@".class { width: (1 + 1) }" completion:^(NSError *error, NSString *compiledCSS) { 32 | if (error) { 33 | NSLog(@"error: %@", error); 34 | return; 35 | } 36 | NSLog(@"compiled CSS: %@", compiledCSS); 37 | }]; 38 | } 39 | 40 | 41 | //- (void)testDOM { 42 | // [CSKLess compileLessStylesheet:@".widthTest { width: (1 + 1) }" completion:^(NSError *error, NSString *compiledCSS) { 43 | // if (error) { 44 | // NSLog(@"error: %@", error); 45 | // return; 46 | // } 47 | // NSLog(@"compiled CSS: %@", compiledCSS); 48 | // [[CSKDOM alloc] initWithStylesheet:compiledCSS callback:^(NSError *error, NSArray *computedDOM) { 49 | // 50 | // }]; 51 | // }]; 52 | //// [CSKDOM new]; 53 | //} 54 | 55 | - (void)testStylesheet { 56 | // NSString *stylesheetPath = @"/Users/macbook/Dev/Extensions/Sketch/Sketch-CSS/Examples/flexBox.css"; 57 | NSString *stylesheetPath = @"/Users/macbook/Dev/Extensions/Sketch/CSSketch/Examples/Less - Netflix Player Redesign.less"; 58 | NSURL *stylesheetURL = [NSURL fileURLWithPath:stylesheetPath]; 59 | CSKStylesheet *stylesheetController = [[CSKStylesheet alloc] initWithFile:stylesheetURL]; 60 | 61 | NSString *plistPath = @"/Users/macbook/Dev/Extensions/Sketch/CSSketch/debug.plist"; 62 | NSData *treeData = [NSData dataWithContentsOfFile:plistPath]; 63 | 64 | NSMutableDictionary *layerTree = [NSPropertyListSerialization propertyListWithData:treeData 65 | options:NSPropertyListMutableContainers format:NULL error:NULL]; 66 | stylesheetController.layerTree = layerTree; 67 | 68 | [stylesheetController parseStylesheet]; 69 | // [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]]; 70 | 71 | // let DOM load 72 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; 73 | 74 | 75 | } 76 | 77 | 78 | - (void)testInSketch { 79 | // Close Sketch 80 | NSArray *sketches = [NSRunningApplication 81 | runningApplicationsWithBundleIdentifier:@"com.bohemiancoding.sketch3"]; 82 | if (sketches.count) { 83 | NSRunningApplication *sketch = sketches[0]; 84 | [sketch forceTerminate]; 85 | } 86 | 87 | // launch Sketch 88 | // TODO: change to relative path 89 | [[NSWorkspace sharedWorkspace] openFile:@"/Users/macbook/Dev/Extensions/Sketch/CSSketch/Examples/Less - Netflix Player Redesign.sketch"]; 90 | // [[NSWorkspace sharedWorkspace] openFile:@"/Users/macbook/Dev/Extensions/Sketch/CSSketch/Examples/flexBox.sketch"]; 91 | 92 | // sleep for a bit while Sketch launches 93 | sleep(2); 94 | 95 | NSString *scriptPath = @"/Users/macbook/Dev/Extensions/Sketch/CSSketch/CSSketch-remote.coscript"; 96 | 97 | NSPipe *pipe = [NSPipe pipe]; 98 | NSFileHandle *file = pipe.fileHandleForReading; 99 | 100 | NSTask *task = [[NSTask alloc] init]; 101 | task.launchPath = @"/usr/local/bin/coscript"; 102 | task.arguments = @[scriptPath]; 103 | task.standardOutput = pipe; 104 | 105 | [task launch]; 106 | [file closeFile]; 107 | 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /CSSketch Helper/CSK Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /CSSketch Helper/CSS Launcher/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CSS Launcher 4 | // 5 | // Created by John Coates on 2/4/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | int main(int argc, const char * argv[]) { 13 | @autoreleasepool { 14 | // Close Sketch 15 | NSArray *sketches = [NSRunningApplication 16 | runningApplicationsWithBundleIdentifier:@"com.bohemiancoding.sketch3"]; 17 | if (sketches.count) { 18 | NSRunningApplication *sketch = sketches[0]; 19 | [sketch forceTerminate]; 20 | } 21 | 22 | // launch Sketch 23 | NSString *currentSourcePath = [NSString stringWithFormat:@"%s", __FILE__]; 24 | NSString *launcherFolder = [currentSourcePath stringByDeletingLastPathComponent]; 25 | NSString *helperFolder = [launcherFolder stringByDeletingLastPathComponent]; 26 | NSString *projectFolder = [helperFolder stringByDeletingLastPathComponent]; 27 | NSString *scriptsfolder = [projectFolder stringByAppendingPathComponent:@"Scripts"]; 28 | 29 | NSString *scriptPath = [scriptsfolder stringByAppendingPathComponent:@"CSSketch-remote.coscript"]; 30 | NSLog(@"Launching CSSketch with script: %@", scriptPath); 31 | NSPipe *pipe = [NSPipe pipe]; 32 | NSFileHandle *file = pipe.fileHandleForReading; 33 | 34 | NSTask *task = [[NSTask alloc] init]; 35 | task.launchPath = @"/usr/local/bin/coscript"; 36 | task.arguments = @[scriptPath]; 37 | task.standardOutput = pipe; 38 | 39 | [task launch]; 40 | [file closeFile]; 41 | } 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper.xcodeproj/project.xcworkspace/xcshareddata/CSSketch Helper.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "DB0AA8B77050AFE7C658D7A871F99ECFEA6230C3", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "DB0AA8B77050AFE7C658D7A871F99ECFEA6230C3" : 0, 8 | "3F4433ED5A780EDFF7B8C0257D51FE25CD8299D4" : 0, 9 | "B5579E252A63267B1B29E4E7A348EF9ECB94F757" : 0 10 | }, 11 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "1F81B77F-C239-4FCF-B140-193D31D526C3", 12 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 13 | "DB0AA8B77050AFE7C658D7A871F99ECFEA6230C3" : "CSSketch\/", 14 | "3F4433ED5A780EDFF7B8C0257D51FE25CD8299D4" : "CSSketch\/external\/libsass\/", 15 | "B5579E252A63267B1B29E4E7A348EF9ECB94F757" : "CSSketch\/External\/SketchKit\/" 16 | }, 17 | "DVTSourceControlWorkspaceBlueprintNameKey" : "CSSketch Helper", 18 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 19 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CSSketch Helper\/CSSketch Helper.xcodeproj", 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 21 | { 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/sass\/libsass", 23 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 24 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3F4433ED5A780EDFF7B8C0257D51FE25CD8299D4" 25 | }, 26 | { 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/JohnCoates\/SketchKit", 28 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 29 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "B5579E252A63267B1B29E4E7A348EF9ECB94F757" 30 | }, 31 | { 32 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnCoates\/CSSketch.git", 33 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 34 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "DB0AA8B77050AFE7C658D7A871F99ECFEA6230C3" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper.xcodeproj/xcshareddata/xcschemes/CSK Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper.xcodeproj/xcshareddata/xcschemes/CSS Launcher.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper.xcodeproj/xcshareddata/xcschemes/CSSketch Helper.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper.xcodeproj/xcshareddata/xcschemes/CSSketch-Installer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper.xcodeproj/xcuserdata/macbook.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper.xcodeproj/xcuserdata/macbook.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CSK Tests.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | CSS Launcher.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 5 16 | 17 | CSSketch Helper.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 1 21 | 22 | CSSketch-Installer.xcscheme_^#shared#^_ 23 | 24 | orderHint 25 | 6 26 | 27 | 28 | SuppressBuildableAutocreation 29 | 30 | FA4B27BB1C64182500C82A0C 31 | 32 | primary 33 | 34 | 35 | FA4B27DE1C6542DF00C82A0C 36 | 37 | primary 38 | 39 | 40 | FA56F7CE1BC0D6C4005673E0 41 | 42 | primary 43 | 44 | 45 | FA7D76C71BC1AB5000FCDEF5 46 | 47 | primary 48 | 49 | 50 | FA7D76D41BC1AB6900FCDEF5 51 | 52 | primary 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch Helper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.11 23 | NSHumanReadableCopyright 24 | Copyright © 2015 John Coates. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // CSSketch-Installer 4 | // 5 | // Created by John Coates on 2/5/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // CSSketch-Installer 4 | // 5 | // Created by John Coates on 2/5/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "CSKInstallButton.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @property (weak) IBOutlet NSWindow *window; 15 | 16 | @property (weak) IBOutlet NSImageView *buttonView; 17 | @property (weak) IBOutlet NSTextField *redesignLabel; 18 | @property (weak) IBOutlet NSTextField *installLabel; 19 | @end 20 | 21 | @implementation AppDelegate 22 | 23 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 24 | self.window.delegate = self; 25 | 26 | // install Button 27 | CSKInstallButton *installButton = [[CSKInstallButton alloc] initWithFrame:self.buttonView.frame]; 28 | installButton.cursor = [NSCursor pointingHandCursor]; 29 | installButton.target = self; 30 | installButton.action = @selector(installClick:); 31 | [installButton setTransparent:true]; 32 | [self.window.contentView addSubview:installButton]; 33 | 34 | // redesign Button 35 | CSKInstallButton *redesignButton = [[CSKInstallButton alloc] initWithFrame:self.redesignLabel.frame]; 36 | redesignButton.cursor = [NSCursor pointingHandCursor]; 37 | redesignButton.target = self; 38 | redesignButton.action = @selector(redesignClick:); 39 | [redesignButton setTransparent:true]; 40 | [self.window.contentView addSubview:redesignButton]; 41 | } 42 | 43 | - (void)installClick:(NSButton *)button { 44 | 45 | if (![self installPluginResourceWithName:@"CSSketch"]) { 46 | [self.installLabel setStringValue:@"Install Error"]; 47 | return; 48 | } 49 | 50 | if (![self installPluginResourceWithName:@"SketchKit"]) { 51 | [self.installLabel setStringValue:@"Install Error"]; 52 | return; 53 | } 54 | 55 | [self.installLabel setStringValue:@"Installed!"]; 56 | 57 | } 58 | 59 | - (BOOL)installPluginResourceWithName:(NSString *)name { 60 | NSString *pluginsPath = @"~/Library/Application Support/com.bohemiancoding.sketch3/Plugins"; 61 | pluginsPath = [pluginsPath stringByExpandingTildeInPath]; 62 | 63 | NSBundle *bundle = [NSBundle mainBundle]; 64 | NSString *pluginBundlePath = [bundle pathForResource:name ofType:@"sketchplugin"]; 65 | 66 | if (!pluginBundlePath) { 67 | NSLog(@"Couldn't find plugin %@", name); 68 | return FALSE; 69 | } 70 | 71 | NSString *pluginDestinationPath = [pluginsPath stringByAppendingPathComponent:pluginBundlePath.lastPathComponent]; 72 | 73 | NSFileManager *fileManager = [NSFileManager defaultManager]; 74 | 75 | if ([fileManager fileExistsAtPath:pluginDestinationPath]) { 76 | NSError *error = nil; 77 | [fileManager removeItemAtPath:pluginDestinationPath error:&error]; 78 | 79 | if (error) { 80 | NSLog(@"CSSketch Install Error: Couldn't remove %@: %@", 81 | pluginDestinationPath, error); 82 | return FALSE; 83 | } 84 | } 85 | 86 | // NSLog(@"copying from: %@", pluginBundlePath); 87 | NSError *error = nil; 88 | [fileManager copyItemAtPath:pluginBundlePath toPath:pluginDestinationPath error:&error]; 89 | if (error) { 90 | NSLog(@"CSSketch Install Error: Couldn't copy %@ to %@: %@", 91 | pluginBundlePath, pluginDestinationPath, error); 92 | return FALSE; 93 | } 94 | 95 | return TRUE; 96 | } 97 | 98 | - (void)redesignClick:(NSButton *)button { 99 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://github.com/JohnCoates/CSSketch/issues/19"]]; 100 | } 101 | 102 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 103 | } 104 | 105 | - (BOOL)windowShouldClose:(id)sender { 106 | [[NSApplication sharedApplication] terminate:nil]; 107 | return true; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/InstallerBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "InstallerBackground.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/InstallerBackground.imageset/InstallerBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/InstallerBackground.imageset/InstallerBackground.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/Sketch 3 Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Sketch 3 Icon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/Sketch 3 Icon.imageset/Sketch 3 Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/Sketch 3 Icon.imageset/Sketch 3 Icon.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/installButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "install-button-bg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Assets.xcassets/installButton.imageset/install-button-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch Helper/CSSketch-Installer/Assets.xcassets/installButton.imageset/install-button-bg.png -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/CSKInstallButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKInstallButton.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 2/5/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CSKInstallButton : NSButton 12 | 13 | @property (strong) NSCursor *cursor; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/CSKInstallButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKInstallButton.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 2/5/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKInstallButton.h" 10 | 11 | @implementation CSKInstallButton 12 | 13 | - (void)resetCursorRects 14 | { 15 | if (self.cursor) { 16 | [self addCursorRect:[self bounds] cursor: self.cursor]; 17 | } else { 18 | [super resetCursorRects]; 19 | } 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.1 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 2 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 John Coates. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /CSSketch Helper/CSSketch-Installer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CSSketch-Installer 4 | // 5 | // Created by John Coates on 2/5/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /CSSketch Helper/External/Regex/RegExCategories.h: -------------------------------------------------------------------------------- 1 | // 2 | // RegExCategories.h 3 | // 4 | // https://github.com/bendytree/Objective-C-RegEx-Categories 5 | // 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2013 Josh Wright <@BendyTree> 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | #import 31 | 32 | 33 | /********************************************************/ 34 | /*********************** MACROS *************************/ 35 | /********************************************************/ 36 | 37 | /* 38 | * By default, we create an alias for NSRegularExpression 39 | * called `Rx` and creates a macro `RX()` for quick regex creation. 40 | * 41 | * If you don't want these macros, add the following statement 42 | * before you include this library: 43 | * 44 | * #define DisableRegExCategoriesMacros 45 | */ 46 | 47 | 48 | /** 49 | * Creates a macro (alias) for NSRegularExpression named `Rx`. 50 | * 51 | * ie. 52 | * NSRegularExpression* rx = [[Rx alloc] initWithPattern:@"\d+" options:0 error:nil]; 53 | */ 54 | 55 | #ifndef DisableRegExCategoriesMacros 56 | #define Rx NSRegularExpression 57 | #endif 58 | 59 | 60 | /** 61 | * Creates a macro (alias) for NSRegularExpression named `Rx`. 62 | * 63 | * ie. 64 | * NSRegularExpression* rx = [[Rx alloc] initWithPattern:@"\d+" options:0 error:nil]; 65 | */ 66 | 67 | #ifndef DisableRegExCategoriesMacros 68 | #define RX(pattern) [[NSRegularExpression alloc] initWithPattern:pattern] 69 | #endif 70 | 71 | 72 | 73 | /********************************************************/ 74 | /******************* MATCH OBJECTS **********************/ 75 | /********************************************************/ 76 | 77 | /** 78 | * RxMatch represents a single match. It contains the 79 | * matched value, range, sub groups, and the original 80 | * string. 81 | */ 82 | 83 | @interface RxMatch : NSObject 84 | @property (nonatomic, copy) NSString* value; /* The substring that matched the expression. */ 85 | @property (nonatomic, assign) NSRange range; /* The range of the original string that was matched. */ 86 | @property (nonatomic, copy) NSArray* groups; /* Each object is an RxMatchGroup. */ 87 | @property (nonatomic, copy) NSString* original; /* The full original string that was matched against. */ 88 | @end 89 | 90 | 91 | @interface RxMatchGroup : NSObject 92 | @property (nonatomic, copy) NSString* value; 93 | @property (nonatomic, assign) NSRange range; 94 | @end 95 | 96 | 97 | 98 | 99 | 100 | /** 101 | * Extend NSRegularExpression. 102 | */ 103 | 104 | @interface NSRegularExpression (ObjectiveCRegexCategories) 105 | 106 | 107 | /*******************************************************/ 108 | /******************* INITIALIZATION ********************/ 109 | /*******************************************************/ 110 | 111 | /** 112 | * Initialize an Rx object from a string. 113 | * 114 | * ie. 115 | * Rx* rx = [[Rx alloc] initWithString:@"\d+"]; 116 | * 117 | * Swift: 118 | * var rx = NSRegularExpression(pattern:"\d+"); 119 | */ 120 | 121 | - (NSRegularExpression*) initWithPattern:(NSString*)pattern; 122 | 123 | 124 | /** 125 | * Initialize an Rx object from a string. 126 | * 127 | * ie. 128 | * Rx* rx = [Rx rx:@"\d+"]; 129 | * 130 | * Swift: 131 | * var rx = NSRegularExpression.rx("\d+"); 132 | */ 133 | 134 | + (NSRegularExpression*) rx:(NSString*)pattern; 135 | 136 | 137 | /** 138 | * Initialize an Rx object from a string. By default, NSRegularExpression 139 | * is case sensitive, but this signature allows you to change that. 140 | * 141 | * ie. 142 | * Rx* rx = [Rx rx:@"\d+" ignoreCase:YES]; 143 | * 144 | * Swift: 145 | * var rx = NSRegularExpression.rx("\d+", ignoreCase: true); 146 | */ 147 | 148 | + (NSRegularExpression*) rx:(NSString*)pattern ignoreCase:(BOOL)ignoreCase; 149 | 150 | 151 | /** 152 | * Initialize an Rx object from a string and options. 153 | * 154 | * ie. 155 | * Rx* rx = [Rx rx:@"\d+" options:NSRegularExpressionCaseInsensitive]; 156 | * 157 | * Swift: 158 | * var rx = NSRegularExpression.rx("\d+", options: .CaseInsensitive); 159 | */ 160 | 161 | + (NSRegularExpression*) rx:(NSString*)pattern options:(NSRegularExpressionOptions)options; 162 | 163 | 164 | /*******************************************************/ 165 | /********************** IS MATCH ***********************/ 166 | /*******************************************************/ 167 | 168 | /** 169 | * Returns true if the string matches the regex. May also 170 | * be called on NSString as [@"\d" isMatch:rx]. 171 | * 172 | * ie. 173 | * Rx* rx = RX(@"\d+"); 174 | * BOOL isMatch = [rx isMatch:@"Dog #1"]; // => true 175 | * 176 | * Swift: 177 | * var rx = NSRegularExpression.rx("\d+"); 178 | * var isMatch = rx.isMatch("Dog #1"); // => true 179 | */ 180 | 181 | - (BOOL) isMatch:(NSString*)matchee; 182 | 183 | 184 | /** 185 | * Returns the index of the first match of the passed string. 186 | * 187 | * ie. 188 | * int i = [RX(@"\d+") indexOf:@"Buy 1 dog or buy 2?"]; // => 4 189 | */ 190 | 191 | - (int) indexOf:(NSString*)str; 192 | 193 | 194 | /** 195 | * Splits a string using the regex to identify delimeters. Returns 196 | * an NSArray of NSStrings. 197 | * 198 | * ie. 199 | * NSArray* pieces = [RX(@"[ ,]") split:@"A dog,cat"]; 200 | * => @[@"A", @"dog", @"cat"] 201 | */ 202 | 203 | - (NSArray*) split:(NSString*)str; 204 | 205 | 206 | /** 207 | * Replaces all occurrences in a string with a replacement string. 208 | * 209 | * ie. 210 | * NSString* result = [RX(@"ruf+") replace:@"ruf ruff!" with:@"meow"]; 211 | * => @"meow meow!" 212 | */ 213 | 214 | - (NSString*) replace:(NSString*)string with:(NSString*)replacement; 215 | 216 | 217 | /** 218 | * Replaces all occurrences of a regex using a block. The block receives the match 219 | * and should return the replacement. 220 | * 221 | * ie. 222 | * NSString* result = [RX(@"[A-Z]+") replace:@"i love COW" withBlock:^(NSString*){ return @"lamp"; }]; 223 | * => @"i love lamp" 224 | */ 225 | 226 | - (NSString*) replace:(NSString*)string withBlock:(NSString*(^)(NSString* match))replacer; 227 | 228 | 229 | /** 230 | * Replaces all occurrences of a regex using a block. The block receives a RxMatch object 231 | * that contains all the details of the match and should return a string 232 | * which is what the match is replaced with. 233 | * 234 | * ie. 235 | * NSString* result = [RX(@"\\w+") replace:@"hi bud" withDetailsBlock:^(RxMatch* match){ return [NSString stringWithFormat:@"%i", match.value.length]; }]; 236 | * => @"2 3" 237 | */ 238 | 239 | - (NSString*) replace:(NSString *)string withDetailsBlock:(NSString*(^)(RxMatch* match))replacer; 240 | 241 | 242 | /** 243 | * Returns an array of matched root strings with no other match information. 244 | * 245 | * ie. 246 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 247 | * NSArray* matches = [RX(@"\\w+[@]\\w+[.](\\w+)") matches:str]; 248 | * => @[ @"me@example.com", @"you@example.com" ] 249 | */ 250 | 251 | - (NSArray*) matches:(NSString*)str; 252 | 253 | 254 | /** 255 | * Returns a string which is the first match of the NSRegularExpression. 256 | * 257 | * ie. 258 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 259 | * NSString* match = [RX(@"\\w+[@]\\w+[.](\\w+)") firstMatch:str]; 260 | * => @"me@example.com" 261 | */ 262 | 263 | - (NSString*) firstMatch:(NSString*)str; 264 | 265 | 266 | /** 267 | * Returns an NSArray of RxMatch* objects. Each match contains the matched 268 | * value, range, groups, etc. 269 | * 270 | * ie. 271 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 272 | * NSArray* matches = [str matchesWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 273 | */ 274 | 275 | - (NSArray*) matchesWithDetails:(NSString*)str; 276 | 277 | 278 | /** 279 | * Returns the first match as an RxMatch* object. 280 | * 281 | * ie. 282 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 283 | * Rx* rx = RX(@"\\w+[@]\\w+[.](\\w+)"); 284 | * RxMatch* match = [rx firstMatchWithDetails:str]; 285 | */ 286 | 287 | - (RxMatch*) firstMatchWithDetails:(NSString*)str; 288 | 289 | @end 290 | 291 | 292 | 293 | /** 294 | * A category on NSString to make it easy to use 295 | * Rx in simple operations. 296 | */ 297 | 298 | @interface NSString (ObjectiveCRegexCategories) 299 | 300 | 301 | /** 302 | * Initialize an NSRegularExpression object from a string. 303 | * 304 | * ie. 305 | * NSRegularExpression* rx = [@"\d+" toRx]; 306 | */ 307 | 308 | - (NSRegularExpression*) toRx; 309 | 310 | 311 | /** 312 | * Initialize an NSRegularExpression object from a string with 313 | * a flag denoting case-sensitivity. By default, NSRegularExpression 314 | * is case sensitive. 315 | * 316 | * ie. 317 | * NSRegularExpression* rx = [@"\d+" toRxIgnoreCase:YES]; 318 | */ 319 | 320 | - (NSRegularExpression*) toRxIgnoreCase:(BOOL)ignoreCase; 321 | 322 | 323 | /** 324 | * Initialize an NSRegularExpression object from a string with options. 325 | * 326 | * ie. 327 | * NSRegularExpression* rx = [@"\d+" toRxWithOptions:NSRegularExpressionCaseInsensitive]; 328 | */ 329 | 330 | - (NSRegularExpression*) toRxWithOptions:(NSRegularExpressionOptions)options; 331 | 332 | 333 | /** 334 | * Returns true if the string matches the regex. May also 335 | * be called as on Rx as [rx isMatch:@"some string"]. 336 | * 337 | * ie. 338 | * BOOL isMatch = [@"Dog #1" isMatch:RX(@"\d+")]; // => true 339 | */ 340 | 341 | - (BOOL) isMatch:(NSRegularExpression*)rx; 342 | 343 | 344 | /** 345 | * Returns the index of the first match according to 346 | * the regex passed in. 347 | * 348 | * ie. 349 | * int i = [@"Buy 1 dog or buy 2?" indexOf:RX(@"\d+")]; // => 4 350 | */ 351 | 352 | - (int) indexOf:(NSRegularExpression*)rx; 353 | 354 | 355 | /** 356 | * Splits a string using the regex to identify delimeters. Returns 357 | * an NSArray of NSStrings. 358 | * 359 | * ie. 360 | * NSArray* pieces = [@"A dog,cat" split:RX(@"[ ,]")]; 361 | * => @[@"A", @"dog", @"cat"] 362 | */ 363 | 364 | - (NSArray*) split:(NSRegularExpression*)rx; 365 | 366 | 367 | /** 368 | * Replaces all occurrences of a regex with a replacement string. 369 | * 370 | * ie. 371 | * NSString* result = [@"ruf ruff!" replace:RX(@"ruf+") with:@"meow"]; 372 | * => @"meow meow!" 373 | */ 374 | 375 | - (NSString*) replace:(NSRegularExpression*)rx with:(NSString*)replacement; 376 | 377 | 378 | /** 379 | * Replaces all occurrences of a regex using a block. The block receives the match 380 | * and should return the replacement. 381 | * 382 | * ie. 383 | * NSString* result = [@"i love COW" replace:RX(@"[A-Z]+") withBlock:^(NSString*){ return @"lamp"; }]; 384 | * => @"i love lamp" 385 | */ 386 | 387 | - (NSString*) replace:(NSRegularExpression *)rx withBlock:(NSString*(^)(NSString* match))replacer; 388 | 389 | 390 | /** 391 | * Replaces all occurrences of a regex using a block. The block receives an RxMatch 392 | * object which contains all of the details for each match and should return a string 393 | * which is what the match is replaced with. 394 | * 395 | * ie. 396 | * NSString* result = [@"hi bud" replace:RX(@"\\w+") withDetailsBlock:^(RxMatch* match){ return [NSString stringWithFormat:@"%i", match.value.length]; }]; 397 | * => @"2 3" 398 | */ 399 | 400 | - (NSString*) replace:(NSRegularExpression *)rx withDetailsBlock:(NSString*(^)(RxMatch* match))replacer; 401 | 402 | 403 | /** 404 | * Returns an array of matched root strings with no other match information. 405 | * 406 | * ie. 407 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 408 | * NSArray* matches = [str matches:RX(@"\\w+[@]\\w+[.](\\w+)")]; 409 | * => @[ @"me@example.com", @"you@example.com" ] 410 | */ 411 | 412 | - (NSArray*) matches:(NSRegularExpression*)rx; 413 | 414 | 415 | /** 416 | * Returns a string which is the first match of the NSRegularExpression. 417 | * 418 | * ie. 419 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 420 | * NSString* match = [str firstMatch:RX(@"\\w+[@]\\w+[.](\\w+)")]; 421 | * => @"me@example.com" 422 | */ 423 | 424 | - (NSString*) firstMatch:(NSRegularExpression*)rx; 425 | 426 | 427 | /** 428 | * Returns an NSArray of RxMatch* objects. Each match contains the matched 429 | * value, range, groups, etc. 430 | * 431 | * ie. 432 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 433 | * NSArray* matches = [str matchesWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 434 | */ 435 | 436 | - (NSArray*) matchesWithDetails:(NSRegularExpression*)rx; 437 | 438 | 439 | /** 440 | * Returns an the first match as an RxMatch* object. 441 | * 442 | * ie. 443 | * NSString* str = @"My email is me@example.com and yours is you@example.com"; 444 | * RxMatch* match = [str firstMatchWithDetails:RX(@"\\w+[@]\\w+[.](\\w+)")]; 445 | */ 446 | 447 | - (RxMatch*) firstMatchWithDetails:(NSRegularExpression*)rx; 448 | 449 | @end 450 | 451 | -------------------------------------------------------------------------------- /CSSketch Helper/External/Regex/RegExCategories.m: -------------------------------------------------------------------------------- 1 | // 2 | // RegExCategories.m 3 | // 4 | // https://github.com/bendytree/Objective-C-RegEx-Categories 5 | // 6 | // 7 | // The MIT License (MIT) 8 | // 9 | // Copyright (c) 2013 Josh Wright <@BendyTree> 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | #import "RegExCategories.h" 31 | 32 | @implementation NSRegularExpression (ObjectiveCRegexCategories) 33 | 34 | - (id) initWithPattern:(NSString*)pattern 35 | { 36 | return [self initWithPattern:pattern options:0 error:nil]; 37 | } 38 | 39 | + (NSRegularExpression*) rx:(NSString*)pattern 40 | { 41 | return [[self alloc] initWithPattern:pattern]; 42 | } 43 | 44 | + (NSRegularExpression*) rx:(NSString*)pattern ignoreCase:(BOOL)ignoreCase 45 | { 46 | return [[self alloc] initWithPattern:pattern options:ignoreCase?NSRegularExpressionCaseInsensitive:0 error:nil]; 47 | } 48 | 49 | + (NSRegularExpression*) rx:(NSString*)pattern options:(NSRegularExpressionOptions)options 50 | { 51 | return [[self alloc] initWithPattern:pattern options:options error:nil]; 52 | } 53 | 54 | - (BOOL) isMatch:(NSString*)matchee 55 | { 56 | return [self numberOfMatchesInString:matchee options:0 range:NSMakeRange(0, matchee.length)] > 0; 57 | } 58 | 59 | - (int) indexOf:(NSString*)matchee 60 | { 61 | NSRange range = [self rangeOfFirstMatchInString:matchee options:0 range:NSMakeRange(0, matchee.length)]; 62 | return range.location == NSNotFound ? -1 : (int)range.location; 63 | } 64 | 65 | - (NSArray*) split:(NSString *)str 66 | { 67 | NSRange range = NSMakeRange(0, str.length); 68 | 69 | //get locations of matches 70 | NSMutableArray* matchingRanges = [NSMutableArray array]; 71 | NSArray* matches = [self matchesInString:str options:0 range:range]; 72 | for(NSTextCheckingResult* match in matches) { 73 | [matchingRanges addObject:[NSValue valueWithRange:match.range]]; 74 | } 75 | 76 | //invert ranges - get ranges of non-matched pieces 77 | NSMutableArray* pieceRanges = [NSMutableArray array]; 78 | 79 | //add first range 80 | [pieceRanges addObject:[NSValue valueWithRange:NSMakeRange(0, 81 | (matchingRanges.count == 0 ? str.length : [matchingRanges[0] rangeValue].location))]]; 82 | 83 | //add between splits ranges and last range 84 | for(int i=0; i=0; i--) { 120 | NSTextCheckingResult* match = matches[i]; 121 | NSString* matchStr = [string substringWithRange:match.range]; 122 | NSString* replacement = replacer(matchStr); 123 | [result replaceCharactersInRange:match.range withString:replacement]; 124 | } 125 | 126 | return result; 127 | } 128 | 129 | - (NSString*) replace:(NSString *)string withDetailsBlock:(NSString*(^)(RxMatch* match))replacer 130 | { 131 | //no replacer? just return 132 | if (!replacer) return string; 133 | 134 | //copy the string so we can replace subsections 135 | NSMutableString* replaced = [string mutableCopy]; 136 | 137 | //get matches 138 | NSArray* matches = [self matchesInString:string options:0 range:NSMakeRange(0, string.length)]; 139 | 140 | //replace each match (right to left so indexing doesn't get messed up) 141 | for (int i=(int)matches.count-1; i>=0; i--) { 142 | NSTextCheckingResult* result = matches[i]; 143 | RxMatch* match = [self resultToMatch:result original:string]; 144 | NSString* replacement = replacer(match); 145 | [replaced replaceCharactersInRange:result.range withString:replacement]; 146 | } 147 | 148 | return replaced; 149 | } 150 | 151 | - (NSArray*) matches:(NSString*)str 152 | { 153 | NSMutableArray* matches = [NSMutableArray array]; 154 | 155 | NSArray* results = [self matchesInString:str options:0 range:NSMakeRange(0, str.length)]; 156 | for (NSTextCheckingResult* result in results) { 157 | NSString* match = [str substringWithRange:result.range]; 158 | [matches addObject:match]; 159 | } 160 | 161 | return matches; 162 | } 163 | 164 | - (NSString*) firstMatch:(NSString*)str 165 | { 166 | NSTextCheckingResult* match = [self firstMatchInString:str options:0 range:NSMakeRange(0, str.length)]; 167 | 168 | if (!match) return nil; 169 | 170 | return [str substringWithRange:match.range]; 171 | } 172 | 173 | - (RxMatch*) resultToMatch:(NSTextCheckingResult*)result original:(NSString*)original 174 | { 175 | RxMatch* match = [[RxMatch alloc] init]; 176 | match.original = original; 177 | match.range = result.range; 178 | match.value = result.range.length ? [original substringWithRange:result.range] : nil; 179 | 180 | //groups 181 | NSMutableArray* groups = [NSMutableArray array]; 182 | for(int i=0; i 10 | 11 | @interface NSError (CSSketch) 12 | 13 | + (instancetype)errorWithMessage:(NSString *)message; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Categories/NSError+CSSketch.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+CSSketch.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 3/17/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import "NSError+CSSketch.h" 10 | 11 | @implementation NSError (CSSketch) 12 | 13 | + (instancetype)errorWithMessage:(NSString *)message { 14 | NSError *error; 15 | NSDictionary *userInfo; 16 | 17 | userInfo = @{ 18 | NSLocalizedDescriptionKey : message 19 | }; 20 | error = [NSError errorWithDomain:@"CSK" 21 | code:801 22 | userInfo:userInfo]; 23 | 24 | return error; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Categories/NSString+Paths.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Paths.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/11/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (Paths) 12 | 13 | - (NSString *)stringWithPathRelativeTo:(NSString *)anchorPath; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Categories/NSString+Paths.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Paths.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/11/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "NSString+Paths.h" 10 | 11 | @implementation NSString (Paths) 12 | 13 | - (NSString *)stringWithPathRelativeTo:(NSString *)anchorPath { 14 | NSArray *pathComponents = [self pathComponents]; 15 | NSArray *anchorComponents = [anchorPath pathComponents]; 16 | 17 | NSInteger componentsInCommon = MIN([pathComponents count], [anchorComponents count]); 18 | for (NSInteger i = 0, n = componentsInCommon; i < n; i++) { 19 | if (![[pathComponents objectAtIndex:i] isEqualToString:[anchorComponents objectAtIndex:i]]) { 20 | componentsInCommon = i; 21 | break; 22 | } 23 | } 24 | 25 | NSUInteger numberOfParentComponents = [anchorComponents count] - componentsInCommon; 26 | NSUInteger numberOfPathComponents = [pathComponents count] - componentsInCommon; 27 | 28 | NSMutableArray *relativeComponents = [NSMutableArray arrayWithCapacity: 29 | numberOfParentComponents + numberOfPathComponents]; 30 | for (NSInteger i = 0; i < numberOfParentComponents; i++) { 31 | [relativeComponents addObject:@".."]; 32 | } 33 | [relativeComponents addObjectsFromArray: 34 | [pathComponents subarrayWithRange:NSMakeRange(componentsInCommon, numberOfPathComponents)]]; 35 | return [NSString pathWithComponents:relativeComponents]; 36 | } 37 | 38 | @end -------------------------------------------------------------------------------- /CSSketch Helper/src/Controllers/CSKDocumentController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKDocumentController.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/12/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CSKDocumentController : NSObject 12 | 13 | @property (weak) CSK_MSDocument *document; 14 | 15 | - (instancetype)initWithDocument:(CSK_MSDocument *)document; 16 | - (void)layoutCurrentPageWithStylesheetURL:(NSURL *)stylesheetURL; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Controllers/CSKDocumentController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKDocumentController.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/12/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKDocumentController.h" 10 | 11 | @interface CSKDocumentController () 12 | 13 | 14 | // holds all the stylesheet rules applied to the current page 15 | @property (strong) NSString *pageStylesheetRules; 16 | 17 | @property (strong) CSKStylesheet *stylesheetController; 18 | @property (strong) CSKDOM *domModel; 19 | @end 20 | 21 | @implementation CSKDocumentController 22 | 23 | #pragma mark - Init 24 | 25 | - (instancetype)initWithDocument:(CSK_MSDocument *)document { 26 | self = [super init]; 27 | 28 | if (self) { 29 | self.document = document; 30 | } 31 | 32 | return self; 33 | } 34 | 35 | - (void)dealloc { 36 | if (DEBUG) { 37 | NSLog(@"CSKDocumentController dealloc"); 38 | } 39 | } 40 | 41 | #pragma mark - Entry Point 42 | 43 | - (void)layoutCurrentPageWithStylesheetURL:(NSURL *)stylesheetURL { 44 | self.pageStylesheetRules = @"\n"; 45 | 46 | CSK_MSPage *page = self.document.currentPage; 47 | 48 | NSString *stylesheet = nil; 49 | NSDictionary *layerTree = [CSKLayers layerTreeFromLayer:page stylesheetOuput:&stylesheet]; 50 | 51 | self.stylesheetController = [[CSKStylesheet alloc] initWithFile:stylesheetURL]; 52 | 53 | [self.stylesheetController parseStylesheet:^(NSError *error, NSString *compiledStylesheet) { 54 | 55 | if (error) { 56 | NSString *message = [NSString stringWithFormat:@"Couln't compile stylesheet: %@", error.localizedDescription]; 57 | [CSKMainController displayError:message]; 58 | self.stylesheetController = nil; 59 | return; 60 | 61 | } 62 | 63 | // CSKDOM uses WebKit which needs a UI thread 64 | dispatch_async(dispatch_get_main_queue(), ^{ 65 | // add calculated stylesheet rules 66 | // prior to custom stylesheet so their precedence is worst 67 | NSString *mergedStylesheet = [stylesheet stringByAppendingString:compiledStylesheet]; 68 | 69 | CSKDOM *domModel = [[CSKDOM alloc] initWithStylesheet:mergedStylesheet callback:^(NSError *error, NSDictionary *DOMTree) { 70 | 71 | if (DEBUG) { 72 | NSLog(@"computed dom: %@", DOMTree); 73 | } 74 | 75 | if (error) { 76 | NSLog(@"error: %@", error); 77 | return; 78 | } 79 | else { 80 | [CSKLayers layoutLayersWithDOMTree:DOMTree]; 81 | [[CSKMainController sharedInstance] refreshDocument]; 82 | } 83 | 84 | self.domModel = nil; 85 | 86 | } layerTree:layerTree]; // DOM completion 87 | 88 | self.domModel = domModel; 89 | }); // async dispatch 90 | 91 | 92 | self.stylesheetController = nil; 93 | }]; // stylesheet compile completion 94 | } 95 | 96 | 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Controllers/CSKMainController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKMainController.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/5/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CSKMainController : NSObject 12 | 13 | + (instancetype)sharedInstance; 14 | @property BOOL inSketch; 15 | @property (nonatomic, strong) NSString *embeddedStylesheet; 16 | @property (nonatomic, strong) NSBundle *pluginBundle; 17 | @property (weak) CSK_MSDocument *document; 18 | 19 | // Plugin Entry Points 20 | - (void)layoutLayersWithContext:(NSDictionary *)context; 21 | 22 | 23 | - (void)refreshDocument; 24 | 25 | + (NSBundle *)pluginBundle; 26 | + (void)displayError:(NSString *)error; 27 | 28 | + (BOOL)inSandbox; 29 | + (BOOL)inSketch; 30 | 31 | 32 | @end 33 | 34 | static inline BOOL MSLayerIsGroup(id layer) { 35 | if ([layer isKindOfClass:NSClassFromString(@"MSLayerGroup")] && ![layer isMemberOfClass:NSClassFromString(@"MSShapeGroup")]) { 36 | return TRUE; 37 | } 38 | return FALSE; 39 | } 40 | 41 | static inline BOOL MSLayerIsArtboard(id layer) { 42 | if ([layer isKindOfClass:NSClassFromString(@"MSArtboardGroup")]) { 43 | return TRUE; 44 | } 45 | return FALSE; 46 | } 47 | static inline BOOL MSLayerIsPage(id layer) { 48 | if ([layer isKindOfClass:NSClassFromString(@"MSPage")]) { 49 | return TRUE; 50 | } 51 | return FALSE; 52 | } -------------------------------------------------------------------------------- /CSSketch Helper/src/Controllers/CSKSVGEditorController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKSVGEditorController.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 4/11/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CSKSVGEditorController : NSObject 12 | 13 | + (instancetype)sharedInstance; 14 | 15 | - (BOOL)editCurrentlySelectedShape; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Controllers/CSKSVGEditorController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKSVGEditorController.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 4/11/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKSVGEditorController.h" 10 | #import "CSKMainController.h" 11 | 12 | @implementation CSKSVGEditorController 13 | 14 | + (instancetype)sharedInstance { 15 | static CSKSVGEditorController *instance; 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | instance = [CSKSVGEditorController new]; 19 | }); 20 | 21 | return instance; 22 | } 23 | 24 | - (instancetype)init { 25 | self = [super init]; 26 | 27 | if (self) { 28 | 29 | } 30 | 31 | return self; 32 | } 33 | 34 | 35 | - (BOOL)editCurrentlySelectedShape { 36 | CSKMainController *mainController = [CSKMainController sharedInstance]; 37 | CSK_MSDocument *document = mainController.document; 38 | if (!document) { 39 | return NO; 40 | } 41 | NSArray *selectedLayers = document.selectedLayers; 42 | NSLog(@"selected layers: %@", selectedLayers); 43 | if (selectedLayers.count != 1) { 44 | return NO; 45 | } 46 | 47 | Class MSShapePathLayer = NSClassFromString(@"MSShapePathLayer"); 48 | if (!MSShapePathLayer) { 49 | NSLog(@"error, couldn't get class: %@", @"MSShapePathLayer"); 50 | return NO; 51 | } 52 | id layer = selectedLayers.firstObject; 53 | 54 | if ([layer isKindOfClass:MSShapePathLayer] == NO) { 55 | return NO; 56 | } 57 | 58 | SKK_MSShapePathLayer *shapePathLayer = [[SKK_MSShapePathLayer alloc] initWithShapePathLayer:layer]; 59 | 60 | if (!shapePathLayer) { 61 | return NO; 62 | } 63 | 64 | NSLog(@"shape path layer: %@", shapePathLayer); 65 | SKK_MSShapePath *shapePath = shapePathLayer.path; 66 | if (!shapePath) { 67 | return NO; 68 | } 69 | NSLog(@"shape path: %@", shapePath); 70 | 71 | NSArray *points = shapePath.points; 72 | NSLog(@"points: %@", points); 73 | int count = (int)points.count; 74 | 75 | for (int index = 0; index < count; index += 1) { 76 | STUB_MSCurvePoint *point = points[index]; 77 | // NSLog(@"point: %@", point); 78 | // NSLog(@"point: %@, curveMode: %lld curveFrom (%d): %@, curveTo (%d): %@", 79 | // NSStringFromPoint(point.point), 80 | // point.curveMode, 81 | // point.hasCurveFrom, 82 | // NSStringFromPoint(point.curveFrom), 83 | // point.hasCurveTo, 84 | // NSStringFromPoint(point.curveTo) 85 | // ); 86 | 87 | 88 | NSString *output = [NSString stringWithFormat:@"polygonManager.addPointEntry(polygon, [%.21Lg, %.21Lg], %lld, %d, [%.21Lg, %.21Lg], %d, [%.21Lg, %.21Lg]);", 89 | (long double)point.point.x, (long double)point.point.y, 90 | point.curveMode, 91 | point.hasCurveFrom, 92 | (long double)point.curveFrom.x, (long double)point.curveFrom.y, 93 | point.hasCurveTo, 94 | (long double)point.curveTo.x, (long double)point.curveTo.y 95 | ]; 96 | CFShow((__bridge CFStringRef)output); 97 | 98 | } 99 | 100 | 101 | 102 | return YES; 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Controllers/CSKStylesheet.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKStylesheet.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/4/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void (^CSKStylesheetCompiled)(NSError *error, NSString *compiledStylesheet); 12 | 13 | @interface CSKStylesheet : NSObject 14 | 15 | @property (strong) NSDictionary *layerTree; 16 | 17 | - (instancetype)initWithFile:(NSURL *)fileURL; 18 | - (BOOL)parseStylesheet:(CSKStylesheetCompiled)completionBlock; 19 | - (void)parseStylesheet; 20 | @end 21 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Controllers/CSKStylesheet.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKStylesheet.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/4/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKStylesheet.h" 10 | #import "CSKLess.h" 11 | #import "CSKDOM.h" 12 | 13 | @interface CSKStylesheet () 14 | 15 | 16 | 17 | @property (strong) NSURL *fileURL; 18 | @property (strong) CSKDOM *domModel; 19 | // Callback 20 | @property (strong) id target; 21 | @property SEL action; 22 | 23 | @end 24 | 25 | @implementation CSKStylesheet 26 | 27 | - (instancetype)initWithFile:(NSURL *)fileURL { 28 | self = [super init]; 29 | 30 | if (self) { 31 | _fileURL = fileURL; 32 | } 33 | 34 | return self; 35 | } 36 | 37 | 38 | - (void)dealloc { 39 | if (DEBUG) { 40 | NSLog(@"CSKStylesheet dealloc"); 41 | } 42 | } 43 | 44 | // parses stylesheet 45 | - (void)parseStylesheet { 46 | [self parseStylesheet:^(NSError *error, NSString *compiledStylesheet) { 47 | dispatch_async(dispatch_get_main_queue(), ^{ 48 | 49 | self.domModel = [[CSKDOM alloc] initWithStylesheet:compiledStylesheet callback:^(NSError *error, NSDictionary *DOMTree) { 50 | if (DEBUG) { 51 | NSLog(@"computed dom: %@", DOMTree); 52 | } 53 | 54 | if (error) { 55 | NSLog(@"error: %@", error); 56 | return; 57 | } 58 | else { 59 | [CSKLayers layoutLayersWithDOMTree:DOMTree]; 60 | [[CSKMainController sharedInstance] refreshDocument]; 61 | } 62 | 63 | self.domModel = nil; 64 | } layerTree:self.layerTree]; 65 | }); 66 | }]; 67 | } 68 | - (BOOL)parseStylesheet:(CSKStylesheetCompiled)completionBlock { 69 | 70 | NSError *error = nil; 71 | if ([CSKMainController inSandbox]) { 72 | [self.fileURL startAccessingSecurityScopedResource]; 73 | } 74 | NSString *stylesheet = [NSString stringWithContentsOfFile:self.fileURL.path encoding:NSUTF8StringEncoding error:&error]; 75 | 76 | if ([CSKMainController inSandbox]) { 77 | [self.fileURL stopAccessingSecurityScopedResource]; 78 | } 79 | 80 | if (error) { 81 | NSLog(@"error retrieving contents of stylesheet: %@", error); 82 | NSString *errorString = [NSString stringWithFormat:@"Couldn't read stylesheet %@", self.fileURL.path]; 83 | [CSKMainController displayError:errorString]; 84 | return FALSE; 85 | } 86 | 87 | // process @import 88 | // un-escaped regex: @import[\s]+?(\([^)]+\)[\s]+)?['"]?([^'"\n]+)['"]?;? 89 | // targetting: @import (keyword) "filename"; 90 | NSArray *importMatches = [stylesheet matchesWithDetails:RX(@"@import[\\s]+?(\\([^)]+\\)[\\s]+)?['\"]([^'\"\\n]+)['\"]?;?")]; 91 | 92 | if (importMatches.count) { 93 | for (RxMatch *match in importMatches) { 94 | RxMatchGroup *wholeMatch = match.groups[0]; 95 | // RxMatchGroup *keywordMatch = match.groups[1]; 96 | RxMatchGroup *filenameMatch = match.groups[2]; 97 | NSString *filename = filenameMatch.value; 98 | 99 | NSString *pathComponent = [NSString stringWithFormat:@"/../%@", filename]; 100 | NSString *filenamePath = [[self.fileURL.path stringByAppendingPathComponent:pathComponent] stringByStandardizingPath]; 101 | 102 | if ([CSKMainController inSandbox] == FALSE) { 103 | NSString *importContents = [NSString stringWithContentsOfFile:filenamePath 104 | encoding:NSUTF8StringEncoding 105 | error:&error]; 106 | 107 | if (error) { 108 | NSString *errorString = [NSString stringWithFormat:@"Couldn't import stylesheet %@", filenamePath]; 109 | [CSKMainController displayError:errorString]; 110 | return FALSE; 111 | } 112 | stylesheet = [stylesheet stringByReplacingOccurrencesOfString:wholeMatch.value withString:importContents]; 113 | 114 | } 115 | else { 116 | // TODO: add prompt for file selection or for directory access 117 | NSString *error = [NSString stringWithFormat:@"@import is not supported in Sandboxed Sketch!"]; 118 | [CSKMainController displayError:error]; 119 | } 120 | 121 | } 122 | } 123 | 124 | if (DEBUG) { 125 | NSLog(@"stylesheet length %d: %@", (int)stylesheet.length, stylesheet); 126 | } 127 | if (stylesheet.length == 0) { 128 | completionBlock(nil, stylesheet); 129 | return TRUE; 130 | } 131 | 132 | NSString *extension = [self fileURL].path.pathExtension.lowercaseString; 133 | 134 | if ([extension isEqualToString:@"less"]) { 135 | [CSKLess compileLessStylesheet:stylesheet completion:^(NSError *error, NSString *compiledCSS) { 136 | if (error) { 137 | NSString *errorMessage = [NSString stringWithFormat:@"CSSketch: Error compiling {less} stylesheet: %@", error]; 138 | [CSKMainController displayError:errorMessage]; 139 | NSLog(@"%@", errorMessage); 140 | } 141 | 142 | if (DEBUG) { 143 | NSLog(@"compiled less stylesheet: %@", compiledCSS); 144 | } 145 | 146 | completionBlock(error, compiledCSS); 147 | 148 | }]; 149 | } 150 | else if ([extension isEqualToString:@"sass"] || [extension isEqualToString:@"scss"]) { 151 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 152 | NSError *error = nil; 153 | NSString *compiledCSS = [CocoaSass compileSass:stylesheet 154 | extension:extension 155 | error:&error]; 156 | 157 | if (error != nil || !compiledCSS) { 158 | compiledCSS = @""; 159 | } 160 | 161 | 162 | if (DEBUG) { 163 | NSLog(@"compiled %@ stylesheet: %@", extension, compiledCSS); 164 | } 165 | dispatch_async(dispatch_get_main_queue(), ^{ 166 | completionBlock(error, compiledCSS); 167 | }); 168 | }); 169 | } 170 | else { 171 | completionBlock(nil, stylesheet); 172 | } 173 | 174 | return TRUE; 175 | } 176 | 177 | 178 | @end 179 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Headers/CSKSketchHeaders.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKSketchHeaders.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/5/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | // Description: This file stubs out headers for Sketch classes used. The're prefixed with CSK_ so we don't have 9 | // to actually link to the Sketch binary, we just implement stub classes. 10 | 11 | #import 12 | #import 13 | 14 | @class CSK_MSLayer, CSK_MSContentDrawView, CSK_MSStyle, CSK_MSStyleBorder, CSK_MSColor; 15 | @class CSK_MSStyleShadow, CSK_MSPage, CSK_MSStyleFill, CSK_MSTextLayer, CSK_MSAbsoluteRect; 16 | 17 | @interface CSK_MSColor : NSObject 18 | @property(readonly, nonatomic) double red; 19 | @property(readonly, nonatomic) double green; 20 | @property(readonly, nonatomic) double blue; 21 | @property(readonly, nonatomic) double alpha; 22 | + (id)colorWithRed:(double)arg1 green:(double)arg2 blue:(double)arg3 alpha:(double)arg4; 23 | @end 24 | 25 | @interface CSK_MSStylePartCollection : NSArray 26 | - (id)addNewStylePart; 27 | @end 28 | 29 | @interface CSK_MSStyle 30 | @property (readonly) CSK_MSStyleBorder *border; 31 | @property (readonly) CSK_MSStyleShadow *shadow; 32 | @property (readonly) CSK_MSStyleShadow *innerShadow; 33 | @property(retain, nonatomic) CSK_MSStyleFill *fill; 34 | @property(retain, nonatomic) CSK_MSStylePartCollection *borders; 35 | @property(retain, nonatomic) CSK_MSStylePartCollection *shadows; 36 | @property(retain, nonatomic) CSK_MSStylePartCollection *fills; 37 | @end 38 | 39 | 40 | @interface CSK_MSStyleFill 41 | @property(nonatomic) unsigned long long fillType; 42 | @property(copy, nonatomic) CSK_MSColor *color; 43 | @end 44 | 45 | @interface CSK_MSStyleBorder 46 | @property(nonatomic) double thickness; 47 | @property(nonatomic) long long position; 48 | @property(copy, nonatomic) CSK_MSColor *color; 49 | @end 50 | 51 | @interface CSK_MSStyleShadow 52 | @property(nonatomic) double spread; 53 | @property(nonatomic) double offsetY; 54 | @property(nonatomic) double offsetX; 55 | @property(nonatomic) double blurRadius; 56 | @property(copy, nonatomic) CSK_MSColor *color; 57 | 58 | @end 59 | 60 | @interface CSK_MSDocument : NSDocument 61 | @property(retain, nonatomic) NSWindow *documentWindow; 62 | - (CSK_MSPage *)currentPage; 63 | - (CSK_MSContentDrawView *)currentView; 64 | - (void)displayMessage:(NSString *)message; 65 | - (void)reloadInspector; 66 | 67 | - (NSArray *)selectedLayers; 68 | @end 69 | 70 | @interface CSK_MSLayer : NSObject 71 | @property (readonly) NSString *name; 72 | @property (readonly) NSArray *layers; 73 | @property (readonly) NSString *objectID; 74 | @property(nonatomic) struct CGRect frameInArtboard; 75 | @property(nonatomic) struct CGRect rect; 76 | @property (readonly) CSK_MSStyle *style; 77 | @property(retain, nonatomic) CSK_MSAbsoluteRect *absoluteRect; 78 | - (CSK_MSLayer *)parentArtboard; 79 | 80 | 81 | // OLD Version of invalidateCachedImmutableModelObjects 82 | // version < 3.5 83 | - (void)invalidateLightweightCopy:(id)arg1; 84 | // version >= 3.5 85 | - (void)invalidateCachedImmutableModelObjects; 86 | 87 | // groups only 88 | // version < 3.5 89 | - (BOOL)resizeRoot:(BOOL)resize; 90 | // version >= 3.5 91 | - (BOOL)resizeToFitChildrenWithOption:(long long)option; 92 | 93 | 94 | - (void)hideSelectionTemporarily; 95 | @end 96 | 97 | @interface CSK_MSArtboardGroup : CSK_MSLayer 98 | @property(nonatomic) BOOL hasBackgroundColor; 99 | @property(copy, nonatomic) CSK_MSColor *backgroundColor; 100 | @end 101 | 102 | static const long long CSKMSLayerDirtyTypeTextColor = 3; 103 | 104 | @interface CSK_MSTextLayer : CSK_MSLayer 105 | @property(nonatomic) double fontSize; 106 | @property(copy, nonatomic) NSString *stringValue; 107 | @property(copy, nonatomic) CSK_MSColor *textColor; 108 | @property(retain, nonatomic) NSTextStorage *storage; 109 | - (void)markLayerDirtyOfType:(unsigned long long)arg1; 110 | - (void)layerDidChange; 111 | - (void)syncTextStyleAttributes; 112 | - (void)prepareForUndo; 113 | @end 114 | 115 | @interface CSK_MSPage : CSK_MSLayer 116 | 117 | @end 118 | 119 | @interface CSK_MSPluginCommand : NSObject 120 | - (id)valueForKey:(NSString *)key 121 | onLayer:(CSK_MSLayer *)layer 122 | forPluginIdentifier:(NSString *)identifier; 123 | - (void)setValue:(id)value 124 | forKey:(NSString *)key 125 | onLayer:(CSK_MSLayer *)layer 126 | forPluginIdentifier:(NSString *)pluginIdentifier; 127 | 128 | @end 129 | 130 | @interface CSK_MSContentDrawView : NSObject 131 | 132 | // Sketch < 3.5 133 | - (void)refresh; 134 | // Sketch >= 3.5 135 | - (void)refreshTiles; 136 | // Sketch >= 3.8 137 | - (void)refreshOverlayOfViews; 138 | @end 139 | 140 | @interface CSK_MSAbsoluteRect : NSObject 141 | - (CGRect)rect; 142 | - (void)setRect:(CGRect)rect; 143 | @end -------------------------------------------------------------------------------- /CSSketch Helper/src/Headers/CSKSketchHeaders.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKSketchHeaders.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/5/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKSketchHeaders.h" 10 | 11 | @implementation CSK_MSLayer 12 | 13 | - (BOOL)resizeToFitChildrenWithOption:(long long)option { 14 | return true; 15 | } 16 | 17 | - (BOOL)resizeRoot:(BOOL)resize { 18 | return true; 19 | } 20 | 21 | - (void)hideSelectionTemporarily { 22 | 23 | } 24 | 25 | // OLD Version of invalidateCachedImmutableModelObjects 26 | - (void)invalidateLightweightCopy:(id)arg1 { 27 | 28 | } 29 | 30 | - (void)invalidateCachedImmutableModelObjects { 31 | 32 | } 33 | @end 34 | 35 | @implementation CSK_MSDocument 36 | 37 | - (void)displayMessage:(NSString *)message { 38 | 39 | } 40 | - (CSK_MSPage *)currentPage { 41 | return nil; 42 | } 43 | 44 | - (CSK_MSContentDrawView *)currentView{ 45 | return nil; 46 | } 47 | 48 | - (void)reloadInspector { 49 | 50 | } 51 | 52 | - (NSArray *)selectedLayers { 53 | return nil; 54 | } 55 | 56 | @end 57 | 58 | 59 | 60 | 61 | @implementation CSK_MSPluginCommand 62 | - (id)valueForKey:(NSString *)key onLayer:(CSK_MSLayer *)layer forPluginIdentifier:(NSString *)identifier { 63 | return nil; 64 | } 65 | - (void)setValue:(id)value forKey:(NSString *)key onLayer:(CSK_MSLayer *)layer forPluginIdentifier:(NSString *)pluginIdentifier { 66 | 67 | } 68 | @end 69 | 70 | @implementation CSK_MSContentDrawView 71 | - (void)refresh {} 72 | - (void)refreshTiles {} 73 | - (void)refreshOverlayOfViews {} 74 | @end 75 | 76 | @implementation CSK_MSColor 77 | + (id)colorWithRed:(double)arg1 green:(double)arg2 blue:(double)arg3 alpha:(double)arg4 { 78 | return [NSClassFromString(@"MSColor") colorWithRed:arg1 green:arg2 blue:arg3 alpha:arg4]; 79 | } 80 | @end -------------------------------------------------------------------------------- /CSSketch Helper/src/Headers/PrefixHeader.pch: -------------------------------------------------------------------------------- 1 | // 2 | // PrecompiledHeader.pch 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/5/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "CSKSketchHeaders.h" 13 | #import "CSKStylesheet.h" 14 | #import "CSKMainController.h" 15 | #import "CSKDom.h" 16 | #import "CSKFileMonitor.h" 17 | #import "RegExCategories.h" 18 | #import "CSKLayerCSS.h" 19 | #import "NSString+Paths.h" 20 | #import "CSKDocumentController.h" 21 | #import "CSKLayers.h" 22 | 23 | // Sass 24 | #import "CocoaSass.h" 25 | 26 | // Errors 27 | #import "NSError+CSSketch.h" 28 | 29 | #ifdef DEBUG 30 | #define DEBUG 1 31 | #else 32 | #define DEBUG 0 33 | #endif 34 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/DOM/CSKDOM.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKDOM.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/4/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef void (^CSKDOMComputedCallback)(NSError *error, NSDictionary *computedDOM); 13 | 14 | @interface CSKDOM : NSObject 15 | 16 | - (instancetype)initWithStylesheet:(NSString *)stylesheet 17 | callback:(CSKDOMComputedCallback)callbackBlock 18 | layerTree:(NSDictionary *)layerTree 19 | NS_DESIGNATED_INITIALIZER; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/DOM/CSKDOM.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKDOM.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/4/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKDOM.h" 10 | #import "RegExCategories.h" 11 | #import 12 | 13 | @interface CSKDOM () 14 | 15 | @property (strong) WebView *webView; 16 | @property (strong) JSContext *context; 17 | @property (copy) CSKDOMComputedCallback callbackBlock; 18 | @property (strong) NSString *stylesheet; 19 | @property (strong) NSDictionary *layerTree; 20 | @property (weak) DOMDocument *document; 21 | 22 | @end 23 | 24 | @implementation CSKDOM 25 | 26 | - (instancetype)init { 27 | self = [self initWithStylesheet:nil callback:nil layerTree:nil]; 28 | [NSException raise:@"Wrong Initializer!" format:@"Use initWithStylesheet:"]; 29 | return nil; 30 | } 31 | 32 | - (instancetype)initWithStylesheet:(NSString *)stylesheet 33 | callback:(CSKDOMComputedCallback)callbackBlock 34 | layerTree:(NSDictionary *)layerTree 35 | { 36 | self = [super init]; 37 | 38 | if (self) { 39 | self.stylesheet = stylesheet; 40 | self.callbackBlock = callbackBlock; 41 | self.layerTree = layerTree; 42 | //static const char* simpleUserAgentStyleSheet = "html,body,div{display:block}head{display:none}body{margin:8px}div:focus,span:focus,a:focus{outline:auto 5px -webkit-focus-ring-color}a:any-link{color:-webkit-link;text-decoration:underline}a:any-link:active{color:-webkit-activelink}"; 43 | 44 | // TODO possibly: modify body size for size of page 45 | NSString *resetDefaultStylesheet = [CSKMainController sharedInstance].embeddedStylesheet; 46 | 47 | NSString *HTMLString = [NSString 48 | stringWithFormat:@"", 49 | resetDefaultStylesheet, 50 | stylesheet]; 51 | NSURL *baseURL = [NSURL URLWithString:@"internal"]; 52 | _webView = [WebView new]; 53 | [_webView.mainFrame loadHTMLString:HTMLString baseURL:baseURL]; 54 | _webView.frameLoadDelegate = self; 55 | 56 | if (![self inCocoaScript]) { 57 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]]; 58 | } 59 | } 60 | 61 | return self; 62 | } 63 | 64 | - (void)dealloc { 65 | self.webView.frameLoadDelegate = nil; 66 | if (DEBUG){ 67 | NSLog(@"CSKDOM dealloc"); 68 | } 69 | } 70 | 71 | - (BOOL)inCocoaScript { 72 | if (NSClassFromString(@"COSTarget")) { 73 | return TRUE; 74 | } 75 | else { 76 | return FALSE; 77 | } 78 | } 79 | - (void)webView:(WebView *)sender 80 | didFinishLoadForFrame:(WebFrame *)frame { 81 | DOMDocument *document = frame.DOMDocument; 82 | DOMElement *body = document.body; 83 | 84 | self.document = document; 85 | 86 | // create DOM elements 87 | [self walkLayerTree:(NSMutableDictionary *)self.layerTree parentElement:body]; 88 | 89 | // store CSS attributes from DOM elements 90 | [self walkLayerTreeAndStoreComputedProperties:(NSMutableDictionary *)self.layerTree]; 91 | NSString *finalHTML = document.documentElement.outerHTML; 92 | 93 | if (DEBUG && ![CSKMainController inSandbox]) { 94 | [self writeDebugHTMLFileWithContents:finalHTML]; 95 | } 96 | 97 | self.callbackBlock(nil, self.layerTree); 98 | } 99 | 100 | - (void)writeDebugHTMLFileWithContents:(NSString *)finalHTML { 101 | NSString *sourcePath = [NSString stringWithUTF8String:__FILE__]; 102 | NSString *rootProjectPath = [sourcePath stringByAppendingString:@"/../../../../../"].stringByStandardizingPath; 103 | NSString *debugHTMLPath = [rootProjectPath stringByAppendingPathComponent:@"Examples/debug.html"]; 104 | 105 | NSLog(@"writing debug file to: %@", debugHTMLPath); 106 | [finalHTML writeToFile:debugHTMLPath 107 | atomically:true 108 | encoding:NSUTF8StringEncoding error:nil]; 109 | NSLog(@"final document: %@", finalHTML); 110 | 111 | } 112 | 113 | - (void)walkLayerTree:(NSMutableDictionary *)layerTree parentElement:(DOMElement *)parent { 114 | DOMElement *element = [self.document createElement:@"layer"]; 115 | [element setAttribute:@"objectID" value:layerTree[@"objectID"]]; 116 | 117 | CSK_MSLayer *layer = layerTree[@"layer"]; 118 | 119 | if ([layer isKindOfClass:NSClassFromString(@"MSTextLayer")]) { 120 | [element setAttribute:@"type" value:@"text"]; 121 | } 122 | else if ([layer isKindOfClass:NSClassFromString(@"MSArtboardGroup")]) { 123 | [element setAttribute:@"type" value:@"artboard"]; 124 | } 125 | 126 | NSString *name = layerTree[@"name"]; 127 | NSArray *allClasses = [name matches:RX(@"(^|\\s)\\.([^\\s]+)")]; 128 | 129 | if (allClasses.count) { 130 | NSString *classes = [allClasses componentsJoinedByString:@" "]; 131 | 132 | // clean up name 133 | name = [name stringByReplacingOccurrencesOfString:classes withString:@""]; 134 | name = [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 135 | 136 | // strip . 137 | classes = [classes stringByReplacingOccurrencesOfString:@"." withString:@""]; 138 | 139 | [element setAttribute:@"class" value:classes]; 140 | 141 | // if ([classes containsString:@".optIn"]) { 142 | // layerTree[@"CSSOptIn"] = @(TRUE); 143 | // } 144 | } 145 | 146 | [element setAttribute:@"name" value:name]; 147 | 148 | [parent appendChild:element]; 149 | 150 | NSArray *children = layerTree[@"children"]; 151 | 152 | if (children) { 153 | for (NSMutableDictionary *childLayer in children) { 154 | [self walkLayerTree:childLayer parentElement:element]; 155 | } 156 | } 157 | 158 | layerTree[@"element"] = element; 159 | } 160 | 161 | - (void)walkLayerTreeAndStoreComputedProperties:(NSMutableDictionary *)layerTree { 162 | DOMElement *element = layerTree[@"element"]; 163 | NSDictionary *rules = [self rulesForElement:element]; 164 | layerTree[@"rules"] = rules; 165 | [layerTree removeObjectForKey:@"element"]; 166 | 167 | NSArray *children = layerTree[@"children"]; 168 | 169 | if (children) { 170 | for (NSMutableDictionary *childLayer in children) { 171 | [self walkLayerTreeAndStoreComputedProperties:childLayer]; 172 | } 173 | } 174 | 175 | 176 | } 177 | 178 | - (NSDictionary *)rulesForElement:(DOMElement *)element { 179 | DOMDocument *document = self.webView.mainFrame.DOMDocument; 180 | DOMCSSRuleList *rules = [document getMatchedCSSRules:element pseudoElement:nil]; 181 | 182 | NSMutableDictionary *properties = [NSMutableDictionary new]; 183 | 184 | // NSLog(@"style: %@", element.style.cssText); 185 | DOMCSSRule *rule; 186 | for (int i=0; i < rules.length; i++) { 187 | rule = [rules item:i]; 188 | // NSLog(@"rule: %@", rule.cssText); 189 | 190 | if ([rule isKindOfClass:[DOMCSSStyleRule class]]) { 191 | 192 | DOMCSSStyleRule *styleRule = (DOMCSSStyleRule *)rule; 193 | 194 | DOMCSSStyleDeclaration *declaration = styleRule.style; 195 | for (int itemIndex=0; itemIndex < declaration.length; itemIndex++) { 196 | NSString *item = [declaration item:itemIndex]; 197 | NSString *value = [declaration getPropertyValue:item]; 198 | properties[item] = value; 199 | } 200 | } 201 | } 202 | // NSLog(@"un-matched properties: %@", properties); 203 | 204 | NSMutableDictionary *computedProperties = [NSMutableDictionary new]; 205 | 206 | DOMCSSStyleDeclaration *computedStyle = [document getComputedStyle:element pseudoElement:nil]; 207 | // NSLog(@"computed: %@", computedStyle.cssText); 208 | for (NSString *property in properties) { 209 | NSString *computedValue = [computedStyle getPropertyValue:property]; 210 | 211 | if (!computedValue) { 212 | NSLog(@"couldn't get computed value for %@", property); 213 | continue; 214 | } 215 | 216 | computedProperties[property] = computedValue; 217 | } 218 | 219 | // added to all elements 220 | 221 | // go through offset parents to find offset 222 | // relative to document 223 | int offsetTop = 0; 224 | int offsetLeft = 0; 225 | 226 | DOMElement *currentElement = element; 227 | 228 | while (currentElement) { 229 | offsetTop += currentElement.offsetTop; 230 | offsetLeft += currentElement.offsetLeft; 231 | 232 | currentElement = currentElement.offsetParent; 233 | 234 | // stop at artboard or page 235 | if ([currentElement.tagName isEqualToString:@"artboard"] || 236 | [currentElement.tagName isEqualToString:@"page"] 237 | ) { 238 | currentElement = nil; 239 | } 240 | } 241 | 242 | computedProperties[@"offsetTop"] = @(offsetTop); 243 | computedProperties[@"offsetLeft"] = @(offsetLeft); 244 | computedProperties[@"offsetParent"] = element.offsetParent; 245 | 246 | 247 | return computedProperties; 248 | } 249 | 250 | @end 251 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/Files/CSKFileMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKFileMonitor.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/3/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class CSKFileMonitor; 12 | 13 | typedef void (^CSKFileMonitorChangeDetected)(CSKFileMonitor *fileMonitor); 14 | typedef void (^CSKFileMonitorRenameDetected)(CSKFileMonitor *fileMonitor, NSURL *oldFileURL, NSURL *newFileURL); 15 | 16 | @interface CSKFileMonitor : NSObject 17 | 18 | @property (weak) CSK_MSDocument *document; 19 | @property (weak) CSK_MSPage *page; 20 | @property (strong) CSK_MSPluginCommand *command; 21 | @property (copy) CSKFileMonitorChangeDetected changeBlock; 22 | @property (copy) CSKFileMonitorRenameDetected renameBlock; 23 | 24 | - (id)initWithFileURL:(NSURL *)fileURL bookmark:(NSData *)bookmark; 25 | - (BOOL)startMonitoring; 26 | @end 27 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/Files/CSKFileMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKFileMonitor.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/3/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKFileMonitor.h" 10 | 11 | @interface CSKFileMonitor () 12 | 13 | @property (strong) NSURL *fileURL; 14 | @property (strong) NSData *fileBookmark; 15 | @property BOOL isMonitoring; 16 | @property (strong) dispatch_source_t source; 17 | 18 | // last change in milliseconds since 1970 19 | @property int64_t lastChangeMS; 20 | 21 | @end 22 | 23 | @implementation CSKFileMonitor 24 | 25 | - (id)initWithFileURL:(NSURL *)fileURL bookmark:(NSData *)bookmark { 26 | self = [super init]; 27 | 28 | if (self) { 29 | self.fileURL = fileURL; 30 | 31 | if (!bookmark) { 32 | 33 | NSError *error = nil; 34 | NSURLBookmarkCreationOptions bookmarkOptions = 0; 35 | self.fileBookmark = [fileURL bookmarkDataWithOptions:bookmarkOptions 36 | includingResourceValuesForKeys:nil relativeToURL:nil error:&error]; 37 | if (error) { 38 | NSLog(@"error creating bookmark! %@", error); 39 | } 40 | } 41 | 42 | self.fileBookmark = bookmark; 43 | self.isMonitoring = false; 44 | } 45 | 46 | return self; 47 | } 48 | 49 | - (void)dealloc { 50 | if (DEBUG) { 51 | NSLog(@"filemonitor dealloc"); 52 | } 53 | [self stopMonitoring]; 54 | } 55 | 56 | - (BOOL)startMonitoring { 57 | if (self.isMonitoring) { 58 | return true; 59 | } 60 | 61 | self.lastChangeMS = 0; 62 | self.isMonitoring = true; 63 | 64 | if ([CSKMainController inSandbox]) { 65 | [self.fileURL startAccessingSecurityScopedResource]; 66 | } 67 | 68 | int fileDescriptor = open(self.fileURL.path.UTF8String, O_RDONLY); 69 | 70 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 71 | dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileDescriptor, 72 | DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB | DISPATCH_VNODE_RENAME, 73 | queue); 74 | self.source = source; 75 | 76 | CSKFileMonitor * __weak weakSelf = self; 77 | dispatch_source_set_event_handler(source, ^ 78 | { 79 | if (DEBUG) { 80 | NSLog(@"file monitor changes detected"); 81 | } 82 | dispatch_source_vnode_flags_t flags = dispatch_source_get_data(source); 83 | 84 | 85 | if (flags & DISPATCH_VNODE_RENAME) { 86 | if (DEBUG) { 87 | NSLog(@"renamed!"); 88 | } 89 | 90 | NSURLBookmarkResolutionOptions bookmarkOptions = 0; 91 | if ([CSKMainController inSandbox]) { 92 | bookmarkOptions = NSURLBookmarkResolutionWithSecurityScope; 93 | } 94 | 95 | NSURL *currentURL = weakSelf.fileURL; 96 | NSError *error; 97 | NSURL *newURL = [NSURL URLByResolvingBookmarkData:weakSelf.fileBookmark 98 | options:bookmarkOptions relativeToURL:nil bookmarkDataIsStale:NULL error:&error]; 99 | if (error) { 100 | NSLog(@"couldn't resolve file bookmark, error: %@", error); 101 | } 102 | else { 103 | weakSelf.fileURL = newURL; 104 | if (DEBUG) { 105 | NSLog(@"%@ renamed to %@", currentURL, newURL); 106 | } 107 | weakSelf.renameBlock(weakSelf, currentURL, newURL); 108 | } 109 | } 110 | else { 111 | NSTimeInterval timestamp = [NSDate date].timeIntervalSince1970; 112 | int64_t timestampMS = timestamp * 1000; 113 | 114 | // wait at least 200 MS between change notifications 115 | 116 | int64_t minimumTimePassed = 200; 117 | 118 | if (timestampMS - weakSelf.lastChangeMS >= minimumTimePassed) { 119 | weakSelf.lastChangeMS = timestampMS; 120 | weakSelf.changeBlock(weakSelf); 121 | } 122 | } 123 | 124 | // cancel 125 | [weakSelf stopMonitoring]; 126 | // re-start 127 | [weakSelf startMonitoring]; 128 | }); 129 | 130 | dispatch_source_set_cancel_handler(source, ^ 131 | { 132 | close(fileDescriptor); 133 | [weakSelf.fileURL stopAccessingSecurityScopedResource]; 134 | }); 135 | 136 | dispatch_resume(source); 137 | return true; 138 | } 139 | 140 | - (void)stopMonitoring { 141 | self.isMonitoring = false; 142 | dispatch_source_cancel(self.source); 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/Layers/CSKLayers.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKLayers.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/12/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CSKLayers : NSObject 12 | 13 | + (void)layoutLayersWithDOMTree:(NSDictionary *)DOMTree; 14 | 15 | + (NSDictionary *)layerTreeFromLayer:(CSK_MSLayer *)layer stylesheetOuput:(NSString **)stylesheetOutput; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/Layers/CSKLayers.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKLayers.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/12/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKLayers.h" 10 | 11 | static const BOOL DEBUG_WriteOutLayerTree = FALSE; 12 | @implementation CSKLayers 13 | 14 | #pragma mark - Laying Out 15 | 16 | + (void)layoutLayersWithDOMTree:(NSDictionary *)DOMTree { 17 | if (![CSKMainController inSketch]) { 18 | return; 19 | } 20 | 21 | 22 | BOOL hasChildren = FALSE; 23 | NSArray *children = DOMTree[@"children"]; 24 | 25 | if (children && children.count) { 26 | hasChildren = TRUE; 27 | } 28 | 29 | CSK_MSLayer *layer = DOMTree[@"layer"]; 30 | 31 | // NSNumber *CSSOptIn = DOMTree[@"CSSOptIn"]; 32 | 33 | // we can normally skip groups for css properties 34 | if (!hasChildren) { 35 | if (DEBUG) { 36 | NSLog(@"setting CSS for %@", DOMTree[@"name"]); 37 | } 38 | 39 | // CSS handler 40 | [CSKLayerCSS handleCSSPropertiesWithDOMLeaf:DOMTree layer:layer]; 41 | 42 | } 43 | else if ([layer isKindOfClass:NSClassFromString(@"MSArtboardGroup")]) { 44 | [CSKLayerCSS handleBackgroundColorWithDOMLeaf:DOMTree layer:layer]; 45 | } 46 | 47 | 48 | if (hasChildren) { 49 | for (NSDictionary *child in children) { 50 | [self layoutLayersWithDOMTree:child]; 51 | } 52 | 53 | // reset group bounds 54 | if ([layer isKindOfClass:NSClassFromString(@"MSLayerGroup")]) { 55 | if ([layer respondsToSelector:@selector(resizeToFitChildrenWithOption:)]) { 56 | [layer resizeToFitChildrenWithOption:1]; 57 | } 58 | else if ([layer respondsToSelector:@selector(resizeRoot:)]) { 59 | [layer resizeRoot:true]; 60 | } 61 | else { 62 | NSLog(@"ERROR: Can't resize MSLayerGroup to fit children, methods missing!"); 63 | } 64 | } 65 | } 66 | } 67 | 68 | 69 | #pragma mark - Layer Tree 70 | + (NSDictionary *)layerTreeFromLayer:(CSK_MSLayer *)layer stylesheetOuput:(NSString **)stylesheetOutput { 71 | NSMutableDictionary *leaf = [NSMutableDictionary new]; 72 | 73 | NSString *stylesheet = *stylesheetOutput; 74 | 75 | BOOL topOfTree = FALSE; 76 | 77 | if (!stylesheet) { // top of tree 78 | stylesheet = @"\n"; 79 | topOfTree = TRUE; 80 | 81 | } 82 | 83 | NSString *name = layer.name; 84 | leaf[@"layer"] = layer; 85 | 86 | leaf[@"name"] = name; 87 | leaf[@"objectID"] = layer.objectID; 88 | 89 | NSNumber *left =@(layer.rect.origin.x); 90 | NSNumber *top = @(layer.rect.origin.y); 91 | if (MSLayerIsArtboard(layer)) { 92 | left = @(0); 93 | top = @(0); 94 | } 95 | 96 | if (!MSLayerIsPage(layer)) 97 | { 98 | NSNumber *width = @(layer.rect.size.width); 99 | NSNumber *height = @(layer.rect.size.height); 100 | 101 | // add size rule 102 | NSString *styleSheetRule; 103 | styleSheetRule = [NSString stringWithFormat:@"[objectID=\"%@\"] {\nposition:absolute; left:%@; top:%@; width: %@px; height: %@px;\n}\n", 104 | layer.objectID, 105 | left, top, 106 | width, 107 | height]; 108 | 109 | stylesheet = [stylesheet stringByAppendingString:styleSheetRule]; 110 | } 111 | 112 | if (MSLayerIsGroup(layer)) { 113 | NSMutableArray *childrenList; 114 | childrenList = [NSMutableArray new]; 115 | 116 | NSArray *children = layer.layers; 117 | 118 | for (CSK_MSLayer *childLayer in children) { 119 | NSDictionary *childTree = [self layerTreeFromLayer:childLayer stylesheetOuput:&stylesheet]; 120 | 121 | if (childTree) { 122 | [childrenList addObject:childTree]; 123 | } 124 | } 125 | 126 | // reverse order so it's correctly ordered 127 | leaf[@"children"] = childrenList.reverseObjectEnumerator.allObjects; 128 | } 129 | 130 | 131 | *stylesheetOutput = stylesheet; 132 | 133 | if (DEBUG && topOfTree && DEBUG_WriteOutLayerTree && [CSKMainController inSandbox] == FALSE) { 134 | [self saveDebugTree:leaf]; 135 | } 136 | 137 | return leaf; 138 | } 139 | 140 | #pragma mark - DEBUG 141 | 142 | + (void)saveDebugTree:(NSDictionary *)layerTree { 143 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 144 | 145 | dispatch_async(queue, ^{ 146 | NSDictionary *saveableTree = [self saveableTree:layerTree]; 147 | 148 | BOOL wrote = [saveableTree writeToFile:@"/Users/macbook/Dev/Extensions/Sketch/CSSketch/debug.plist" 149 | atomically:TRUE]; 150 | if (DEBUG) { 151 | NSLog(@"wrote to file: %d", wrote); 152 | } 153 | }); 154 | } 155 | 156 | + (NSDictionary *)saveableTree:(NSDictionary *)tree { 157 | NSMutableDictionary *leaf = [tree mutableCopy]; 158 | 159 | // remove layer so we can serialize this tree 160 | [leaf removeObjectForKey:@"layer"]; 161 | 162 | NSMutableArray *children = [NSMutableArray new]; 163 | 164 | for (NSDictionary *child in leaf[@"children"]) { 165 | [children addObject:[self saveableTree:child]]; 166 | } 167 | 168 | // replace children with saveable children 169 | leaf[@"children"] = children; 170 | 171 | return leaf; 172 | } 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/Layout/CSKLayerCSS.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKLayerCSS.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/9/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CSKLayerCSS : NSObject 12 | 13 | + (void)handleCSSPropertiesWithDOMLeaf:(NSDictionary *)leaf layer:(CSK_MSLayer *)layer; 14 | 15 | // artboards 16 | + (void)handleBackgroundColorWithDOMLeaf:(NSDictionary *)DOMLeaf layer:(CSK_MSLayer *)layer; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/Less/CSKLess.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKLess.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/4/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CSKLess : NSObject 12 | 13 | typedef void (^LessCompileCompletionBlock)(NSError *error, NSString *compiledCSS); 14 | 15 | + (void)compileLessStylesheet:(NSString *)script completion:(LessCompileCompletionBlock)completionBlock; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/Less/CSKLess.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKLess.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/4/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKLess.h" 10 | #import 11 | 12 | 13 | 14 | @implementation CSKLess 15 | + (void)compileLessStylesheet:(NSString *)stylesheet 16 | completion:(LessCompileCompletionBlock)completionBlock { 17 | 18 | NSString *lessScriptPath = [[CSKMainController pluginBundle] pathForResource:@"less-rhino-1.7.5" 19 | ofType:@"js" 20 | inDirectory:@"external"]; 21 | NSError *error = nil; 22 | NSString *lessScript = [NSString stringWithContentsOfFile:lessScriptPath 23 | encoding:NSUTF8StringEncoding 24 | error:&error]; 25 | 26 | if (error) { 27 | NSLog(@"Error, couldn't read Less script %@", lessScriptPath); 28 | [CSKMainController displayError:@"Couldn't read Less script!"]; 29 | return; 30 | } 31 | 32 | JSContext *context = [JSContext new]; 33 | 34 | NSError *(^errorFromJSError)(id error) = ^(id jsError) { 35 | NSError *error; 36 | NSString *errorString = [NSString stringWithFormat:@"%@", jsError]; 37 | NSDictionary *userInfo; 38 | userInfo = @{ 39 | NSLocalizedDescriptionKey : errorString 40 | }; 41 | error = [NSError errorWithDomain:@"CSK" 42 | code:1020 43 | userInfo:userInfo]; 44 | return error; 45 | }; 46 | 47 | [context setExceptionHandler:^(JSContext *context, JSValue *exception) { 48 | NSString *format = [NSString stringWithFormat:@"%@ - %@", exception, exception.toDictionary]; 49 | completionBlock(errorFromJSError(format), nil); 50 | 51 | }]; 52 | __block BOOL parserBlockRan = FALSE; 53 | void (^parserBlock)(JSValue *error, JSValue *output) = ^(JSValue *jsError, JSValue *tree) { 54 | if (![jsError isNull]) { 55 | completionBlock(errorFromJSError(jsError), nil); 56 | return; 57 | } 58 | 59 | JSContext *context = [JSContext currentContext]; 60 | 61 | context[@"parsedTree"] = tree; 62 | JSValue *compiledCSS = [context evaluateScript:@"parsedTree.toCSS({})"]; 63 | completionBlock(nil, compiledCSS.toString); 64 | 65 | parserBlockRan = true; 66 | }; 67 | 68 | [context setObject:parserBlock forKeyedSubscript:@"parserBlock"]; 69 | [context evaluateScript:lessScript]; 70 | 71 | context[@"lessStylesheet"] = stylesheet; 72 | [context evaluateScript:@"var parser = new(less.Parser);"]; 73 | [context evaluateScript:@"parser.parse(lessStylesheet, parserBlock);"]; 74 | 75 | // error in case parser block didn't run; 76 | if (parserBlockRan == FALSE) { 77 | completionBlock(errorFromJSError(@"Couldn't run Less Parser!"), nil); 78 | } 79 | 80 | } 81 | 82 | @end 83 | 84 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/Toolbar/CSKToolbarProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // CSKToolbarProxy.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/5/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CSKToolbarProxy : NSObject 13 | 14 | @property (weak) NSWindow *window; 15 | @property (weak) CSK_MSDocument *document; 16 | @property (strong) CSK_MSPluginCommand *command; 17 | 18 | - (instancetype)initWithOriginalToolbarDelegate:(id )toolbarDelegate; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Models/Toolbar/CSKToolbarProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // CSKToolbarProxy.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/5/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CSKToolbarProxy.h" 10 | #import "CSKSVGEditorController.h" 11 | 12 | @interface CSKToolbarProxy () 13 | 14 | @property (strong) id toolbarDelegate; 15 | 16 | @end 17 | 18 | @implementation CSKToolbarProxy 19 | 20 | - (instancetype)initWithOriginalToolbarDelegate:(id )toolbarDelegate { 21 | self = [super init]; 22 | 23 | if (self) { 24 | self.toolbarDelegate = toolbarDelegate; 25 | } 26 | 27 | return self; 28 | } 29 | 30 | - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar 31 | itemForItemIdentifier:(NSString *)itemIdentifier 32 | willBeInsertedIntoToolbar:(BOOL)flag { 33 | if ([itemIdentifier isEqualToString:@"CSSketch"]) { 34 | NSBundle *currentBundle = [NSBundle bundleForClass:[self class]]; 35 | NSString *imagePath = [currentBundle pathForResource:@"icon-layout24" ofType:@"png"]; 36 | NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:@"CSSketch"]; 37 | toolbarItem.label = @"CSS Layout"; 38 | toolbarItem.paletteLabel = toolbarItem.label; 39 | toolbarItem.toolTip = toolbarItem.label; 40 | toolbarItem.target = self; 41 | toolbarItem.action = @selector(toolbarClick:); 42 | toolbarItem.image = [[NSImage alloc] initWithContentsOfFile:imagePath]; 43 | toolbarItem.enabled = true; 44 | return toolbarItem; 45 | } 46 | else if ([itemIdentifier isEqualToString:@"CSSketch-SVG"]) { 47 | NSBundle *currentBundle = [NSBundle bundleForClass:[self class]]; 48 | NSString *imagePath = [currentBundle pathForResource:@"icon-layout24" ofType:@"png"]; 49 | NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:@"CSSketch-SVG"]; 50 | toolbarItem.label = @"Edit SVG"; 51 | toolbarItem.paletteLabel = toolbarItem.label; 52 | toolbarItem.toolTip = toolbarItem.label; 53 | toolbarItem.target = self; 54 | toolbarItem.action = @selector(svgEditClick:); 55 | toolbarItem.image = [[NSImage alloc] initWithContentsOfFile:imagePath]; 56 | toolbarItem.enabled = true; 57 | return toolbarItem; 58 | } 59 | NSLog(@"item for identifier: %@", itemIdentifier); 60 | return [self.toolbarDelegate toolbar:toolbar 61 | itemForItemIdentifier:itemIdentifier 62 | willBeInsertedIntoToolbar:flag]; 63 | } 64 | 65 | - (void)toolbarClick:(id)item { 66 | if (!self.document) { 67 | return; 68 | } 69 | 70 | NSDictionary *context; 71 | context = @{@"document" : self.document, @"command" : self.command}; 72 | 73 | [[CSKMainController sharedInstance] layoutLayersWithContext:context]; 74 | } 75 | 76 | - (void)svgEditClick:(id)item { 77 | if (!self.document) { 78 | return; 79 | } 80 | 81 | NSLog(@"SVG edit!"); 82 | [[CSKSVGEditorController sharedInstance] editCurrentlySelectedShape]; 83 | } 84 | 85 | - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { 86 | return [self.toolbarDelegate toolbarDefaultItemIdentifiers:toolbar]; 87 | } 88 | 89 | - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar { 90 | NSMutableArray *allowedItems = [[self.toolbarDelegate 91 | toolbarAllowedItemIdentifiers:toolbar] mutableCopy]; 92 | [allowedItems addObject:@"CSSketch"]; 93 | [allowedItems addObject:@"CSSketch-SVG"]; 94 | 95 | return allowedItems; 96 | } 97 | 98 | - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar { 99 | NSMutableArray *selectable = [[self.toolbarDelegate 100 | toolbarSelectableItemIdentifiers:toolbar] mutableCopy]; 101 | // check if Edit is selectable 102 | // if it's not, then SVG edit shouldn't be either! 103 | // __block BOOL isEditSelectable = FALSE; 104 | [selectable enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, NSUInteger idx, BOOL * _Nonnull stop) { 105 | // if ([identifier isEqualToString:@"]) 106 | // NSLog(@"checking identifier: %@", identifier); 107 | }]; 108 | 109 | 110 | 111 | return selectable; 112 | } 113 | 114 | - (void)toolbarWillAddItem:(NSNotification *)notification { 115 | if ([self.toolbarDelegate respondsToSelector:@selector(toolbarWillAddItem:)]) { 116 | [self.toolbarDelegate toolbarWillAddItem:notification]; 117 | } 118 | } 119 | 120 | - (void)toolbarDidRemoveItem:(NSNotification *)notification { 121 | if ([self.toolbarDelegate respondsToSelector:@selector(toolbarDidRemoveItem:)]) { 122 | [self.toolbarDelegate toolbarDidRemoveItem:notification]; 123 | } 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Research/RS_MSTextLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // RS_MSTextLayer.h 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/12/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface RS_MSTextLayer : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /CSSketch Helper/src/Research/RS_MSTextLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // RS_MSTextLayer.m 3 | // CSSketch Helper 4 | // 5 | // Created by John Coates on 10/12/15. 6 | // Copyright © 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import "RS_MSTextLayer.h" 10 | #import 11 | 12 | @implementation RS_MSTextLayer 13 | 14 | #pragma mark - Hook 15 | 16 | + (NSArray *)targetClasses { 17 | return @[@"MSTextLayer"]; 18 | } 19 | 20 | + (void)installedHooksForClass:(NSString *)targetClass { 21 | NSLog(@"woo, isntalled hooks for: %@", targetClass); 22 | } 23 | 24 | - (void)markLayerDirtyOfType:(unsigned long long)arg1 hook:(MONCallHandler *)callHandler { 25 | NSLog(@"markLayerDirtyOfType:%d", (int)arg1); 26 | [callHandler callOriginalMethod]; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Frameworks/SketchKit.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Frameworks/SketchKit.framework/SketchKit: -------------------------------------------------------------------------------- 1 | Versions/Current/SketchKit -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Frameworks/SketchKit.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 15E65 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | SketchKit 11 | CFBundleIdentifier 12 | com.johncoates.SketchKit 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | SketchKit 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0.1 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 7D1014 33 | DTPlatformVersion 34 | GM 35 | DTSDKBuild 36 | 15E60 37 | DTSDKName 38 | macosx10.11 39 | DTXcode 40 | 0731 41 | DTXcodeBuild 42 | 7D1014 43 | NSHumanReadableCopyright 44 | Copyright © 2016 John Coates. All rights reserved. 45 | 46 | 47 | -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Frameworks/SketchKit.framework/Versions/A/SketchKit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Frameworks/SketchKit.framework/Versions/A/SketchKit -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Frameworks/SketchKit.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 15E65 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | CSSketch Helper 11 | CFBundleIdentifier 12 | com.johncoates.CSSketch-Helper 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | CSSketch Helper 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.2 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 1.11 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 7D1014 33 | DTPlatformVersion 34 | GM 35 | DTSDKBuild 36 | 15E60 37 | DTSDKName 38 | macosx10.11 39 | DTXcode 40 | 0731 41 | DTXcodeBuild 42 | 7D1014 43 | NSHumanReadableCopyright 44 | Copyright © 2015 John Coates. All rights reserved. 45 | 46 | 47 | -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/MacOS/CSSketch Helper: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/MacOS/CSSketch Helper -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Resources/embedded.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin:0; 3 | padding:0; 4 | width: 5000px; 5 | height: 5000px 6 | overflow: auto; 7 | } 8 | layer, artboard, page { 9 | display:block; 10 | overflow: auto; 11 | } -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Resources/icon-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Resources/icon-layout.png -------------------------------------------------------------------------------- /CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Resources/icon-layout24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/CSSketch.sketchplugin/CSSketch Helper.bundle/Contents/Resources/icon-layout24.png -------------------------------------------------------------------------------- /CSSketch.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CSSketch", 3 | "description" : "CSS properties inside of Sketch", 4 | "author" : "John Coates", 5 | "authorEmail" : "acapulco1988@gmail.com", 6 | "homepage": "https://github.com/JohnCoates/CSSketch", 7 | "version" : "1.2", 8 | "identifier" : "com.johncoates.CSSketch", 9 | "compatibleVersion": "3.3", 10 | "bundleVersion": "1", 11 | "commands" : [ 12 | { 13 | "name" : "Set Page's Stylesheet", 14 | "script" : "src/CSSketch_cached.js", 15 | "handler" : "setPageStylesheet", 16 | "identifier" : "setPageStylesheet", 17 | "shortcut" : "cmd ctrl 4", 18 | }, 19 | { 20 | "name" : "Layout Layers", 21 | "script" : "src/CSSketch_cached.js", 22 | "handler" : "layoutLayers", 23 | "identifier" : "layoutLayers", 24 | "shortcut" : "cmd ctrl 0", 25 | } 26 | ], 27 | "menu" : { 28 | "items" :[ 29 | "setPageStylesheet", 30 | "layoutLayers" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CSSketch.sketchplugin/Contents/Sketch/src/CSSketch.js: -------------------------------------------------------------------------------- 1 | var CSSketchContext = null; 2 | 3 | var layoutLayers = function(context) { 4 | CSSketchContext = context; 5 | 6 | if (loadCSSketchAsNeeded()) { 7 | var mainController = CSKMainController.sharedInstance(); 8 | mainController.layoutLayersWithContext(context); 9 | } 10 | } 11 | 12 | var setPageStylesheet = function(context) { 13 | CSSketchContext = context; 14 | 15 | if (loadCSSketchAsNeeded()) { 16 | var mainController = CSKMainController.sharedInstance(); 17 | mainController.selectStylesheetWithContext(context); 18 | } 19 | } 20 | 21 | function loadCSSketchAsNeeded() { 22 | var pluginsPath = @"~/Library/Application Support/com.bohemiancoding.sketch3/Plugins"; 23 | pluginsPath = [pluginsPath stringByExpandingTildeInPath]; 24 | // Load CSSketch 25 | if (!NSClassFromString("CSKMainController")) { 26 | var pluginFolder = [pluginsPath stringByAppendingPathComponent:"CSSKetch.sketchplugin"]; 27 | var bundlePath = [pluginFolder stringByAppendingPathComponent:"CSSketch Helper.bundle"]; 28 | 29 | var error = null; 30 | if (!loadBundle(bundlePath)) { 31 | return false 32 | } 33 | } 34 | return true; 35 | } 36 | 37 | function loadBundle(filePath) { 38 | var bundleURL = NSURL.fileURLWithPath(filePath); 39 | var bundle = [NSBundle bundleWithURL: bundleURL]; 40 | if (bundle == null) { 41 | showNotification("CSSketch bundle missing from " + filePath); 42 | return false; 43 | } 44 | 45 | var loaded = [bundle load]; 46 | 47 | if (!loaded) { 48 | showNotification("Couldn't load CSSketch bundle! Try allowing apps downloaded from anywhere (System Preferences -> Security & Privacy)"); 49 | } 50 | return loaded; 51 | } 52 | 53 | function showNotification(message) { 54 | // NSUserNotification only shows if app is in-active 55 | var notification = [[NSUserNotification alloc] init]; 56 | notification.title = @"CSSketch"; 57 | notification.informativeText = message; 58 | notification.soundName = NSUserNotificationDefaultSoundName; 59 | [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; 60 | 61 | // displayMessage only shows if app is active 62 | CSSketchContext.document.displayMessage("CSSketch: "+ message); 63 | } 64 | -------------------------------------------------------------------------------- /CSSketch.sketchplugin/Contents/Sketch/src/CSSketch_cached.js: -------------------------------------------------------------------------------- 1 | // This files exists to load CSSketch.js 2 | // When a script file is specified in manifest.json it's cached, so any changes 3 | // made to it after it's been used once will not take effect until Sketch is restared. 4 | // However, when a file is imported like in the line below the cache is bypassed, 5 | // allowing for changes to be made while keeping Sketch open. 6 | @import 'CSSketch.js' 7 | -------------------------------------------------------------------------------- /Documentation/dribbbleFollow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/Documentation/dribbbleFollow.png -------------------------------------------------------------------------------- /Examples/CSSketch - Installer.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/Examples/CSSketch - Installer.sketch -------------------------------------------------------------------------------- /Examples/CSSketch - Keyboard.less: -------------------------------------------------------------------------------- 1 | 2 | /* Imports */ 3 | @import "common.less"; 4 | 5 | [type="artboard"] { 6 | background-color: @background; 7 | } 8 | 9 | @originX: 0; 10 | @originY: 100; 11 | .row { 12 | position: absolute; 13 | left: 0; 14 | top: 0; 15 | } 16 | 17 | .second { 18 | top: 50; 19 | } 20 | .third { 21 | top: 100px; 22 | } 23 | 24 | [name="letters"] { 25 | position: absolute; 26 | left: 0; 27 | top: @originY + 31px; 28 | width: 640px; 29 | display: flex; 30 | justify-content:center; 31 | } 32 | 33 | [name="boxes"] { 34 | // .reset; 35 | position: absolute; 36 | left: 0; 37 | top: @originY + 30px; 38 | width: 640px; 39 | display: flex; 40 | justify-content: center; 41 | float: left; 42 | } 43 | 44 | [type="text"], [name="Shift-key"], [name="Delete-key"] { 45 | .reset; 46 | width: @keyWidth; 47 | margin-left: @keyMarginLeft; 48 | color: @keyForegroundColor; 49 | text-transform: uppercase; 50 | } 51 | 52 | [name="boxes"] > layer { 53 | .reset; 54 | width: @keyWidth; 55 | margin-left: @keyMarginLeft; 56 | background-color: @keyBackgroundColor; 57 | } 58 | 59 | .delete, [name="boxes"] > .delete { 60 | width: @deleteKeyWidth; 61 | } 62 | .shift, [name="boxes"] > .shift { 63 | width: @shiftKeyWidth; 64 | } 65 | 66 | [name="Shift"] { 67 | width: 18.56px; 68 | height: 15.52px; 69 | margin-top: 9px; 70 | margin-left: (@shiftKeyWidth / 2) - (18.56 / 2); 71 | background-color: @keyForegroundColor; 72 | } 73 | 74 | [name="Delete"] { 75 | width: 22px; 76 | height: 16px; 77 | margin-top: 10px; 78 | margin-left: (@deleteKeyWidth / 2) - (22px / 2) - 1; 79 | background-color: @keyForegroundColor; 80 | } 81 | 82 | // Non-Standard keys 83 | @deleteKeyWidth: @keyWidth + 8; 84 | @shiftKeyWidth: @deleteKeyWidth; 85 | 86 | // Palette variables 87 | @_background: "@{currentPalette}-background"; 88 | @_keyForeground: "@{currentPalette}-keyForeground"; 89 | @_keyBackground: "@{currentPalette}-keyBackground"; 90 | 91 | // Key Colors 92 | @keyForegroundColor: @@_keyForeground; 93 | @keyBackgroundColor: @@_keyBackground; 94 | @background: @@_background; 95 | 96 | 97 | 98 | 99 | // Palette - light 100 | @light-background: #404C5B; 101 | @light-keyForeground: #D3707E; 102 | @light-keyBackground: #F9F9F9; 103 | 104 | // Palette - dark 105 | @dark-background: #7B848F; 106 | @dark-keyForeground: #74D4FE; 107 | @dark-keyBackground: #2B3134; 108 | 109 | // Palette choices 110 | @paletteLight: light; 111 | @paletteDark: dark; 112 | 113 | /* Quick Customizing */ 114 | 115 | // Current Palette 116 | @currentPalette: @paletteLight; 117 | 118 | // Key Sizes 119 | @keyWidth: 36px; 120 | @keyMarginLeft: 14px; 121 | -------------------------------------------------------------------------------- /Examples/CSSketch - Keyboard.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/Examples/CSSketch - Keyboard.sketch -------------------------------------------------------------------------------- /Examples/CSSketch - Netflix Player Redesign.less: -------------------------------------------------------------------------------- 1 | /* Timeline */ 2 | 3 | #scrubPlaceLine { 4 | /* scrub place line: */ 5 | border: 2px solid #FFFFFF; 6 | // TODO: change border type 7 | } 8 | 9 | // Match based on name (case sensitive) 10 | [name="scrub preview"] { 11 | @horizontal: 1px; 12 | @vertical: 2px; 13 | @blur: 15px; 14 | @spread: 12px; 15 | @color: rgba(0,0,0,0.4); 16 | box-shadow: @horizontal @vertical @blur @spread @color; 17 | } 18 | 19 | /* Bottom Bar */ 20 | 21 | [name="Bottom Bar"] { 22 | width: 100%; 23 | } 24 | 25 | .background.bottomBar { 26 | background: rgba(0,0,0,0.30); 27 | width: 100%; 28 | backdrop-filter: blur(2px); 29 | } 30 | 31 | /* Background */ 32 | 33 | // Match only elements that incoporate both selectors 34 | .darken.background { 35 | background-color: rgba(0, 0, 0, 0.37); 36 | 37 | // TODO 38 | // opacity: 0.37; 39 | } 40 | 41 | .background { 42 | width: 100%; 43 | height: 100%; 44 | } 45 | -------------------------------------------------------------------------------- /Examples/CSSketch - Netflix Player Redesign.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/Examples/CSSketch - Netflix Player Redesign.sketch -------------------------------------------------------------------------------- /Examples/CSSketch - flexBox.less: -------------------------------------------------------------------------------- 1 | // All elements are given the following CSS attributes: 2 | // position: absolute 3 | // left: (x position) 4 | // top: (y position) 5 | // .reset counteracts these attributes 6 | .reset() { 7 | position: relative; 8 | left: initial; 9 | top: initial; 10 | } 11 | 12 | /* Mixins for support for systems before El Capitan */ 13 | .flex-display(@display: flex) { 14 | display: ~"-webkit-@{display}"; // Yosemite & older support 15 | display: @display; 16 | } 17 | .flex-direction(@direction: row) { 18 | -webkit-flex-direction: @direction; // Yosemite & older support 19 | flex-direction: @direction; 20 | } 21 | 22 | /* flexBox.sketch CSS */ 23 | 24 | .row { 25 | .reset; 26 | .flex-display(flex); 27 | .flex-direction(row); 28 | margin-top: 200px; 29 | width: 1800px; 30 | } 31 | .column:first-child { 32 | background-color: #B65A59; 33 | } 34 | .column:nth-child(2) { 35 | background-color: #6B9EC3; 36 | } 37 | .column:last-child { 38 | background-color: #9A4E97; 39 | } 40 | .column { 41 | .reset; 42 | width: 200px; 43 | height: 200px; 44 | margin-left: 70px; 45 | border: 12px solid rgba(0, 0, 0, .1); 46 | box-sizing: border-box; 47 | } 48 | 49 | [name="colors"] { 50 | .reset; 51 | top: initial; 52 | left: initial; 53 | position: absolute; 54 | .flex-display(flex); 55 | .flex-direction(row); 56 | margin-top: 160px; 57 | right: 100px; 58 | width: 500px; 59 | } 60 | 61 | [name="color"] { 62 | .reset; 63 | width: 100px; 64 | height: 100px; 65 | margin-left: 60px; 66 | border: 12px solid rgba(0, 0, 0, .1); 67 | box-sizing: border-box; 68 | } 69 | -------------------------------------------------------------------------------- /Examples/CSSketch - flexBox.sass: -------------------------------------------------------------------------------- 1 | // All elements are given the following CSS attributes: 2 | // position: absolute 3 | // left: (x position) 4 | // top: (y position) 5 | // .reset counteracts these attributes 6 | =reset() 7 | position: relative 8 | left: 0 9 | top: 0 10 | 11 | /* Mixins for support for systems before El Capitan */ 12 | =flexbox() 13 | display: -webkit-flex // Yosemite & older support 14 | display: flex 15 | 16 | =flex-direction($direction: row) 17 | -webkit-flex-direction: $direction // Yosemite & older support 18 | flex-direction: $direction 19 | 20 | 21 | /* flexBox.sketch CSS */ 22 | 23 | .row 24 | +reset() 25 | +flexbox() 26 | +flex-direction(row) 27 | margin-top: 200px 28 | width: 1800px 29 | 30 | .column:first-child 31 | background-color: #B65A59 32 | 33 | .column:nth-child(2) 34 | background-color: #6B9EC3 35 | 36 | .column:last-child 37 | background-color: #9A4E97 38 | 39 | .column 40 | +reset() 41 | width: 200px 42 | height: 200px 43 | margin-left: 70px 44 | border: 12px solid rgba(0, 0, 0, .1) 45 | box-sizing: border-box 46 | 47 | [type="text"] 48 | font-size: 40px 49 | font-family: Helvetica 50 | line-height: 40px 51 | letter-spacing: 6px 52 | -------------------------------------------------------------------------------- /Examples/CSSketch - flexBox.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/Examples/CSSketch - flexBox.sketch -------------------------------------------------------------------------------- /Examples/CSSketch - layout.less: -------------------------------------------------------------------------------- 1 | .reset() { 2 | position: relative; 3 | left: 0; 4 | top: 0; 5 | } 6 | 7 | [name="rectangle"]:nth-child(odd) { 8 | background-color: #B65A59; 9 | } 10 | 11 | [name="rectangle"]:nth-child(even) { 12 | background-color: #6B9EC3; 13 | } 14 | 15 | /* Mixins for support for systems before El Capitan */ 16 | .flex-display(@display: flex) { 17 | display: ~"-webkit-@{display}"; // Yosemite & older support 18 | display: @display; 19 | } 20 | .flex-direction(@direction: row) { 21 | -webkit-flex-direction: @direction; // Yosemite & older support 22 | flex-direction: @direction; 23 | } 24 | 25 | .flex-flow(@flow) { 26 | -webkit-flex-flow: @flow; // Yosemite & older support 27 | flex-flow: @flow; 28 | } 29 | 30 | .align-content(@align) { 31 | -webkit-align-content: @align; // Yosemite & older support 32 | align-content: @align; 33 | } 34 | 35 | .justify-content(@align) { 36 | -webkit-justify-content: @align; // Yosemite & older support 37 | justify-content: @align; 38 | } 39 | 40 | [name="table"] { 41 | .reset; 42 | width: 100%; 43 | height: 100%; 44 | .flex-display(flex); 45 | .flex-flow(row wrap); 46 | .align-content(flex-start); 47 | .justify-content(center); 48 | } 49 | 50 | [name="rectangle"] { 51 | .reset; 52 | @size : 80px; 53 | width: @size; 54 | height: @size; 55 | margin-left: 15px; 56 | margin-right: 15px; 57 | margin-top: 20px; 58 | } 59 | 60 | [type="text"] { 61 | color: #000; 62 | } 63 | 64 | [name="font one"] { 65 | background-color: #fff; 66 | } 67 | -------------------------------------------------------------------------------- /Examples/CSSketch - layout.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/Examples/CSSketch - layout.sketch -------------------------------------------------------------------------------- /Examples/common.less: -------------------------------------------------------------------------------- 1 | .reset() { 2 | position: relative; 3 | left: 0; 4 | top: 0; 5 | } 6 | -------------------------------------------------------------------------------- /External/CocoaSass/CocoaSass.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /External/CocoaSass/CocoaSass.xcodeproj/project.xcworkspace/xcshareddata/CocoaSass.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "DB0AA8B77050AFE7C658D7A871F99ECFEA6230C3", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "DB0AA8B77050AFE7C658D7A871F99ECFEA6230C3" : 0, 8 | "3F4433ED5A780EDFF7B8C0257D51FE25CD8299D4" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "91CFA82C-16E5-49C7-9782-F4AFC031EB98", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "DB0AA8B77050AFE7C658D7A871F99ECFEA6230C3" : "CSSketch\/", 13 | "3F4433ED5A780EDFF7B8C0257D51FE25CD8299D4" : "CSSketch\/external\/libsass\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "CocoaSass", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "external\/CocoaSass\/CocoaSass.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/sass\/libsass", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3F4433ED5A780EDFF7B8C0257D51FE25CD8299D4" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnCoates\/CSSketch.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "DB0AA8B77050AFE7C658D7A871F99ECFEA6230C3" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /External/CocoaSass/CocoaSass.xcodeproj/xcshareddata/xcschemes/CocoaSass.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /External/CocoaSass/CocoaSass/CocoaSass.h: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaSass.h 3 | // CocoaSass 4 | // 5 | // Created by John Coates on 1/31/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CocoaSass : NSObject 12 | 13 | + (NSString *)compileSass:(NSString *)contents 14 | extension:(NSString *)extension // must be sass or scss 15 | error:(NSError **)errorOut; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /External/CocoaSass/CocoaSass/CocoaSass.m: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaSass.m 3 | // CocoaSass 4 | // 5 | // Created by John Coates on 1/31/16. 6 | // Copyright © 2016 John Coates. All rights reserved. 7 | // 8 | 9 | #import "CocoaSass.h" 10 | #include 11 | 12 | @implementation CocoaSass 13 | 14 | + (NSString *)compileSass:(NSString *)contents 15 | extension:(NSString *)extension 16 | error:(NSError **)errorOut { 17 | struct Sass_Data_Context *context; 18 | // sass_option_set_include_path 19 | // sass_option_set_include_path 20 | 21 | // pass memory ownership to libsass 22 | char *contentsCopy = strdup(contents.UTF8String); 23 | context = sass_make_data_context(contentsCopy); 24 | 25 | struct Sass_Options *options = sass_data_context_get_options(context); 26 | sass_option_set_output_style(options, SASS_STYLE_NESTED); 27 | sass_option_set_precision(options, 5); 28 | 29 | // .sass = true, .scss = false 30 | if ([extension isEqualToString:@"sass"]) { 31 | sass_option_set_is_indented_syntax_src(options, true); 32 | } 33 | else { 34 | sass_option_set_is_indented_syntax_src(options, false); 35 | } 36 | 37 | 38 | struct Sass_Compiler *compiler = sass_make_data_compiler(context); 39 | sass_compiler_parse(compiler); 40 | sass_compiler_execute(compiler); 41 | 42 | int errorStatus = sass_context_get_error_status((struct Sass_Context *)context); 43 | if (errorStatus) { 44 | const char *errorMessage = sass_context_get_error_message((struct Sass_Context *)context); 45 | NSString *errorString; 46 | 47 | if (errorMessage) { 48 | errorString = [NSString stringWithFormat:@"Error compiling Sass #%d: %s", 49 | errorStatus, 50 | errorMessage]; 51 | } 52 | else { 53 | errorString = [NSString stringWithFormat:@"Error compiling Sass #%d", errorStatus]; 54 | } 55 | 56 | *errorOut = [self errorWithMessage:errorString]; 57 | sass_delete_compiler(compiler); 58 | return nil; 59 | } 60 | 61 | const char *output = sass_context_get_output_string((struct Sass_Context *)context); 62 | if (!output) { 63 | NSString *errorString = [NSString stringWithFormat:@"Couldn't generate Sass output. Error code (%d)", errorStatus]; 64 | *errorOut = [self errorWithMessage:errorString]; 65 | NSLog(@"%@", errorString); 66 | sass_delete_compiler(compiler); 67 | return nil; 68 | 69 | } 70 | NSString *compiledContents = [NSString stringWithUTF8String:output]; 71 | sass_delete_compiler(compiler); 72 | 73 | return compiledContents; 74 | } 75 | 76 | + (NSError *)errorWithMessage:(NSString *)message { 77 | NSError *error; 78 | NSDictionary *userInfo; 79 | 80 | userInfo = @{ 81 | NSLocalizedDescriptionKey : message 82 | }; 83 | error = [NSError errorWithDomain:@"CSKSass" 84 | code:801 85 | userInfo:userInfo]; 86 | 87 | return error; 88 | } 89 | 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /External/MonolithOSX.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /External/MonolithOSX.framework/Modules: -------------------------------------------------------------------------------- 1 | Versions/Current/Modules -------------------------------------------------------------------------------- /External/MonolithOSX.framework/MonolithOSX: -------------------------------------------------------------------------------- 1 | Versions/Current/MonolithOSX -------------------------------------------------------------------------------- /External/MonolithOSX.framework/PrivateHeaders: -------------------------------------------------------------------------------- 1 | Versions/Current/PrivateHeaders -------------------------------------------------------------------------------- /External/MonolithOSX.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /External/MonolithOSX.framework/Versions/A/Headers/MONCallHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // MONCallHandler.h 3 | // Monolith 4 | // 5 | // Created by John Coates on 4/21/15. 6 | // Copyright (c) 2015 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // Constants 12 | 13 | extern NSUInteger MONRegisterSize; // Size of regular register (32-bit: 4 bytes, 64-bit: 8 bytes) 14 | extern NSUInteger MONVFPRegisterSize; // Size of VFP registers (8 bytes) 15 | extern NSUInteger MONOriginalStackStride; // How many bytes seperate the original stack from the end of our pushed registers 16 | 17 | extern NSUInteger MONRegisterArguments; // how many method arguments can be stored in registers 18 | extern NSUInteger MONVFPRegisterArguments; // how many VFP arguments can be stored in registers 19 | extern NSUInteger MONStoredGeneralRegisters; // how many registers are stored in the stack 20 | extern NSUInteger MONStoredVFPRegisters; // how many VFP registers are in the stack 21 | 22 | @interface MONCallHandler : NSObject 23 | 24 | /// Calls original method and returns an NSObject encapsulating the return value 25 | - (id)callOriginalMethod; 26 | 27 | @end 28 | 29 | 30 | @interface MONCallHandler (Setters) 31 | 32 | /// Sets and argument before a call to -callOriginalMethod 33 | /// Pass an NSObject encapsulating the original format. 34 | - (BOOL)setArgument:(NSUInteger)argumentIndex toValue:(id)object; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /External/MonolithOSX.framework/Versions/A/Headers/MONHook.h: -------------------------------------------------------------------------------- 1 | // 2 | // MONHook.h 3 | // Monolith 4 | // 5 | // Created by John Coates on 5/29/14. 6 | // Copyright (c) 2014 John Coates. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | MONHook is protocol used for hooking other classes. 13 | 14 | ### Implementation Notes 15 | 16 | You must implement + (NSArray *)targetClasses; 17 | 18 | ## How To Hook a Class 19 | 20 | If your target method is: 21 | 22 | @code 23 | - (BOOL)shouldPlayInline; 24 | @endcode 25 | 26 | Your hook would look like this: 27 | 28 | @code 29 | - (BOOL)shouldPlayInline_hook:(MONCallHandler *)callHandler { 30 | NSNumber *originalReturnValue = [callHandler callOriginalMethod]; 31 | NSLog(@"[%@ %@] originally returned: %@, replacing return value with TRUE", 32 | NSStringFromClass([self class]), 33 | NSStringFromSelector(_cmd), 34 | originalReturnValue 35 | ); 36 | 37 | return TRUE; 38 | } 39 | @endcode 40 | 41 | If the method you're hooking takes arguments, simply postfix this to the method: hook:(MONCallHandler *)callHandler 42 | 43 | */ 44 | 45 | @protocol MONHook 46 | @required 47 | 48 | /** @name targetClasses */ 49 | 50 | /** 51 | * This method must be implemented. 52 | * 53 | * @warning `targetClasses` must not return `nil`. 54 | * @return The class or classes that you're setting hooks for. 55 | */ 56 | #ifdef __IPHONE_9_0 57 | + (NSArray *)targetClasses; 58 | #else 59 | + (NSArray *)targetClasses; 60 | #endif 61 | @optional 62 | 63 | /** 64 | Whether hooks should be automatically installed. 65 | 66 | This is the preferred way to handle hooks. A manual hooking method will be added in the future. 67 | @return Whether this class should automatically install hooks on load. Defaults to YES 68 | */ 69 | + (BOOL)shouldAutomaticallyInstallHooks; 70 | 71 | 72 | /** 73 | When this method is implemented, it will be called to notify a class 74 | that its hooks have been installed. 75 | */ 76 | 77 | + (void)installedHooksForClass:(NSString *)targetClass; 78 | 79 | @end 80 | 81 | 82 | // Subclassing MONHook is deprecated 83 | __attribute((unavailable("Use MONHook as a protocol instead of subclassing."))) @interface MONHook : NSObject 84 | @end 85 | -------------------------------------------------------------------------------- /External/MonolithOSX.framework/Versions/A/MonolithOSX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/External/MonolithOSX.framework/Versions/A/MonolithOSX -------------------------------------------------------------------------------- /External/MonolithOSX.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 15A284 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | MonolithOSX 11 | CFBundleIdentifier 12 | com.johncoates.MonolithOSX 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | MonolithOSX 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 0.901 21 | CFBundleSignature 22 | ???? 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | 0.03 29 | DTCompiler 30 | com.apple.compilers.llvm.clang.1_0 31 | DTPlatformBuild 32 | 7A218 33 | DTPlatformVersion 34 | GM 35 | DTSDKBuild 36 | 15A278 37 | DTSDKName 38 | macosx10.11 39 | DTXcode 40 | 0700 41 | DTXcodeBuild 42 | 7A218 43 | UIDeviceFamily 44 | 45 | 1 46 | 2 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /External/MonolithOSX.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 John Coates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## Unsupported 2 | CSSketch is no longer supported, and doesn't work with the latest versions of Sketch. I'm no longer designing as much, so I haven't had time to keep it updated. If you're interested in maintaining this project and know Objective-C and reverse engineering, email me at john@johncoates.me 3 | 4 | ## CSSketch - CSS for Sketch 3 5 | CSSketch is a Sketch 3 plugin that enables you to modify your designs quickly by attaching it a stylesheet to it that you can use to lay out your designs, change colors and shadows quickly, etc. 6 | It supports [Sass](http://sass-lang.com/) and [{less}](http://lesscss.org) stylesheets, which means you can put functions and variables in your stylesheet to make your workflow even more efficient. 7 | 8 | CSSketch is completely open source, so feel free to contribute to its development! 9 | 10 | ## The Story Behind CSSketch 11 | #### [Programmers Design Differently: Why I Built a CSS Plugin for Sketch 3](https://medium.com/@punksomething/programmers-design-differently-why-i-built-a-css-plugin-for-sketch-3-52a1246305a4) on Medium.com 12 | 13 | ## Screencast 14 | 15 | ![Screencast](https://raw.githubusercontent.com/JohnCoates/CSSketch/master/screencast.gif) 16 | 17 | ## Download & Install 18 | #### CSSketch is available through [Sketch Toolbox](http://sketchtoolbox.com/). This is the best option for installation, as it automatically keeps CSSketch up to date. 19 | 20 | ## Download & Install Manually 21 | Download from [Github](https://github.com/JohnCoates/CSSketch/archive/master.zip) 22 | 23 | Open CSSketch.sketchplugin and Sketch will ask if you'd like it installed 24 | 25 | 26 | ## Using CSSketch 27 | 28 | * To enable CSSketch you must run the menu bar option every time Sketch 3 launches. You can find it on the menu bar at Plugins -> CSSketch -> Layout Layers. 29 | * A stylesheet is set on a per-page basis. Set the stylesheet at Plugins -> CSSketch -> Set Page's Stylesheet for every page you want a stylesheet for. Once this is set, simply make changes to the stylesheet and save them to see the effects. 30 | * Take a look in Examples/ for examples of what you can do right now. 31 | * **Please Note:** Handling for many CSS rules is missing currently. 32 | 33 | 34 | ## Targeting Your Sketch File 35 | 36 | You can use the following attributes to target various components of your sketch file in combination with other css targeting methods: 37 | 38 | * `[type="artboard"]` - To target all artboards 39 | * `[type="text"]` - To target all text 40 | * `[name="yourLayerName"]` - To target all layers with `yourLayerName` in their name 41 | * `.className` - To target all layers named `.className` 42 | 43 | ## Features 44 | * **Less Support:** Variables and functions are supported. Find out more at [LessCSS.org](http://lesscss.org) 45 | * **Auto Detect Stylesheet changes:** Set the stylesheet once, and that's it. Changes are detected and applied as soon as you save the stylesheet. 46 | * **Webkit Engine:** CSSketch is powered by the WebKit engine, the same code that powers Safari and Google Chrome. 47 | * **Toolbar Icon:** A toolbar icon is added after running CSSketch for easy applying of changes. 48 | * **Sandbox Support:** CSSketch works with the Mac App Store version of Sketch 3 as well as the regular version. 49 | * **Stylesheet Path Store In Document:** You only ever have to set the stylesheet path once. It's then stored in the document for future use. 50 | 51 | ## Supported CSS 52 | * CSSketch uses the system version of WebKit. Upgrade to El Capitan for the best compatibility with CSS standards. 53 | * All layout variables should work fine. 54 | * All elements are given default values of position:absolute, top, and left, with their current positions on the artboard. 55 | * For selectors: All layers are the element type "layer". The name attribute is set to a layer's name, minus the classes. The type attribute is set to "text" for text, and "artboard" for artboards. 56 | * Background-color is supported 57 | * Border is supported (only solid lines) 58 | * Shadow is supported 59 | * Color is supported 60 | * Text-transform is supported 61 | 62 | ## Extra Resources Posted On Dribbble! 63 | [![Dribbble](https://raw.githubusercontent.com/JohnCoates/CSSketch/master/Documentation/dribbbleFollow.png)](https://dribbble.com/johncoates) 64 | 65 | 66 | ## Community 67 | - **Find a bug?** [Open an issue](https://github.com/JohnCoates/CSSketch/issues/new). Try to be as specific as possible. 68 | - **Have a feature request** [Open an issue](https://github.com/JohnCoates/CSSketch/issues/new). Tell me why this feature would be useful, and why you and others would want it. 69 | 70 | ## Contribute 71 | Lot of CSS rules have yet to be added. They're decently easy to add. I appreciate all pull requests! You'll want to modify the following files to add CSS rule handling: 72 | * CSS Rule handlers: [CSSketch Helper/src/Models/Layout/CSKLayerCSS.m](https://github.com/JohnCoates/CSSketch/blob/master/CSSketch%20Helper/src/Models/Layout/CSKLayerCSS.m) 73 | * Sketch Header Stubs: [CSketch Helper/src/Headers/CSSketchHeaders.h](https://github.com/JohnCoates/CSSketch/blob/master/CSSketch%20Helper/src/Headers/CSKSketchHeaders.h) 74 | 75 | ## Changelog 76 | - February 2016 - 1.1: Sass support, installer, now uses [SketchKit](https://github.com/JohnCoates/SketchKit) 77 | - October 2015 - 1.0: first release 78 | 79 | ## License 80 | [MIT License](https://raw.githubusercontent.com/JohnCoates/CSSketch/master/LICENSE) 81 | 82 | ## Author 83 | Maintained and created by John Coates [@punksomething](http://twitter.com/punksomething) 84 | -------------------------------------------------------------------------------- /Scripts/CSSketch-remote.coscript: -------------------------------------------------------------------------------- 1 | // Check if we're in Sketch 2 | if (!NSClassFromString("MSDocument")){ 3 | // launch Sketch with document then run this plugin 4 | var currentScript = [COScript currentCOScript]; 5 | var scriptEnvironment = [currentScript env]; 6 | var scriptURL = scriptEnvironment["scriptURL"]; 7 | var scriptPath = [scriptURL path]; 8 | 9 | var scriptsFolder = [scriptPath stringByDeletingLastPathComponent]; 10 | var projectFolder = [scriptsFolder stringByDeletingLastPathComponent]; 11 | var sketchDocumentsFolder = [projectFolder stringByAppendingPathComponent:"Examples"]; 12 | // var file = "CSSketch - Skyfall.sketch" 13 | // var file = "CSSketch - Netflix Player Redesign.sketch" 14 | var file = "CSSketch - flexBox.sketch" 15 | var documentPath = [sketchDocumentsFolder stringByAppendingPathComponent:file]; 16 | 17 | // Submitted tests 18 | // var testsFolder = [projectFolder stringByAppendingPathComponent:"test"]; 19 | // var documentPath = [testsFolder stringByAppendingPathComponent:"twoClasses.sketch"]; 20 | 21 | // Open Sketch document 22 | [[NSWorkspace sharedWorkspace] openFile:documentPath]; 23 | 24 | var url = [NSURL fileURLWithPath:scriptPath]; 25 | var app = [COScript app:"Sketch"]; 26 | var delegate = [app delegate]; 27 | 28 | [delegate runPluginAtURL:url]; 29 | } 30 | else { 31 | var pluginsPath = @"~/Library/Application Support/com.bohemiancoding.sketch3/Plugins"; 32 | pluginsPath = [pluginsPath stringByExpandingTildeInPath]; 33 | // Load SketchKit 34 | if (!NSClassFromString("SKK_MSLayer")) { 35 | 36 | var frameworkPluginFolder = [pluginsPath stringByAppendingPathComponent:"SketchKit.sketchplugin"]; 37 | var frameworkBundlePath = [frameworkPluginFolder stringByAppendingPathComponent:"SketchKit.framework"]; 38 | 39 | var error = null; 40 | if (!loadBundle(frameworkBundlePath)) { 41 | log("error"); 42 | } 43 | } 44 | 45 | // Load CSSketch 46 | if (!NSClassFromString("CSKMainController")) { 47 | var pluginFolder = [pluginsPath stringByAppendingPathComponent:"CSSKetch.sketchplugin"]; 48 | var bundlePath = [pluginFolder stringByAppendingPathComponent:"CSSketch Helper.bundle"]; 49 | 50 | var error = null; 51 | if (!loadBundle(bundlePath)) { 52 | log("error"); 53 | } 54 | } 55 | 56 | var mainController = CSKMainController.sharedInstance(); 57 | mainController.layoutLayersWithContext(sketch); 58 | } 59 | 60 | 61 | function loadBundle(filePath) { 62 | var bundleURL = NSURL.fileURLWithPath(filePath); 63 | var bundle = [NSBundle bundleWithURL: bundleURL]; 64 | if (bundle == null) { 65 | showNotification("Bundle missing!"); 66 | error = error; 67 | return false; 68 | } 69 | 70 | var loaded = [bundle load]; 71 | 72 | if (!loaded) { 73 | showNotification("Couldn't load SketchKit bundle."); 74 | } 75 | else { 76 | // log("loaded bundle!") 77 | } 78 | return loaded; 79 | } 80 | 81 | function showNotification(message) { 82 | var notification = [[NSUserNotification alloc] init]; 83 | notification.title = @"SketchKit"; 84 | notification.informativeText = message; 85 | notification.soundName = NSUserNotificationDefaultSoundName; 86 | [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /Scripts/CenterFinder/canvasDraw.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | var init = function (canvasID) { 3 | var canvasDraw = {}; 4 | canvasDraw.canvas = document.getElementById(canvasID); 5 | canvasDraw.context = canvasDraw.canvas.getContext("2d"); 6 | canvasDraw.drawStrokedClosedPath = function (pathEntries, rect) { 7 | canvasDraw.context.lineWidth = 1; 8 | rect = { x: rect[0], y: rect[1], width: rect[2], height: rect[3]}; 9 | var scale = rect.width; 10 | 11 | // translate point to screen pixel 12 | var screenX = function (x) { 13 | return rect.x + (scale * x); 14 | } 15 | 16 | var screenY = function (y) { 17 | return rect.y + (scale * y); 18 | } 19 | 20 | var length = pathEntries.length; 21 | for (var key = 0; key < length; key++) { 22 | var entry = pathEntries[key] 23 | var point = entry.point 24 | 25 | if (key == 0) { 26 | this.context.moveTo(screenX(point.x), screenY(point.y)); 27 | this.context.beginPath(); 28 | continue; 29 | } 30 | var previousEntry = pathEntries[key - 1]; 31 | 32 | if (typeof entry.controlPoint1 != 'undefined') { 33 | this.context.bezierCurveTo( 34 | screenX(entry.controlPoint1.x), screenY(entry.controlPoint1.y), 35 | screenX(entry.controlPoint2.x), screenY(entry.controlPoint2.y), 36 | screenX(point.x), screenY(point.y)); 37 | continue; 38 | } 39 | 40 | if (typeof entry.curveMode == 'undefined') { 41 | this.context.lineTo(screenX(point.x), screenY(point.y)); 42 | continue; 43 | } 44 | 45 | var curveFrom = entry.curveFrom; 46 | var curveTo = entry.curveTo; 47 | var previousPoint = previousEntry.point; 48 | 49 | var controlPoint1 = { x: previousEntry.curveFrom.x, y: previousEntry.curveFrom.y }; 50 | var controlPoint2 = { x: curveTo.x, y: curveTo.y }; 51 | 52 | if (entry.curveMode == 1) { 53 | controlPoint2.x = point.x; 54 | controlPoint2.y = point.y; 55 | } 56 | 57 | if (previousEntry.curveMode == 1) { 58 | controlPoint1.x = previousPoint.x; 59 | controlPoint1.y = previousPoint.y; 60 | } 61 | // canvasDraw.context.fillStyle = 'blue'; 62 | // this.context.fillRect(screenX(controlPoint2x), screenY(controlPoint2y), 4, 4); 63 | 64 | this.context.bezierCurveTo(screenX(controlPoint1.x), screenY(controlPoint1.y), 65 | screenX(controlPoint2.x), screenY(controlPoint2.y), 66 | screenX(point.x), screenY(point.y)); 67 | } 68 | 69 | this.context.closePath(); 70 | this.context.stroke(); 71 | } 72 | 73 | canvasDraw.clear = function () { 74 | this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); 75 | } 76 | 77 | canvasDraw.drawCentroid = function (centroid, rect) { 78 | var originX = rect[0]; 79 | var originY = rect[1]; 80 | var width = rect[2]; 81 | var height = rect[3]; 82 | var scale = width; 83 | 84 | this.context.fillStyle = 'green'; 85 | var x = originX + (scale * centroid.x); 86 | var y = originY + (scale * centroid.y); 87 | 88 | var centroidWidth = 2; 89 | var centroidHeight = 2; 90 | x = x - (centroidWidth / 2); 91 | y = y - (centroidHeight / 2); 92 | 93 | this.context.fillRect(x, y, centroidWidth, centroidHeight); 94 | } 95 | 96 | return canvasDraw; 97 | }; 98 | 99 | 100 | return init; 101 | }); 102 | -------------------------------------------------------------------------------- /Scripts/CenterFinder/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CenterFinder 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Scripts/CenterFinder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CenterFinder", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Find center of shapes.", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "webpack", 9 | "dev": "webpack-dev-server -inline --devtool eval --progress --colors --hot" 10 | }, 11 | "author": "John Coates", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "webpack": "^1.12.14", 15 | "webpack-dev-server": "^1.14.1" 16 | }, 17 | "dependencies": { 18 | "webpack": "^1.12.14" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Scripts/CenterFinder/polygonManager.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | var polygonManager = {}; 3 | polygonManager.addPointEntry = function (targetArray, point, curveMode, hasCurveFrom, curveFrom, hasCurveTo, curveTo) { 4 | var entry = {}; 5 | entry.point = {x: point[0], y: point[1]}; 6 | if (typeof curveMode != 'undefined') { 7 | entry.curveMode = curveMode; 8 | entry.curveTo = {x: curveTo[0], y: curveTo[1]}; 9 | entry.curveFrom = {x: curveFrom[0], y: curveFrom[1]}; 10 | } 11 | targetArray.push(entry) 12 | } 13 | 14 | polygonManager.addBezierCurveEntry = function (targetArray, point, controlPoint1, controlPoint2) { 15 | var entry = {}; 16 | entry.point = {x: point[0], y: point[1]}; 17 | if (typeof controlPoint1 != 'undefined') { 18 | entry.controlPoint1 = {x: controlPoint1[0], y: controlPoint1[1]}; 19 | entry.controlPoint2 = {x: controlPoint2[0], y: controlPoint2[1]}; 20 | } 21 | targetArray.push(entry) 22 | } 23 | 24 | polygonManager.convertPolygonToSquarePixels = function (path, ratio) { 25 | var length = path.length; 26 | var farthestDistanceFromCentroid = {x: 0, y: 0}; 27 | var xScale = ratio.x / ratio.y; 28 | var yScale = ratio.y / ratio.x; 29 | 30 | for (var index = 0; index < length; index +=1 ) { 31 | var entry = path[index]; 32 | var point = entry.point; 33 | point.y = point.y * yScale; 34 | entry.point = point; 35 | 36 | if (typeof entry.controlPoint1 != 'undefined') { 37 | entry.controlPoint1.y *= yScale; 38 | entry.controlPoint2.y *= yScale; 39 | } 40 | 41 | if (typeof entry.curveFrom != 'undefined') { 42 | entry.curveFrom.y *= yScale; 43 | entry.curveTo.y *= yScale; 44 | } 45 | 46 | path[index] = entry; 47 | } 48 | 49 | return path; 50 | } 51 | 52 | // from https://github.com/mapbox/fontnik/blob/37a5e17d7ab27c6e4db255b23448544dc07bd8ac/lib/curve4_div.js 53 | function Curve4Div() {} 54 | 55 | Curve4Div.curve_collinearity_epsilon = 1e-30; 56 | Curve4Div.curve_angle_tolerance_epsilon = 0.01; 57 | Curve4Div.curve_recursion_limit = 32; 58 | 59 | Curve4Div.prototype.approximation_scale = 1.0; 60 | Curve4Div.prototype.angle_tolerance = 0.0; 61 | Curve4Div.prototype.cusp_limit = 0.0; 62 | 63 | 64 | Curve4Div.prototype.init = function(x1, y1, x2, y2, x3, y3, x4, y4) { 65 | this.points = []; 66 | this.distance_tolerance_square = 0.5 / this.approximation_scale; 67 | this.distance_tolerance_square *= this.distance_tolerance_square; 68 | this.bezier(x1, y1, x2, y2, x3, y3, x4, y4); 69 | }; 70 | 71 | Curve4Div.prototype.bezier = function(x1, y1, x2, y2, x3, y3, x4, y4) { 72 | this.points.push(this.point_d(x1, y1)); 73 | this.recursive_bezier(x1, y1, x2, y2, x3, y3, x4, y4, 0); 74 | this.points.push(this.point_d(x4, y4)); 75 | }; 76 | 77 | Curve4Div.prototype.calc_sq_distance = function (x1, y1, x2, y2) { 78 | var dx = x2 - x1; 79 | var dy = y2 - y1; 80 | return dx * dx + dy * dy; 81 | } 82 | 83 | Curve4Div.prototype.point_d = function (x, y) { 84 | return [x, y]; 85 | } 86 | 87 | Curve4Div.prototype.recursive_bezier = function(x1, y1, x2, y2, x3, y3, x4, y4, level) { 88 | if (level > Curve4Div.curve_recursion_limit) { 89 | return; 90 | } 91 | 92 | // Calculate all the mid-points of the line segments 93 | var x12 = (x1 + x2) / 2; 94 | var y12 = (y1 + y2) / 2; 95 | var x23 = (x2 + x3) / 2; 96 | var y23 = (y2 + y3) / 2; 97 | var x34 = (x3 + x4) / 2; 98 | var y34 = (y3 + y4) / 2; 99 | var x123 = (x12 + x23) / 2; 100 | var y123 = (y12 + y23) / 2; 101 | var x234 = (x23 + x34) / 2; 102 | var y234 = (y23 + y34) / 2; 103 | var x1234 = (x123 + x234) / 2; 104 | var y1234 = (y123 + y234) / 2; 105 | 106 | // Try to approximate the full cubic curve by a single straight line 107 | var dx = x4 - x1; 108 | var dy = y4 - y1; 109 | 110 | var d2 = Math.abs(((x2 - x4) * dy - (y2 - y4) * dx)); 111 | var d3 = Math.abs(((x3 - x4) * dy - (y3 - y4) * dx)); 112 | var da1, da2, k; 113 | 114 | switch ((Math.floor(d2 > Curve4Div.curve_collinearity_epsilon) << 1) + 115 | Math.floor(d3 > Curve4Div.curve_collinearity_epsilon)) { 116 | case 0: 117 | // All collinear OR p1==p4 118 | k = dx * dx + dy * dy; 119 | if (k === 0) { 120 | d2 = this.calc_sq_distance(x1, y1, x2, y2); 121 | d3 = this.calc_sq_distance(x4, y4, x3, y3); 122 | } else { 123 | k = 1 / k; 124 | da1 = x2 - x1; 125 | da2 = y2 - y1; 126 | d2 = k * (da1 * dx + da2 * dy); 127 | da1 = x3 - x1; 128 | da2 = y3 - y1; 129 | d3 = k * (da1 * dx + da2 * dy); 130 | if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { 131 | // Simple collinear case, 1---2---3---4 132 | // We can leave just two endpoints 133 | return; 134 | } 135 | if (d2 <= 0) { 136 | d2 = this.calc_sq_distance(x2, y2, x1, y1); 137 | } else if (d2 >= 1) { 138 | d2 = this.calc_sq_distance(x2, y2, x4, y4); 139 | } else { 140 | d2 = this.calc_sq_distance(x2, y2, x1 + d2 * dx, y1 + d2 * dy); 141 | } 142 | 143 | if (d3 <= 0) { 144 | d3 = this.calc_sq_distance(x3, y3, x1, y1); 145 | } else if (d3 >= 1) { 146 | d3 = this.calc_sq_distance(x3, y3, x4, y4); 147 | } else { 148 | d3 = this.calc_sq_distance(x3, y3, x1 + d3 * dx, y1 + d3 * dy); 149 | } 150 | } 151 | 152 | if (d2 > d3) { 153 | if (d2 < this.distance_tolerance_square) { 154 | this.points.push(this.point_d(x2, y2)); 155 | return; 156 | } 157 | } else { 158 | if (d3 < this.distance_tolerance_square) { 159 | this.points.push(this.point_d(x3, y3)); 160 | return; 161 | } 162 | } 163 | break; 164 | 165 | case 1: 166 | // p1,p2,p4 are collinear, p3 is significant 167 | if (d3 * d3 <= this.distance_tolerance_square * (dx * dx + dy * dy)) { 168 | if (this.angle_tolerance < Curve4Div.curve_angle_tolerance_epsilon) { 169 | this.points.push(this.point_d(x23, y23)); 170 | return; 171 | } 172 | 173 | // Angle Condition 174 | da1 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - Math.atan2(y3 - y2, x3 - x2)); 175 | if (da1 >= Math.PI) da1 = 2 * Math.PI - da1; 176 | 177 | if (da1 < this.angle_tolerance) { 178 | this.points.push(this.point_d(x2, y2)); 179 | this.points.push(this.point_d(x3, y3)); 180 | return; 181 | } 182 | 183 | if (this.cusp_limit !== 0.0) { 184 | if (da1 > this.cusp_limit) { 185 | this.points.push(this.point_d(x3, y3)); 186 | return; 187 | } 188 | } 189 | } 190 | break; 191 | 192 | case 2: 193 | // p1,p3,p4 are collinear, p2 is significant 194 | if (d2 * d2 <= this.distance_tolerance_square * (dx * dx + dy * dy)) { 195 | if (this.angle_tolerance < Curve4Div.curve_angle_tolerance_epsilon) { 196 | this.points.push(this.point_d(x23, y23)); 197 | return; 198 | } 199 | 200 | // Angle Condition 201 | da1 = Math.abs(Math.atan2(y3 - y2, x3 - x2) - Math.atan2(y2 - y1, x2 - x1)); 202 | if (da1 >= Math.PI) da1 = 2 * Math.PI - da1; 203 | 204 | if (da1 < this.angle_tolerance) { 205 | this.points.push(this.point_d(x2, y2)); 206 | this.points.push(this.point_d(x3, y3)); 207 | return; 208 | } 209 | 210 | if (this.cusp_limit !== 0.0) { 211 | if (da1 > this.cusp_limit) { 212 | this.points.push(this.point_d(x2, y2)); 213 | return; 214 | } 215 | } 216 | } 217 | break; 218 | 219 | case 3: 220 | // Regular case 221 | if ((d2 + d3) * (d2 + d3) <= this.distance_tolerance_square * (dx * dx + dy * dy)) { 222 | // If the curvature doesn't exceed the distance_tolerance value 223 | // we tend to finish subdivisions. 224 | if (this.angle_tolerance < Curve4Div.curve_angle_tolerance_epsilon) { 225 | this.points.push(this.point_d(x23, y23)); 226 | return; 227 | } 228 | 229 | // Angle & Cusp Condition 230 | k = Math.atan2(y3 - y2, x3 - x2); 231 | da1 = Math.abs(k - Math.atan2(y2 - y1, x2 - x1)); 232 | da2 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - k); 233 | if (da1 >= Math.PI) da1 = 2 * Math.PI - da1; 234 | if (da2 >= Math.PI) da2 = 2 * Math.PI - da2; 235 | 236 | if (da1 + da2 < this.angle_tolerance) { 237 | // Finally we can stop the recursion 238 | this.points.push(this.point_d(x23, y23)); 239 | return; 240 | } 241 | 242 | if (this.cusp_limit !== 0.0) { 243 | if (da1 > this.cusp_limit) { 244 | this.points.push(this.point_d(x2, y2)); 245 | return; 246 | } 247 | 248 | if (da2 > this.cusp_limit) { 249 | this.points.push(this.point_d(x3, y3)); 250 | return; 251 | } 252 | } 253 | } 254 | break; 255 | } 256 | 257 | // Continue subdivision 258 | this.recursive_bezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1); 259 | this.recursive_bezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1); 260 | }; 261 | 262 | polygonManager.extrudeBezierPath = function (pointEntries) { 263 | var previousEntry = null; 264 | var nextPreviousEntry = null; 265 | var newPointEntries = []; 266 | for (var key in pointEntries) { 267 | if (nextPreviousEntry) { 268 | previousEntry = nextPreviousEntry; 269 | nextPreviousEntry = null; 270 | } 271 | 272 | var entry = pointEntries[key] 273 | nextPreviousEntry = entry; 274 | var point = entry.point 275 | var pointX = point.x; 276 | var pointY = point.y; 277 | if (!previousEntry) { 278 | var newEntry = { point: entry.point }; 279 | newPointEntries.push(newEntry); 280 | continue; 281 | } 282 | if (typeof entry.curveMode == 'undefined') { 283 | var newEntry = { point: entry.point }; 284 | newPointEntries.push(newEntry); 285 | continue; 286 | } 287 | if (entry.curveMode == 1) { 288 | var newEntry = { point: entry.point }; 289 | newPointEntries.push(newEntry); 290 | continue; 291 | } 292 | 293 | var previousX = previousEntry.point.x; 294 | var previousY = previousEntry.point.y; 295 | var controlPoint1x = previousEntry.curveFrom.x; 296 | var controlPoint1y = previousEntry.curveFrom.y; 297 | var controlPoint2x = entry.curveTo.x; 298 | var controlPoint2y = entry.curveTo.y; 299 | 300 | if (entry.curveMode == 1) { 301 | controlPoint2x = pointX; 302 | controlPoint2y = pointY; 303 | } 304 | 305 | if (previousEntry.curveMode == 1) { 306 | controlPoint1x = previousX; 307 | controlPoint1y = previousY; 308 | } 309 | 310 | var curve4 = new Curve4Div(); 311 | curve4.approximation_scale = 100; 312 | curve4.init(previousX, previousY, controlPoint1x, controlPoint1y, controlPoint2x, controlPoint2y, pointX, pointY); 313 | var extrudedPoints = curve4.points; 314 | 315 | for (var extrudedKey in extrudedPoints) { 316 | var extrudedPoint = extrudedPoints[extrudedKey]; 317 | var newEntry = { point: { x: extrudedPoint[0], y: extrudedPoint[1] } }; 318 | newPointEntries.push(newEntry); 319 | } 320 | } 321 | 322 | return newPointEntries; 323 | } 324 | 325 | polygonManager.findExtrudedPathCentroid = function (pointEntries) { 326 | // https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon 327 | // n is supposed to be all path entries, with the last one being 328 | // the same as the first one, to close the path. Our polygons ommit the 329 | // last path entry, so instead of summing to n-1, we sum to full n, 330 | // wrapping the last value to the first value 331 | var n = pointEntries.length; 332 | var cX = 0 333 | var cY = 0 334 | 335 | for (var index = 0; index < n; index++) { 336 | var point = pointEntries[index].point; 337 | var nextIndex = index + 1; 338 | if (index == n - 1) { 339 | nextIndex = 0; 340 | } 341 | var nextPoint = pointEntries[nextIndex].point; 342 | var xi = point.x; 343 | var yi = point.y; 344 | var xiPlus1 = nextPoint.x; 345 | var yiPlus1 = nextPoint.y; 346 | 347 | var factor = xi * yiPlus1 - xiPlus1 * yi; 348 | cX += (xi + xiPlus1) * factor; 349 | cY += (yi + yiPlus1) * factor; 350 | } 351 | 352 | var area6 = this.findExtrudedPathArea(pointEntries) * 6; 353 | var factor = 1 / area6; 354 | cX = factor * cX; 355 | cY = factor * cY; 356 | 357 | return { x: cX, y: cY }; 358 | } 359 | 360 | // returns signed area 361 | polygonManager.findExtrudedPathArea = function (pointEntries) { 362 | // https://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon 363 | // n is supposed to be all path entries, with the last one being 364 | // the same as the first one, to close the path. Our polygons ommit the 365 | // last path entry, so instead of summing to n-1, we sum to full n, 366 | // wrapping the last value to the first value 367 | var sum = 0; 368 | var n = pointEntries.length; 369 | var area = 0; 370 | for (var index = 0; index < n; index++) { 371 | var point = pointEntries[index].point; 372 | var nextIndex = index + 1; 373 | if (index == n - 1) { 374 | nextIndex = 0; 375 | } 376 | var nextPoint = pointEntries[nextIndex].point; 377 | 378 | var xi = point.x; 379 | var yi = point.y; 380 | var xiPlus1 = nextPoint.x; 381 | var yiPlus1 = nextPoint.y; 382 | 383 | area += xi * yiPlus1 - xiPlus1 * yi; 384 | } 385 | 386 | return area / 2; 387 | } 388 | 389 | polygonManager.rotatedExtrudedPath = function (pointEntries, rotationDegrees, size, origin) { 390 | // https://en.wikipedia.org/wiki/Transformation_matrix#Rotation 391 | var newEntries = []; 392 | var length = pointEntries.length; 393 | var radians = rotationDegrees * (Math.PI / 180); 394 | 395 | for (var index = 0; index < length; index++) { 396 | var point = pointEntries[index].point; 397 | var rotatedPoint = this.rotatePointAroundOrigin(point, radians, origin); 398 | 399 | var newEntry = { point: rotatedPoint }; 400 | newEntries.push(newEntry); 401 | } 402 | 403 | return newEntries; 404 | } 405 | 406 | polygonManager.rotatePoint = function (point, radians) { 407 | var theta = radians; 408 | var x = point.x; 409 | var y = point.y; 410 | var theta = radians; 411 | x = x * Math.cos(theta) + y * Math.sin(theta); 412 | y = (0 - x) * Math.sin(theta) + y * Math.cos(theta); 413 | // x = x * Math.cos(theta) - y * Math.sin(theta); 414 | // y = x * Math.sin(theta) + y * Math.cos(theta); 415 | 416 | return { x: x, y: y}; 417 | } 418 | 419 | polygonManager.rotatePointAroundOrigin = function (point, radians, origin) { 420 | var theta = radians; 421 | var x1, y1; 422 | var x0 = origin.x, 423 | y0 = origin.y, 424 | x = point.x, 425 | y = point.y; 426 | x1 = x0 + (x - x0) * Math.cos(theta) + (y - y0) * Math.sin(theta); 427 | y1 = y0 - (x - x0) * Math.sin(theta) + (y - y0) * Math.cos(theta); 428 | return { x: x1, y: y1}; 429 | } 430 | 431 | return polygonManager; 432 | }); 433 | -------------------------------------------------------------------------------- /Scripts/CenterFinder/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | module.exports = { 3 | entry: { 4 | bundle: [ 5 | 'webpack/hot/only-dev-server', 6 | 'webpack-dev-server/client?http://localhost:8080', 7 | path.resolve(__dirname, 'main.js') 8 | ], 9 | }, 10 | output: { 11 | path: __dirname, 12 | filename: "bundle.js", 13 | publicPath: "http://localhost:8080/" 14 | }, 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /Scripts/CentroidFinder.js: -------------------------------------------------------------------------------- 1 | // from https://github.com/jywarren/thermographer/blob/793050dd3f69317f8bb1e2ec57c89d2305bed731/thermographer-website/public/src/geometry.js 2 | var geometry = { 3 | // http://stackoverflow.com/questions/2792443/finding-the-centroid-of-a-polygon 4 | /** 5 | * Finds the centroid of a polygon 6 | * @param {Node[]} polygon Array of nodes that make up the polygon 7 | * @return A tuple, in [x, y] format, with the coordinates of the centroid 8 | * @type Number[] 9 | */ 10 | poly_centroid: function(polygon) { 11 | var n = polygon.length 12 | var cx = 0, cy = 0 13 | var a = geometry.poly_area(polygon,true) 14 | console.log("area: ", a); 15 | var centroid = [] 16 | var i,j 17 | var factor = 0 18 | 19 | for (i=0;i= n) { 22 | j = 0; 23 | } 24 | 25 | 26 | factor = (polygon[i].x * polygon[j].y - polygon[j].x * polygon[i].y) 27 | cx += (polygon[i].x + polygon[j].x) * factor 28 | cy += (polygon[i].y + polygon[j].y) * factor 29 | } 30 | 31 | a *= 6 32 | factor = 1/a 33 | cx *= factor 34 | cy *= factor 35 | centroid[0] = cx 36 | centroid[1] = cy 37 | return centroid 38 | }, 39 | /** 40 | * Finds the area of a polygon 41 | * @param {Fred.Point[]} points Array of points with p.x and 42 | p.y properties that make up the polygon 43 | * @param {Boolean} [signed] If true, returns a signed area, else 44 | returns a positive area. 45 | * Defaults to false. 46 | * @return Area of the polygon 47 | * @type Number 48 | */ 49 | poly_area: function(points, signed) { 50 | var area = 0 51 | var length = points.length 52 | for (var index = 0;index < length-1; index++) { 53 | console.log("point: ", point); 54 | point = points[index]; 55 | if (index < point.length-1) next = points[index+1] 56 | else next = points[0] 57 | area += point.x * next.y - next.x * point.y; 58 | } 59 | if (signed) return area/2 60 | else return Math.abs(area/2) 61 | } 62 | }; 63 | 64 | 65 | // from https://github.com/mapbox/fontnik/blob/37a5e17d7ab27c6e4db255b23448544dc07bd8ac/lib/curve4_div.js 66 | function point_d(x, y) { 67 | return [x, y]; 68 | } 69 | 70 | function calc_sq_distance(x1, y1, x2, y2) { 71 | var dx = x2 - x1; 72 | var dy = y2 - y1; 73 | return dx * dx + dy * dy; 74 | } 75 | function Curve4Div() {} 76 | 77 | Curve4Div.curve_collinearity_epsilon = 1e-30; 78 | Curve4Div.curve_angle_tolerance_epsilon = 0.01; 79 | Curve4Div.curve_recursion_limit = 32; 80 | 81 | Curve4Div.prototype.approximation_scale = 1.0; 82 | Curve4Div.prototype.angle_tolerance = 0.0; 83 | Curve4Div.prototype.cusp_limit = 0.0; 84 | 85 | 86 | Curve4Div.prototype.init = function(x1, y1, x2, y2, x3, y3, x4, y4) { 87 | this.points = []; 88 | this.distance_tolerance_square = 0.5 / this.approximation_scale; 89 | this.distance_tolerance_square *= this.distance_tolerance_square; 90 | this.bezier(x1, y1, x2, y2, x3, y3, x4, y4); 91 | }; 92 | 93 | Curve4Div.prototype.bezier = function(x1, y1, x2, y2, x3, y3, x4, y4) { 94 | this.points.push(point_d(x1, y1)); 95 | this.recursive_bezier(x1, y1, x2, y2, x3, y3, x4, y4, 0); 96 | this.points.push(point_d(x4, y4)); 97 | }; 98 | 99 | Curve4Div.prototype.recursive_bezier = function(x1, y1, x2, y2, x3, y3, x4, y4, level) { 100 | if (level > Curve4Div.curve_recursion_limit) { 101 | return; 102 | } 103 | 104 | // Calculate all the mid-points of the line segments 105 | var x12 = (x1 + x2) / 2; 106 | var y12 = (y1 + y2) / 2; 107 | var x23 = (x2 + x3) / 2; 108 | var y23 = (y2 + y3) / 2; 109 | var x34 = (x3 + x4) / 2; 110 | var y34 = (y3 + y4) / 2; 111 | var x123 = (x12 + x23) / 2; 112 | var y123 = (y12 + y23) / 2; 113 | var x234 = (x23 + x34) / 2; 114 | var y234 = (y23 + y34) / 2; 115 | var x1234 = (x123 + x234) / 2; 116 | var y1234 = (y123 + y234) / 2; 117 | 118 | // Try to approximate the full cubic curve by a single straight line 119 | var dx = x4 - x1; 120 | var dy = y4 - y1; 121 | 122 | var d2 = Math.abs(((x2 - x4) * dy - (y2 - y4) * dx)); 123 | var d3 = Math.abs(((x3 - x4) * dy - (y3 - y4) * dx)); 124 | var da1, da2, k; 125 | 126 | switch ((Math.floor(d2 > Curve4Div.curve_collinearity_epsilon) << 1) + 127 | Math.floor(d3 > Curve4Div.curve_collinearity_epsilon)) { 128 | case 0: 129 | // All collinear OR p1==p4 130 | k = dx * dx + dy * dy; 131 | if (k === 0) { 132 | d2 = calc_sq_distance(x1, y1, x2, y2); 133 | d3 = calc_sq_distance(x4, y4, x3, y3); 134 | } else { 135 | k = 1 / k; 136 | da1 = x2 - x1; 137 | da2 = y2 - y1; 138 | d2 = k * (da1 * dx + da2 * dy); 139 | da1 = x3 - x1; 140 | da2 = y3 - y1; 141 | d3 = k * (da1 * dx + da2 * dy); 142 | if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1) { 143 | // Simple collinear case, 1---2---3---4 144 | // We can leave just two endpoints 145 | return; 146 | } 147 | if (d2 <= 0) { 148 | d2 = calc_sq_distance(x2, y2, x1, y1); 149 | } else if (d2 >= 1) { 150 | d2 = calc_sq_distance(x2, y2, x4, y4); 151 | } else { 152 | d2 = calc_sq_distance(x2, y2, x1 + d2 * dx, y1 + d2 * dy); 153 | } 154 | 155 | if (d3 <= 0) { 156 | d3 = calc_sq_distance(x3, y3, x1, y1); 157 | } else if (d3 >= 1) { 158 | d3 = calc_sq_distance(x3, y3, x4, y4); 159 | } else { 160 | d3 = calc_sq_distance(x3, y3, x1 + d3 * dx, y1 + d3 * dy); 161 | } 162 | } 163 | 164 | if (d2 > d3) { 165 | if (d2 < this.distance_tolerance_square) { 166 | this.points.push(point_d(x2, y2)); 167 | return; 168 | } 169 | } else { 170 | if (d3 < this.distance_tolerance_square) { 171 | this.points.push(point_d(x3, y3)); 172 | return; 173 | } 174 | } 175 | break; 176 | 177 | case 1: 178 | // p1,p2,p4 are collinear, p3 is significant 179 | if (d3 * d3 <= this.distance_tolerance_square * (dx * dx + dy * dy)) { 180 | if (this.angle_tolerance < Curve4Div.curve_angle_tolerance_epsilon) { 181 | this.points.push(point_d(x23, y23)); 182 | return; 183 | } 184 | 185 | // Angle Condition 186 | da1 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - Math.atan2(y3 - y2, x3 - x2)); 187 | if (da1 >= Math.PI) da1 = 2 * Math.PI - da1; 188 | 189 | if (da1 < this.angle_tolerance) { 190 | this.points.push(point_d(x2, y2)); 191 | this.points.push(point_d(x3, y3)); 192 | return; 193 | } 194 | 195 | if (this.cusp_limit !== 0.0) { 196 | if (da1 > this.cusp_limit) { 197 | this.points.push(point_d(x3, y3)); 198 | return; 199 | } 200 | } 201 | } 202 | break; 203 | 204 | case 2: 205 | // p1,p3,p4 are collinear, p2 is significant 206 | if (d2 * d2 <= this.distance_tolerance_square * (dx * dx + dy * dy)) { 207 | if (this.angle_tolerance < Curve4Div.curve_angle_tolerance_epsilon) { 208 | this.points.push(point_d(x23, y23)); 209 | return; 210 | } 211 | 212 | // Angle Condition 213 | da1 = Math.abs(Math.atan2(y3 - y2, x3 - x2) - Math.atan2(y2 - y1, x2 - x1)); 214 | if (da1 >= Math.PI) da1 = 2 * Math.PI - da1; 215 | 216 | if (da1 < this.angle_tolerance) { 217 | this.points.push(point_d(x2, y2)); 218 | this.points.push(point_d(x3, y3)); 219 | return; 220 | } 221 | 222 | if (this.cusp_limit !== 0.0) { 223 | if (da1 > this.cusp_limit) { 224 | this.points.push(point_d(x2, y2)); 225 | return; 226 | } 227 | } 228 | } 229 | break; 230 | 231 | case 3: 232 | // Regular case 233 | if ((d2 + d3) * (d2 + d3) <= this.distance_tolerance_square * (dx * dx + dy * dy)) { 234 | // If the curvature doesn't exceed the distance_tolerance value 235 | // we tend to finish subdivisions. 236 | if (this.angle_tolerance < Curve4Div.curve_angle_tolerance_epsilon) { 237 | this.points.push(point_d(x23, y23)); 238 | return; 239 | } 240 | 241 | // Angle & Cusp Condition 242 | k = Math.atan2(y3 - y2, x3 - x2); 243 | da1 = Math.abs(k - Math.atan2(y2 - y1, x2 - x1)); 244 | da2 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - k); 245 | if (da1 >= Math.PI) da1 = 2 * Math.PI - da1; 246 | if (da2 >= Math.PI) da2 = 2 * Math.PI - da2; 247 | 248 | if (da1 + da2 < this.angle_tolerance) { 249 | // Finally we can stop the recursion 250 | this.points.push(point_d(x23, y23)); 251 | return; 252 | } 253 | 254 | if (this.cusp_limit !== 0.0) { 255 | if (da1 > this.cusp_limit) { 256 | this.points.push(point_d(x2, y2)); 257 | return; 258 | } 259 | 260 | if (da2 > this.cusp_limit) { 261 | this.points.push(point_d(x3, y3)); 262 | return; 263 | } 264 | } 265 | } 266 | break; 267 | } 268 | 269 | // Continue subdivision 270 | this.recursive_bezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1); 271 | this.recursive_bezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1); 272 | }; 273 | 274 | function addPoint(array, point, curveMode, hasCurveFrom, curveFrom, hasCurveTo, curveTo) { 275 | var entry = {}; 276 | entry.point = point; 277 | if (typeof curveMode != 'undefined') { 278 | entry.curveMode = curveMode; 279 | // if (hasCurveTo) { 280 | entry.curveTo = curveTo; 281 | // } 282 | // if (hasCurveFrom) { 283 | entry.curveFrom = curveFrom; 284 | // } 285 | 286 | 287 | } 288 | array.push(entry) 289 | } 290 | 291 | // Facebook 292 | var facebook = []; 293 | addPoint(facebook,[0.666667, 0.234115], 4, 1, [0.666667, 0.234115], 0, [0.666667, 0.234115]); 294 | addPoint(facebook,[0.825521, 0.166667], 4, 0, [0.825521, 0.166667], 1, [0.686458, 0.166667]); 295 | addPoint(facebook,[1.000000, 0.166667], 1, 0, [1.000000, 0.166667], 0, [1.000000, 0.166667]); 296 | addPoint(facebook,[1.000000, 0.000000], 1, 0, [1.000000, 0.000000], 0, [1.000000, 0.000000]); 297 | addPoint(facebook,[0.708854, 0.000000], 4, 1, [0.352083, 0.000000], 0, [0.708854, 0.000000]); 298 | addPoint(facebook,[0.234375, 0.222135], 4, 0, [0.234375, 0.081771], 1, [0.234375, 0.081771]); 299 | addPoint(facebook,[0.234375, 0.333333], 1, 0, [0.234375, 0.333333], 0, [0.234375, 0.333333]); 300 | addPoint(facebook,[0.000000, 0.333333], 1, 0, [0.000000, 0.333333], 0, [0.000000, 0.333333]); 301 | addPoint(facebook,[0.000000, 0.500000], 1, 0, [0.000000, 0.500000], 0, [0.000000, 0.500000]); 302 | addPoint(facebook,[0.234375, 0.500000], 1, 0, [0.234375, 0.500000], 0, [0.234375, 0.500000]); 303 | addPoint(facebook,[0.234375, 1.000000], 1, 0, [0.234375, 1.000000], 0, [0.234375, 1.000000]); 304 | addPoint(facebook,[0.666667, 1.000000], 1, 0, [0.666667, 1.000000], 0, [0.666667, 1.000000]); 305 | addPoint(facebook,[0.666667, 0.500000], 1, 0, [0.666667, 0.500000], 0, [0.666667, 0.500000]); 306 | addPoint(facebook,[0.960417, 0.500000], 1, 0, [0.960417, 0.500000], 0, [0.960417, 0.500000]); 307 | addPoint(facebook,[1.000000, 0.333333], 1, 0, [1.000000, 0.333333], 0, [1.000000, 0.333333]); 308 | addPoint(facebook,[0.666667, 0.333333], 1, 0, [0.666667, 0.333333], 0, [0.666667, 0.333333]); 309 | 310 | function extrudeBezierPath(pathEntries) { 311 | var previousEntry = null; 312 | var nextPreviousEntry = null; 313 | var newPoints = []; 314 | for (var key in pathEntries) { 315 | if (pathEntries.hasOwnProperty(key) == false) { 316 | continue; 317 | } 318 | if (nextPreviousEntry) { 319 | previousEntry = nextPreviousEntry; 320 | nextPreviousEntry = null; 321 | } 322 | 323 | var entry = pathEntries[key] 324 | nextPreviousEntry = entry; 325 | var point = entry.point 326 | var pointX = point[0]; 327 | var pointY = point[1]; 328 | if (!previousEntry) { 329 | newPoints.push(point); 330 | continue; 331 | } 332 | if (typeof entry.curveMode == 'undefined') { 333 | newPoints.push(point); 334 | continue; 335 | } 336 | if (entry.curveMode == 1) { 337 | newPoints.push(point); 338 | continue; 339 | } 340 | 341 | var previousX = previousEntry.point[0]; 342 | var previousY = previousEntry.point[1]; 343 | var controlPoint1x = previousEntry.curveFrom[0]; 344 | var controlPoint1y = previousEntry.curveFrom[1]; 345 | var controlPoint2x = entry.curveTo[0]; 346 | var controlPoint2y = entry.curveTo[1]; 347 | 348 | if (entry.curveMode == 1) { 349 | controlPoint2x = pointX; 350 | controlPoint2y = pointY; 351 | } 352 | 353 | if (previousEntry.curveMode == 1) { 354 | controlPoint1x = previousX; 355 | controlPoint1y = previousY; 356 | } 357 | 358 | var curve4 = new Curve4Div(); 359 | curve4.approximation_scale = 2; 360 | curve4.init(previousX, previousY, controlPoint1x, controlPoint1y, controlPoint2x, controlPoint2y, pointX, pointY); 361 | var extrudedPoints = curve4.points; 362 | for (var extrudedKey in extrudedPoints) { 363 | if (extrudedPoints.hasOwnProperty(extrudedKey) == false) { 364 | continue; 365 | } 366 | var extrudedPoint = extrudedPoints[extrudedKey]; 367 | newPoints.push(extrudedPoint); 368 | } 369 | } 370 | 371 | return newPoints; 372 | } 373 | 374 | var extrudedFacebook = extrudeBezierPath(facebook); 375 | var facebookPolygon = []; 376 | for (var extrudedKey in extrudedFacebook) { 377 | if (extrudedFacebook.hasOwnProperty(extrudedKey) == false) { 378 | continue; 379 | } 380 | var point = extrudedFacebook[extrudedKey]; 381 | facebookPolygon.push({x: point[0], y: point[1]}); 382 | 383 | // console.log("addPoint(extrudedFacebook, [" + point +"]);"); 384 | } 385 | 386 | console.log(facebookPolygon); 387 | console.log("centroid:", geometry.poly_centroid(facebookPolygon)); 388 | 389 | //var curve4 = new Curve4Div(); 390 | //curve4.approximation_scale = 2; 391 | //curve4.init(prev[0], prev[1], segment.x1, segment.y1, segment.x2, segment.y2, segment.x, segment.y); 392 | //console.log(curve4.points); -------------------------------------------------------------------------------- /Scripts/Classes/Xcode.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | def syscall(*cmd) 3 | begin 4 | # puts "Syscall: #{cmd}" 5 | stdout, stderr, status = Open3.capture3(*cmd) 6 | status.success? && stdout.slice!(0..-(1 + $/.size)) # strip trailing eol 7 | rescue 8 | puts "Error with command: #{cmd}" 9 | end 10 | end 11 | 12 | 13 | class XcodeBuild 14 | attr_reader :buildFolder 15 | 16 | def initialize(projectDirectory:nil, target:nil, configuration:"Release", sdk:"iphoneos", buildFolder:"Release") 17 | @projectDirectory = projectDirectory 18 | @target = target 19 | @configuration = configuration 20 | @sdk = sdk 21 | @buildFolder = buildFolder 22 | 23 | if projectDirectory == nil || target == nil 24 | puts "Error: Invalid projectDirectory or target passed to XcodeBuild" 25 | exit 26 | end 27 | end 28 | 29 | def build() 30 | Dir.chdir @projectDirectory do 31 | xcPrettyPath = syscall "which xcpretty" 32 | if xcPrettyPath 33 | useXCPRetty = true 34 | else 35 | useXCPRetty = false 36 | end 37 | 38 | puts "Building target #{@target}, configuration: #{@configuration}, sdk: #{@sdk} directory: #{@projectDirectory}, build folder:#{@buildFolder}" 39 | 40 | command = [ 41 | "xcodebuild", 42 | "-target \"#{@target}\"", 43 | "-configuration", @configuration, 44 | "-sdk", @sdk, 45 | "build", 46 | "CONFIGURATION_BUILD_DIR=\"#{@buildFolder}\"", 47 | "CONFIGURATION_TEMP_DIR=\"#{@buildFolder}/temp\"", 48 | "BUILD_DIR=\"#{@buildFolder}\"", 49 | "PROJECT_TEMP_DIR=\"#{@buildFolder}/tempProject\"", 50 | "OBJROOT=\"#{@buildFolder}/Build\"" 51 | ] 52 | 53 | if useXCPRetty 54 | # add to begining 55 | command.unshift "set -o pipefail && " 56 | 57 | command.push "| xcpretty -c -s" 58 | end 59 | 60 | command = command.join " " 61 | 62 | puts command 63 | 64 | 65 | 66 | # strip coloring for code runner 67 | if useXCPRetty && ENV['TERM_PROGRAM'] == "CodeRunner" 68 | command = command + " --no-color" 69 | end 70 | system command 71 | exitstatus = $?.exitstatus 72 | if exitstatus == 0 73 | return true 74 | else 75 | return false 76 | end 77 | end # project directory 78 | 79 | 80 | end 81 | 82 | def buildSetting(settingName) 83 | if !@buildSettings 84 | retrieveProperties 85 | end 86 | 87 | if !@buildSettings 88 | puts "Error: Couldn't read build settings for #{@target}" 89 | exit 90 | end 91 | 92 | if !@buildSettings[settingName] 93 | puts "Error: Couldn't get build setting #{settingName}" 94 | exit 95 | end 96 | 97 | return @buildSettings[settingName] 98 | end 99 | 100 | def retrieveProperties() 101 | Dir.chdir @projectDirectory do 102 | command = [ 103 | "xcodebuild", 104 | "-target", @target, 105 | "-configuration", @configuration, 106 | "-sdk", @sdk, 107 | "build", 108 | "CONFIGURATION_BUILD_DIR=\"#{@buildFolder}\"", 109 | "OBJROOT=\"#{@buildFolder}/Build\"", 110 | "-showBuildSettings"].join " " 111 | 112 | xcodeRawBuildSettings = syscall command 113 | 114 | if !xcodeRawBuildSettings 115 | puts "Error: Couldn't get settings for #{@target}" 116 | exit; 117 | end 118 | 119 | # get xcode variables 120 | # taken from https://gist.github.com/Cocoanetics/6765089 121 | xcodeBuildSettings = Hash.new 122 | # pattern for each line 123 | linePattern = Regexp.new(/^\s*(.*?)\s=\s(.*)$/) 124 | # extract the variables 125 | xcodeRawBuildSettings.each_line do |line| 126 | match = linePattern.match(line) 127 | #store found variable in hash 128 | if (match) 129 | xcodeBuildSettings[match[1]] = match[2] 130 | end 131 | end 132 | @buildSettings = xcodeBuildSettings 133 | end # project directory 134 | 135 | return @buildSettings 136 | end # retrieveProperties 137 | 138 | def executableOutputPath() 139 | # get executable path 140 | executablePath = buildSetting 'EXECUTABLE_PATH' 141 | 142 | if !executablePath 143 | puts "Error: Couldn't get variable EXECUTABLE_PATH for target #{@target}" 144 | exit 1 145 | end 146 | 147 | outputPath = "#{@buildFolder}/#{executablePath}" 148 | outputPath = File.expand_path(outputPath) 149 | 150 | if File.exists?(outputPath) == false 151 | puts "Error: Build binary missing from #{outputPath}!" 152 | exit 1 153 | end 154 | 155 | return outputPath 156 | end 157 | 158 | # signs executable file, returns path of executable file 159 | def signExecutableOutput(entitlementsPath = nil) 160 | executablePath = executableOutputPath 161 | # sign with entitlements 162 | if signBinary(executablePath, entitlementsPath) == false 163 | puts "failed to sign binary @ #{stagingBinary}" 164 | exit 1 165 | end 166 | 167 | return executablePath 168 | end 169 | 170 | end # XcodeBuild 171 | 172 | module Xcode 173 | def self.build(target:nil, configuration:"Release", sdk:"iphoneos", buildFolder:"Release") 174 | xcPrettyPath = syscall "which xcpretty" 175 | if xcPrettyPath 176 | useXCPRetty = true 177 | else 178 | useXCPRetty = false 179 | end 180 | 181 | command = [ 182 | "xcodebuild", 183 | "-target=\"#{target}\"", 184 | "-configuration", configuration, 185 | "-sdk", sdk, 186 | "build", 187 | "CONFIGURATION_BUILD_DIR=\"#{buildFolder}\"", 188 | "CONFIGURATION_TEMP_DIR=\"#{buildFolder}/temp\"", 189 | "BUILD_DIR=\"#{buildFolder}\"", 190 | "PROJECT_TEMP_DIR=\"#{buildFolder}/tempProject\"", 191 | "OBJROOT=\"#{buildFolder}/Build\"" 192 | ] 193 | 194 | if useXCPRetty 195 | # add to begining 196 | command.unshift "set -o pipefail && " 197 | 198 | command.push "| xcpretty -c -s" 199 | end 200 | 201 | command = command.join " " 202 | 203 | # strip coloring for code runner 204 | if useXCPRetty && ENV['TERM_PROGRAM'] == "CodeRunner" 205 | command = command + " --no-color" 206 | end 207 | system command 208 | exitstatus = $?.exitstatus 209 | if exitstatus == 0 210 | return true 211 | else 212 | return false 213 | end 214 | end 215 | 216 | def self.getVariables(target:nil, configuration:"Release", sdk:"iphoneos", buildFolder:"Release") 217 | xcodeRawBuildSettings = syscall "xcodebuild", 218 | "-target \"#{target}\"", 219 | "-configuration", configuration, 220 | "-sdk", sdk, 221 | "build", 222 | "CONFIGURATION_BUILD_DIR=#{buildFolder}", 223 | "OBJROOT=#{buildFolder}/Build", 224 | "-showBuildSettings" 225 | 226 | if !xcodeRawBuildSettings 227 | puts "Error, couldn't get settings!" 228 | end 229 | 230 | # get xcode variables 231 | # taken from https://gist.github.com/Cocoanetics/6765089 232 | xcodeBuildSettings = Hash.new 233 | # pattern for each line 234 | linePattern = Regexp.new(/^\s*(.*?)\s=\s(.*)$/) 235 | # extract the variables 236 | xcodeRawBuildSettings.each_line do |line| 237 | match = linePattern.match(line) 238 | #store found variable in hash 239 | if (match) 240 | xcodeBuildSettings[match[1]] = match[2] 241 | end 242 | end 243 | return xcodeBuildSettings 244 | end 245 | end 246 | -------------------------------------------------------------------------------- /Scripts/buildRelease.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | require 'fileutils' 4 | 5 | # require XcodeBuild class 6 | classesDirectory = File.expand_path(File.dirname(__FILE__) + "/Classes") 7 | xcodeClassPath = File.expand_path("#{classesDirectory}/Xcode") 8 | require xcodeClassPath 9 | 10 | rootDirectory = File.expand_path(File.dirname(__FILE__) + "/../") 11 | projectDirectory = rootDirectory + "/CSSketch Helper" 12 | releaseDirectory = "#{rootDirectory}/Release" 13 | 14 | releaseBuild = XcodeBuild.new(projectDirectory:projectDirectory, 15 | target:"CSSketch Install", 16 | configuration:"Release", 17 | sdk:"macosx", 18 | buildFolder:releaseDirectory) 19 | 20 | # build device 21 | if releaseBuild.build == false 22 | exit 1 23 | end 24 | 25 | Dir.chdir releaseDirectory do 26 | system "zip -r CSSketch_Install.zip \"./CSSketch Install.app/\"" 27 | end 28 | 29 | system "open \"#{releaseDirectory}\"" 30 | 31 | puts "Release built" -------------------------------------------------------------------------------- /Scripts/setupSymlinks.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # Setup Symlinks for easy development debugging 3 | require 'fileutils' 4 | require 'open3' 5 | 6 | projectDirectory = File.expand_path(File.dirname(__FILE__) + "/../") 7 | plugin = projectDirectory + "/CSSketch.sketchplugin" 8 | frameworkPlugin = projectDirectory + "/External/SketchKit/SketchKit.sketchplugin" 9 | 10 | pluginsFolder = File.expand_path("~/Library/Application Support/com.bohemiancoding.sketch3/Plugins") 11 | 12 | pluginDestination = pluginsFolder + "/CSSketch.sketchplugin" 13 | if File.exists?(pluginDestination) == true 14 | FileUtils.rm_r(pluginDestination) 15 | end 16 | 17 | if File.symlink(plugin, pluginDestination) == false 18 | puts "Failed to make plugin symlink!" 19 | exit 1 20 | end 21 | 22 | puts "Made plugin symlink." -------------------------------------------------------------------------------- /screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnCoates/CSSketch/1adc9fb0dac35e836702b597e604b3848268f5cc/screencast.gif --------------------------------------------------------------------------------