├── Export to Framer.sketchplugin ├── LICENSE.md ├── README.md ├── examples ├── GoogleNow.psd ├── GoogleNow │ ├── app.js │ ├── desktop │ │ ├── iphone5.js │ │ └── iphone5.png │ ├── framer │ │ ├── framer.js │ │ ├── framerps.js │ │ ├── framerps.log │ │ └── views.GoogleNow.js │ ├── images │ │ ├── Actions.png │ │ ├── Background.png │ │ ├── Context.png │ │ ├── Logo.png │ │ ├── MovieCard.png │ │ ├── Searchbox.png │ │ ├── StatusBar.png │ │ ├── TimeCard.png │ │ ├── Top.png │ │ ├── TrafficCard.png │ │ ├── bars.png │ │ └── carrier.png │ ├── index.html │ └── style.css └── GoogleNow_exported_by_Framer │ ├── app.js │ ├── framer │ ├── framer.js │ ├── framerps.js │ ├── framerps.log │ └── views.GoogleNow.js │ ├── images │ ├── Actions.png │ ├── Background.png │ ├── Context.png │ ├── Logo.png │ ├── MovieCard.png │ ├── Searchbox.png │ ├── StatusBar.png │ ├── TimeCard.png │ ├── Top.png │ ├── TrafficCard.png │ ├── bars.png │ └── carrier.png │ └── index.html ├── sandbox.js ├── sketch-framer-config.js └── sketch-framer-logo.png /Export to Framer.sketchplugin: -------------------------------------------------------------------------------- 1 | // Sketch Framer (ctrl alt command f) 2 | 3 | /* Import the variables and complain if they are undefined */ 4 | #import 'sketch-framer-config.js' 5 | #import 'sandbox.js' 6 | 7 | 8 | /* Configuration */ 9 | var framerjs_url = "http://shortcuts-for-framer.s3.amazonaws.com/framer.js"; 10 | 11 | function major_version() { 12 | return [NSApp applicationVersion].substr(0,1); 13 | } 14 | 15 | function should_become_view(layer) { 16 | return is_group(layer) || [layer name].slice(-1) == '+'; 17 | } 18 | function is_group(layer) { 19 | return [layer isMemberOfClass:[MSLayerGroup class]] || [layer isMemberOfClass:[MSArtboardGroup class]] 20 | } 21 | function should_ignore_layer(layer) { 22 | return [layer name].slice(-1) == '-' || [layer className] == "MSPage"; 23 | } 24 | function sanitize_filename(name){ 25 | return name.replace(/(\s|:|\/)/g ,"_").replace(/__/g,"_").replace("*","").replace("+","").replace("@@hidden",""); 26 | } 27 | function has_art(layer) { 28 | // return true; 29 | 30 | if(is_group(layer) && !should_flatten_layer(layer)) { 31 | var has_art = false; 32 | 33 | var sublayers = [layer layers]; 34 | for (var sub=([sublayers count] - 1); sub >= 0; sub--) { 35 | var sublayer = [sublayers objectAtIndex:sub] 36 | if(!should_ignore_layer(sublayer) && !should_become_view(sublayer)) { 37 | has_art = true; 38 | } 39 | } 40 | return has_art; 41 | 42 | } else { 43 | return true; 44 | } 45 | } 46 | function export_layer(layer, depth) { 47 | log_depth("Exporting <" + [layer name] + ">", depth); 48 | var filename = images_folder + "/" + sanitize_filename([layer name]) + ".png"; 49 | 50 | if(show_errors && [layer isMemberOfClass:[MSArtboardGroup class]] && major_version == 3) { 51 | [[NSApplication sharedApplication] displayDialog:"Sketch Framer currently doesn't support artboards with Sketch 3. Please remove the artboard and have layers directly on the canvas. You can group everything at the top level and move it to (0,0) for intended results." withTitle:"Sketch Framer known bugs"] 52 | } 53 | 54 | // copy off-screen 55 | var layer_copy = [layer duplicate]; 56 | var frame = [layer_copy frame]; 57 | [frame setX:-100000]; 58 | [frame setY:-100000]; 59 | 60 | var artnames = []; 61 | var has_art = false; 62 | 63 | // remove subgroups and only leave shapes 64 | if(is_group(layer) && !should_flatten_layer(layer)) { 65 | // log([layer name] + " is a group and i'm trying to extract its shapes") 66 | var sublayers = [layer_copy layers]; 67 | for (var sub=([sublayers count] - 1); sub >= 0; sub--) { 68 | var sublayer = [sublayers objectAtIndex:sub] 69 | if(!should_ignore_layer(sublayer)) { 70 | // log(" " + [layer name] + "." + [sublayer name] + " (" + [sublayer className] + ")"); 71 | if(should_become_view(sublayer)) { 72 | log(" REMOVING " + [sublayer name]); 73 | [sublayer removeFromParent]; 74 | } else { 75 | log(" KEEPING " + [sublayer name]); 76 | artnames.push([sublayer name]) 77 | has_art = true 78 | } 79 | } 80 | 81 | } 82 | } else { 83 | // not a group, single art 84 | has_art = true; 85 | } 86 | 87 | if(!has_art) { 88 | log_depth("Exporting <" + [layer name] + ">, no image", depth); 89 | } else { 90 | log_depth("Exporting <" + [layer name] + "> including children art (" + artnames.join(", ") + ")", depth); 91 | 92 | // export 93 | var rect = [layer_copy rectByAccountingForStyleSize:[[layer_copy absoluteRect] rect]], 94 | slice = [MSSlice sliceWithRect:rect scale:2]; 95 | 96 | if (in_sandbox()) { 97 | sandboxAccess.accessFilePath_withBlock_persistPermission(target_folder, function(){ 98 | [doc saveArtboardOrSlice:slice toFile:filename]; 99 | }, true) 100 | } else { 101 | [doc saveArtboardOrSlice:slice toFile:filename]; 102 | } 103 | } 104 | 105 | // remove it 106 | [layer_copy removeFromParent]; 107 | } 108 | function should_flatten_layer(layer) { 109 | var name = [layer name]; 110 | if(name.slice(-1) == "*") { 111 | return true; 112 | } else { 113 | return false; 114 | } 115 | } 116 | function log_depth(message, depth) { 117 | var padding = ""; 118 | for(var i=0; i" 120 | } 121 | log(padding + " " + message); 122 | } 123 | function process_layer(layer,metadata_container,depth) { 124 | 125 | if (should_ignore_layer(layer)) { 126 | log_depth("Ignoring <" + [layer name] + "> of type <" + [layer className] + ">", depth); 127 | return; 128 | } 129 | 130 | if(should_become_view(layer)){ 131 | log_depth("Processing <" + [layer name] + "> of type <" + [layer className] + ">", depth); 132 | // If layer is a group, do: 133 | 134 | // temporarily show it 135 | if (![layer isVisible]) { 136 | [layer setName:[layer name] + "@@hidden"] 137 | } 138 | [layer setIsVisible:true]; 139 | 140 | // - Get layer data 141 | var layer_data = extract_metadata_from(layer); 142 | layer_data.children = []; 143 | 144 | // - Export image if layer has no subgroups 145 | if (!should_flatten_layer(layer) && is_group(layer)) { 146 | var sublayers = [layer layers]; 147 | // Apparently, Sketch returns child layers in reverse order (back to front), 148 | // so we'll loop backwards through them: 149 | for (var sub=([sublayers count] - 1); sub >= 0; sub--) { 150 | var current = [sublayers objectAtIndex:sub] 151 | process_layer(current,layer_data.children,depth+1); 152 | } 153 | } 154 | export_layer(layer, depth); 155 | metadata_container.push(layer_data); 156 | 157 | if ([layer name].indexOf("@@hidden") != -1) { 158 | // If it was hidden, make it hidden again and fix the name 159 | var _name = [layer name].replace("@@hidden", ""); 160 | [layer setIsVisible:false]; 161 | [layer setName:_name]; 162 | } 163 | } 164 | 165 | if ([layer name].indexOf("@@mask") != -1) { 166 | var _name = [layer name].replace("@@mask", ""); 167 | log("Re-enabling mask " + _name); 168 | [layer setHasClippingMask:true]; 169 | [layer setName:_name]; 170 | } 171 | } 172 | function save_file_from_string(filename,the_string) { 173 | var path = [@"" stringByAppendingString:filename], 174 | str = [@"" stringByAppendingString:the_string]; 175 | 176 | if (in_sandbox()) { 177 | sandboxAccess.accessFilePath_withBlock_persistPermission(filename, function(){ 178 | [str writeToFile:path atomically:true encoding:NSUTF8StringEncoding error:null]; 179 | }, true) 180 | } else { 181 | [str writeToFile:path atomically:true encoding:NSUTF8StringEncoding error:null]; 182 | } 183 | } 184 | function get_next_id() { 185 | return ++object_id; 186 | } 187 | function mask_bounds(layer) { 188 | var sublayers = [layer layers]; 189 | var effective_mask = null; 190 | 191 | for (var sub=0; sub < [sublayers count]; sub++) { 192 | var current = [sublayers objectAtIndex:sub]; 193 | if(current && [current hasClippingMask]) { 194 | // If a native mask is detected, rename it and disable it (for now) so we can export its contents 195 | var _name = [current name] + "@@mask"; 196 | [current setName:_name]; 197 | [current setHasClippingMask:false]; 198 | log("Disabling mask " + [current name]); 199 | 200 | if (!effective_mask) { 201 | // Only the bottom-most one will be effective 202 | log("Effective mask " + _name) 203 | effective_mask = current 204 | } 205 | } 206 | } 207 | 208 | if (effective_mask) { 209 | return metadata_for(effective_mask); 210 | } else { 211 | return null; 212 | } 213 | } 214 | function calculate_real_position_for(layer) { 215 | 216 | var gkrect = [GKRect rectWithRect:[layer rectByAccountingForStyleSize:[[layer absoluteRect] rect]]], 217 | absrect = [layer absoluteRect]; 218 | 219 | var rulerDeltaX = [absrect rulerX] - [absrect x], 220 | rulerDeltaY = [absrect rulerY] - [absrect y], 221 | GKRectRulerX = [gkrect x] + rulerDeltaX, 222 | GKRectRulerY = [gkrect y] + rulerDeltaY; 223 | 224 | return { 225 | x: Math.round(GKRectRulerX), 226 | y: Math.round(GKRectRulerY) 227 | } 228 | } 229 | function metadata_for(layer) { 230 | var frame = [layer frame], 231 | gkrect = [GKRect rectWithRect:[layer rectByAccountingForStyleSize:[[layer absoluteRect] rect]]], 232 | absrect = [layer absoluteRect], 233 | position = calculate_real_position_for(layer), 234 | x,y,w,h,r 235 | 236 | if ([layer className] == "MSArtboardGroup") { 237 | // FIXME: This is probably still wrong, test with different artboard positions 238 | x = [[layer absoluteRect] rulerX], 239 | y = [[layer absoluteRect] rulerY], 240 | // x = position.x, 241 | // y = position.y, 242 | w = [frame width], 243 | h = [frame height], 244 | r = { 245 | x: x, 246 | y: y, 247 | width: w, 248 | height: h 249 | } 250 | } else { 251 | x = position.x, 252 | y = position.y, 253 | w = [gkrect width], 254 | h = [gkrect height], 255 | r = { 256 | x: x, 257 | y: y, 258 | width: w, 259 | height: h 260 | } 261 | } 262 | 263 | log("{ x:"+x+", y:"+y+", width:"+w+", height:"+h+"}") 264 | 265 | return r 266 | } 267 | function extract_metadata_from(layer) { 268 | var maskFrame = mask_bounds(layer); 269 | var layerFrame = metadata_for(layer); 270 | // call maskframe first so it disables the mask, so we can get correct layerframe 271 | 272 | //metadata.id = get_next_id(); // FIXME 273 | 274 | var metadata = { 275 | name: sanitize_filename([layer name]), 276 | layerFrame: layerFrame, 277 | maskFrame: maskFrame 278 | }; 279 | 280 | if(has_art(layer)) { 281 | metadata.image = { 282 | path: "images/" + sanitize_filename([layer name]) + ".png", 283 | frame: layerFrame 284 | }; 285 | metadata.imageType = "png"; 286 | // TODO: Find out how the modification hash is calculated in Framer.app 287 | // metadata.modification = new Date(); 288 | } 289 | 290 | // if it was invisible, mark this in the metadata as well 291 | if ([layer name].indexOf("@@hidden") != -1) { 292 | metadata.visible = false 293 | } 294 | 295 | return metadata; 296 | } 297 | 298 | var document_path = [[doc fileURL] path].split([doc displayName])[0], 299 | document_name = [doc displayName].replace(".sketch",""), 300 | target_folder = document_path + document_name, 301 | images_folder = target_folder + "/images", 302 | framer_folder = target_folder + "/framer", 303 | file_manager = [NSFileManager defaultManager], 304 | object_id = 0 305 | 306 | function main() { 307 | 308 | log('#################################################################################################################################################'); 309 | 310 | // Create folders 311 | if (in_sandbox()) { 312 | sandboxAccess.accessFilePath_withBlock_persistPermission(target_folder, function(){ 313 | [file_manager createDirectoryAtPath:target_folder withIntermediateDirectories:true attributes:nil error:nil]; 314 | [file_manager createDirectoryAtPath:images_folder withIntermediateDirectories:true attributes:nil error:nil]; 315 | [file_manager createDirectoryAtPath:framer_folder withIntermediateDirectories:true attributes:nil error:nil]; 316 | }, true) 317 | } else { 318 | [file_manager createDirectoryAtPath:target_folder withIntermediateDirectories:true attributes:nil error:nil]; 319 | [file_manager createDirectoryAtPath:images_folder withIntermediateDirectories:true attributes:nil error:nil]; 320 | [file_manager createDirectoryAtPath:framer_folder withIntermediateDirectories:true attributes:nil error:nil]; 321 | } 322 | 323 | // Get JS files from Github 324 | var task = [[NSTask alloc] init], 325 | argsArray = [NSArray arrayWithObjects:"-O", framerjs_url, nil]; 326 | [task setCurrentDirectoryPath:framer_folder]; 327 | [task setLaunchPath:"/usr/bin/curl"]; 328 | [task setArguments:argsArray]; 329 | [task launch]; 330 | 331 | // Get library files if one if configured and isn't yet downloaded 332 | if(FramerLibraryUrl) { 333 | if(![file_manager fileExistsAtPath:(framer_folder + "/" + FramerLibraryFileName)]) { 334 | var task2 = [[NSTask alloc] init], 335 | argsArray = [NSArray arrayWithObjects:"-O", FramerLibraryUrl, nil]; 336 | [task2 setCurrentDirectoryPath:framer_folder]; 337 | [task2 setLaunchPath:"/usr/bin/curl"]; 338 | [task2 setArguments:argsArray]; 339 | [task2 launch]; 340 | } 341 | } 342 | 343 | // Get all layers in current page 344 | var all_layers = [[doc currentPage] layers], 345 | metadata = []; 346 | 347 | for (var i=0; i < [all_layers count]; i++) { 348 | process_layer([all_layers objectAtIndex:i],metadata, 0); 349 | } 350 | 351 | file_path = framer_folder + "/views." + document_name + ".js"; 352 | 353 | var file_contents = "window.FramerPS = window.FramerPS || {};\nwindow.FramerPS['" + document_name +"'] = " + JSON.stringify(metadata,null,2); 354 | save_file_from_string(file_path,file_contents); 355 | 356 | // Save JS files from templates: 357 | save_file_from_string(framer_folder + "/framerps.js", FramerPSJSContents); 358 | if(![file_manager fileExistsAtPath:(target_folder + "/" + FramerScriptFileName)]) { 359 | save_file_from_string(target_folder + "/" + FramerScriptFileName, FramerScriptFileContents); 360 | } 361 | 362 | // Create HTML and open in default browser if it's the first time we're exporting 363 | if(![file_manager fileExistsAtPath:(target_folder + "/index.html")]) { 364 | save_file_from_string(target_folder + "/index.html", FramerIndexFileContents.replace("{{ views }}",'')); 365 | var open_task = [[NSTask alloc] init], 366 | open_task_args = [NSArray arrayWithObjects:(target_folder + "/index.html"), nil]; 367 | 368 | [open_task setCurrentDirectoryPath:framer_folder]; 369 | [open_task setLaunchPath:"/usr/bin/open"]; 370 | [open_task setArguments:open_task_args]; 371 | [open_task launch]; 372 | } 373 | 374 | log('#################################################################################################################################################'); 375 | 376 | // Display message in toolbar 377 | [doc showMessage: "Sketch Framer: Project exported to “" + target_folder + "”"]; 378 | 379 | } 380 | 381 | /* 382 | 383 | This plugin will export your Sketch document to a Framer project. 384 | 385 | This command REQUIRES at least Sketch Version 2.3 (419), 386 | as it uses an API not available in earlier versions 387 | 388 | - Every layer group will become an ImageView, backed by the pixel and/or shape layers in that layer group. 389 | - Views are accessible by name under the PSD object (yeah, I know...) 390 | - Framer respects the layer hierarchy, so a sub layer group will become a subview. This way you can define your view structure in Photoshop. 391 | - TODO: Layer groups with vector masks will become views with clipping enabled. You can optionally add "scroll" to the layer group name to make it a ScrollView. 392 | - Layer group names should be unique, if they're not Framer will silently rename them on export. 393 | 394 | // TODO: Check for duplicated names in exported files 395 | // TODO: Calculate proper IDs (not sure what they're used for, but hey...) 396 | // TODO: Calculate proper modification hash 397 | // TODO: Embed framer.js on this file, instead of 'curl'ing it from Github 398 | 399 | */ 400 | 401 | main(); 402 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | LICENSE AGREEMENT FOR SKETCH-FRAMER PLUGIN 2 | 3 | Copyright (c) 2013, Ale Muñoz All rights reserved. 4 | 5 | BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | - Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | 13 | - Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This plugin is no longer maintained, since Framer Studio now imports from Sketch natively 2 | 3 | ## The code and the rest of the README are left here for historical reasons 4 | 5 | --- 6 | 7 | # Sketch Framer 8 | 9 | ![](sketch-framer-logo.png?raw=true) 10 | 11 | A plugin to export [Sketch.app](http://www.bohemiancoding.com/sketch) documents into [FramerJS](http://framerjs.com) to make interactive prototypes. 12 | 13 | ## Common questions 14 | * Only works with Sketch Beta right now because the App Store version is sandboxed. [Download Sketch Beta here](http://www.bohemiancoding.com/sketch/beta/). 15 | * Make sure you copy **both** files as specified below. 16 | 17 | 18 | ## Sketch 3 Support 19 | We're hard at work updating the plugin for Sketch 3! You can use the plugin with a few caveats: 20 | 21 | * Artboards aren't supported yet. Please ungroup your artboard and have elements directly on the canvas. We suggest you group everything at top level, and move the parent group to 0,0 position. 22 | * Symbols aren't supported. We suggest making a copy of your file, going to *Manage Symbols* and deleting the symbols, which will effectively detach all the symbol layers from the symbol (but keep them on the canvas). 23 | 24 | ## Installation 25 | 1. Download the repository using [this link](https://github.com/bomberstudios/sketch-framer/archive/master.zip) 26 | 2. Grab these files from the ZIP: `Export to Framer.sketchplugin`, `sandbox.js` and `sketch-framer-config.js` 27 | 3. In Sketch 3, select `Plugins > Reveal Plugins Folder...` from the menu bar, and put those files in this folder. 28 | 29 | This is what your plugins folder should look like after you copy them: 30 | 31 | ![Directory structure](https://cloud.githubusercontent.com/assets/3832/2870262/086f5c10-d2b9-11e3-8e6a-88a5c3d96587.png) 32 | 33 | Then you'll see the plugin in the plugins menu: 34 | 35 | ![Plugins menu](https://f.cloud.github.com/assets/200566/2153606/d9fd17be-9429-11e3-9d15-674f17f9953f.png) 36 | 37 | ## Usage 38 | 1. Create your layered Sketch file, and save it somewhere 39 | 2. Run the plugin from the plugins menu 40 | 3. The framer prototype will be generated in a folder right next to where the Sketch file is saved 41 | 42 | ![Exported files](https://f.cloud.github.com/assets/200566/2153636/3be2cbf4-942a-11e3-9def-01dc19d83324.png) 43 | 44 | ## Tips 45 | * Don't use artboards (Known bug, trying to fix it) 46 | * Group everything in your document in a main "Phone" group, and align it to (0,0) 47 | * Use unique names for each group in your document to avoid conflicts 48 | * Every group in your document will become a Framer view 49 | 50 | ## Special operations 51 | * **Flatten** To have a group flattened so its child groups don't export individually, append `*` to its name. Example: `Card*`. Flattening complex groups will improve performance. 52 | * **Shape/text layers** To export a shape or a text layer as a view, put it in a group, or append `+` to its name. Otherwise they will export as a background image. 53 | * **Ignore** To ignore a layer, append `-` to its name. Example: `Ignored-` 54 | * **Hidden layers** Hidden layers in Sketch will be exported as hidden layers in Framer. To show the layer in framer, try `view.visible = true` 55 | * **Masks** Native masks now work! You can also use `Scroll` in the name of a group that includes a mask to make that group scrollable. 56 | 57 | ## Configuration 58 | You can customize the exported files (index.html, app.js) by tweaking sketch-framer-config.js. The most common thing you might want to do is import a library file and include it in all your projects. 59 | 60 | ## Known bugs 61 | * Very complicated files may cause the plugin to crash, or result in a sluggish Framer mockup. Flatten groups (by appending `*` to their names or using Sketch 3's `Layer > Flatten Selection to Bitmap` command) where you don't need to access the contents individually. 62 | * Only rectangular masks work. If you're masking with an odd shape, try flattening the group (by appending `*` to its name) 63 | * Effects that expand beyond the boundaries of a layer (eg. drop shadows, outside borders) cause minor positioning bugs. Try flattening these layers to bitmaps. 64 | * Avoid background blur, it causes bugs in positioning. 65 | 66 | 67 | ## Help us improve Sketch Framer 68 | 69 | To propose changes, fork the repository and submit a pull request! 70 | 71 | ## Questions? 72 | 73 | Reach out to [@bomberstudios](https://twitter.com/bomberstudios) or [@gem_ray](https://twitter.com/gem_ray) on Twitter! 74 | -------------------------------------------------------------------------------- /examples/GoogleNow.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow.psd -------------------------------------------------------------------------------- /examples/GoogleNow/app.js: -------------------------------------------------------------------------------- 1 | // By Noah Levin www.noahlevin.com 2 | 3 | animateSpeed = "300"; 4 | animateCurveSpeed = "500"; 5 | animateInCurve = "bezier-curve(0.535, 0, 0.135, 1.35)"; 6 | animateOutCurve = animateInCurve; 7 | animateOrigin = "50% 50%"; 8 | homeCardBorder = "1px solid rgba(0,0,0,.2)"; 9 | homeCardShadowSize = "0 1px 2px rgba(0,0,0,.2)"; 10 | homeTrafficScale = ".952"; 11 | homeTrafficY = "960"; 12 | homeMovieScale = ".92"; 13 | homeMovieY = "927"; 14 | homeTimeScale = ".88"; 15 | homeTimeY = "946"; 16 | nowTrafficY = "298"; 17 | nowMovieY = "795"; 18 | nowTimeY = "1380"; 19 | nowCardBorder = "1px solid transparent"; 20 | nowCardShadowSize = "0 2px 1px rgba(0,0,0,.2)"; 21 | 22 | PSD["Content"].y += 40 23 | 24 | gotoNow = function() { 25 | 26 | document.getElementsByTagName('body')[0].className = 'now'; 27 | 28 | PSD["Logo"].animate({ 29 | properties: { 30 | y: 17, 31 | scale: 0.6 32 | }, 33 | curve: animateInCurve, 34 | time: animateCurveSpeed 35 | }); 36 | 37 | PSD["Searchbox"].animate({ 38 | properties: { 39 | y: 165, 40 | scale: 1.05, 41 | height: 73 42 | }, 43 | curve: animateInCurve, 44 | time: animateCurveSpeed 45 | }); 46 | 47 | PSD["TrafficCard"].animate({ 48 | properties: { 49 | y: nowTrafficY, 50 | scale: 1 51 | }, 52 | curve: animateInCurve, 53 | time: animateCurveSpeed 54 | }); 55 | 56 | PSD["MovieCard"].animate({ 57 | properties: { 58 | y: nowMovieY, 59 | scale: 1 60 | }, 61 | curve: animateInCurve, 62 | time: animateCurveSpeed 63 | }); 64 | 65 | PSD["TimeCard"].animate({ 66 | properties: { 67 | y: nowTimeY, 68 | scale: 1 69 | }, 70 | curve: animateInCurve, 71 | time: animateCurveSpeed 72 | }); 73 | 74 | PSD["Context"].animate({ 75 | properties: { 76 | opacity: 1, 77 | y: -20 78 | }, 79 | curve: "ease-out", 80 | time: animateSpeed 81 | }); 82 | 83 | PSD["Actions"].animate({ 84 | properties: { 85 | y: 760, 86 | opacity: 0 87 | }, 88 | curve: "ease-out", 89 | time: animateSpeed 90 | }); 91 | 92 | PSD["Top"].animate({ 93 | properties: { 94 | y: -20, 95 | opacity: 0 96 | }, 97 | curve: "ease-out", 98 | time: animateSpeed 99 | }); 100 | }; 101 | 102 | gotoHome = function() { 103 | 104 | document.getElementsByTagName('body')[0].className = 'home'; 105 | 106 | PSD["Logo"].animate({ 107 | properties: { 108 | y: 301, 109 | scale: 1 110 | }, 111 | curve: animateOutCurve, 112 | time: animateCurveSpeed 113 | }); 114 | 115 | PSD["Searchbox"].animate({ 116 | properties: { 117 | y: 470, 118 | scale: 1, 119 | height: 93 120 | }, 121 | curve: animateOutCurve, 122 | time: animateCurveSpeed 123 | }); 124 | 125 | PSD["TrafficCard"].animate({ 126 | properties: { 127 | y: homeTrafficY, 128 | scale: homeTrafficScale 129 | }, 130 | curve: animateOutCurve, 131 | time: animateCurveSpeed 132 | }); 133 | 134 | PSD["MovieCard"].animate({ 135 | properties: { 136 | y: homeMovieY, 137 | scale: homeMovieScale 138 | }, 139 | curve: animateOutCurve, 140 | time: animateCurveSpeed 141 | }); 142 | 143 | PSD["TimeCard"].animate({ 144 | properties: { 145 | y: homeTimeY, 146 | scale: homeTimeScale 147 | }, 148 | curve: animateOutCurve, 149 | time: animateCurveSpeed 150 | }); 151 | 152 | PSD["Actions"].animate({ 153 | properties: { 154 | y: 820, 155 | opacity: 1 156 | }, 157 | curve: "ease-in", 158 | time: animateSpeed 159 | }); 160 | 161 | PSD["Top"].animate({ 162 | properties: { 163 | y: 22, 164 | opacity: 1 165 | }, 166 | curve: "ease-in", 167 | time: animateSpeed 168 | }); 169 | 170 | PSD["Context"].animate({ 171 | properties: { 172 | opacity: 0, 173 | y: 0 174 | }, 175 | curve: "ease-in", 176 | time: animateSpeed 177 | }); 178 | 179 | }; 180 | 181 | isIphone = function() { 182 | if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPod/i)) { 183 | return true; 184 | } 185 | }; 186 | 187 | toggler = utils.toggle(gotoNow, gotoHome); 188 | 189 | delay = function(ms, func) { 190 | return setTimeout(func, ms); 191 | }; 192 | 193 | gotoHome(); 194 | 195 | pointerType = "click"; 196 | 197 | if (isIphone()) { 198 | pointerType = "touchstart"; 199 | } 200 | 201 | PSD["Content"].on(pointerType, function(e) { 202 | var movePage; 203 | 204 | e.preventDefault(); 205 | movePage = toggler(); 206 | return movePage(); 207 | }); 208 | 209 | -------------------------------------------------------------------------------- /examples/GoogleNow/desktop/iphone5.js: -------------------------------------------------------------------------------- 1 | // iPhone 2 | iphone = new ImageView({ 3 | x:-170, y:-390, 4 | width:764, height:1604 5 | }); 6 | iphone.image = "desktop/iphone5.png"; 7 | iphone.style.backgroundColor = "transparent"; 8 | iphone.scale = 0.50; 9 | 10 | // Screen within iPhone container 11 | screen = new View({ 12 | x: 62, y:276, 13 | width:640, height:1096, 14 | superView:iphone 15 | }); 16 | 17 | // Place content into the screen 18 | PSD["Content"].superView = screen; 19 | 20 | // Text instructions to encourage mobile viewing 21 | textView = new View({ 22 | x: 430, y: 150, 23 | width:250, height:300 24 | }); 25 | 26 | textView.html = "This prototype is best experienced on an iPhone5. Open this page in mobile safari and add to home screen." 27 | textView.style = { 28 | "font-size": "18px", 29 | "line-height": "22px", 30 | "text-align": "left", 31 | "color": "rgba(255,255,255,0.85)", 32 | }; -------------------------------------------------------------------------------- /examples/GoogleNow/desktop/iphone5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/desktop/iphone5.png -------------------------------------------------------------------------------- /examples/GoogleNow/framer/framerps.js: -------------------------------------------------------------------------------- 1 | var loadViews = function() { 2 | 3 | var Views = [] 4 | var ViewsByName = {} 5 | 6 | createView = function(info, superView) { 7 | 8 | // console.log("createView", info.name, "superView: ", superView) 9 | 10 | var viewType, viewFrame 11 | var viewInfo = { 12 | clip: false 13 | } 14 | 15 | if (info.image) { 16 | viewType = ImageView 17 | viewFrame = info.image.frame 18 | viewInfo.image = "images/" + info.name + "." + info.imageType 19 | } 20 | 21 | else { 22 | viewType = View 23 | viewFrame = info.layerFrame 24 | } 25 | 26 | // If this layer group has a mask, we take the mask bounds 27 | // as the frame and clip the layer 28 | if (info.maskFrame) { 29 | viewFrame = info.maskFrame 30 | viewInfo.clip = true 31 | 32 | // If the layer name has "scroll" we make this a scroll view 33 | if (info.name.toLowerCase().indexOf("scroll") != -1) { 34 | viewType = ScrollView 35 | } 36 | 37 | // If the layer name has "paging" we make this a paging view 38 | if (info.name.toLowerCase().indexOf("paging") != -1) { 39 | viewType = ui.PagingView 40 | } 41 | 42 | } 43 | 44 | var view = new viewType(viewInfo) 45 | 46 | view.frame = viewFrame 47 | 48 | // If the view has a contentview (like a scrollview) we add it 49 | // to that one instead. 50 | if (superView && superView.contentView) { 51 | view.superView = superView.contentView 52 | } else { 53 | view.superView = superView 54 | } 55 | 56 | view.name = info.name 57 | view.viewInfo = info 58 | 59 | Views.push(view) 60 | ViewsByName[info.name] = view 61 | 62 | // If the layer name contains draggable we create a draggable for this layer 63 | if (info.name.toLowerCase().indexOf("draggable") != -1) { 64 | view.draggable = new ui.Draggable(view) 65 | } 66 | 67 | for (var i in info.children) { 68 | createView(info.children[info.children.length - 1 - i], view) 69 | } 70 | 71 | } 72 | 73 | // Loop through all the photoshop documents 74 | for (var documentName in FramerPS) { 75 | // Load the layers for this document 76 | for (var layerIndex in FramerPS[documentName]) { 77 | createView(FramerPS[documentName][layerIndex]) 78 | } 79 | } 80 | 81 | 82 | for (var i in Views) { 83 | 84 | var view = Views[i] 85 | 86 | // // Views without subviews and image should be 0x0 pixels 87 | if (!view.image && !view.viewInfo.maskFrame && !view.subViews.length) { 88 | console.log(view.name, view.viewInfo.maskFrame) 89 | view.frame = {x:0, y:0, width:0, height:0} 90 | } 91 | 92 | function shouldCorrectView(view) { 93 | return !view.image && !view.viewInfo.maskFrame 94 | } 95 | 96 | // If a view has no image or mask, make it the size of it's combined subviews 97 | if (shouldCorrectView(view)) { 98 | 99 | var frame = null 100 | 101 | function traverse(views) { 102 | views.map(function(view) { 103 | 104 | if (shouldCorrectView(view)) { 105 | return 106 | } 107 | 108 | if (!frame) { 109 | frame = view.frame 110 | } else { 111 | frame = frame.merge(view.frame) 112 | } 113 | 114 | traverse(view.subViews) 115 | }) 116 | } 117 | 118 | traverse(view.subViews) 119 | view.frame = frame 120 | 121 | } 122 | 123 | // Correct all the view frames for the superView coordinate system 124 | if (view.superView) { 125 | view.frame = view.superView.convertPoint(view.frame) 126 | } 127 | 128 | } 129 | 130 | return ViewsByName 131 | 132 | } 133 | 134 | window.PSD = loadViews() 135 | -------------------------------------------------------------------------------- /examples/GoogleNow/framer/framerps.log: -------------------------------------------------------------------------------- 1 | {"docName":"GoogleNow","path":"~/Desktop/Framer2.old/static/examples/GoogleNow","relativeImagePath":"images","imagePath":"~/Desktop/Framer2.old/static/examples/GoogleNow/images","framerPath":"~/Desktop/Framer2.old/static/examples/GoogleNow/framer","viewInfoPath":"~/Desktop/Framer2.old/static/examples/GoogleNow/framer/views.GoogleNow.js","logPaths":["/tmp/framerps.log","~/Desktop/Framer2.old/static/examples/GoogleNow/framer/framerps.log"]} 2 | 3 | Running for ~/Desktop/Framer2.old/static/examples/GoogleNow 4 | PROGRESS: 76.9230769230769 5 | StatusBar: Exporting image 6 | PROGRESS: 100 7 | 2 views changed 8 | Skipping ~/Desktop/Framer2.old/static/examples/GoogleNow/index.html (exists) 9 | Skipping ~/Desktop/Framer2.old/static/examples/GoogleNow/app.js (exists) 10 | Skipping ~/Desktop/Framer2.old/static/examples/GoogleNow/framer/framer.js (exists) 11 | Skipping ~/Desktop/Framer2.old/static/examples/GoogleNow/framer/framerps.js (exists) 12 | 13 | SUCCESS: ~/Desktop/Framer2.old/static/examples/GoogleNow 14 | -------------------------------------------------------------------------------- /examples/GoogleNow/framer/views.GoogleNow.js: -------------------------------------------------------------------------------- 1 | window.FramerPS = window.FramerPS || {}; 2 | window.FramerPS['GoogleNow'] = [ 3 | { 4 | "id": 47, 5 | "name": "Content", 6 | "layerFrame": { 7 | "x": 0, 8 | "y": 0, 9 | "width": 640, 10 | "height": 1096 11 | }, 12 | "maskFrame": null, 13 | "image": null, 14 | "imageType": null, 15 | "children": [ 16 | { 17 | "id": 26, 18 | "name": "Searchbox", 19 | "layerFrame": { 20 | "x": 0, 21 | "y": 0, 22 | "width": 640, 23 | "height": 1096 24 | }, 25 | "maskFrame": null, 26 | "image": { 27 | "path": "images/Searchbox.png", 28 | "frame": { 29 | "x": 36, 30 | "y": 470, 31 | "width": 569, 32 | "height": 93 33 | } 34 | }, 35 | "imageType": "png", 36 | "children": [ 37 | 38 | ], 39 | "modification": "1363253030" 40 | }, 41 | { 42 | "id": 28, 43 | "name": "Logo", 44 | "layerFrame": { 45 | "x": 0, 46 | "y": 0, 47 | "width": 640, 48 | "height": 1096 49 | }, 50 | "maskFrame": null, 51 | "image": { 52 | "path": "images/Logo.png", 53 | "frame": { 54 | "x": 112, 55 | "y": 301, 56 | "width": 417, 57 | "height": 144 58 | } 59 | }, 60 | "imageType": "png", 61 | "children": [ 62 | 63 | ], 64 | "modification": "1363253054" 65 | }, 66 | { 67 | "id": 35, 68 | "name": "TrafficCard", 69 | "layerFrame": { 70 | "x": 0, 71 | "y": 0, 72 | "width": 640, 73 | "height": 1096 74 | }, 75 | "maskFrame": null, 76 | "image": { 77 | "path": "images/TrafficCard.png", 78 | "frame": { 79 | "x": 25, 80 | "y": 312, 81 | "width": 588, 82 | "height": 460 83 | } 84 | }, 85 | "imageType": "png", 86 | "children": [ 87 | 88 | ], 89 | "modification": "1363253058" 90 | }, 91 | { 92 | "id": 33, 93 | "name": "MovieCard", 94 | "layerFrame": { 95 | "x": 0, 96 | "y": 0, 97 | "width": 640, 98 | "height": 1096 99 | }, 100 | "maskFrame": null, 101 | "image": { 102 | "path": "images/MovieCard.png", 103 | "frame": { 104 | "x": 23, 105 | "y": 104, 106 | "width": 596, 107 | "height": 889 108 | } 109 | }, 110 | "imageType": "png", 111 | "children": [ 112 | 113 | ], 114 | "modification": "1363253062" 115 | }, 116 | { 117 | "id": 39, 118 | "name": "TimeCard", 119 | "layerFrame": { 120 | "x": 0, 121 | "y": 0, 122 | "width": 640, 123 | "height": 1096 124 | }, 125 | "maskFrame": null, 126 | "image": { 127 | "path": "images/TimeCard.png", 128 | "frame": { 129 | "x": 21, 130 | "y": 479, 131 | "width": 596, 132 | "height": 139 133 | } 134 | }, 135 | "imageType": "png", 136 | "children": [ 137 | 138 | ], 139 | "modification": "1363282568" 140 | }, 141 | { 142 | "id": 41, 143 | "name": "Context", 144 | "layerFrame": { 145 | "x": 0, 146 | "y": 0, 147 | "width": 640, 148 | "height": 1096 149 | }, 150 | "maskFrame": null, 151 | "image": { 152 | "path": "images/Context.png", 153 | "frame": { 154 | "x": 0, 155 | "y": 0, 156 | "width": 640, 157 | "height": 291 158 | } 159 | }, 160 | "imageType": "png", 161 | "children": [ 162 | 163 | ], 164 | "modification": "1363282574" 165 | }, 166 | { 167 | "id": 15, 168 | "name": "Actions", 169 | "layerFrame": { 170 | "x": 0, 171 | "y": 0, 172 | "width": 640, 173 | "height": 1096 174 | }, 175 | "maskFrame": null, 176 | "image": { 177 | "path": "images/Actions.png", 178 | "frame": { 179 | "x": 108, 180 | "y": 820, 181 | "width": 430, 182 | "height": 99 183 | } 184 | }, 185 | "imageType": "png", 186 | "children": [ 187 | 188 | ], 189 | "modification": "1079211296" 190 | }, 191 | { 192 | "id": 23, 193 | "name": "Top", 194 | "layerFrame": { 195 | "x": 0, 196 | "y": 0, 197 | "width": 640, 198 | "height": 1096 199 | }, 200 | "maskFrame": null, 201 | "image": { 202 | "path": "images/Top.png", 203 | "frame": { 204 | "x": 21, 205 | "y": 22, 206 | "width": 585, 207 | "height": 60 208 | } 209 | }, 210 | "imageType": "png", 211 | "children": [ 212 | 213 | ], 214 | "modification": "2076152754" 215 | }, 216 | { 217 | "id": 43, 218 | "name": "Background", 219 | "layerFrame": { 220 | "x": 0, 221 | "y": 0, 222 | "width": 640, 223 | "height": 1096 224 | }, 225 | "maskFrame": null, 226 | "image": { 227 | "path": "images/Background.png", 228 | "frame": { 229 | "x": 0, 230 | "y": 0, 231 | "width": 640, 232 | "height": 1096 233 | } 234 | }, 235 | "imageType": "png", 236 | "children": [ 237 | 238 | ], 239 | "modification": "1086689512" 240 | } 241 | ], 242 | "modification": "424363071" 243 | }, 244 | { 245 | "id": 74, 246 | "name": "StatusBar", 247 | "layerFrame": { 248 | "x": 0, 249 | "y": 0, 250 | "width": 640, 251 | "height": 1096 252 | }, 253 | "maskFrame": null, 254 | "image": { 255 | "path": "images/StatusBar.png", 256 | "frame": { 257 | "x": 0, 258 | "y": 0, 259 | "width": 640, 260 | "height": 40 261 | } 262 | }, 263 | "imageType": "png", 264 | "children": [ 265 | { 266 | "id": 71, 267 | "name": "bars", 268 | "layerFrame": { 269 | "x": 0, 270 | "y": 0, 271 | "width": 640, 272 | "height": 1096 273 | }, 274 | "maskFrame": null, 275 | "image": { 276 | "path": "images/bars.png", 277 | "frame": { 278 | "x": 8, 279 | "y": 9, 280 | "width": 38, 281 | "height": 20 282 | } 283 | }, 284 | "imageType": "png", 285 | "children": [ 286 | 287 | ], 288 | "modification": "911915084" 289 | }, 290 | { 291 | "id": 64, 292 | "name": "carrier", 293 | "layerFrame": { 294 | "x": 0, 295 | "y": 0, 296 | "width": 640, 297 | "height": 1096 298 | }, 299 | "maskFrame": null, 300 | "image": { 301 | "path": "images/carrier.png", 302 | "frame": { 303 | "x": 52, 304 | "y": 8, 305 | "width": 140, 306 | "height": 21 307 | } 308 | }, 309 | "imageType": "png", 310 | "children": [ 311 | 312 | ], 313 | "modification": "930959674" 314 | } 315 | ], 316 | "modification": "1565249211" 317 | } 318 | ] -------------------------------------------------------------------------------- /examples/GoogleNow/images/Actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/Actions.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/Background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/Background.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/Context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/Context.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/Logo.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/MovieCard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/MovieCard.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/Searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/Searchbox.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/StatusBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/StatusBar.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/TimeCard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/TimeCard.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/Top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/Top.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/TrafficCard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/TrafficCard.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/bars.png -------------------------------------------------------------------------------- /examples/GoogleNow/images/carrier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow/images/carrier.png -------------------------------------------------------------------------------- /examples/GoogleNow/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Now 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 49 | 50 | 51 | 52 | 53 | 54 | 63 | 64 | -------------------------------------------------------------------------------- /examples/GoogleNow/style.css: -------------------------------------------------------------------------------- 1 | body.now div[name='Logo'] { 2 | -webkit-filter: brightness(100%) drop-shadow(rgba(0,0,0,0.2) 0 2px 1px); 3 | } 4 | 5 | body.now div[name='Background'] { 6 | opacity: 0.95; 7 | } 8 | 9 | body.home div[name='Context'] { 10 | opacity: 0; 11 | } 12 | 13 | body.home div[name='TrafficCard'], 14 | body.home div[name='MovieCard'], 15 | body.home div[name='TimeCard'] { 16 | border: 1px solid rgba(0,0,0,.2); 17 | box-shadow: 0 1px 2px rgba(0,0,0,.2); 18 | } 19 | 20 | body.now div[name='TrafficCard'], 21 | body.now div[name='MovieCard'], 22 | body.now div[name='TimeCard']{ 23 | border: 1px solid transparent; 24 | box-shadow: 0 2px 1px rgba(0,0,0,.2); 25 | } 26 | 27 | body.now div[name='Searchbox'] { 28 | border-radius: 2px; 29 | background-image: none !important; 30 | background-color: #FFF; 31 | box-shadow: 0 2px 2px rgba(0,0,0,.2); 32 | } -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/app.js: -------------------------------------------------------------------------------- 1 | // Hello, welcome to your new Framer project. This is where you should 2 | // start coding. Feel free to remove all of this code. 3 | // 4 | // Just to rehash: Framer just converted all your layer groups into framer 5 | // views. Just drop index.html (next to this file) on your browser to see 6 | // the result. Every view is available under the global PSD object, so if you 7 | // had a layer group called MyPhoto you can find it under PSD["MyPhoto"]. 8 | // 9 | // You can safely re-run the Framer app any time and this code will stay 10 | // intact. Framer will only update the graphics. 11 | // 12 | // Some links that could come in handy: 13 | // 14 | // - Docs: http://www.framer.com/documentation 15 | // - Examples: http://www.framer.com/examples 16 | 17 | 18 | // ============================================================== 19 | // Example: bounce all the views! 20 | 21 | 22 | // Simple reusable function that binds a bounce to a click on a view 23 | function bounceOnClick(view) { 24 | 25 | // If the view is a normal view (not a scrollview) 26 | if (view instanceof View) { 27 | 28 | // Listen to a click event 29 | view.on("click", function(event) { 30 | 31 | // Stop sending the click event to underlying views after this 32 | event.stopPropagation() 33 | 34 | // "Wind up" the spring 35 | view.scale = 0.7 36 | 37 | // And scale back to full size with a spring curve 38 | view.animate({ 39 | properties:{scale:1.0}, 40 | curve: "spring(1000,15,500)" 41 | }) 42 | }) 43 | } 44 | } 45 | 46 | 47 | // Loop through all the exported views 48 | for (var layerGroupName in PSD) { 49 | bounceOnClick(PSD[layerGroupName]); 50 | } -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/framer/framerps.js: -------------------------------------------------------------------------------- 1 | var loadViews = function() { 2 | 3 | var Views = [] 4 | var ViewsByName = {} 5 | 6 | createView = function(info, superView) { 7 | 8 | // console.log("createView", info.name, "superView: ", superView) 9 | 10 | var viewType, viewFrame 11 | var viewInfo = { 12 | clip: false 13 | } 14 | 15 | if (info.image) { 16 | viewType = ImageView 17 | viewFrame = info.image.frame 18 | viewInfo.image = "images/" + info.name + "." + info.imageType 19 | } 20 | 21 | else { 22 | viewType = View 23 | viewFrame = info.layerFrame 24 | } 25 | 26 | // If this layer group has a mask, we take the mask bounds 27 | // as the frame and clip the layer 28 | if (info.maskFrame) { 29 | viewFrame = info.maskFrame 30 | viewInfo.clip = true 31 | 32 | // If the layer name has "scroll" we make this a scroll view 33 | if (info.name.toLowerCase().indexOf("scroll") != -1) { 34 | viewType = ScrollView 35 | } 36 | 37 | // If the layer name has "paging" we make this a paging view 38 | if (info.name.toLowerCase().indexOf("paging") != -1) { 39 | viewType = ui.PagingView 40 | } 41 | 42 | } 43 | 44 | var view = new viewType(viewInfo) 45 | 46 | view.frame = viewFrame 47 | 48 | // If the view has a contentview (like a scrollview) we add it 49 | // to that one instead. 50 | if (superView && superView.contentView) { 51 | view.superView = superView.contentView 52 | } else { 53 | view.superView = superView 54 | } 55 | 56 | view.name = info.name 57 | view.viewInfo = info 58 | 59 | Views.push(view) 60 | ViewsByName[info.name] = view 61 | 62 | // If the layer name contains draggable we create a draggable for this layer 63 | if (info.name.toLowerCase().indexOf("draggable") != -1) { 64 | view.draggable = new ui.Draggable(view) 65 | } 66 | 67 | for (var i in info.children) { 68 | createView(info.children[info.children.length - 1 - i], view) 69 | } 70 | 71 | } 72 | 73 | // Loop through all the photoshop documents 74 | for (var documentName in FramerPS) { 75 | // Load the layers for this document 76 | for (var layerIndex in FramerPS[documentName]) { 77 | createView(FramerPS[documentName][layerIndex]) 78 | } 79 | } 80 | 81 | 82 | for (var i in Views) { 83 | 84 | var view = Views[i] 85 | 86 | // // Views without subviews and image should be 0x0 pixels 87 | if (!view.image && !view.viewInfo.maskFrame && !view.subViews.length) { 88 | console.log(view.name, view.viewInfo.maskFrame) 89 | view.frame = {x:0, y:0, width:0, height:0} 90 | } 91 | 92 | function shouldCorrectView(view) { 93 | return !view.image && !view.viewInfo.maskFrame 94 | } 95 | 96 | // If a view has no image or mask, make it the size of it's combined subviews 97 | if (shouldCorrectView(view)) { 98 | 99 | var frame = null 100 | 101 | function traverse(views) { 102 | views.map(function(view) { 103 | 104 | if (shouldCorrectView(view)) { 105 | return 106 | } 107 | 108 | if (!frame) { 109 | frame = view.frame 110 | } else { 111 | frame = frame.merge(view.frame) 112 | } 113 | 114 | traverse(view.subViews) 115 | }) 116 | } 117 | 118 | traverse(view.subViews) 119 | view.frame = frame 120 | 121 | } 122 | 123 | // Correct all the view frames for the superView coordinate system 124 | if (view.superView) { 125 | view.frame = view.superView.convertPoint(view.frame) 126 | } 127 | 128 | } 129 | 130 | return ViewsByName 131 | 132 | } 133 | 134 | window.PSD = loadViews() 135 | -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/framer/framerps.log: -------------------------------------------------------------------------------- 1 | {"docName":"GoogleNow","path":"~/Downloads/GoogleNow","relativeImagePath":"images","imagePath":"~/Downloads/GoogleNow/images","framerPath":"~/Downloads/GoogleNow/framer","viewInfoPath":"~/Downloads/GoogleNow/framer/views.GoogleNow.js","logPaths":["/tmp/framerps.log","~/Downloads/GoogleNow/framer/framerps.log"]} 2 | 3 | Running for ~/Downloads/GoogleNow 4 | Could not read data file 5 | Searchbox: Exporting image 6 | PROGRESS: 15.3846153846154 7 | Logo: Exporting image 8 | PROGRESS: 23.0769230769231 9 | TrafficCard: Exporting image 10 | PROGRESS: 30.7692307692308 11 | MovieCard: Exporting image 12 | PROGRESS: 38.4615384615385 13 | TimeCard: Exporting image 14 | PROGRESS: 46.1538461538462 15 | Context: Exporting image 16 | PROGRESS: 53.8461538461538 17 | Actions: Exporting image 18 | PROGRESS: 61.5384615384615 19 | Top: Exporting image 20 | PROGRESS: 69.2307692307692 21 | Background: Exporting image 22 | PROGRESS: 76.9230769230769 23 | PROGRESS: 76.9230769230769 24 | StatusBar: Exporting image 25 | bars: Exporting image 26 | PROGRESS: 92.3076923076923 27 | carrier: Exporting image 28 | PROGRESS: 100 29 | PROGRESS: 100 30 | 13 views changed 31 | Writing ~/Downloads/GoogleNow/index.html 32 | Writing ~/Downloads/GoogleNow/app.js 33 | Writing ~/Downloads/GoogleNow/framer/framer.js 34 | Writing ~/Downloads/GoogleNow/framer/framerps.js 35 | 36 | SUCCESS: ~/Downloads/GoogleNow 37 | -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/framer/views.GoogleNow.js: -------------------------------------------------------------------------------- 1 | window.FramerPS = window.FramerPS || {}; 2 | window.FramerPS['GoogleNow'] = [ 3 | { 4 | "id": 47, 5 | "name": "Content", 6 | "layerFrame": { 7 | "x": 0, 8 | "y": 0, 9 | "width": 640, 10 | "height": 1096 11 | }, 12 | "maskFrame": null, 13 | "image": null, 14 | "imageType": null, 15 | "children": [ 16 | { 17 | "id": 26, 18 | "name": "Searchbox", 19 | "layerFrame": { 20 | "x": 0, 21 | "y": 0, 22 | "width": 640, 23 | "height": 1096 24 | }, 25 | "maskFrame": null, 26 | "image": { 27 | "path": "images/Searchbox.png", 28 | "frame": { 29 | "x": 36, 30 | "y": 470, 31 | "width": 569, 32 | "height": 93 33 | } 34 | }, 35 | "imageType": "png", 36 | "children": [ 37 | 38 | ], 39 | "modification": "1765794287" 40 | }, 41 | { 42 | "id": 28, 43 | "name": "Logo", 44 | "layerFrame": { 45 | "x": 0, 46 | "y": 0, 47 | "width": 640, 48 | "height": 1096 49 | }, 50 | "maskFrame": null, 51 | "image": { 52 | "path": "images/Logo.png", 53 | "frame": { 54 | "x": 112, 55 | "y": 301, 56 | "width": 417, 57 | "height": 144 58 | } 59 | }, 60 | "imageType": "png", 61 | "children": [ 62 | 63 | ], 64 | "modification": "1765794285" 65 | }, 66 | { 67 | "id": 35, 68 | "name": "TrafficCard", 69 | "layerFrame": { 70 | "x": 0, 71 | "y": 0, 72 | "width": 640, 73 | "height": 1096 74 | }, 75 | "maskFrame": null, 76 | "image": { 77 | "path": "images/TrafficCard.png", 78 | "frame": { 79 | "x": 25, 80 | "y": 312, 81 | "width": 588, 82 | "height": 460 83 | } 84 | }, 85 | "imageType": "png", 86 | "children": [ 87 | 88 | ], 89 | "modification": "1328512217" 90 | }, 91 | { 92 | "id": 33, 93 | "name": "MovieCard", 94 | "layerFrame": { 95 | "x": 0, 96 | "y": 0, 97 | "width": 640, 98 | "height": 1096 99 | }, 100 | "maskFrame": null, 101 | "image": { 102 | "path": "images/MovieCard.png", 103 | "frame": { 104 | "x": 23, 105 | "y": 104, 106 | "width": 596, 107 | "height": 889 108 | } 109 | }, 110 | "imageType": "png", 111 | "children": [ 112 | 113 | ], 114 | "modification": "1765794258" 115 | }, 116 | { 117 | "id": 39, 118 | "name": "TimeCard", 119 | "layerFrame": { 120 | "x": 0, 121 | "y": 0, 122 | "width": 640, 123 | "height": 1096 124 | }, 125 | "maskFrame": null, 126 | "image": { 127 | "path": "images/TimeCard.png", 128 | "frame": { 129 | "x": 21, 130 | "y": 479, 131 | "width": 596, 132 | "height": 139 133 | } 134 | }, 135 | "imageType": "png", 136 | "children": [ 137 | 138 | ], 139 | "modification": "1765794255" 140 | }, 141 | { 142 | "id": 41, 143 | "name": "Context", 144 | "layerFrame": { 145 | "x": 0, 146 | "y": 0, 147 | "width": 640, 148 | "height": 1096 149 | }, 150 | "maskFrame": null, 151 | "image": { 152 | "path": "images/Context.png", 153 | "frame": { 154 | "x": 0, 155 | "y": 0, 156 | "width": 640, 157 | "height": 291 158 | } 159 | }, 160 | "imageType": "png", 161 | "children": [ 162 | 163 | ], 164 | "modification": "1328512218" 165 | }, 166 | { 167 | "id": 15, 168 | "name": "Actions", 169 | "layerFrame": { 170 | "x": 0, 171 | "y": 0, 172 | "width": 640, 173 | "height": 1096 174 | }, 175 | "maskFrame": null, 176 | "image": { 177 | "path": "images/Actions.png", 178 | "frame": { 179 | "x": 108, 180 | "y": 820, 181 | "width": 430, 182 | "height": 99 183 | } 184 | }, 185 | "imageType": "png", 186 | "children": [ 187 | 188 | ], 189 | "modification": "115466896" 190 | }, 191 | { 192 | "id": 23, 193 | "name": "Top", 194 | "layerFrame": { 195 | "x": 0, 196 | "y": 0, 197 | "width": 640, 198 | "height": 1096 199 | }, 200 | "maskFrame": null, 201 | "image": { 202 | "path": "images/Top.png", 203 | "frame": { 204 | "x": 21, 205 | "y": 22, 206 | "width": 585, 207 | "height": 60 208 | } 209 | }, 210 | "imageType": "png", 211 | "children": [ 212 | 213 | ], 214 | "modification": "570314131" 215 | }, 216 | { 217 | "id": 43, 218 | "name": "Background", 219 | "layerFrame": { 220 | "x": 0, 221 | "y": 0, 222 | "width": 640, 223 | "height": 1096 224 | }, 225 | "maskFrame": null, 226 | "image": { 227 | "path": "images/Background.png", 228 | "frame": { 229 | "x": 0, 230 | "y": 0, 231 | "width": 640, 232 | "height": 1096 233 | } 234 | }, 235 | "imageType": "png", 236 | "children": [ 237 | 238 | ], 239 | "modification": "1745625594" 240 | } 241 | ], 242 | "modification": "367787602" 243 | }, 244 | { 245 | "id": 74, 246 | "name": "StatusBar", 247 | "layerFrame": { 248 | "x": 0, 249 | "y": 0, 250 | "width": 640, 251 | "height": 1096 252 | }, 253 | "maskFrame": null, 254 | "image": { 255 | "path": "images/StatusBar.png", 256 | "frame": { 257 | "x": 0, 258 | "y": 0, 259 | "width": 640, 260 | "height": 40 261 | } 262 | }, 263 | "imageType": "png", 264 | "children": [ 265 | { 266 | "id": 71, 267 | "name": "bars", 268 | "layerFrame": { 269 | "x": 0, 270 | "y": 0, 271 | "width": 640, 272 | "height": 1096 273 | }, 274 | "maskFrame": null, 275 | "image": { 276 | "path": "images/bars.png", 277 | "frame": { 278 | "x": 8, 279 | "y": 9, 280 | "width": 38, 281 | "height": 20 282 | } 283 | }, 284 | "imageType": "png", 285 | "children": [ 286 | 287 | ], 288 | "modification": "240803089" 289 | }, 290 | { 291 | "id": 64, 292 | "name": "carrier", 293 | "layerFrame": { 294 | "x": 0, 295 | "y": 0, 296 | "width": 640, 297 | "height": 1096 298 | }, 299 | "maskFrame": null, 300 | "image": { 301 | "path": "images/carrier.png", 302 | "frame": { 303 | "x": 52, 304 | "y": 8, 305 | "width": 140, 306 | "height": 21 307 | } 308 | }, 309 | "imageType": "png", 310 | "children": [ 311 | 312 | ], 313 | "modification": "1410945054" 314 | } 315 | ], 316 | "modification": "458415921" 317 | } 318 | ] -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/Actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/Actions.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/Background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/Background.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/Context.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/Context.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/Logo.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/MovieCard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/MovieCard.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/Searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/Searchbox.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/StatusBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/StatusBar.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/TimeCard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/TimeCard.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/Top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/Top.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/TrafficCard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/TrafficCard.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/bars.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/images/carrier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/examples/GoogleNow_exported_by_Framer/images/carrier.png -------------------------------------------------------------------------------- /examples/GoogleNow_exported_by_Framer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /sandbox.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is a port to JSTalk of 3 | 4 | Here's the original license for AppSandboxFileAccess: 5 | 6 | ## License 7 | 8 | Copyright (c) 2013, Leigh McCulloch All rights reserved. 9 | 10 | BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 11 | 12 | Redistribution and use in source and binary forms, with or without modification, 13 | are permitted provided that the following conditions are met: 14 | 15 | - Redistributions of source code must retain the above copyright notice, 16 | this list of conditions and the following disclaimer. 17 | 18 | - Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 31 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | */ 33 | 34 | var AppSandboxFileAccessPersist = { 35 | keyForBookmarkDataForURL: function(url) { 36 | log("AppSandboxFileAccessPersist.keyForBookmarkDataForURL("+url+")") 37 | var urlStr = [url absoluteString]; 38 | log("> " + [NSString stringWithFormat:@"bd_%1$@", urlStr]) 39 | return [NSString stringWithFormat:@"bd_%1$@", urlStr]; 40 | }, 41 | bookmarkDataForURL: function(url) { 42 | log("AppSandboxFileAccessPersist.bookmarkDataForURL('"+ url +"')") 43 | var defaults = [NSUserDefaults standardUserDefaults]; 44 | 45 | // loop through the bookmarks one path at a time down the URL 46 | var subUrl = url; 47 | while ([subUrl path].length() > 1) { // give up when only '/' is left in the path 48 | var key = AppSandboxFileAccessPersist.keyForBookmarkDataForURL(subUrl); 49 | var bookmark = [defaults dataForKey:key]; 50 | if (bookmark) { // if a bookmark is found, return it 51 | return bookmark; 52 | } 53 | subUrl = [subUrl URLByDeletingLastPathComponent]; 54 | } 55 | // no bookmarks for the URL, or parent to the URL were found 56 | return nil; 57 | }, 58 | setBookmarkData: function(data, url) { 59 | log("AppSandboxFileAccessPersist.setBookmarkData") 60 | log("data: " + data) 61 | log("URL: " + url) 62 | var defaults = [NSUserDefaults standardUserDefaults]; 63 | var key = AppSandboxFileAccessPersist.keyForBookmarkDataForURL(url); 64 | [defaults setObject:data forKey:key]; 65 | } 66 | } 67 | 68 | var AppSandboxFileAccess = { 69 | init: function(opts){ 70 | this.message = opts.message || "Please authorize Sketch to write to this folder. You will only need to do this once." 71 | this.prompt = opts.prompt || "Authorize", 72 | this.title = opts.title || "Sketch Authorization" 73 | return this; 74 | }, 75 | askPermissionForUrl: function(url) { 76 | log("AppSandboxFileAccess.askPermissionForUrl("+url+")") 77 | // this url will be the url allowed, it might be a parent url of the url passed in 78 | var allowedUrl; 79 | 80 | // create delegate that will limit which files in the open panel can be selected, to ensure only a folder 81 | // or file giving permission to the file requested can be selected 82 | // AppSandboxFileAccessOpenSavePanelDelegate *openPanelDelegate = [[AppSandboxFileAccessOpenSavePanelDelegate alloc] initWithFileURL:url]; 83 | 84 | // check that the url exists, if it doesn't, find the parent path of the url that does exist and ask permission for that 85 | var fileManager = [NSFileManager defaultManager]; 86 | var path = [url path]; 87 | while (path.length() > 1) { // give up when only '/' is left in the path or if we get to a path that exists 88 | if ([fileManager fileExistsAtPath:path]) { 89 | break; 90 | } 91 | path = [path stringByDeletingLastPathComponent]; 92 | } 93 | log("Looks like we have a winner: " + path) 94 | url = [NSURL fileURLWithPath:path]; 95 | 96 | // display the open panel 97 | var openPanel = [NSOpenPanel openPanel]; 98 | [openPanel setMessage:this.message]; 99 | [openPanel setPrompt:this.prompt]; 100 | [openPanel setTitle:this.title]; 101 | // [openPanel setDelegate:openPanelDelegate]; 102 | [openPanel setCanCreateDirectories:false]; 103 | [openPanel setCanChooseFiles:true]; 104 | [openPanel setCanChooseDirectories:true]; 105 | [openPanel setAllowsMultipleSelection:false]; 106 | [openPanel setShowsHiddenFiles:false]; 107 | [openPanel setExtensionHidden:false]; 108 | [openPanel setDirectoryURL:url]; 109 | [[NSApplication sharedApplication] activateIgnoringOtherApps:true]; 110 | var openPanelButtonPressed = [openPanel runModal]; 111 | if (openPanelButtonPressed == NSFileHandlingPanelOKButton) { 112 | allowedUrl = [openPanel URL]; 113 | } 114 | return allowedUrl; 115 | }, 116 | persistPermissionPath: function(path) { 117 | this.persistPermissionURL([NSURL fileURLWithPath:path]); 118 | }, 119 | persistPermissionURL: function(url) { 120 | log("AppSandboxFileAccess.persistPermissionURL("+url+")") 121 | // store the sandbox permissions 122 | url = [[url URLByStandardizingPath] URLByResolvingSymlinksInPath] 123 | var bookmarkData = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope 124 | includingResourceValuesForKeys:nil 125 | relativeToURL:nil 126 | error:null]; 127 | if (bookmarkData) { 128 | AppSandboxFileAccessPersist.setBookmarkData(bookmarkData, url); 129 | } 130 | }, 131 | accessFilePath_withBlock_persistPermission: function(path, block, persist) { 132 | log("AppSandboxFileAccess.accessFilePath_withBlock_persistPermission") 133 | log("path: " + path) 134 | return AppSandboxFileAccess.accessFileURL_withBlock_persistPermission([NSURL fileURLWithPath:path], block, persist); 135 | }, 136 | accessFileURL_withBlock_persistPermission: function(fileUrl, block, persist) { 137 | log("AppSandboxFileAccess.accessFileURL_withBlock_persistPermission") 138 | log("fileUrl: " + fileUrl) 139 | log("block: " + block) 140 | log("persist: " + persist) 141 | var allowedUrl = false; 142 | // standardize the file url and remove any symlinks so that the url we lookup in bookmark data would match a url given by the askPermissionForUrl method 143 | var fileUrl = [[fileUrl URLByStandardizingPath] URLByResolvingSymlinksInPath]; 144 | // lookup bookmark data for this url, this will automatically load bookmark data for a parent path if we have it 145 | var bookmarkData = AppSandboxFileAccessPersist.bookmarkDataForURL(fileUrl); 146 | 147 | if (bookmarkData) { 148 | log("Bookmark data found") 149 | // resolve the bookmark data into an NSURL object that will allow us to use the file 150 | var bookmarkDataIsStale; 151 | allowedUrl = [NSURL URLByResolvingBookmarkData:bookmarkData options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:bookmarkDataIsStale error:null]; 152 | // if the bookmark data is stale, we'll create new bookmark data further down 153 | if (bookmarkDataIsStale) { 154 | bookmarkData = nil; 155 | } 156 | } else { 157 | log("No bookmark data found") 158 | } 159 | 160 | // if allowed url is nil, we need to ask the user for permission 161 | if (!allowedUrl) { 162 | allowedUrl = AppSandboxFileAccess.askPermissionForUrl(fileUrl); 163 | if (!allowedUrl) { 164 | // if the user did not give permission, exit out here 165 | return false; 166 | } 167 | } 168 | // if we have no bookmark data, we need to create it, this may be because our bookmark data was stale, or this is the first time being given permission 169 | if (persist && !bookmarkData) { 170 | AppSandboxFileAccess.persistPermissionURL(allowedUrl); 171 | } 172 | // execute the block with the file access permissions 173 | try { 174 | [allowedUrl startAccessingSecurityScopedResource]; 175 | block(); 176 | } finally { 177 | [allowedUrl stopAccessingSecurityScopedResource]; 178 | } 179 | return true; 180 | } 181 | } 182 | function in_sandbox(){ 183 | var environ = [[NSProcessInfo processInfo] environment]; 184 | return (nil != [environ objectForKey:@"APP_SANDBOX_CONTAINER_ID"]); 185 | } 186 | var sandboxAccess = AppSandboxFileAccess.init({ 187 | message: "Please authorize Sketch to write to this folder. You will only need to do this once per folder.", 188 | prompt: "Authorize", 189 | title: "Sketch Authorization" 190 | }) 191 | 192 | 193 | if (show_errors && in_sandbox()) { 194 | [[NSApplication sharedApplication] displayDialog:"Please use this plugin with Sketch Beta. You can search 'Sketch Beta' in Google to download it.\n\n(Or manually copy framer.js info the framer folder)." withTitle:"Sketch Framer known bugs"] 195 | } 196 | -------------------------------------------------------------------------------- /sketch-framer-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | If you remove any variables from this file, the plugin won't work. If the plugin stops working, replace this file with a fresh copy from Github. 3 | */ 4 | 5 | 6 | /* 7 | If you have a common library file you'd like to include in your mocks, put a valid URL in FramerLibraryUrl. This will create a copy of the file in the Framer folder of the prototype, also insert a line to index.html to include the script. 8 | 9 | By default this is undefined, so no library will get included. 10 | 11 | Example: 12 | var FramerLibraryUrl = "http://www.website.com/library.js"; 13 | */ 14 | 15 | var FramerLibraryUrl; 16 | var show_errors = true; 17 | 18 | 19 | 20 | 21 | /* Don't touch the following. They are auto-generated based on the Library URL. */ 22 | var extra_script_line; 23 | var FramerLibraryFileName; 24 | 25 | if(FramerLibraryUrl) { 26 | FramerLibraryFileName = FramerLibraryUrl.replace(/^.*(\\|\/|\:)/, ''); 27 | extra_script_line = "\n\t\t" 28 | } 29 | /* End of auto-generated block */ 30 | 31 | 32 | 33 | 34 | /* Contents of index.html */ 35 | var FramerIndexFileContents = "\n\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t{{ views }}\n\t\t\n\t\t" + (extra_script_line || "") + "\n\t\t\n\t\n"; 36 | 37 | 38 | /* Contents of FramerPS.js */ 39 | var FramerPSJSContents = "var loadViews = function() {\n\t\n\tvar Views = []\n\tvar ViewsByName = {}\n\t\n\tcreateView = function(info, superView) {\n\t\t\n\t\t// console.log(\"createView\", info.name, \"superView: \", superView)\n\t\t\n\t\tvar viewType, viewFrame\n\t\tvar viewInfo = {\n\t\t\tclip: false\n\t\t}\n\t\t\n\t\tif (info.image) {\n\t\t\tviewType = ImageView\n\t\t\tviewFrame = info.image.frame\n\t\t\tviewInfo.image = \"images/\" + info.name + \".\" + info.imageType\n\t\t}\n\t\t\n\t\telse {\n\t\t\tviewType = View\n\t\t\tviewFrame = info.layerFrame\n\t\t\tviewInfo.style = { background: 'transparent' }\n\t\t}\n\n\t\tviewInfo.visible = info.visible\n\t\t\n\t\t// If this layer group has a mask, we take the mask bounds\n\t\t// as the frame and clip the layer\n\t\tif (info.maskFrame) {\n\t\t\tviewFrame = info.maskFrame\n\t\t\tviewInfo.clip = true\n\t\t\t\n\t\t\t// If the layer name has \"scroll\" we make this a scroll view\n\t\t\tif (info.name.toLowerCase().indexOf(\"scroll\") != -1) {\n\t\t\t\tviewType = ScrollView\n\t\t\t}\n\t\t\t\n\t\t\t// If the layer name has \"paging\" we make this a paging view\n\t\t\tif (info.name.toLowerCase().indexOf(\"paging\") != -1) {\n\t\t\t\tviewType = ui.PagingView\n\t\t\t}\n\n\t\t}\n\t\t\n\t\tvar view = new viewType(viewInfo)\n\t\t\n\t\tview.frame = viewFrame\n\t\t\n\t\t// If the view has a contentview (like a scrollview) we add it\n\t\t// to that one instead.\n\t\tif (superView && superView.contentView) {\n\t\t\tview.superView = superView.contentView\n\t\t} else {\n\t\t\tview.superView = superView\n\t\t}\n\t\t\n\t\tview.name = info.name\n\t\tview.viewInfo = info\n\t\t\n\t\tViews.push(view)\n\t\tViewsByName[info.name] = view\n\n\t\t// If the layer name contains draggable we create a draggable for this layer\n\t\tif (info.name.toLowerCase().indexOf(\"draggable\") != -1) {\n\t\t\tview.draggable = new ui.Draggable(view)\n\t\t}\n\n\t\tfor (var i in info.children) {\n\t\t\tcreateView(info.children[info.children.length - 1 - i], view)\n\t\t}\n\n\t}\n\t\t\n\t// Loop through all the photoshop documents\n\tfor (var documentName in FramerPS) {\n\t\t// Load the layers for this document\n\t\tfor (var layerIndex in FramerPS[documentName]) {\n\t\t\tcreateView(FramerPS[documentName][layerIndex])\n\t\t}\n\t}\n\t\n\t\n\tfor (var i in Views) {\n\t\t\n\t\tvar view = Views[i]\n\t\t\n\t\t// // Views without subviews and image should be 0x0 pixels\n\t\tif (!view.image && !view.viewInfo.maskFrame && !view.subViews.length) {\n\t\t\tconsole.log(view.name, view.viewInfo.maskFrame)\n\t\t\tview.frame = {x:0, y:0, width:0, height:0}\n\t\t}\n\t\t\n\t\tfunction shouldCorrectView(view) {\n\t\t\treturn !view.image && !view.viewInfo.maskFrame\n\t\t}\n\n\t\t// If a view has no image or mask, make it the size of it's combined subviews\n\t\tif (shouldCorrectView(view)) {\n\n\t\t\tvar frame = null\n\t\t\t\n\t\t\tfunction traverse(views) {\n\t\t\t\tviews.map(function(view) {\n\n\t\t\t\t\tif (shouldCorrectView(view)) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!frame) {\n\t\t\t\t\t\tframe = view.frame\n\t\t\t\t\t} else {\n\t\t\t\t\t\tframe = frame.merge(view.frame)\n\t\t\t\t\t}\n\n\t\t\t\t\ttraverse(view.subViews)\n\t\t\t\t})\n\t\t\t}\n\t\t\t\n\t\t\ttraverse(view.subViews)\n\t\t\tview.frame = frame\n\t\t\t\n\t\t}\n\t\t\n\t\t// Correct all the view frames for the superView coordinate system\n\t\tif (view.superView) {\n\t\t\tview.frame = view.superView.convertPoint(view.frame)\n\t\t}\n\t\t\n\t}\n\t\n\treturn ViewsByName\n\n}\n\nwindow.PSD = loadViews()\n" 40 | 41 | 42 | /* The default app.js that will be created, and its contents */ 43 | /* eg. you could set this to make a blank app.coffee file instead. */ 44 | var FramerScriptFileName = "app.js"; 45 | var FramerScriptFileContents = "// Hello, welcome to your new Framer project. This is where you should \n// start coding. Feel free to remove all of this code.\n// \n// Just to rehash: Framer just converted all your layer groups into framer\n// views. Just drop index.html (next to this file) on your browser to see\n// the result. Every view is available under the global PSD object, so if you\n// had a layer group called MyPhoto you can find it under PSD[\"MyPhoto\"].\n// \n// You can safely re-run the Framer app any time and this code will stay \n// intact. Framer will only update the graphics.\n// \n// Some links that could come in handy:\n// \n// \t- Docs: \thttp://www.framer.com/documentation\n// \t- Examples: http://www.framer.com/examples\n\n\n// ==============================================================\n// Example: bounce all the views!\n\n\n// Simple reusable function that binds a bounce to a click on a view\nfunction bounceOnClick(view) {\n\t\n\t// If the view is a normal view (not a scrollview)\n\tif (view instanceof View) {\n\t\t\n\t\t// Listen to a click event\n\t\tview.on(\"click\", function(event) {\n\t\t\t\n\t\t\t// Stop sending the click event to underlying views after this\n\t\t\tevent.stopPropagation()\n\t\t\t\n\t\t\t// \"Wind up\" the spring\n\t\t\tview.scale = 0.7\n\t\t\t\n\t\t\t// And scale back to full size with a spring curve\n\t\t\tview.animate({\n\t\t\t\tproperties:{scale:1.0},\n\t\t\t\tcurve: \"spring(1000,15,500)\"\n\t\t\t})\n\t\t})\n\t}\n}\n\n\n// Loop through all the exported views\nfor (var layerGroupName in PSD) {\n\tbounceOnClick(PSD[layerGroupName]);\n}"; -------------------------------------------------------------------------------- /sketch-framer-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bomberstudios/sketch-framer/1f07f1427b9b7bcc9273b8a56ed4d7d24a999a41/sketch-framer-logo.png --------------------------------------------------------------------------------