ZeroNet/plugins/Sidebar/media/Sidebar.coffee

626 lines
21 KiB
CoffeeScript

class Sidebar extends Class
constructor: (@wrapper) ->
@tag = null
@container = null
@opened = false
@width = 410
@console = new Console(@)
@fixbutton = $(".fixbutton")
@fixbutton_addx = 0
@fixbutton_addy = 0
@fixbutton_initx = 0
@fixbutton_inity = 15
@fixbutton_targetx = 0
@move_lock = null
@page_width = $(window).width()
@page_height = $(window).height()
@frame = $("#inner-iframe")
@initFixbutton()
@dragStarted = 0
@globe = null
@preload_html = null
@original_set_site_info = @wrapper.setSiteInfo # We going to override this, save the original
# Start in opened state for debugging
if window.top.location.hash == "#ZeroNet:OpenSidebar"
@startDrag()
@moved("x")
@fixbutton_targetx = @fixbutton_initx - @width
@stopDrag()
initFixbutton: ->
# Detect dragging
@fixbutton.on "mousedown touchstart", (e) =>
if e.button > 0 # Right or middle click
return
e.preventDefault()
# Disable previous listeners
@fixbutton.off "click touchend touchcancel"
# Make sure its not a click
@dragStarted = (+ new Date)
# Fullscreen drag bg to capture mouse events over iframe
$(".drag-bg").remove()
$("<div class='drag-bg'></div>").appendTo(document.body)
$("body").one "mousemove touchmove", (e) =>
mousex = e.pageX
mousey = e.pageY
if not mousex
mousex = e.originalEvent.touches[0].pageX
mousey = e.originalEvent.touches[0].pageY
@fixbutton_addx = @fixbutton.offset().left - mousex
@fixbutton_addy = @fixbutton.offset().top - mousey
@startDrag()
@fixbutton.parent().on "click touchend touchcancel", (e) =>
if (+ new Date) - @dragStarted < 100
window.top.location = @fixbutton.find(".fixbutton-bg").attr("href")
@stopDrag()
@resized()
$(window).on "resize", @resized
resized: =>
@page_width = $(window).width()
@page_height = $(window).height()
@fixbutton_initx = @page_width - 75 # Initial x position
if @opened
@fixbutton.css
left: @fixbutton_initx - @width
else
@fixbutton.css
left: @fixbutton_initx
# Start dragging the fixbutton
startDrag: ->
#@move_lock = "x" # Temporary until internals not finished
@log "startDrag", @fixbutton_initx, @fixbutton_inity
@fixbutton_targetx = @fixbutton_initx # Fallback x position
@fixbutton_targety = @fixbutton_inity # Fallback y position
@fixbutton.addClass("dragging")
# IE position wrap fix
if navigator.userAgent.indexOf('MSIE') != -1 or navigator.appVersion.indexOf('Trident/') > 0
@fixbutton.css("pointer-events", "none")
# Don't go to homepage
@fixbutton.one "click", (e) =>
@stopDrag()
@fixbutton.removeClass("dragging")
moved_x = Math.abs(@fixbutton.offset().left - @fixbutton_initx)
moved_y = Math.abs(@fixbutton.offset().top - @fixbutton_inity)
if moved_x > 5 or moved_y > 10
# If moved more than some pixel the button then don't go to homepage
e.preventDefault()
# Animate drag
@fixbutton.parents().on "mousemove touchmove", @animDrag
@fixbutton.parents().on "mousemove touchmove" ,@waitMove
# Stop dragging listener
@fixbutton.parents().one "mouseup touchend touchcancel", (e) =>
e.preventDefault()
@stopDrag()
# Wait for moving the fixbutton
waitMove: (e) =>
document.body.style.perspective = "1000px"
document.body.style.height = "100%"
document.body.style.willChange = "perspective"
document.documentElement.style.height = "100%"
#$(document.body).css("backface-visibility", "hidden").css("perspective", "1000px").css("height", "900px")
# $("iframe").css("backface-visibility", "hidden")
moved_x = Math.abs(parseInt(@fixbutton[0].style.left) - @fixbutton_targetx)
moved_y = Math.abs(parseInt(@fixbutton[0].style.top) - @fixbutton_targety)
if moved_x > 5 and (+ new Date) - @dragStarted + moved_x > 50
@moved("x")
@fixbutton.stop().animate {"top": @fixbutton_inity}, 1000
@fixbutton.parents().off "mousemove touchmove" ,@waitMove
else if moved_y > 5 and (+ new Date) - @dragStarted + moved_y > 50
@moved("y")
@fixbutton.parents().off "mousemove touchmove" ,@waitMove
moved: (direction) ->
@log "Moved", direction
@move_lock = direction
if direction == "y"
$(document.body).addClass("body-console")
return @console.createHtmltag()
@createHtmltag()
$(document.body).addClass("body-sidebar")
@container.on "mousedown touchend touchcancel", (e) =>
if e.target != e.currentTarget
return true
@log "closing"
if $(document.body).hasClass("body-sidebar")
@close()
return true
$(window).off "resize"
$(window).on "resize", =>
$(document.body).css "height", $(window).height()
@scrollable()
@resized()
# Override setsiteinfo to catch changes
@wrapper.setSiteInfo = (site_info) =>
@setSiteInfo(site_info)
@original_set_site_info.apply(@wrapper, arguments)
# Preload world.jpg
img = new Image();
img.src = "/uimedia/globe/world.jpg";
setSiteInfo: (site_info) ->
RateLimit 1500, =>
@updateHtmlTag()
RateLimit 30000, =>
@displayGlobe()
# Create the sidebar html tag
createHtmltag: ->
@when_loaded = $.Deferred()
if not @container
@container = $("""
<div class="sidebar-container"><div class="sidebar scrollable"><div class="content-wrapper"><div class="content">
</div></div></div></div>
""")
@container.appendTo(document.body)
@tag = @container.find(".sidebar")
@updateHtmlTag()
@scrollable = window.initScrollable()
updateHtmlTag: ->
if @preload_html
@setHtmlTag(@preload_html)
@preload_html = null
else
@wrapper.ws.cmd "sidebarGetHtmlTag", {}, @setHtmlTag
setHtmlTag: (res) =>
if @tag.find(".content").children().length == 0 # First update
@log "Creating content"
@container.addClass("loaded")
morphdom(@tag.find(".content")[0], '<div class="content">'+res+'</div>')
# @scrollable()
@when_loaded.resolve()
else # Not first update, patch the html to keep unchanged dom elements
morphdom @tag.find(".content")[0], '<div class="content">'+res+'</div>', {
onBeforeMorphEl: (from_el, to_el) -> # Ignore globe loaded state
if from_el.className == "globe" or from_el.className.indexOf("noupdate") >= 0
return false
else
return true
}
# Save and forget privatekey for site signing
@tag.find("#privatekey-add").off("click, touchend").on "click touchend", (e) =>
@wrapper.displayPrompt "Enter your private key:", "password", "Save", "", (privatekey) =>
@wrapper.ws.cmd "userSetSitePrivatekey", [privatekey], (res) =>
@wrapper.notifications.add "privatekey", "done", "Private key saved for site signing", 5000
return false
@tag.find("#privatekey-forget").off("click, touchend").on "click touchend", (e) =>
@wrapper.displayConfirm "Remove saved private key for this site?", "Forget", (res) =>
if not res
return false
@wrapper.ws.cmd "userSetSitePrivatekey", [""], (res) =>
@wrapper.notifications.add "privatekey", "done", "Saved private key removed", 5000
return false
animDrag: (e) =>
mousex = e.pageX
mousey = e.pageY
if not mousex and e.originalEvent.touches
mousex = e.originalEvent.touches[0].pageX
mousey = e.originalEvent.touches[0].pageY
overdrag = @fixbutton_initx - @width - mousex
if overdrag > 0 # Overdragged
overdrag_percent = 1 + overdrag/300
mousex = (mousex + (@fixbutton_initx-@width)*overdrag_percent)/(1+overdrag_percent)
targetx = @fixbutton_initx - mousex - @fixbutton_addx
targety = @fixbutton_inity - mousey - @fixbutton_addy
if @move_lock == "x"
targety = @fixbutton_inity
else if @move_lock == "y"
targetx = @fixbutton_initx
if not @move_lock or @move_lock == "x"
@fixbutton[0].style.left = (mousex + @fixbutton_addx) + "px"
if @tag
@tag[0].style.transform = "translateX(#{0 - targetx}px)"
if not @move_lock or @move_lock == "y"
@fixbutton[0].style.top = (mousey + @fixbutton_addy) + "px"
if @console.tag
@console.tag[0].style.transform = "translateY(#{0 - targety}px)"
#if @move_lock == "x"
# @fixbutton[0].style.left = "#{@fixbutton_targetx} px"
#@fixbutton[0].style.top = "#{@fixbutton_inity}px"
#if @move_lock == "y"
# @fixbutton[0].style.top = "#{@fixbutton_targety} px"
# Check if opened
if (not @opened and targetx > @width/3) or (@opened and targetx > @width*0.9)
@fixbutton_targetx = @fixbutton_initx - @width # Make it opened
else
@fixbutton_targetx = @fixbutton_initx
if (not @console.opened and 0 - targety > @page_height/10) or (@console.opened and 0 - targety > @page_height*0.8)
@fixbutton_targety = @page_height - @fixbutton_inity - 50
else
@fixbutton_targety = @fixbutton_inity
# Stop dragging the fixbutton
stopDrag: ->
@fixbutton.parents().off "mousemove touchmove"
@fixbutton.off "mousemove touchmove"
@fixbutton.css("pointer-events", "")
$(".drag-bg").remove()
if not @fixbutton.hasClass("dragging")
return
@fixbutton.removeClass("dragging")
# Move back to initial position
if @fixbutton_targetx != @fixbutton.offset().left or @fixbutton_targety != @fixbutton.offset().top
# Animate fixbutton
if @move_lock == "y"
top = @fixbutton_targety
left = @fixbutton_initx
if @move_lock == "x"
top = @fixbutton_inity
left = @fixbutton_targetx
@fixbutton.stop().animate {"left": left, "top": top}, 500, "easeOutBack", =>
# Switch back to auto align
if @fixbutton_targetx == @fixbutton_initx # Closed
@fixbutton.css("left", "auto")
else # Opened
@fixbutton.css("left", left)
$(".fixbutton-bg").trigger "mouseout" # Switch fixbutton back to normal status
@stopDragX()
@console.stopDragY()
@move_lock = null
stopDragX: ->
# Animate sidebar and iframe
if @fixbutton_targetx == @fixbutton_initx or @move_lock == "y"
# Closed
targetx = 0
@opened = false
else
# Opened
targetx = @width
if @opened
@onOpened()
else
@when_loaded.done =>
@onOpened()
@opened = true
# Revent sidebar transitions
if @tag
@tag.css("transition", "0.4s ease-out")
@tag.css("transform", "translateX(-#{targetx}px)").one transitionEnd, =>
@tag.css("transition", "")
if not @opened
@container.remove()
@container = null
if @tag
@tag.remove()
@tag = null
# Revert body transformations
@log "stopdrag", "opened:", @opened
if not @opened
@onClosed()
sign: (inner_path, privatekey) ->
@wrapper.displayProgress("sign", "Signing: #{inner_path}...", 0)
@wrapper.ws.cmd "siteSign", {privatekey: privatekey, inner_path: inner_path, update_changed_files: true}, (res) =>
if res == "ok"
@wrapper.displayProgress("sign", "#{inner_path} signed!", 100)
else
@wrapper.displayProgress("sign", "Error signing #{inner_path}", -1)
publish: (inner_path, privatekey) ->
@wrapper.ws.cmd "sitePublish", {privatekey: privatekey, inner_path: inner_path, sign: true, update_changed_files: true}, (res) =>
if res == "ok"
@wrapper.notifications.add "sign", "done", "#{inner_path} Signed and published!", 5000
onOpened: ->
@log "Opened"
@scrollable()
# Re-calculate height when site admin opened or closed
@tag.find("#checkbox-owned, #checkbox-autodownloadoptional").off("click touchend").on "click touchend", =>
setTimeout (=>
@scrollable()
), 300
# Site limit button
@tag.find("#button-sitelimit").off("click touchend").on "click touchend", =>
@wrapper.ws.cmd "siteSetLimit", $("#input-sitelimit").val(), (res) =>
if res == "ok"
@wrapper.notifications.add "done-sitelimit", "done", "Site storage limit modified!", 5000
@updateHtmlTag()
return false
# Site autodownload limit button
@tag.find("#button-autodownload_bigfile_size_limit").off("click touchend").on "click touchend", =>
@wrapper.ws.cmd "siteSetAutodownloadBigfileLimit", $("#input-autodownload_bigfile_size_limit").val(), (res) =>
if res == "ok"
@wrapper.notifications.add "done-bigfilelimit", "done", "Site bigfile auto download limit modified!", 5000
@updateHtmlTag()
return false
# Site start download optional files
@tag.find("#button-autodownload_previous").off("click touchend").on "click touchend", =>
@wrapper.ws.cmd "siteUpdate", {"address": @wrapper.site_info.address, "check_files": true}, =>
@wrapper.notifications.add "done-download_optional", "done", "Optional files downloaded", 5000
@wrapper.notifications.add "start-download_optional", "info", "Optional files download started", 5000
return false
# Database reload
@tag.find("#button-dbreload").off("click touchend").on "click touchend", =>
@wrapper.ws.cmd "dbReload", [], =>
@wrapper.notifications.add "done-dbreload", "done", "Database schema reloaded!", 5000
@updateHtmlTag()
return false
# Database rebuild
@tag.find("#button-dbrebuild").off("click touchend").on "click touchend", =>
@wrapper.notifications.add "done-dbrebuild", "info", "Database rebuilding...."
@wrapper.ws.cmd "dbRebuild", [], =>
@wrapper.notifications.add "done-dbrebuild", "done", "Database rebuilt!", 5000
@updateHtmlTag()
return false
# Update site
@tag.find("#button-update").off("click touchend").on "click touchend", =>
@tag.find("#button-update").addClass("loading")
@wrapper.ws.cmd "siteUpdate", @wrapper.site_info.address, =>
@wrapper.notifications.add "done-updated", "done", "Site updated!", 5000
@tag.find("#button-update").removeClass("loading")
return false
# Pause site
@tag.find("#button-pause").off("click touchend").on "click touchend", =>
@tag.find("#button-pause").addClass("hidden")
@wrapper.ws.cmd "sitePause", @wrapper.site_info.address
return false
# Resume site
@tag.find("#button-resume").off("click touchend").on "click touchend", =>
@tag.find("#button-resume").addClass("hidden")
@wrapper.ws.cmd "siteResume", @wrapper.site_info.address
return false
# Delete site
@tag.find("#button-delete").off("click touchend").on "click touchend", =>
@wrapper.displayConfirm "Are you sure?", ["Delete this site", "Blacklist"], (confirmed) =>
if confirmed == 1
@tag.find("#button-delete").addClass("loading")
@wrapper.ws.cmd "siteDelete", @wrapper.site_info.address, ->
document.location = $(".fixbutton-bg").attr("href")
else if confirmed == 2
@wrapper.displayPrompt "Blacklist this site", "text", "Delete and Blacklist", "Reason", (reason) =>
@tag.find("#button-delete").addClass("loading")
@wrapper.ws.cmd "siteblockAdd", [@wrapper.site_info.address, reason]
@wrapper.ws.cmd "siteDelete", @wrapper.site_info.address, ->
document.location = $(".fixbutton-bg").attr("href")
return false
# Owned checkbox
@tag.find("#checkbox-owned").off("click touchend").on "click touchend", =>
@wrapper.ws.cmd "siteSetOwned", [@tag.find("#checkbox-owned").is(":checked")]
# Owned checkbox
@tag.find("#checkbox-autodownloadoptional").off("click touchend").on "click touchend", =>
@wrapper.ws.cmd "siteSetAutodownloadoptional", [@tag.find("#checkbox-autodownloadoptional").is(":checked")]
# Change identity button
@tag.find("#button-identity").off("click touchend").on "click touchend", =>
@wrapper.ws.cmd "certSelect"
return false
# Save settings
@tag.find("#button-settings").off("click touchend").on "click touchend", =>
@wrapper.ws.cmd "fileGet", "content.json", (res) =>
data = JSON.parse(res)
data["title"] = $("#settings-title").val()
data["description"] = $("#settings-description").val()
json_raw = unescape(encodeURIComponent(JSON.stringify(data, undefined, '\t')))
@wrapper.ws.cmd "fileWrite", ["content.json", btoa(json_raw), true], (res) =>
if res != "ok" # fileWrite failed
@wrapper.notifications.add "file-write", "error", "File write error: #{res}"
else
@wrapper.notifications.add "file-write", "done", "Site settings saved!", 5000
if @wrapper.site_info.privatekey
@wrapper.ws.cmd "siteSign", {privatekey: "stored", inner_path: "content.json", update_changed_files: true}
@updateHtmlTag()
return false
# Open site directory
@tag.find("#link-directory").off("click touchend").on "click touchend", =>
@wrapper.ws.cmd "serverShowdirectory", ["site", @wrapper.site_info.address]
return false
# Copy site with peers
@tag.find("#link-copypeers").off("click touchend").on "click touchend", (e) =>
copy_text = e.currentTarget.href
handler = (e) =>
e.clipboardData.setData('text/plain', copy_text)
e.preventDefault()
@wrapper.notifications.add "copy", "done", "Site address with peers copied to your clipboard", 5000
document.removeEventListener('copy', handler, true)
document.addEventListener('copy', handler, true)
document.execCommand('copy')
return false
# Sign and publish content.json
$(document).on "click touchend", =>
@tag?.find("#button-sign-publish-menu").removeClass("visible")
@tag?.find(".contents + .flex").removeClass("sign-publish-flex")
@tag.find(".contents-content").off("click touchend").on "click touchend", (e) =>
$("#input-contents").val(e.currentTarget.innerText);
return false;
menu = new Menu(@tag.find("#menu-sign-publish"))
menu.elem.css("margin-top", "-130px") # Open upwards
menu.addItem "Sign", =>
inner_path = @tag.find("#input-contents").val()
@wrapper.ws.cmd "fileRules", {inner_path: inner_path}, (rules) =>
if @wrapper.site_info.auth_address in rules.signers
# ZeroID or other ID provider
@sign(inner_path)
else if @wrapper.site_info.privatekey
# Privatekey stored in users.json
@sign(inner_path, "stored")
else
# Ask the user for privatekey
@wrapper.displayPrompt "Enter your private key:", "password", "Sign", "", (privatekey) => # Prompt the private key
@sign(inner_path, privatekey)
@tag.find(".contents + .flex").removeClass "active"
menu.hide()
menu.addItem "Publish", =>
inner_path = @tag.find("#input-contents").val()
@wrapper.ws.cmd "sitePublish", {"inner_path": inner_path, "sign": false}
@tag.find(".contents + .flex").removeClass "active"
menu.hide()
@tag.find("#menu-sign-publish").off("click touchend").on "click touchend", =>
if window.visible_menu == menu
@tag.find(".contents + .flex").removeClass "active"
menu.hide()
else
@tag.find(".contents + .flex").addClass "active"
@tag.find(".content-wrapper").prop "scrollTop", 10000
menu.show()
return false
$("body").on "click", =>
if @tag
@tag.find(".contents + .flex").removeClass "active"
@tag.find("#button-sign-publish").off("click touchend").on "click touchend", =>
inner_path = @tag.find("#input-contents").val()
@wrapper.ws.cmd "fileRules", {inner_path: inner_path}, (rules) =>
if @wrapper.site_info.auth_address in rules.signers
# ZeroID or other ID provider
@publish(inner_path, null)
else if @wrapper.site_info.privatekey
# Privatekey stored in users.json
@publish(inner_path, "stored")
else
# Ask the user for privatekey
@wrapper.displayPrompt "Enter your private key:", "password", "Sign", "", (privatekey) => # Prompt the private key
@publish(inner_path, privatekey)
return false
# Close
@tag.find(".close").off("click touchend").on "click touchend", (e) =>
@close()
return false
@loadGlobe()
close: ->
@move_lock = "x"
@startDrag()
@stopDrag()
onClosed: ->
$(window).off "resize"
$(window).on "resize", @resized
$(document.body).css("transition", "0.6s ease-in-out").removeClass("body-sidebar").on transitionEnd, (e) =>
if e.target == document.body and not $(document.body).hasClass("body-sidebar") and not $(document.body).hasClass("body-console")
$(document.body).css("height", "auto").css("perspective", "").css("will-change", "").css("transition", "").off transitionEnd
@unloadGlobe()
# We dont need site info anymore
@wrapper.setSiteInfo = @original_set_site_info
loadGlobe: =>
if @tag.find(".globe").hasClass("loading")
setTimeout (=>
if typeof(DAT) == "undefined" # Globe script not loaded, do it first
script_tag = $("<script>")
script_tag.attr("nonce", @wrapper.script_nonce)
script_tag.attr("src", "/uimedia/globe/all.js")
script_tag.on("load", @displayGlobe)
document.head.appendChild(script_tag[0])
else
@displayGlobe()
), 600
displayGlobe: =>
img = new Image();
img.src = "/uimedia/globe/world.jpg";
img.onload = =>
@wrapper.ws.cmd "sidebarGetPeers", [], (globe_data) =>
if @globe
@globe.scene.remove(@globe.points)
@globe.addData( globe_data, {format: 'magnitude', name: "hello", animated: false} )
@globe.createPoints()
@tag?.find(".globe").removeClass("loading")
else if typeof(DAT) != "undefined"
try
@globe = new DAT.Globe( @tag.find(".globe")[0], {"imgDir": "/uimedia/globe/"} )
@globe.addData( globe_data, {format: 'magnitude', name: "hello"} )
@globe.createPoints()
@globe.animate()
catch e
console.log "WebGL error", e
@tag?.find(".globe").addClass("error").text("WebGL not supported")
@tag?.find(".globe").removeClass("loading")
unloadGlobe: =>
if not @globe
return false
@globe.unload()
@globe = null
wrapper = window.wrapper
setTimeout ( ->
window.sidebar = new Sidebar(wrapper)
), 500
window.transitionEnd = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend'