Skip to content

Instantly share code, notes, and snippets.

@selfsame
Created February 16, 2014 22:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save selfsame/9041668 to your computer and use it in GitHub Desktop.
Save selfsame/9041668 to your computer and use it in GitHub Desktop.
if window.DEBUG
console.log "running outliner.js"
# Outliner should be a window.tools object, should easily match up with selection
$(window.ifrm).ready ->
window.tools['outliner'] =
toolbox: 0
moving: 0
move_start: [0,0]
move_triggered: 0 #if elements are moved more than a few pixels, trigger element move repositioning
move_visuals: 0 #the visual representation of a node/s being moved around by the mouse
move_whom: 0 #the node bing moved (temp, this will end up finding out all selected nodes)
moving_over: 0 #the node under the mouse at the moment
off_limits: [] #nodes currently being moved should not be candidates for getting move operations
insertion_mode: 0 #0 put into element, 1 put before, 2 put after
input_edit_mode: 0
mousedown_element: 0
init: ()->
@toolbox = new window.widgets.ToolBox('Outliner')
window.tool_groups.detailed_group.add_tab @toolbox, true, true
@toolbox.element.attr('id', 'outliner' )
@toolbox.element.css('height', '50%')
@toolbox.view.attr('id', 'outliner_view')
@toolbox.element.append $("<div id='outliner_effects'>
<div id='outliner_group'></div>
<div class='outliner_insertion_graphic'></div>
</div>")
#reroute mouse interactions to the tool
$('#outliner').mousemove (e)->
window.tools['outliner'].mousemove e
$('#outliner').mousedown (e)->
window.tools['outliner'].mousedown e
$('#outliner').mouseup (e)->
window.tools['outliner'].mouseup e
$('#outliner').mouseleave (e)->
window.tools['outliner'].mouseleave e
show_insertion_element: (element, mode)->
node = @node_from_element element
#console.log "show_insertion_element", element, node
_t.debug.watch 'show_oiv', mode
if node? and node.length > 0
#if node.data('represents')[0] isnt element
# mode = 2
@show_insertion node, mode
else
hide_insertion: ()->
oiv = $('#outliner_effects .outliner_insertion_graphic')
oiv.hide()
show_insertion: (node, mode)->
oiv = $('#outliner_effects .outliner_insertion_graphic')
oiv.removeClass 'only_insert'
oiv.show()
moo = node.offset()
moopo = node.parent().offset()
oiv.css('width', $('#outliner').width() - moo.left)
tool_box_o = $('#outliner').offset()
tool_box_h = $('#outliner').height()
view_o = $('#outliner_view').offset()
view_h = $('#outliner_view').height()
_t.debug.watch 'oiv mode', mode
if mode is 0
oiv.offset({left:moo.left-12+6, top:moopo.top+10})
oiv.height( moo.top - moopo.top-10 )
else if mode is 1 #and @moving_over.parent().attr('id') isnt 'outliner'
oiv.offset({left:moo.left-12+6, top:moopo.top+10})
oiv.height( moo.top - moopo.top+6 + node.height()-12 )
else if mode is 2
if node.height() <= 16
oiv.addClass 'only_insert'
oiv.offset({left:moo.left+6, top:moo.top+10})
oiv.height( node.height()-10)
#animate the outliner view to attempt to show off area actions
#oiv_b = oiv.offset().top + oiv.height()
#tool_b = tool_box_o.top + tool_box_h
#console.log 'ANIMATE OUTLINRE VIEW: ', oiv_b, tool_b
#if oiv_b > tool_b
# diff = oiv_b - tool_b
# $('#outliner_view').stop()
# $('#outliner_view').animate( {top: diff*-1}, 200 )
mousedown: (e)->
@mousedown_element = e.target
frame_update: ()->
if window.tools['outliner'].move_triggered
console.log 'frame update'
window.requestAnimFrame window.tools.outliner.frame_update
#scroll view if near top or bottom and room to scroll
e = window.tools.outliner.cached_mouse_e
if e
t_h = window.tools.outliner.toolbox.viewport.height()
o_top = window.tools.outliner.toolbox.viewport.offset().top
o_h = t_h + o_top
if e.clientY < o_top + 20 or e.clientY > o_h - 20
#console.log 'moving at edge'
view = window.tools.outliner.toolbox.view
v_t = parseInt(view.css('top'))
v_h = view.height()
if e.clientY < o_top + 20
diff = ( (o_top + 20) - e.clientY )
if v_t < 0
view.css('top', v_t+(diff/3))
if e.clientY > o_h - 20
diff = ( e.clientY - (o_h - 20) )
if v_t+v_h > o_h
view.css('top', v_t-(diff/3))
window.tools.outliner.toolbox.resize()
mousemove: (e)->
if @input_edit_mode is 1
console.log 'edit mode active'
return
if window.tools['outliner'].moving and window.tools['outliner'].move_triggered is 0
if Math.abs( e.clientX-window.tools['outliner'].move_start[0]) > 3 or Math.abs( e.clientY-window.tools['outliner'].move_start[1]) > 3
console.log 'outliner move triggered'
window.tools['outliner'].move_triggered = 1
#start up a requestanimation frame function while we are moving some nodes
@frame_update()
#setting up the move visuals, duplicate all selected nodes and strip mouse events
#allow unselected nodes to resolve and be moved
#find last clicked node, if it isn't selected, construct a new selection for it
#console.log @moving_over.data('selected')
if not @moving_over.hasClass 'outliner_selected'
window.tools['select'].resolve(0, $(@moving_over).data('represents')[0], 0, 0, 1 )
@off_limits = []
recurse_nodes = (element, parent)->
# recurse to find selected nodes, once found we grab them and their children, stopping recursion for that part of the tree
for node in $(element).children()
parent_two = parent
if $(node).hasClass('outliner_selected')
window.tools['outliner'].off_limits.push node
clone = $(node).clone(true)
parent.append clone
$(node).addClass 'outliner_node_active_move'
else
recurse_nodes node, parent
recurse_nodes $('#outliner_view'), $('#outliner_group')
window.tools['outliner'].move_visuals = $('#outliner_group')
#console.log 'offlimits', @off_limits
if window.tools['outliner'].move_triggered
$('#outliner_effects').css('cursor','pointer')
window.tools['outliner'].move_visuals.offset( {left: e.clientX, top: e.clientY})
@cached_mouse_e = e
if e.clientY > $('#outliner_view').offset().top + $('#outliner_view').height()
@moving_over = $('#outliner_view').children().first().children('.outliner_node').last()
if @moving_over
kill = 0
oiv = $('#outliner_effects .outliner_insertion_graphic')
for element in @off_limits
if $.contains( element, @moving_over[0]) or element is @moving_over[0]
kill = 1
oiv.hide()
@moving_over = 0
else if @moving_over
y = e.clientY - @moving_over.offset().top
oiv.show()
oiv.removeClass 'only_insert'
moo = @moving_over.offset()
moopo = @moving_over.parent().offset()
oiv.css('width', $('#outliner').width() - moo.left)
tool_box_o = $('#outliner').offset()
## arrange visuals
info =
from_outliner:true
before: []
after: []
left: []
right: []
if y < 6 and @moving_over.parent().attr('id') isnt 'outliner'
@insertion_mode = 1
#oiv.css('border', '1px solid yellow')
oiv.offset({left:moo.left-12+6, top:moopo.top+10})
oiv.height( moo.top - moopo.top-10 )
prev = @moving_over.data('represents').prev()
if prev.length > 0
info.before = [new _t.arrange.proxy_class(prev[0])]
info.after = [new _t.arrange.proxy_class(@moving_over.data('represents')[0])]
_t.arrange.display info
else if y > 12 and @moving_over.parent().attr('id') isnt 'outliner'
@insertion_mode = 2
#oiv.css('border', '1px solid yellow')
oiv.offset({left:moo.left-12+6, top:moopo.top+10})
oiv.height( moo.top - moopo.top+6 + @moving_over.height()-12 )
after = @moving_over.data('represents').next()
if after.length > 0
info.after = [new _t.arrange.proxy_class(after[0])]
info.before =[ new _t.arrange.proxy_class(@moving_over.data('represents')[0])]
_t.arrange.display info
else if @moving_over.data('represents')[0].nodeName not in ['IMG']
@insertion_mode = 0
if @moving_over.height() <= 16
oiv.addClass 'only_insert'
#oiv.css('border', '2px solid yellow')
oiv.offset({left:moo.left+6, top:moo.top+10})
oiv.height( @moving_over.height()-10)
info.inside = new _t.arrange.proxy_class( @moving_over.data('represents')[0] )
_t.arrange.display info
else
oiv.hide()
#console.log @insertion_mode
#console.log moo, oiv
clear_node_styles: ()->
oiv = $('#outliner_effects .outliner_insertion_graphic')
oiv.hide()
$('#outliner_group').html ''
window.tools['outliner'].moving = 0
window.tools['outliner'].move_triggered = 0
window.tools['outliner'].move_visuals = 0
$('#outliner .outliner_node').removeClass 'outliner_node_active_move'
mouseup: (e)->
#if @mousedown_element is 0
# return
if e.target is $('#outliner')[0]
window.tools.select.update_selection_set [], 1
window.tools.history.check_incremental()
if @input_edit_mode is 1 or not window.tools['outliner'].move_triggered
@clear_node_styles()
return
if window.tools['outliner'].move_triggered
#first decode how the move altered the node heirarchy
if @moving_over
#check if a valid scenario for moving group
group_valid = 0
if @insertion_mode is 0 and @moving_over.data('represents')[0].nodeName not in window.void_element_list
group_valid = 1
if @insertion_mode in [1,2] and @moving_over.data('top_doc') isnt 1
group_valid = 1
if group_valid is 1
for node in $('#outliner_group').children()
chosen = $(node).data('represents')
#store the offset before the move
chosen.data('cache_offset', chosen.offset() )
t = chosen.offset().top
l = chosen.offset().left
chosen.detach()
console.log '$$$: ', @moving_over.data('represents')
if @insertion_mode is 0
console.log 'insertion mode 0'
@moving_over.data('represents').append chosen
#chosen.offset( {top:t,left:l})
@moving_over.data('represents').data('outliner_expansion',1)
else if @insertion_mode is 1
console.log 'insertion mode 1'
console.log @moving_over.data('represents')
@moving_over.data('represents').before chosen
console.log @moving_over.data('represents')
else if @insertion_mode is 2
console.log 'insertion mode 2'
@moving_over.data('represents').after chosen
#reset the offset (but not for text childs)
#ep = chosen.parents('.edit-text')
#if ep.length < 0
# chosen.offset( {left:chosen.data('cache_offset').left, top:chosen.data('cache_offset').top} )
window.tools.history.save_state 'rearranged document heirarchy'
@recalc_outliner()
window.tools['select'].update_selection_set(0,1)
@clear_node_styles()
@mousedown_element = 0
mouseleave: (e)->
@clear_node_styles()
_t.tracking.show_outliner_mouseover = false
toggle_node_expansion: (node) ->
#console.log 'toggle',node
element = node.data('represents')
if element.data('outliner_expansion') is 0
element.data('outliner_expansion', 1) #expand
for child in element.children()
window.tools.outliner.recurse_node($(child), node, 0)
node.css('height', 'auto')
node.children('.arrow').attr('src', 'img/metric_expanded.png')
else if element.data('outliner_expansion') is 1
element.data('outliner_expansion', 0) #collapse
node.css('height', '17px')
node.children('.outliner_node').detach()
node.children('.arrow').attr('src', 'img/metric_collapsed.png')
@toolbox.resize()
remove_selections_from_collapse: (node)->
#possibly temporary, but deselct any children when collapsing a node
for child in node.find('.outliner_node')
e = $(child).data('represents')[0]
if e
#console.log e
if e in window.tools['select'].selected_elements
window.tools['select'].selected_elements.remove( e )
window.tools['select'].update_selection_set(0,1)
recurse_node: (element, parent, ecountered_selection) ->
#console.log 'recursing with:', ecountered_selection
#construct node from element and attach to parent, then recurse on any children of that element
#element and parent are jquery objects
###colorized_pos = '<span>'
if element.css('position') is 'absolute'
colorized_pos = '<span class="_outliner_absolute">'
if element.css('position') is 'relative'
colorized_pos = '<span class="_outliner_relative">'
if element.css('position') is 'fixed'
colorized_pos = '<span class="_outliner_fixed">'
if element.css('position') is 'static'
colorized_pos = '<span class="_outliner_static">'
###
if not element? or not element[0]?
return
id = element.attr('id')
if id is 'document' and element.parent().is($$('html'))
@setup_body_impostor = 1
eclass = ''
if element.attr('class')
eclass = "class='"+element.attr('class')+"'"
if not id
id = ''
ico = "<img class='eletype' src='./img/outliner_icon_div.png' />"
if element[0].nodeName is 'IMG'
ico = "<img class='eletype' src='./img/outliner_icon_img.png' />"
if element.hasClass('edit-text')
ico = "<img class='eletype' src='./img/outliner_icon_text.png' />"
if element[0].nodeName is 'P'
ico = "<img class='eletype' src='./img/outliner_icon_p.png' />"
style_link = '<div class="style_link"></div>'
arrow = ''
if element.children().length > 0
arrow = "<img class='arrow' src='./img/metric_expanded.png' />"
if @setup_body_impostor is 0
parent.append( '<p class="outliner_node"><span class="outliner_background">&nbsp;</span>'+arrow+'&#60;body&#62;</p>' )
@setup_body_impostor = 1
else
parent.append( '<p class="outliner_node"><span class="outliner_background">&nbsp;</span>'+arrow+'&#60;'+element[0].nodeName.toLowerCase()+'&#62; #'+'<span class="node-name">'+id+'</span> '+eclass+'</p>' )
child = parent.children().last()
child.append style_link
if element.attr('style') and element.attr('style').split(':').length > 1
child.addClass('has_inline')
child.data('represents', element )
#background = child.children('.outliner_background')
#background.offset
# left: $('#outliner_view').offset().left
#check the width, needed for scrolling the outliner on x axis
s = child.children('.node-name')
if s.length > 0
w = s.offset().left + s.width()
if w > @outliner_width
@outliner_width = w
#if a element is selected, expand all it's parents
if element[0] in window.tools['select'].selected_elements
for parent in child.parents()
r = $(parent).data('represents')
if r
if $(r).data('outliner_expansion') is 0
#console.log 'found collapsed parent: ', r
@toggle_node_expansion $(parent)
#indicate position
c = $(child.children('.eletype'))
#console.log c
if child.data('represents')
cp = child.data('represents').css('position')
if cp is 'relative'
c.css('background-color', '#f7bc7d')
if cp is 'absolute'
c.css('background-color', 'white')
if cp is 'fixed'
c.css('background-color', 'red')
#we're going to track the collapsed/expanded state of the outliner in the actual elements
exdata = element.data('outliner_expansion')
if exdata isnt 0 and exdata isnt 1
element.data('outliner_expansion', 0) #1 is expanded
else #expansion data allready exist
if exdata is 0
element.data('outliner_expansion', 1) #ugly
@toggle_node_expansion( child ) #default is expanded so we toggle it if previously setup
#check for selection indicator
#if ecountered_selection is 1
# child.css('color', '#2882dc')
if element[0] in window.tools['select'].selected_elements
if ecountered_selection is 0
ecountered_selection = 1
child.addClass 'outliner_selected'
child.mousedown (e) ->
window.tools.outliner.mousedown_element = this
if e.clientY < $(this).offset().top+18
if e.clientX > $(this).offset().left+18 #don't do selection if it's an arrow that was clicked
if $(child).data('top_doc') #prevent body from getting moved, just select it
else
window.tools['outliner'].moving = 1
window.tools['outliner'].move_start = [e.clientX, e.clientY]
window.tools['outliner'].move_whom = $(this)
child.mousemove (e) ->
#let outliner know I'm being moved in
#we test the mouse-y to only consider this node if it's not over a child
if e.clientY < $(this).offset().top+18
window.tools['outliner'].moving_over = $(this)
_t.tracking.show_outliner_mouseover = $(this).data('represents')
child.mouseup (e) ->
#if a move event wasn't called for, route this to the selection resolve
#if window.tools.outliner.mousedown_element is 0
# return
if e.clientY < $(this).offset().top+18
#the id span, which is clickable
span = $($(this).children('.node-name')[0])
so = span.offset()
sw = span.outerWidth()
#console.log '!====', span[0], window.tools.outliner.mousedown_element
if e.clientX < $(this).offset().left+18
#the arrow was clicked
if $(this).children('.arrow').length > 0 #make sure it's a collapsable node
window.tools['outliner'].remove_selections_from_collapse $(this)
window.tools['outliner'].toggle_node_expansion $(this)
window.tools['outliner'].moving = 0
else if so and window.tools.outliner.mousedown_element is span[0]
if e.clientX > so.left and e.clientX < so.left + sw # and $(this).hasClass('outliner_selected') and window.tools['select'].selected_elements.length is 1
#if clicking a solitary selected node, you can rename it
input = $('<input style="min-width:60px;" value="'+span.text()+'"">')
input.width span.width()
span.replaceWith input
input.focus()
window.tools.outliner.input_edit_mode = 1
input.change ->
$(this).parent().data('represents').attr('id', $(this).val())
$(this).replaceWith( '<span class="node-name">'+$(this).val()+'</span>' )
window.tools.outliner.input_edit_mode = 0
input.blur ->
$(this).replaceWith( '<span class="node-name">'+$(this).val()+'</span>' )
window.tools.outliner.input_edit_mode = 0
window.tools['outliner'].moving = 0
else if window.tools['outliner'].move_triggered is 0
sb = 0
target = $(this).data('represents')
if $(this).hasClass('outliner_selected')
sb = 1
console.log target
window.tools['select'].resolve(sb, $(this).data('represents')[0], 0, 0, 1 )
window.tools['outliner'].moving = 0
else
window.tools['outliner'].moving = 0
window.tools.outliner.mousedown_element = 0
#Now recurse any children
if element.data('forbidden') isnt 1 and exdata is 1
if element.children().length > 0
for sub in element.children()
@recurse_node( $(sub), child, ecountered_selection)
fix_mouse_event: (e) ->
nm = window.mouse_with_scroll(e.clientX, e.clientY)
e =
clientX: nm[0]
clientY: nm[1]
return e
recalc_outliner: () ->
console.log 'recalc_outliner()'
console.log $$('#document')
#recursivly navigate the DOM tree, creating the outliner proxies
@outliner_width = 0
view = $('#outliner_view')
view.html ''
#window.get_title_bar($('#outliner_view'),'Outliner')
@setup_body_impostor = 0
@recurse_node( $$('#document'), view, 0 )
view.children().first().data('top_doc', 1)
view.css('min-width', @outliner_width)
@toolbox.resize()
node_from_element: (element)->
#this assumes the node tree is exact match of the dom
if not element
return
element = $(element)[0]
recurse_map = (element, map)->
if element and element isnt $$('html')[0]
if element is $$('body')[0]
map.push 0
return
else
map.push $(element).index()
recurse_map $(element).parent()[0], map
e_map = []
#e_map.push $(element).index() #include the first node
recurse_map element, e_map
#console.log "NODE_FROM_ELEMENT:", element, e_map
e_map = e_map.reverse()
target = $('#outliner_view')
for i in e_map
if target.children('.outliner_node').length > 0
target = $(target.children('.outliner_node')[i])
else
console.log "OUTLINER - node_from_element - can't find exposed node"
#console.log target
if target.length <= 0
console.log "BAD NEWS OUTLINER: ", e_map
return target
recalc_outliner_selection: (mode=0) ->
#console.log 'recalc_outliner_selection, mode: ', mode
$('#outliner .outliner_node').removeClass 'outliner_selected'
highest = 9999999
lowest = -9999999
for element in window.tools.select.selected_elements
node = @node_from_element element
if node and node.length > 0
node.addClass 'outliner_selected'
node.data('selected', 1)
if mode is 0
n_o = node.offset()
if not n_o?
console.log "BAD NEWS OUTLINER: ", node
nn = n_o.top
if nn < highest
highest = nn
if nn > lowest
lowest = nn
if mode is 0
lowest += 30
#console.log '--- ', highest, lowest
t_h = $('#outliner > .title-bar').height()
if $('#outliner > .title-bar').css('display') is 'none'
t_h = 0
o_top = $('#outliner').offset().top + t_h
o_h = $('#outliner').height() - t_h
view = @toolbox.view
v_t = parseInt(view.css('top'))
v_h = view.height()
hnode = highest - o_top - v_t
lnode = lowest - o_top - v_t
#console.log hnode, lnode, v_t, v_h
animate_scroll = (v)->
v_perc = Math.abs(v) / v_h
scroll_y = _t.outliner.toolbox.scroll_y
scroll_bar = _t.outliner.toolbox.scrollbar_y
sh = scroll_y.height()
bh = scroll_bar.height()
available = sh - bh
scroll_bar.animate({top:sh*v_perc}, 480)
if hnode < (v_t*-1)
#console.log 'highest is above by: ', -hnode
view.stop()
view.animate {'top': -hnode},500, ()->
_t.outliner.toolbox.resize()
animate_scroll(-hnode)
else if lnode > (o_h + (v_t*-1))
#console.log 'lowest is below by: ', -(lnode-o_h)
view.stop()
view.animate {'top': -(lnode-o_h)},500, ()->
_t.outliner.toolbox.resize()
animate_scroll(-(lnode-o_h))
recalc_outliner_partial: (elements)->
# elements is a list of dom nodes
# for each, if it's expanded, recalculate it and any descendants.
# ignore if an element is descended from any other
for child in elements
node = @node_from_element child
if node
temp = $('<div></div>')
@recurse_node( $(child), temp, 0 )
node.html ''
node.replaceWith temp.children()[0]
highlight: (element)->
$('#outliner_view').children().each () ->
if $(this).data('represents')[0] is element
$(this).css('background-color', '#a0d4ff')
$(this).data('selected', 1)
unhighlight: (element)->
$('#outliner_view').children().each () ->
if $(this).data('represents')[0] is element
$(this).css('background-color', '#ffffff')
$(this).data('selected', 0)
clear_highlights: ()->
$('#outliner_view').children().each () ->
$(this).css('background-color', '#ffffff')
$(this).data('selected', 0)
copy_element: ->
if window.tools.text.manipulating
return
window.clipboard = []
#@recurse_clipboard( $$('#document'), window.clipboard )
_t.clipboard.copy_fp_selection()
recurse_clipboard: (element, finalx)->
for child in element.children()
if $(child).data('selection_box')
finalx.push $(child).clone('true')
else
@recurse_clipboard( $(child), finalx)
paste_element: ->
window.tools.history.check_incremental()
if window.tools.text.manipulating
return
topmost_el = false
topmost = 99999999
new_elements = []
for element in window.clipboard
element = $(element)
e = element.clone(false)
new_elements.push e[0]
for key of element.data()
console.log "DATA: ", key
# TODO: better data coverage
if key in ["userCreated", "outliner_expansion"]
e.data(key, element.data(key))
depth = $(element).parents().length
if depth < topmost
topmost = depth
topmost_el = element
if topmost_el is false
target = $$('#document')
else
target = topmost_el.parent()
for e in new_elements
target.append e
_t.select.selected_elements = new_elements
@recalc_outliner()
window.update_document_dimensions()
window.tools.history.save_state('paste elements')
delete_element: ->
l = _t.tracking.sb_active.length-1
for i in [0 .. l]
sb = _t.tracking.sb_active[0]
element = sb.represents
_t.tracking.free_selection_box sb
$(element).detach()
_t.select.selected_elements = []
_t.select.refresh_selection_handles()
@recalc_outliner()
window.update_document_dimensions()
window.tools.history.save_state('delete elements')
window.tools['outliner'].init()
window.tools['outliner'].recalc_outliner()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment