Skip to content

Instantly share code, notes, and snippets.

@elliottkember
Last active December 14, 2021 12:49
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elliottkember/b1d4614f120afc5ef82f to your computer and use it in GitHub Desktop.
Save elliottkember/b1d4614f120afc5ef82f to your computer and use it in GitHub Desktop.
Layer browser for imported Sketch / PSD files in Framer
class LayerBrowser
constructor: (options) ->
@objects = []
@restore = options.restore
@_setup()
@add options.import if options.import
add: (object) ->
@objects.push object
for layerName in Object.keys(object)
layer = object[layerName]
if @restore
if localStorage[layerName]
layer.visible = localStorage[layerName] != "hidden"
layer.on "change:visible", ->
localStorage[layerName] = if layer.visible then "visible" else "hidden"
@_draw()
_setup: ->
@layer = new Layer x: 20, y: 20, width: 250, height: 20, opacity: 0.95
@layer.draggable.enabled = true
@layer.style =
backgroundColor: '#999'
"border": "1px solid rgba(86, 86, 86, 0.87)"
"-webkit-border-radius": "6px"
"-moz-box-shadow": "0 2px 7px rgba(0, 0, 0, 0.81)"
"box-shadow": "0 2px 7px rgba(0, 0, 0, 0.81)"
"border-bottom": "1px solid rgba(66, 66, 66, 0.87)"
"border-top": "none"
"cursor": "default"
"max-height": "400px"
@header = new Layer x: 0, y: 0, width: @layer.width, height: 20, superLayer: @layer
@header.style =
"box-shadow": "none"
"font": '11px/15px "Lucida Grande"'
"border-top": "1px solid rgba(151, 151, 151, 0.92)"
"height": "20px"
"-webkit-border-top-left-radius": "6px"
"-webkit-border-bottom-left-radius": "0px"
"-webkit-border-bottom-right-radius": "0px"
"background-image": "-webkit-gradient(linear, left top, left bottom, from(rgba(108, 108, 108, 0.9)), color-stop(0.5, rgba(87, 87, 87, 0.88)), color-stop(0.5, rgba(75, 75, 75, 0.86)), to(rgba(75, 75, 75, 0.86)))"
@close = new Layer x: 4, y: 2, width: 13, height: 13, superLayer: @header, backgroundColor: 'transparent'
@close.style =
"textAlign": 'center'
"borderRadius": '10px'
"opacity": 1
"width": "13px"
"height": "13px"
"background-image": "url(layerbrowser/z_close.png)"
"display": "block"
"position": "absolute"
"margin-top": "1px"
"-webkit-transition-duration": "0.1s"
"-webkit-transition-property": "opacity"
"cursor": "pointer"
@close.on Events.Click, =>
if @layer.height == 20
@_draw()
@close.animate time: 0.1, properties: rotationZ: 0
else
@close.animate time: 0.1, properties: rotationZ: -45
@layer.height = 20
@contentLayer = new Layer superLayer: @layer, y: 20, height: 150, width: @layer.width
@contentLayer.style =
"width": "100%"
"height": "100%"
"max-height": "380px"
"overflow": "scroll"
"position": "relative"
"overflow-y": "auto"
"pointer-events": "auto"
"-webkit-border-radius": "0 0 6px 6px"
@contentLayer.on Events.TouchStart, (e) =>
e.stopPropagation()
@_reset()
_reset: ->
@contentLayer._element.innerHTML = ""
_draw: ->
@_reset()
@lines = []
indent = 0
for object in @objects
for key in Object.keys object
layer = object[key]
if !layer.superLayer
@_drawLayer layer
_line: (layer, lineHeight, lineNumber, indent) ->
line = new Layer
x: 0,
y: lineNumber * lineHeight
width: @layer.width,
height: lineHeight
backgroundColor: 'transparent'
line.style =
"fontSize": "12px"
"borderBottom": "1px solid rgba(255, 255, 255, 0.1)"
"paddingTop": "4px"
"paddingLeft": 10 * indent + 10 + "px"
"backgroundColor": 'rgba(79, 79, 79, 0.87)'
"font": 'normal 11px/18px "Lucida Grande"'
"color": 'rgba(255, 255, 255, 0.85)'
"border-left": "1px solid rgba(255, 255, 255, 0.12)"
"border-right": "1px solid rgba(255, 255, 255, 0.12)"
"-webkit-transition-duration": "0.07s"
"-webkit-transition-property": "background"
html = ""
html += "   " for [0...indent]
line.html = html
if !layer.visible
line.backgroundColor = 'rgba(100, 100, 100, 0.87)'
if layer.subLayers.length > 0
line.html += "▶ "
else
line.backgroundColor = 'rgba(79, 79, 79, 0.87)'
if layer.subLayers.length > 0
line.html += "▼ "
line.html += "#{layer.name}"
originalBackground = line.backgroundColor
layer.on Events.MouseOver, => line.backgroundColor = 'rgba(79, 79, 79, 0.1)'
layer.on Events.MouseOut, => line.backgroundColor = originalBackground
line.on Events.MouseOver, => layer.style['outline'] = '1px solid red'
line.on Events.MouseOut, => layer.style['outline'] = 'none'
selectHTML = (states, selectedState) ->
select = "<select>"
for state in states
select += "<option #{'selected' if state == selectedState}>#{state}</option>"
select += "</select>"
if Object.keys(layer.states._states).length > 1
selectLayer = new Layer
superLayer: line
maxX: line.width - 10
y: -line.height + 5
height: line.height
backgroundColor: 'transparent'
selectLayer.html = selectHTML(Object.keys(layer.states._states), layer.states.current)
layer.states.on Events.StateDidSwitch, (oldState, newState) =>
selectLayer.html = selectHTML(Object.keys(layer.states._states), newState)
# Change the layer's state when the input changes
selectLayer.on "change", (e) => layer.states.switch e.target.value
# Don't let this propagate up and show/hide the layer
selectLayer.on Events.Click, (e) => e.stopPropagation()
line.on Events.Click, =>
layer.visible = !layer.visible
localStorage[layer.name] = (if layer.visible then "visible" else "hidden")
# This used to toggle all sublayers when restoring a layer. I don't know whether I like this so much. Hiding a layer should hide its sublayers anyway.
# for layer in layer.subLayers
# localStorage[layer.name] = "visible" if @restore
# layer.visible = true
@_draw()
line
_drawLayer: (layer, indent=0) ->
lineHeight = 24
line = @_line layer, lineHeight, @lines.length, indent
# We have an array of these for telling what the legend's height should be
@lines.push line
# Add the line to the legend!
@contentLayer.addSubLayer(line)
# Make sure the legend is the right height!
@layer.height = @lines.length * line.height + 20
@contentLayer.height = @lines.length * line.height + 20
# Print all visible sublayers' lines if this layer is visible.
if layer.subLayers && layer.visible
for subLayer in layer.subLayers
@_drawLayer(subLayer, indent + 1)
exports = window if typeof exports == 'undefined'
exports.LayerBrowser = LayerBrowser
# Import the script
eval CoffeeScript.compile "layerbrowser.coffee"
# Load some layers
layers = Framer.Importer.load "imported/my-sketch-file"
# create a browser!
layerBrowser = new LayerBrowser restore: true, import: layers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment