First release, remove not used lines from gitignore
This commit is contained in:
parent
c0bfb3b062
commit
d28e1cb4a6
85 changed files with 7205 additions and 50 deletions
286
src/Ui/UiRequest.py
Normal file
286
src/Ui/UiRequest.py
Normal file
|
@ -0,0 +1,286 @@
|
|||
import time, re, os, mimetypes, json
|
||||
from Config import config
|
||||
from Site import SiteManager
|
||||
from Ui.UiWebsocket import UiWebsocket
|
||||
|
||||
status_texts = {
|
||||
200: "200 OK",
|
||||
400: "400 Bad Request",
|
||||
403: "403 Forbidden",
|
||||
404: "404 Not Found",
|
||||
}
|
||||
|
||||
|
||||
|
||||
class UiRequest:
|
||||
def __init__(self, server = None):
|
||||
if server:
|
||||
self.server = server
|
||||
self.log = server.log
|
||||
self.get = {} # Get parameters
|
||||
self.env = {} # Enviroment settings
|
||||
self.start_response = None # Start response function
|
||||
|
||||
|
||||
# Call the request handler function base on path
|
||||
def route(self, path):
|
||||
if config.ui_restrict and self.env['REMOTE_ADDR'] != config.ui_restrict: # Restict Ui access by ip
|
||||
return self.error403()
|
||||
|
||||
if path == "/":
|
||||
return self.actionIndex()
|
||||
elif path == "/favicon.ico":
|
||||
return self.actionFile("src/Ui/media/img/favicon.ico")
|
||||
# Media
|
||||
elif path.startswith("/uimedia/"):
|
||||
return self.actionUiMedia(path)
|
||||
elif path.startswith("/media"):
|
||||
return self.actionSiteMedia(path)
|
||||
# Websocket
|
||||
elif path == "/Websocket":
|
||||
return self.actionWebsocket()
|
||||
# Debug
|
||||
elif path == "/Debug" and config.debug:
|
||||
return self.actionDebug()
|
||||
elif path == "/Console" and config.debug:
|
||||
return self.actionConsole()
|
||||
# Test
|
||||
elif path == "/Test/Websocket":
|
||||
return self.actionFile("Data/temp/ws_test.html")
|
||||
elif path == "/Test/Stream":
|
||||
return self.actionTestStream()
|
||||
# Site media wrapper
|
||||
else:
|
||||
return self.actionWrapper(path)
|
||||
|
||||
|
||||
# Get mime by filename
|
||||
def getContentType(self, file_name):
|
||||
content_type = mimetypes.guess_type(file_name)[0]
|
||||
if not content_type:
|
||||
if file_name.endswith("json"): # Correct json header
|
||||
content_type = "application/json"
|
||||
else:
|
||||
content_type = "application/octet-stream"
|
||||
return content_type
|
||||
|
||||
|
||||
# Send response headers
|
||||
def sendHeader(self, status=200, content_type="text/html; charset=utf-8", extra_headers=[]):
|
||||
headers = []
|
||||
headers.append(("Version", "HTTP/1.1"))
|
||||
headers.append(("Access-Control-Allow-Origin", "*")) # Allow json access
|
||||
headers.append(("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")) # Allow json access
|
||||
headers.append(("Cache-Control", "no-cache, no-store, private, must-revalidate, max-age=0")) # No caching at all
|
||||
#headers.append(("Cache-Control", "public, max-age=604800")) # Cache 1 week
|
||||
headers.append(("Content-Type", content_type))
|
||||
for extra_header in extra_headers:
|
||||
headers.append(extra_header)
|
||||
self.start_response(status_texts[status], headers)
|
||||
|
||||
|
||||
# Renders a template
|
||||
def render(self, template_path, *args, **kwargs):
|
||||
#template = SimpleTemplate(open(template_path), lookup=[os.path.dirname(template_path)])
|
||||
#yield str(template.render(*args, **kwargs).encode("utf8"))
|
||||
template = open(template_path).read()
|
||||
yield template.format(**kwargs)
|
||||
|
||||
|
||||
# - Actions -
|
||||
|
||||
# Redirect to an url
|
||||
def actionRedirect(self, url):
|
||||
self.start_response('301 Redirect', [('Location', url)])
|
||||
yield "Location changed: %s" % url
|
||||
|
||||
|
||||
def actionIndex(self):
|
||||
return self.actionRedirect("/"+config.homepage)
|
||||
|
||||
|
||||
# Render a file from media with iframe site wrapper
|
||||
def actionWrapper(self, path):
|
||||
if self.env.get("HTTP_X_REQUESTED_WITH"): return self.error403() # No ajax allowed on wrapper
|
||||
|
||||
match = re.match("/(?P<site>[A-Za-z0-9]+)(?P<inner_path>/.*|$)", path)
|
||||
if match:
|
||||
inner_path = match.group("inner_path").lstrip("/")
|
||||
if not inner_path: inner_path = "index.html" # If inner path defaults to index.html
|
||||
|
||||
site = self.server.sites.get(match.group("site"))
|
||||
if site and site.content and not site.bad_files: # Its downloaded
|
||||
title = site.content["title"]
|
||||
else:
|
||||
title = "Loading %s..." % match.group("site")
|
||||
site = SiteManager.need(match.group("site")) # Start download site
|
||||
if not site: self.error404()
|
||||
|
||||
|
||||
self.sendHeader(extra_headers=[("X-Frame-Options", "DENY")])
|
||||
return self.render("src/Ui/template/wrapper.html",
|
||||
inner_path=inner_path,
|
||||
address=match.group("site"),
|
||||
title=title,
|
||||
auth_key=site.settings["auth_key"],
|
||||
permissions=json.dumps(site.settings["permissions"]),
|
||||
show_loadingscreen=json.dumps(not os.path.isfile(site.getPath(inner_path))),
|
||||
homepage=config.homepage
|
||||
)
|
||||
|
||||
else: # Bad url
|
||||
return self.error404(path)
|
||||
|
||||
|
||||
# Serve a media for site
|
||||
def actionSiteMedia(self, path):
|
||||
match = re.match("/media/(?P<site>[A-Za-z0-9]+)/(?P<inner_path>.*)", path)
|
||||
|
||||
referer = self.env.get("HTTP_REFERER")
|
||||
if referer: # Only allow same site to receive media
|
||||
referer = re.sub("http://.*?/", "/", referer) # Remove server address
|
||||
referer = referer.replace("/media", "") # Media
|
||||
if not referer.startswith("/"+match.group("site")): return self.error403() # Referer not starts same address as requested path
|
||||
|
||||
if match: # Looks like a valid path
|
||||
file_path = "data/%s/%s" % (match.group("site"), match.group("inner_path"))
|
||||
allowed_dir = os.path.abspath("data/%s" % match.group("site")) # Only files within data/sitehash allowed
|
||||
if ".." in file_path or not os.path.dirname(os.path.abspath(file_path)).startswith(allowed_dir): # File not in allowed path
|
||||
return self.error403()
|
||||
else:
|
||||
if config.debug and file_path.split("/")[-1].startswith("all."): # When debugging merge *.css to all.css and *.js to all.js
|
||||
site = self.server.sites.get(match.group("site"))
|
||||
if site.settings["own"]:
|
||||
from Debug import DebugMedia
|
||||
DebugMedia.merge(file_path)
|
||||
if os.path.isfile(file_path): # File exits
|
||||
return self.actionFile(file_path)
|
||||
else: # File not exits, try to download
|
||||
site = SiteManager.need(match.group("site"), all_file=False)
|
||||
self.sendHeader(content_type=self.getContentType(file_path)) # ?? Get Exception without this
|
||||
result = site.needFile(match.group("inner_path")) # Wait until file downloads
|
||||
return self.actionFile(file_path)
|
||||
|
||||
else: # Bad url
|
||||
return self.error404(path)
|
||||
|
||||
|
||||
# Serve a media for ui
|
||||
def actionUiMedia(self, path):
|
||||
match = re.match("/uimedia/(?P<inner_path>.*)", path)
|
||||
if match: # Looks like a valid path
|
||||
file_path = "src/Ui/media/%s" % match.group("inner_path")
|
||||
allowed_dir = os.path.abspath("src/Ui/media") # Only files within data/sitehash allowed
|
||||
if ".." in file_path or not os.path.dirname(os.path.abspath(file_path)).startswith(allowed_dir): # File not in allowed path
|
||||
return self.error403()
|
||||
else:
|
||||
if config.debug and match.group("inner_path").startswith("all."): # When debugging merge *.css to all.css and *.js to all.js
|
||||
from Debug import DebugMedia
|
||||
DebugMedia.merge(file_path)
|
||||
return self.actionFile(file_path)
|
||||
else: # Bad url
|
||||
return self.error400()
|
||||
|
||||
|
||||
# Stream a file to client
|
||||
def actionFile(self, file_path, block_size = 64*1024):
|
||||
if os.path.isfile(file_path):
|
||||
# Try to figure out content type by extension
|
||||
content_type = self.getContentType(file_path)
|
||||
|
||||
self.sendHeader(content_type = content_type) # TODO: Dont allow external access: extra_headers=[("Content-Security-Policy", "default-src 'unsafe-inline' data: http://localhost:43110 ws://localhost:43110")]
|
||||
if self.env["REQUEST_METHOD"] != "OPTIONS":
|
||||
file = open(file_path, "rb")
|
||||
while 1:
|
||||
try:
|
||||
block = file.read(block_size)
|
||||
if block:
|
||||
yield block
|
||||
else:
|
||||
raise StopIteration
|
||||
except StopIteration:
|
||||
file.close()
|
||||
break
|
||||
else: # File not exits
|
||||
yield self.error404(file_path)
|
||||
|
||||
|
||||
# On websocket connection
|
||||
def actionWebsocket(self):
|
||||
ws = self.env.get("wsgi.websocket")
|
||||
if ws:
|
||||
auth_key = self.get["auth_key"]
|
||||
# Find site by auth_key
|
||||
site = None
|
||||
for site_check in self.server.sites.values():
|
||||
if site_check.settings["auth_key"] == auth_key: site = site_check
|
||||
|
||||
if site: # Correct auth key
|
||||
ui_websocket = UiWebsocket(ws, site, self.server)
|
||||
site.websockets.append(ui_websocket) # Add to site websockets to allow notify on events
|
||||
ui_websocket.start()
|
||||
for site_check in self.server.sites.values(): # Remove websocket from every site (admin sites allowed to join other sites event channels)
|
||||
if ui_websocket in site_check.websockets:
|
||||
site_check.websockets.remove(ui_websocket)
|
||||
return "Bye."
|
||||
else: # No site found by auth key
|
||||
self.log.error("Auth key not found: %s" % auth_key)
|
||||
return self.error403()
|
||||
else:
|
||||
start_response("400 Bad Request", [])
|
||||
return "Not a websocket!"
|
||||
|
||||
|
||||
# Debug last error
|
||||
def actionDebug(self):
|
||||
# Raise last error from DebugHook
|
||||
import sys
|
||||
last_error = sys.modules["src.main"].DebugHook.last_error
|
||||
if last_error:
|
||||
raise last_error[0], last_error[1], last_error[2]
|
||||
else:
|
||||
self.sendHeader()
|
||||
yield "No error! :)"
|
||||
|
||||
|
||||
# Just raise an error to get console
|
||||
def actionConsole(self):
|
||||
raise Exception("Here is your console")
|
||||
|
||||
|
||||
# - Tests -
|
||||
|
||||
def actionTestStream(self):
|
||||
self.sendHeader()
|
||||
yield " "*1080 # Overflow browser's buffer
|
||||
yield "He"
|
||||
time.sleep(1)
|
||||
yield "llo!"
|
||||
yield "Running websockets: %s" % len(self.server.websockets)
|
||||
self.server.sendMessage("Hello!")
|
||||
|
||||
|
||||
# - Errors -
|
||||
|
||||
# Send bad request error
|
||||
def error400(self):
|
||||
self.sendHeader(400)
|
||||
return "Bad Request"
|
||||
|
||||
|
||||
# You are not allowed to access this
|
||||
def error403(self):
|
||||
self.sendHeader(403)
|
||||
return "Forbidden"
|
||||
|
||||
|
||||
# Send file not found error
|
||||
def error404(self, path = None):
|
||||
self.sendHeader(404)
|
||||
return "Not Found: %s" % path
|
||||
|
||||
# - Reload for eaiser developing -
|
||||
def reload(self):
|
||||
import imp
|
||||
global UiWebsocket
|
||||
UiWebsocket = imp.load_source("UiWebsocket", "src/Ui/UiWebsocket.py").UiWebsocket
|
93
src/Ui/UiServer.py
Normal file
93
src/Ui/UiServer.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
from gevent import monkey; monkey.patch_all(thread = False)
|
||||
import logging, time, cgi, string, random
|
||||
from gevent.pywsgi import WSGIServer
|
||||
from gevent.pywsgi import WSGIHandler
|
||||
from lib.geventwebsocket.handler import WebSocketHandler
|
||||
from Ui import UiRequest
|
||||
from Site import SiteManager
|
||||
from Config import config
|
||||
|
||||
# Skip websocket handler if not necessary
|
||||
class UiWSGIHandler(WSGIHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UiWSGIHandler, self).__init__(*args, **kwargs)
|
||||
self.ws_handler = WebSocketHandler(*args, **kwargs)
|
||||
|
||||
|
||||
def run_application(self):
|
||||
if "HTTP_UPGRADE" in self.environ: # Websocket request
|
||||
self.ws_handler.__dict__ = self.__dict__ # Match class variables
|
||||
self.ws_handler.run_application()
|
||||
else: # Standard HTTP request
|
||||
#print self.application.__class__.__name__
|
||||
return super(UiWSGIHandler, self).run_application()
|
||||
|
||||
|
||||
class UiServer:
|
||||
def __init__(self):
|
||||
self.ip = config.ui_ip
|
||||
self.port = config.ui_port
|
||||
if self.ip == "*": self.ip = "" # Bind all
|
||||
#self.sidebar_websockets = [] # Sidebar websocket connections
|
||||
#self.auth_key = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(12)) # Global admin auth key
|
||||
self.sites = SiteManager.list()
|
||||
self.log = logging.getLogger(__name__)
|
||||
|
||||
self.ui_request = UiRequest(self)
|
||||
|
||||
|
||||
# Handle WSGI request
|
||||
def handleRequest(self, env, start_response):
|
||||
path = env["PATH_INFO"]
|
||||
self.ui_request.env = env
|
||||
self.ui_request.start_response = start_response
|
||||
if env.get("QUERY_STRING"):
|
||||
self.ui_request.get = dict(cgi.parse_qsl(env['QUERY_STRING']))
|
||||
else:
|
||||
self.ui_request.get = {}
|
||||
return self.ui_request.route(path)
|
||||
|
||||
|
||||
# Send a message to all connected client
|
||||
def sendMessage(self, message):
|
||||
sent = 0
|
||||
for ws in self.websockets:
|
||||
try:
|
||||
ws.send(message)
|
||||
sent += 1
|
||||
except Exception, err:
|
||||
self.log.error("addMessage error: %s" % err)
|
||||
self.server.websockets.remove(ws)
|
||||
return sent
|
||||
|
||||
|
||||
# Reload the UiRequest class to prevent restarts in debug mode
|
||||
def reload(self):
|
||||
import imp
|
||||
self.ui_request = imp.load_source("UiRequest", "src/Ui/UiRequest.py").UiRequest(self)
|
||||
self.ui_request.reload()
|
||||
|
||||
|
||||
# Bind and run the server
|
||||
def start(self):
|
||||
handler = self.handleRequest
|
||||
|
||||
if config.debug:
|
||||
# Auto reload UiRequest on change
|
||||
from Debug import DebugReloader
|
||||
DebugReloader(self.reload)
|
||||
|
||||
# Werkzeug Debugger
|
||||
try:
|
||||
from werkzeug.debug import DebuggedApplication
|
||||
handler = DebuggedApplication(self.handleRequest, evalex=True)
|
||||
except Exception, err:
|
||||
self.log.info("%s: For debugging please download Werkzeug (http://werkzeug.pocoo.org/)" % err)
|
||||
from Debug import DebugReloader
|
||||
self.log.write = lambda msg: self.log.debug(msg.strip()) # For Wsgi access.log
|
||||
self.log.info("--------------------------------------")
|
||||
self.log.info("Web interface: http://%s:%s/" % (config.ui_ip, config.ui_port))
|
||||
self.log.info("--------------------------------------")
|
||||
|
||||
|
||||
WSGIServer((self.ip, self.port), handler, handler_class=UiWSGIHandler, log=self.log).serve_forever()
|
217
src/Ui/UiWebsocket.py
Normal file
217
src/Ui/UiWebsocket.py
Normal file
|
@ -0,0 +1,217 @@
|
|||
import json, gevent, time, sys, hashlib
|
||||
from Config import config
|
||||
from Site import SiteManager
|
||||
|
||||
class UiWebsocket:
|
||||
def __init__(self, ws, site, server):
|
||||
self.ws = ws
|
||||
self.site = site
|
||||
self.server = server
|
||||
self.next_message_id = 1
|
||||
self.waiting_cb = {} # Waiting for callback. Key: message_id, Value: function pointer
|
||||
self.channels = [] # Channels joined to
|
||||
|
||||
|
||||
# Start listener loop
|
||||
def start(self):
|
||||
ws = self.ws
|
||||
if self.site.address == config.homepage and not self.site.page_requested: # Add open fileserver port message or closed port error to homepage at first request after start
|
||||
if config.ip_external:
|
||||
self.site.notifications.append(["done", "Congratulation, your port <b>"+str(config.fileserver_port)+"</b> is opened. <br>You are full member of ZeroNet network!", 10000])
|
||||
elif config.ip_external == False:
|
||||
self.site.notifications.append(["error", "Your network connection is restricted. Please, open <b>"+str(config.fileserver_port)+"</b> port <br>on your router to become full member of ZeroNet network.", 0])
|
||||
self.site.page_requested = True # Dont add connection notification anymore
|
||||
|
||||
for notification in self.site.notifications: # Send pending notification messages
|
||||
self.cmd("notification", notification)
|
||||
self.site.notifications = []
|
||||
while True:
|
||||
try:
|
||||
message = ws.receive()
|
||||
if message:
|
||||
self.handleRequest(message)
|
||||
except Exception, err:
|
||||
if err.message != 'Connection is already closed':
|
||||
if config.debug: # Allow websocket errors to appear on /Debug
|
||||
import sys
|
||||
sys.modules["src.main"].DebugHook.handleError()
|
||||
self.site.log.error("WebSocket error: %s" % err)
|
||||
return "Bye."
|
||||
|
||||
|
||||
# Event in a channel
|
||||
def event(self, channel, *params):
|
||||
if channel in self.channels: # We are joined to channel
|
||||
if channel == "siteChanged":
|
||||
site = params[0] # Triggerer site
|
||||
site_info = self.siteInfo(site)
|
||||
if len(params) > 1 and params[1]: # Extra data
|
||||
site_info.update(params[1])
|
||||
self.cmd("setSiteInfo", site_info)
|
||||
|
||||
|
||||
# Send response to client (to = message.id)
|
||||
def response(self, to, result):
|
||||
self.send({"cmd": "response", "to": to, "result": result})
|
||||
|
||||
|
||||
# Send a command
|
||||
def cmd(self, cmd, params={}, cb = None):
|
||||
self.send({"cmd": cmd, "params": params}, cb)
|
||||
|
||||
|
||||
# Encode to json and send message
|
||||
def send(self, message, cb = None):
|
||||
message["id"] = self.next_message_id # Add message id to allow response
|
||||
self.next_message_id += 1
|
||||
self.ws.send(json.dumps(message))
|
||||
if cb: # Callback after client responsed
|
||||
self.waiting_cb[message["id"]] = cb
|
||||
|
||||
|
||||
# Handle incoming messages
|
||||
def handleRequest(self, data):
|
||||
req = json.loads(data)
|
||||
cmd = req["cmd"]
|
||||
permissions = self.site.settings["permissions"]
|
||||
if cmd == "response":
|
||||
self.actionResponse(req)
|
||||
elif cmd == "ping":
|
||||
self.actionPing(req["id"])
|
||||
elif cmd == "channelJoin":
|
||||
self.actionChannelJoin(req["id"], req["params"])
|
||||
elif cmd == "siteInfo":
|
||||
self.actionSiteInfo(req["id"], req["params"])
|
||||
elif cmd == "serverInfo":
|
||||
self.actionServerInfo(req["id"], req["params"])
|
||||
elif cmd == "siteUpdate":
|
||||
self.actionSiteUpdate(req["id"], req["params"])
|
||||
# Admin commands
|
||||
elif cmd == "sitePause" and "ADMIN" in permissions:
|
||||
self.actionSitePause(req["id"], req["params"])
|
||||
elif cmd == "siteResume" and "ADMIN" in permissions:
|
||||
self.actionSiteResume(req["id"], req["params"])
|
||||
elif cmd == "siteList" and "ADMIN" in permissions:
|
||||
self.actionSiteList(req["id"], req["params"])
|
||||
elif cmd == "channelJoinAllsite" and "ADMIN" in permissions:
|
||||
self.actionChannelJoinAllsite(req["id"], req["params"])
|
||||
# Unknown command
|
||||
else:
|
||||
self.response(req["id"], "Unknown command: %s" % cmd)
|
||||
|
||||
|
||||
# - Actions -
|
||||
|
||||
# Do callback on response {"cmd": "response", "to": message_id, "result": result}
|
||||
def actionResponse(self, req):
|
||||
if req["to"] in self.waiting_cb:
|
||||
self.waiting_cb(req["result"]) # Call callback function
|
||||
else:
|
||||
self.site.log.error("Websocket callback not found: %s" % req)
|
||||
|
||||
|
||||
# Send a simple pong answer
|
||||
def actionPing(self, to):
|
||||
self.response(to, "pong")
|
||||
|
||||
|
||||
# Format site info
|
||||
def siteInfo(self, site):
|
||||
ret = {
|
||||
"auth_id": self.site.settings["auth_key"][0:10],
|
||||
"auth_id_md5": hashlib.md5(self.site.settings["auth_key"][0:10]).hexdigest(),
|
||||
"address": site.address,
|
||||
"settings": site.settings,
|
||||
"content_updated": site.content_updated,
|
||||
"bad_files": site.bad_files.keys(),
|
||||
"last_downloads": site.last_downloads,
|
||||
"peers": len(site.peers),
|
||||
"tasks": [task["inner_path"] for task in site.worker_manager.tasks],
|
||||
"content": site.content
|
||||
}
|
||||
if site.settings["serving"] and site.content: ret["peers"] += 1 # Add myself if serving
|
||||
return ret
|
||||
|
||||
|
||||
# Send site details
|
||||
def actionSiteInfo(self, to, params):
|
||||
ret = self.siteInfo(self.site)
|
||||
self.response(to, ret)
|
||||
|
||||
|
||||
# Join to an event channel
|
||||
def actionChannelJoin(self, to, params):
|
||||
if params["channel"] not in self.channels:
|
||||
self.channels.append(params["channel"])
|
||||
|
||||
|
||||
# Server variables
|
||||
def actionServerInfo(self, to, params):
|
||||
ret = {
|
||||
"ip_external": config.ip_external,
|
||||
"platform": sys.platform,
|
||||
"fileserver_ip": config.fileserver_ip,
|
||||
"fileserver_port": config.fileserver_port,
|
||||
"ui_ip": config.ui_ip,
|
||||
"ui_port": config.ui_port,
|
||||
"debug": config.debug
|
||||
}
|
||||
self.response(to, ret)
|
||||
|
||||
|
||||
# - Admin actions -
|
||||
|
||||
# List all site info
|
||||
def actionSiteList(self, to, params):
|
||||
ret = []
|
||||
SiteManager.load() # Reload sites
|
||||
for site in self.server.sites.values():
|
||||
if not site.content: continue # Broken site
|
||||
ret.append(self.siteInfo(site))
|
||||
self.response(to, ret)
|
||||
|
||||
|
||||
# Join to an event channel on all sites
|
||||
def actionChannelJoinAllsite(self, to, params):
|
||||
if params["channel"] not in self.channels: # Add channel to channels
|
||||
self.channels.append(params["channel"])
|
||||
|
||||
for site in self.server.sites.values(): # Add websocket to every channel
|
||||
if self not in site.websockets:
|
||||
site.websockets.append(self)
|
||||
|
||||
|
||||
# Update site content.json
|
||||
def actionSiteUpdate(self, to, params):
|
||||
address = params.get("address")
|
||||
site = self.server.sites.get(address)
|
||||
if site and (site.address == self.site.address or "ADMIN" in self.site.settings["permissions"]):
|
||||
gevent.spawn(site.update)
|
||||
else:
|
||||
self.response(to, {"error": "Unknown site: %s" % address})
|
||||
|
||||
|
||||
# Pause site serving
|
||||
def actionSitePause(self, to, params):
|
||||
address = params.get("address")
|
||||
site = self.server.sites.get(address)
|
||||
if site:
|
||||
site.settings["serving"] = False
|
||||
site.saveSettings()
|
||||
site.updateWebsocket()
|
||||
else:
|
||||
self.response(to, {"error": "Unknown site: %s" % address})
|
||||
|
||||
|
||||
# Resume site serving
|
||||
def actionSiteResume(self, to, params):
|
||||
address = params.get("address")
|
||||
site = self.server.sites.get(address)
|
||||
if site:
|
||||
site.settings["serving"] = True
|
||||
site.saveSettings()
|
||||
gevent.spawn(site.update)
|
||||
time.sleep(0.001) # Wait for update thread starting
|
||||
site.updateWebsocket()
|
||||
else:
|
||||
self.response(to, {"error": "Unknown site: %s" % address})
|
3
src/Ui/__init__.py
Normal file
3
src/Ui/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from UiServer import UiServer
|
||||
from UiRequest import UiRequest
|
||||
from UiWebsocket import UiWebsocket
|
40
src/Ui/media/Loading.coffee
Normal file
40
src/Ui/media/Loading.coffee
Normal file
|
@ -0,0 +1,40 @@
|
|||
class Loading
|
||||
constructor: ->
|
||||
if window.show_loadingscreen then @showScreen()
|
||||
|
||||
|
||||
showScreen: ->
|
||||
$(".loadingscreen").css("display", "block").addClassLater("ready")
|
||||
@screen_visible = true
|
||||
@printLine " Connecting..."
|
||||
|
||||
|
||||
|
||||
# We dont need loadingscreen anymore
|
||||
hideScreen: ->
|
||||
if @screen_visible # Hide with animate
|
||||
$(".loadingscreen").addClass("done").removeLater(2000)
|
||||
else # Not visible, just remove
|
||||
$(".loadingscreen").remove()
|
||||
@screen_visible = false
|
||||
|
||||
|
||||
# Append text to last line of loadingscreen
|
||||
print: (text, type="normal") ->
|
||||
if not @screen_visible then return false
|
||||
$(".loadingscreen .console .cursor").remove() # Remove previous cursor
|
||||
last_line = $(".loadingscreen .console .console-line:last-child")
|
||||
if type == "error" then text = "<span class='console-error'>#{text}</span>"
|
||||
last_line.html(last_line.html()+text)
|
||||
|
||||
|
||||
# Add line to loading screen
|
||||
printLine: (text, type="normal") ->
|
||||
if not @screen_visible then return false
|
||||
$(".loadingscreen .console .cursor").remove() # Remove previous cursor
|
||||
if type == "error" then text = "<span class='console-error'>#{text}</span>" else text = text+"<span class='cursor'> </span>"
|
||||
$(".loadingscreen .console").append("<div class='console-line'>#{text}</div>")
|
||||
|
||||
|
||||
|
||||
window.Loading = Loading
|
68
src/Ui/media/Notifications.coffee
Normal file
68
src/Ui/media/Notifications.coffee
Normal file
|
@ -0,0 +1,68 @@
|
|||
class Notifications
|
||||
constructor: (@elem) ->
|
||||
@
|
||||
|
||||
test: ->
|
||||
setTimeout (=>
|
||||
@add("connection", "error", "Connection lost to <b>UiServer</b> on <b>localhost</b>!")
|
||||
@add("message-Anyone", "info", "New from <b>Anyone</b>.")
|
||||
), 1000
|
||||
setTimeout (=>
|
||||
@add("connection", "done", "<b>UiServer</b> connection recovered.", 5000)
|
||||
), 3000
|
||||
|
||||
|
||||
add: (id, type, body, timeout=0) ->
|
||||
@log id, type, body, timeout
|
||||
# Close notifications with same id
|
||||
for elem in $(".notification-#{id}")
|
||||
@close $(elem)
|
||||
|
||||
# Create element
|
||||
elem = $(".notification.template", @elem).clone().removeClass("template")
|
||||
elem.addClass("notification-#{type}").addClass("notification-#{id}")
|
||||
|
||||
# Update text
|
||||
if type == "error"
|
||||
$(".notification-icon", elem).html("!")
|
||||
else if type == "done"
|
||||
$(".notification-icon", elem).html("<div class='icon-success'></div>")
|
||||
else
|
||||
$(".notification-icon", elem).html("i")
|
||||
|
||||
$(".body", elem).html(body)
|
||||
|
||||
elem.appendTo(@elem)
|
||||
|
||||
# Timeout
|
||||
if timeout
|
||||
$(".close", elem).remove() # No need of close button
|
||||
setTimeout (=>
|
||||
@close elem
|
||||
), timeout
|
||||
|
||||
# Animate
|
||||
width = elem.outerWidth()
|
||||
if not timeout then width += 20 # Add space for close button
|
||||
elem.css({"width": "50px", "transform": "scale(0.01)"})
|
||||
elem.animate({"scale": 1}, 800, "easeOutElastic")
|
||||
elem.animate({"width": width}, 700, "easeInOutCubic")
|
||||
|
||||
# Close button
|
||||
$(".close", elem).on "click", =>
|
||||
@close elem
|
||||
return false
|
||||
|
||||
@
|
||||
|
||||
|
||||
close: (elem) ->
|
||||
elem.stop().animate {"width": 0, "opacity": 0}, 700, "easeInOutCubic"
|
||||
elem.slideUp 300, (-> elem.remove())
|
||||
|
||||
|
||||
log: (args...) ->
|
||||
console.log "[Notifications]", args...
|
||||
|
||||
|
||||
window.Notifications = Notifications
|
33
src/Ui/media/Sidebar.coffee
Normal file
33
src/Ui/media/Sidebar.coffee
Normal file
|
@ -0,0 +1,33 @@
|
|||
class Sidebar
|
||||
constructor: ->
|
||||
@initFixbutton()
|
||||
|
||||
|
||||
initFixbutton: ->
|
||||
$(".fixbutton-bg").on "mouseover", ->
|
||||
$(@).stop().animate({"scale": 0.7}, 800, "easeOutElastic")
|
||||
$(".fixbutton-burger").stop().animate({"opacity": 1.5, "left": 0}, 800, "easeOutElastic")
|
||||
$(".fixbutton-text").stop().animate({"opacity": 0, "left": 20}, 300, "easeOutCubic")
|
||||
|
||||
$(".fixbutton-bg").on "mouseout", ->
|
||||
$(@).stop().animate({"scale": 0.6}, 300, "easeOutCubic")
|
||||
$(".fixbutton-burger").stop().animate({"opacity": 0, "left": -20}, 300, "easeOutCubic")
|
||||
$(".fixbutton-text").stop().animate({"opacity": 1, "left": 0}, 300, "easeOutBack")
|
||||
|
||||
|
||||
###$(".fixbutton-bg").on "click", ->
|
||||
return false
|
||||
###
|
||||
|
||||
$(".fixbutton-bg").on "mousedown", ->
|
||||
$(".fixbutton-burger").stop().animate({"scale": 0.7, "left": 0}, 300, "easeOutCubic")
|
||||
#$("#inner-iframe").toggleClass("back")
|
||||
#$(".wrapper-iframe").stop().animate({"scale": 0.9}, 600, "easeOutCubic")
|
||||
#$("body").addClass("back")
|
||||
|
||||
$(".fixbutton-bg").on "mouseup", ->
|
||||
$(".fixbutton-burger").stop().animate({"scale": 1, "left": 0}, 600, "easeOutElastic")
|
||||
|
||||
|
||||
|
||||
window.Sidebar = Sidebar
|
152
src/Ui/media/Wrapper.coffee
Normal file
152
src/Ui/media/Wrapper.coffee
Normal file
|
@ -0,0 +1,152 @@
|
|||
class Wrapper
|
||||
constructor: (ws_url) ->
|
||||
@log "Created!"
|
||||
|
||||
@loading = new Loading()
|
||||
@notifications = new Notifications($(".notifications"))
|
||||
@sidebar = new Sidebar()
|
||||
|
||||
window.addEventListener("message", @onMessageInner, false)
|
||||
@inner = document.getElementById("inner-iframe").contentWindow
|
||||
@ws = new ZeroWebsocket(ws_url)
|
||||
@ws.next_message_id = 1000000 # Avoid messageid collision :)
|
||||
@ws.onOpen = @onOpenWebsocket
|
||||
@ws.onClose = @onCloseWebsocket
|
||||
@ws.onMessage = @onMessageWebsocket
|
||||
@ws.connect()
|
||||
@ws_error = null # Ws error message
|
||||
|
||||
@site_info = null # Hold latest site info
|
||||
@inner_loaded = false # If iframe loaded or not
|
||||
@inner_ready = false # Inner frame ready to receive messages
|
||||
@wrapperWsInited = false # Wrapper notified on websocket open
|
||||
@site_error = null # Latest failed file download
|
||||
|
||||
window.onload = @onLoad # On iframe loaded
|
||||
@
|
||||
|
||||
|
||||
# Incoming message from UiServer websocket
|
||||
onMessageWebsocket: (e) =>
|
||||
message = JSON.parse(e.data)
|
||||
cmd = message.cmd
|
||||
if cmd == "response"
|
||||
if @ws.waiting_cb[message.to]? # We are waiting for response
|
||||
@ws.waiting_cb[message.to](message.result)
|
||||
else
|
||||
@sendInner message # Pass message to inner frame
|
||||
else if cmd == "notification" # Display notification
|
||||
@notifications.add("notification-#{message.id}", message.params[0], message.params[1], message.params[2])
|
||||
else if cmd == "setSiteInfo"
|
||||
@sendInner message # Pass to inner frame
|
||||
if message.params.address == window.address # Current page
|
||||
@setSiteInfo message.params
|
||||
else
|
||||
@sendInner message # Pass message to inner frame
|
||||
|
||||
|
||||
# Incoming message from inner frame
|
||||
onMessageInner: (e) =>
|
||||
message = e.data
|
||||
cmd = message.cmd
|
||||
if cmd == "innerReady"
|
||||
@inner_ready = true
|
||||
@log "innerReady", @ws.ws.readyState, @wrapperWsInited
|
||||
if @ws.ws.readyState == 1 and not @wrapperWsInited # If ws already opened
|
||||
@sendInner {"cmd": "wrapperOpenedWebsocket"}
|
||||
@wrapperWsInited = true
|
||||
else if cmd == "wrapperNotification"
|
||||
@notifications.add("notification-#{message.id}", message.params[0], message.params[1], message.params[2])
|
||||
else # Send to websocket
|
||||
@ws.send(message) # Pass message to websocket
|
||||
|
||||
|
||||
onOpenWebsocket: (e) =>
|
||||
@ws.cmd "channelJoin", {"channel": "siteChanged"} # Get info on modifications
|
||||
@log "onOpenWebsocket", @inner_ready, @wrapperWsInited
|
||||
if not @wrapperWsInited and @inner_ready
|
||||
@sendInner {"cmd": "wrapperOpenedWebsocket"} # Send to inner frame
|
||||
@wrapperWsInited = true
|
||||
if @inner_loaded # Update site info
|
||||
@reloadSiteInfo()
|
||||
|
||||
# If inner frame not loaded for 2 sec show peer informations on loading screen by loading site info
|
||||
setTimeout (=>
|
||||
if not @site_info then @reloadSiteInfo()
|
||||
), 2000
|
||||
|
||||
if @ws_error
|
||||
@notifications.add("connection", "done", "Connection with <b>UiServer Websocket</b> recovered.", 6000)
|
||||
@ws_error = null
|
||||
|
||||
|
||||
onCloseWebsocket: (e) =>
|
||||
@wrapperWsInited = false
|
||||
setTimeout (=> # Wait a bit, maybe its page closing
|
||||
@sendInner {"cmd": "wrapperClosedWebsocket"} # Send to inner frame
|
||||
if e.code == 1000 # Server error please reload page
|
||||
@ws_error = @notifications.add("connection", "error", "UiServer Websocket error, please reload the page.")
|
||||
else if not @ws_error
|
||||
@ws_error = @notifications.add("connection", "error", "Connection with <b>UiServer Websocket</b> was lost. Reconnecting...")
|
||||
), 500
|
||||
|
||||
|
||||
# Iframe loaded
|
||||
onLoad: (e) =>
|
||||
@log "onLoad", e
|
||||
@inner_loaded = true
|
||||
if not @inner_ready then @sendInner {"cmd": "wrapperReady"} # Inner frame loaded before wrapper
|
||||
if not @site_error then @loading.hideScreen() # Hide loading screen
|
||||
if @ws.ws.readyState == 1 and not @site_info # Ws opened
|
||||
@reloadSiteInfo()
|
||||
|
||||
|
||||
# Send message to innerframe
|
||||
sendInner: (message) ->
|
||||
@inner.postMessage(message, '*')
|
||||
|
||||
|
||||
# Get site info from UiServer
|
||||
reloadSiteInfo: ->
|
||||
@ws.cmd "siteInfo", {}, (site_info) =>
|
||||
@setSiteInfo site_info
|
||||
window.document.title = site_info.content.title+" - ZeroNet"
|
||||
@log "Setting title to", window.document.title
|
||||
|
||||
|
||||
# Got setSiteInfo from websocket UiServer
|
||||
setSiteInfo: (site_info) ->
|
||||
if site_info.event? # If loading screen visible add event to it
|
||||
# File started downloading
|
||||
if site_info.event[0] == "file_added" and site_info.bad_files.length
|
||||
@loading.printLine("#{site_info.bad_files.length} files needs to be downloaded")
|
||||
# File finished downloading
|
||||
else if site_info.event[0] == "file_done"
|
||||
@loading.printLine("#{site_info.event[1]} downloaded")
|
||||
if site_info.event[1] == window.inner_path # File downloaded we currently on
|
||||
@loading.hideScreen()
|
||||
if not $(".loadingscreen").length # Loading screen already removed (loaded +2sec)
|
||||
@notifications.add("modified", "info", "New version of this page has just released.<br>Reload to see the modified content.")
|
||||
# File failed downloading
|
||||
else if site_info.event[0] == "file_failed"
|
||||
@site_error = site_info.event[1]
|
||||
@loading.printLine("#{site_info.event[1]} download failed", "error")
|
||||
# New peers found
|
||||
else if site_info.event[0] == "peers_added"
|
||||
@loading.printLine("Peers found: #{site_info.peers}")
|
||||
|
||||
if @loading.screen_visible and not @site_info # First site info display current peers
|
||||
if site_info.peers > 1
|
||||
@loading.printLine "Peers found: #{site_info.peers}"
|
||||
else
|
||||
@site_error = "No peers found"
|
||||
@loading.printLine "No peers found"
|
||||
@site_info = site_info
|
||||
|
||||
|
||||
log: (args...) ->
|
||||
console.log "[Wrapper]", args...
|
||||
|
||||
|
||||
ws_url = "ws://#{window.location.hostname}:#{window.location.port}/Websocket?auth_key=#{window.auth_key}"
|
||||
window.wrapper = new Wrapper(ws_url)
|
102
src/Ui/media/Wrapper.css
Normal file
102
src/Ui/media/Wrapper.css
Normal file
|
@ -0,0 +1,102 @@
|
|||
body { margin: 0px; padding: 0px; height: 100%; background-color: #D2CECD; overflow: hidden }
|
||||
body.back { background-color: #090909 }
|
||||
a { color: black }
|
||||
|
||||
.template { display: none !important }
|
||||
|
||||
#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0px; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out }
|
||||
#inner-iframe.back { transform: scale(0.95) translate(-300px, 0px); opacity: 0.4 }
|
||||
|
||||
|
||||
/* Fixbutton */
|
||||
|
||||
.fixbutton {
|
||||
position: absolute; right: 35px; top: 15px; width: 40px; z-index: 999;
|
||||
text-align: center; color: white; font-family: Consolas; font-size: 25px; line-height: 40px;
|
||||
}
|
||||
.fixbutton-bg {
|
||||
border-radius: 80px; background-color: rgba(180, 180, 180, 0.5); cursor: pointer;
|
||||
display: block; width: 80px; height: 80px; transition: background-color 0.2s, box-shadow 0.5s; transform: scale(0.6); margin-left: -20px; margin-top: -20px; /* 2x size to prevent blur on anim */
|
||||
/*box-shadow: inset 105px 260px 0px -200px rgba(0,0,0,0.1);*/ /* box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); */
|
||||
}
|
||||
.fixbutton-text { pointer-events: none; position: absolute; z-index: 999; width: 40px; backface-visibility: hidden; perspective: 1000px }
|
||||
.fixbutton-burger { pointer-events: none; position: absolute; z-index: 999; width: 40px; opacity: 0; left: -20px }
|
||||
.fixbutton-bg:hover { background-color: #AF3BFF }
|
||||
|
||||
|
||||
/* Notification */
|
||||
|
||||
.notifications { position: absolute; top: 0px; right: 85px; display: inline-block; z-index: 999; white-space: nowrap }
|
||||
.notification {
|
||||
position: relative; float: right; clear: both; margin: 10px; height: 50px; box-sizing: border-box; overflow: hidden; backface-visibility: hidden; perspective: 1000px;
|
||||
background-color: white; color: #4F4F4F; font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px
|
||||
}
|
||||
.notification-icon {
|
||||
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1;
|
||||
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
|
||||
}
|
||||
.notification .body { max-width: 420px; padding-left: 68px; padding-right: 17px; height: 50px; vertical-align: middle; display: table-cell }
|
||||
.notification.visible { max-width: 350px }
|
||||
|
||||
.notification .close { position: absolute; top: 0px; right: 0px; font-size: 19px; line-height: 13px; color: #DDD; padding: 7px; text-decoration: none }
|
||||
.notification .close:hover { color: black }
|
||||
.notification .close:active, .notification .close:focus { color: #AF3BFF }
|
||||
|
||||
/* Notification types */
|
||||
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
|
||||
.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }
|
||||
|
||||
|
||||
/* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */
|
||||
|
||||
.icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) }
|
||||
|
||||
|
||||
/* Loading screen */
|
||||
|
||||
.loadingscreen { width: 100%; height: 100%; position: absolute; background-color: #EEE; z-index: 1; overflow: hidden; display: none }
|
||||
.loading-text { text-align: center; vertical-align: middle; top: 50%; position: absolute; margin-top: 39px; width: 100% }
|
||||
|
||||
/* Console */
|
||||
.console { line-height: 24px; font-family: monospace; font-size: 14px; color: #ADADAD; text-transform: uppercase; opacity: 0; transform: translateY(-20px); }
|
||||
.console-line:last-child { color: #6C6767 }
|
||||
.console .cursor {
|
||||
background-color: #999; color: #999; animation: pulse 1.5s infinite ease-in-out; margin-right: -9px;
|
||||
display: inline-block; width: 9px; height: 19px; vertical-align: -4px;
|
||||
}
|
||||
.console .console-error { color: #e74c3c; font-weight: bold; animation: pulse 2s infinite linear }
|
||||
|
||||
/* Flipper loading anim */
|
||||
.flipper-container { width: 40px; height: 40px; position: absolute; top: 0%; left: 50%; transform: translate3d(-50%, -50%, 0); perspective: 1200; opacity: 0 }
|
||||
.flipper { position: relative; display: block; height: inherit; width: inherit; animation: flip 1.2s infinite ease-in-out; -webkit-transform-style: preserve-3d; }
|
||||
.flipper .front, .flipper .back {
|
||||
position: absolute; top: 0; left: 0; backface-visibility: hidden; /*transform-style: preserve-3d;*/ display: block;
|
||||
background-color: #d50000; height: 100%; width: 100%; /*outline: 1px solid transparent; /* FF AA fix */
|
||||
}
|
||||
.flipper .back { background-color: white; z-index: 800; transform: rotateY(-180deg) }
|
||||
|
||||
/* Loading ready */
|
||||
.loadingscreen.ready .console { opacity: 1; transform: translateY(0px); transition: all 0.3s }
|
||||
.loadingscreen.ready .flipper-container { top: 50%; opacity: 1; transition: all 1s cubic-bezier(1, 0, 0, 1); }
|
||||
|
||||
|
||||
/* Loading done */
|
||||
.loadingscreen.done { height: 0%; transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); }
|
||||
.loadingscreen.done .console { transform: translateY(300px); opacity: 0; transition: all 1.5s }
|
||||
.loadingscreen.done .flipper-container { opacity: 0; transition: all 1.5s }
|
||||
|
||||
/* Animations */
|
||||
|
||||
@keyframes flip {
|
||||
0% { transform: perspective(120px) rotateX(0deg) rotateY(0deg); }
|
||||
50% { transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }
|
||||
100% { transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 0 }
|
||||
5% { opacity: 1 }
|
||||
30% { opacity: 1 }
|
||||
70% { opacity: 0 }
|
||||
100% { opacity: 0 }
|
||||
}
|
133
src/Ui/media/all.css
Normal file
133
src/Ui/media/all.css
Normal file
|
@ -0,0 +1,133 @@
|
|||
|
||||
|
||||
/* ---- src/Ui/media/Wrapper.css ---- */
|
||||
|
||||
|
||||
body { margin: 0px; padding: 0px; height: 100%; background-color: #D2CECD; overflow: hidden }
|
||||
body.back { background-color: #090909 }
|
||||
a { color: black }
|
||||
|
||||
.template { display: none !important }
|
||||
|
||||
#inner-iframe { width: 100%; height: 100%; position: absolute; border: 0px; -webkit-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -moz-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -o-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; -ms-transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out ; transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.8s ease-in-out }
|
||||
#inner-iframe.back { -webkit-transform: scale(0.95) translate(-300px, 0px); -moz-transform: scale(0.95) translate(-300px, 0px); -o-transform: scale(0.95) translate(-300px, 0px); -ms-transform: scale(0.95) translate(-300px, 0px); transform: scale(0.95) translate(-300px, 0px) ; opacity: 0.4 }
|
||||
|
||||
|
||||
/* Fixbutton */
|
||||
|
||||
.fixbutton {
|
||||
position: absolute; right: 35px; top: 15px; width: 40px; z-index: 999;
|
||||
text-align: center; color: white; font-family: Consolas; font-size: 25px; line-height: 40px;
|
||||
}
|
||||
.fixbutton-bg {
|
||||
-webkit-border-radius: 80px; -moz-border-radius: 80px; -o-border-radius: 80px; -ms-border-radius: 80px; border-radius: 80px ; background-color: rgba(180, 180, 180, 0.5); cursor: pointer;
|
||||
display: block; width: 80px; height: 80px; -webkit-transition: background-color 0.2s, box-shadow 0.5s; -moz-transition: background-color 0.2s, box-shadow 0.5s; -o-transition: background-color 0.2s, box-shadow 0.5s; -ms-transition: background-color 0.2s, box-shadow 0.5s; transition: background-color 0.2s, box-shadow 0.5s ; -webkit-transform: scale(0.6); -moz-transform: scale(0.6); -o-transform: scale(0.6); -ms-transform: scale(0.6); transform: scale(0.6) ; margin-left: -20px; margin-top: -20px; /* 2x size to prevent blur on anim */
|
||||
/*box-shadow: inset 105px 260px 0px -200px rgba(0,0,0,0.1);*/ /* -webkit-box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); -moz-box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); -o-box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); -ms-box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1); box-shadow: inset -75px 183px 0px -200px rgba(0,0,0,0.1) ; */
|
||||
}
|
||||
.fixbutton-text { pointer-events: none; position: absolute; z-index: 999; width: 40px; backface-visibility: hidden; -webkit-perspective: 1000px ; -moz-perspective: 1000px ; -o-perspective: 1000px ; -ms-perspective: 1000px ; perspective: 1000px }
|
||||
.fixbutton-burger { pointer-events: none; position: absolute; z-index: 999; width: 40px; opacity: 0; left: -20px }
|
||||
.fixbutton-bg:hover { background-color: #AF3BFF }
|
||||
|
||||
|
||||
/* Notification */
|
||||
|
||||
.notifications { position: absolute; top: 0px; right: 85px; display: inline-block; z-index: 999; white-space: nowrap }
|
||||
.notification {
|
||||
position: relative; float: right; clear: both; margin: 10px; height: 50px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; overflow: hidden; backface-visibility: hidden; -webkit-perspective: 1000px; -moz-perspective: 1000px; -o-perspective: 1000px; -ms-perspective: 1000px; perspective: 1000px ;
|
||||
background-color: white; color: #4F4F4F; font-family: 'Helvetica Neue', 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 20px
|
||||
}
|
||||
.notification-icon {
|
||||
display: block; width: 50px; height: 50px; position: absolute; float: left; z-index: 1;
|
||||
text-align: center; background-color: #e74c3c; line-height: 45px; vertical-align: bottom; font-size: 40px; color: white;
|
||||
}
|
||||
.notification .body { max-width: 420px; padding-left: 68px; padding-right: 17px; height: 50px; vertical-align: middle; display: table-cell }
|
||||
.notification.visible { max-width: 350px }
|
||||
|
||||
.notification .close { position: absolute; top: 0px; right: 0px; font-size: 19px; line-height: 13px; color: #DDD; padding: 7px; text-decoration: none }
|
||||
.notification .close:hover { color: black }
|
||||
.notification .close:active, .notification .close:focus { color: #AF3BFF }
|
||||
|
||||
/* Notification types */
|
||||
.notification-info .notification-icon { font-size: 22px; font-weight: bold; background-color: #2980b9; line-height: 48px }
|
||||
.notification-done .notification-icon { font-size: 22px; background-color: #27ae60 }
|
||||
|
||||
|
||||
/* Icons (based on http://nicolasgallagher.com/pure-css-gui-icons/demo/) */
|
||||
|
||||
.icon-success { left:6px; width:5px; height:12px; border-width:0 5px 5px 0; border-style:solid; border-color:white; margin-left: 20px; margin-top: 15px; transform:rotate(45deg) }
|
||||
|
||||
|
||||
/* Loading screen */
|
||||
|
||||
.loadingscreen { width: 100%; height: 100%; position: absolute; background-color: #EEE; z-index: 1; overflow: hidden; display: none }
|
||||
.loading-text { text-align: center; vertical-align: middle; top: 50%; position: absolute; margin-top: 39px; width: 100% }
|
||||
|
||||
/* Console */
|
||||
.console { line-height: 24px; font-family: monospace; font-size: 14px; color: #ADADAD; text-transform: uppercase; opacity: 0; -webkit-transform: translateY(-20px); -moz-transform: translateY(-20px); -o-transform: translateY(-20px); -ms-transform: translateY(-20px); transform: translateY(-20px) ; }
|
||||
.console-line:last-child { color: #6C6767 }
|
||||
.console .cursor {
|
||||
background-color: #999; color: #999; -webkit-animation: pulse 1.5s infinite ease-in-out; -moz-animation: pulse 1.5s infinite ease-in-out; -o-animation: pulse 1.5s infinite ease-in-out; -ms-animation: pulse 1.5s infinite ease-in-out; animation: pulse 1.5s infinite ease-in-out ; margin-right: -9px;
|
||||
display: inline-block; width: 9px; height: 19px; vertical-align: -4px;
|
||||
}
|
||||
.console .console-error { color: #e74c3c; font-weight: bold; -webkit-animation: pulse 2s infinite linear ; -moz-animation: pulse 2s infinite linear ; -o-animation: pulse 2s infinite linear ; -ms-animation: pulse 2s infinite linear ; animation: pulse 2s infinite linear }
|
||||
|
||||
/* Flipper loading anim */
|
||||
.flipper-container { width: 40px; height: 40px; position: absolute; top: 0%; left: 50%; -webkit-transform: translate3d(-50%, -50%, 0); -moz-transform: translate3d(-50%, -50%, 0); -o-transform: translate3d(-50%, -50%, 0); -ms-transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0) ; -webkit-perspective: 1200; -moz-perspective: 1200; -o-perspective: 1200; -ms-perspective: 1200; perspective: 1200 ; opacity: 0 }
|
||||
.flipper { position: relative; display: block; height: inherit; width: inherit; -webkit-animation: flip 1.2s infinite ease-in-out; -moz-animation: flip 1.2s infinite ease-in-out; -o-animation: flip 1.2s infinite ease-in-out; -ms-animation: flip 1.2s infinite ease-in-out; animation: flip 1.2s infinite ease-in-out ; -webkit-transform-style: preserve-3d; }
|
||||
.flipper .front, .flipper .back {
|
||||
position: absolute; top: 0; left: 0; backface-visibility: hidden; /*transform-style: preserve-3d;*/ display: block;
|
||||
background-color: #d50000; height: 100%; width: 100%; /*outline: 1px solid transparent; /* FF AA fix */
|
||||
}
|
||||
.flipper .back { background-color: white; z-index: 800; -webkit-transform: rotateY(-180deg) ; -moz-transform: rotateY(-180deg) ; -o-transform: rotateY(-180deg) ; -ms-transform: rotateY(-180deg) ; transform: rotateY(-180deg) }
|
||||
|
||||
/* Loading ready */
|
||||
.loadingscreen.ready .console { opacity: 1; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -o-transform: translateY(0px); -ms-transform: translateY(0px); transform: translateY(0px) ; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s }
|
||||
.loadingscreen.ready .flipper-container { top: 50%; opacity: 1; -webkit-transition: all 1s cubic-bezier(1, 0, 0, 1); -moz-transition: all 1s cubic-bezier(1, 0, 0, 1); -o-transition: all 1s cubic-bezier(1, 0, 0, 1); -ms-transition: all 1s cubic-bezier(1, 0, 0, 1); transition: all 1s cubic-bezier(1, 0, 0, 1) ; }
|
||||
|
||||
|
||||
/* Loading done */
|
||||
.loadingscreen.done { height: 0%; -webkit-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); -moz-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); -o-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); -ms-transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045); transition: all 1s cubic-bezier(0.6, -0.28, 0.735, 0.045) ; }
|
||||
.loadingscreen.done .console { -webkit-transform: translateY(300px); -moz-transform: translateY(300px); -o-transform: translateY(300px); -ms-transform: translateY(300px); transform: translateY(300px) ; opacity: 0; -webkit-transition: all 1.5s ; -moz-transition: all 1.5s ; -o-transition: all 1.5s ; -ms-transition: all 1.5s ; transition: all 1.5s }
|
||||
.loadingscreen.done .flipper-container { opacity: 0; -webkit-transition: all 1.5s ; -moz-transition: all 1.5s ; -o-transition: all 1.5s ; -ms-transition: all 1.5s ; transition: all 1.5s }
|
||||
|
||||
/* Animations */
|
||||
|
||||
@keyframes flip {
|
||||
0% { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -moz-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -o-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -ms-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg) ; }
|
||||
50% { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -moz-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -o-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -ms-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }
|
||||
100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -moz-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -o-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg) ; }
|
||||
}
|
||||
@-webkit-keyframes flip {
|
||||
0% { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -moz-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -o-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -ms-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg) ; }
|
||||
50% { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -moz-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -o-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -ms-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }
|
||||
100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -moz-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -o-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg) ; }
|
||||
}
|
||||
@-moz-keyframes flip {
|
||||
0% { -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -moz-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -o-transform: perspective(120px) rotateX(0deg) rotateY(0deg); -ms-transform: perspective(120px) rotateX(0deg) rotateY(0deg); transform: perspective(120px) rotateX(0deg) rotateY(0deg) ; }
|
||||
50% { -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -moz-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -o-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; -ms-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) ; transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) }
|
||||
100% { -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -moz-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -o-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); -ms-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg); transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg) ; }
|
||||
}
|
||||
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 0 }
|
||||
5% { opacity: 1 }
|
||||
30% { opacity: 1 }
|
||||
70% { opacity: 0 }
|
||||
100% { opacity: 0 }
|
||||
}
|
||||
@-webkit-keyframes pulse {
|
||||
0% { opacity: 0 }
|
||||
5% { opacity: 1 }
|
||||
30% { opacity: 1 }
|
||||
70% { opacity: 0 }
|
||||
100% { opacity: 0 }
|
||||
}
|
||||
@-moz-keyframes pulse {
|
||||
0% { opacity: 0 }
|
||||
5% { opacity: 1 }
|
||||
30% { opacity: 1 }
|
||||
70% { opacity: 0 }
|
||||
100% { opacity: 0 }
|
||||
}
|
||||
|
895
src/Ui/media/all.js
Normal file
895
src/Ui/media/all.js
Normal file
File diff suppressed because one or more lines are too long
BIN
src/Ui/media/img/favicon.ico
Normal file
BIN
src/Ui/media/img/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
src/Ui/media/img/favicon.psd
Normal file
BIN
src/Ui/media/img/favicon.psd
Normal file
Binary file not shown.
4
src/Ui/media/lib/00-jquery.min.js
vendored
Normal file
4
src/Ui/media/lib/00-jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
81
src/Ui/media/lib/ZeroWebsocket.coffee
Normal file
81
src/Ui/media/lib/ZeroWebsocket.coffee
Normal file
|
@ -0,0 +1,81 @@
|
|||
class ZeroWebsocket
|
||||
constructor: (url) ->
|
||||
@url = url
|
||||
@next_message_id = 1
|
||||
@waiting_cb = {}
|
||||
@init()
|
||||
|
||||
|
||||
init: ->
|
||||
@
|
||||
|
||||
|
||||
connect: ->
|
||||
@ws = new WebSocket(@url)
|
||||
@ws.onmessage = @onMessage
|
||||
@ws.onopen = @onOpenWebsocket
|
||||
@ws.onerror = @onErrorWebsocket
|
||||
@ws.onclose = @onCloseWebsocket
|
||||
|
||||
|
||||
onMessage: (e) =>
|
||||
message = JSON.parse(e.data)
|
||||
cmd = message.cmd
|
||||
if cmd == "response"
|
||||
if @waiting_cb[message.to]?
|
||||
@waiting_cb[message.to](message.result)
|
||||
else
|
||||
@log "Websocket callback not found:", message
|
||||
else if cmd == "ping"
|
||||
@response message.id, "pong"
|
||||
else
|
||||
@route cmd, message
|
||||
|
||||
route: (cmd, message) =>
|
||||
@log "Unknown command", message
|
||||
|
||||
|
||||
response: (to, result) ->
|
||||
@send {"cmd": "response", "to": to, "result": result}
|
||||
|
||||
|
||||
cmd: (cmd, params={}, cb=null) ->
|
||||
@send {"cmd": cmd, "params": params}, cb
|
||||
|
||||
|
||||
send: (message, cb=null) ->
|
||||
if not message.id?
|
||||
message.id = @next_message_id
|
||||
@next_message_id += 1
|
||||
@ws.send(JSON.stringify(message))
|
||||
if cb
|
||||
@waiting_cb[message.id] = cb
|
||||
|
||||
|
||||
log: (args...) =>
|
||||
console.log "[ZeroWebsocket]", args...
|
||||
|
||||
|
||||
onOpenWebsocket: (e) =>
|
||||
@log "Open", e
|
||||
if @onOpen? then @onOpen(e)
|
||||
|
||||
|
||||
onErrorWebsocket: (e) =>
|
||||
@log "Error", e
|
||||
if @onError? then @onError(e)
|
||||
|
||||
|
||||
onCloseWebsocket: (e) =>
|
||||
@log "Closed", e
|
||||
if e.code == 1000
|
||||
@log "Server error, please reload the page"
|
||||
else # Connection error
|
||||
setTimeout (=>
|
||||
@log "Reconnecting..."
|
||||
@connect()
|
||||
), 10000
|
||||
if @onClose? then @onClose(e)
|
||||
|
||||
|
||||
window.ZeroWebsocket = ZeroWebsocket
|
27
src/Ui/media/lib/jquery.cssanim.js
Normal file
27
src/Ui/media/lib/jquery.cssanim.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
jQuery.cssHooks['scale'] = {
|
||||
get: function(elem, computed, extra) {
|
||||
var match = window.getComputedStyle(elem).transform.match("[0-9\.]+")
|
||||
if (match) {
|
||||
var scale = parseFloat(match[0])
|
||||
return scale
|
||||
} else {
|
||||
return 1.0
|
||||
}
|
||||
},
|
||||
set: function(elem, val) {
|
||||
//var transforms = $(elem).css("transform").match(/[0-9\.]+/g)
|
||||
var transforms = window.getComputedStyle(elem).transform.match(/[0-9\.]+/g)
|
||||
if (transforms) {
|
||||
transforms[0] = val
|
||||
transforms[3] = val
|
||||
//$(elem).css("transform", 'matrix('+transforms.join(", ")+")")
|
||||
elem.style.transform = 'matrix('+transforms.join(", ")+')'
|
||||
} else {
|
||||
elem.style.transform = "scale("+val+")"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jQuery.fx.step.scale = function(fx) {
|
||||
jQuery.cssHooks['scale'].set(fx.elem, fx.now)
|
||||
};
|
35
src/Ui/media/lib/jquery.csslater.coffee
Normal file
35
src/Ui/media/lib/jquery.csslater.coffee
Normal file
|
@ -0,0 +1,35 @@
|
|||
jQuery.fn.readdClass = (class_name) ->
|
||||
elem = @
|
||||
elem.removeClass class_name
|
||||
setTimeout ( ->
|
||||
elem.addClass class_name
|
||||
), 1
|
||||
return @
|
||||
|
||||
jQuery.fn.removeLater = (time = 500) ->
|
||||
elem = @
|
||||
setTimeout ( ->
|
||||
elem.remove()
|
||||
), time
|
||||
return @
|
||||
|
||||
jQuery.fn.hideLater = (time = 500) ->
|
||||
elem = @
|
||||
setTimeout ( ->
|
||||
elem.css("display", "none")
|
||||
), time
|
||||
return @
|
||||
|
||||
jQuery.fn.addClassLater = (class_name, time = 5) ->
|
||||
elem = @
|
||||
setTimeout ( ->
|
||||
elem.addClass(class_name)
|
||||
), time
|
||||
return @
|
||||
|
||||
jQuery.fn.cssLater = (name, val, time = 500) ->
|
||||
elem = @
|
||||
setTimeout ( ->
|
||||
elem.css name, val
|
||||
), time
|
||||
return @
|
205
src/Ui/media/lib/jquery.easing.1.3.js
Normal file
205
src/Ui/media/lib/jquery.easing.1.3.js
Normal file
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
|
||||
*
|
||||
* Uses the built in easing capabilities added In jQuery 1.1
|
||||
* to offer multiple easing options
|
||||
*
|
||||
* TERMS OF USE - jQuery Easing
|
||||
*
|
||||
* Open source under the BSD License.
|
||||
*
|
||||
* Copyright © 2008 George McGinley Smith
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
*
|
||||
* Neither the name of the author nor the names of contributors may be used to endorse
|
||||
* or promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
// t: current time, b: begInnIng value, c: change In value, d: duration
|
||||
jQuery.easing['jswing'] = jQuery.easing['swing'];
|
||||
|
||||
jQuery.extend( jQuery.easing,
|
||||
{
|
||||
def: 'easeOutQuad',
|
||||
swing: function (x, t, b, c, d) {
|
||||
//alert(jQuery.easing.default);
|
||||
return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
|
||||
},
|
||||
easeInQuad: function (x, t, b, c, d) {
|
||||
return c*(t/=d)*t + b;
|
||||
},
|
||||
easeOutQuad: function (x, t, b, c, d) {
|
||||
return -c *(t/=d)*(t-2) + b;
|
||||
},
|
||||
easeInOutQuad: function (x, t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return c/2*t*t + b;
|
||||
return -c/2 * ((--t)*(t-2) - 1) + b;
|
||||
},
|
||||
easeInCubic: function (x, t, b, c, d) {
|
||||
return c*(t/=d)*t*t + b;
|
||||
},
|
||||
easeOutCubic: function (x, t, b, c, d) {
|
||||
return c*((t=t/d-1)*t*t + 1) + b;
|
||||
},
|
||||
easeInOutCubic: function (x, t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return c/2*t*t*t + b;
|
||||
return c/2*((t-=2)*t*t + 2) + b;
|
||||
},
|
||||
easeInQuart: function (x, t, b, c, d) {
|
||||
return c*(t/=d)*t*t*t + b;
|
||||
},
|
||||
easeOutQuart: function (x, t, b, c, d) {
|
||||
return -c * ((t=t/d-1)*t*t*t - 1) + b;
|
||||
},
|
||||
easeInOutQuart: function (x, t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
|
||||
return -c/2 * ((t-=2)*t*t*t - 2) + b;
|
||||
},
|
||||
easeInQuint: function (x, t, b, c, d) {
|
||||
return c*(t/=d)*t*t*t*t + b;
|
||||
},
|
||||
easeOutQuint: function (x, t, b, c, d) {
|
||||
return c*((t=t/d-1)*t*t*t*t + 1) + b;
|
||||
},
|
||||
easeInOutQuint: function (x, t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
|
||||
return c/2*((t-=2)*t*t*t*t + 2) + b;
|
||||
},
|
||||
easeInSine: function (x, t, b, c, d) {
|
||||
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
|
||||
},
|
||||
easeOutSine: function (x, t, b, c, d) {
|
||||
return c * Math.sin(t/d * (Math.PI/2)) + b;
|
||||
},
|
||||
easeInOutSine: function (x, t, b, c, d) {
|
||||
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
|
||||
},
|
||||
easeInExpo: function (x, t, b, c, d) {
|
||||
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
|
||||
},
|
||||
easeOutExpo: function (x, t, b, c, d) {
|
||||
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
|
||||
},
|
||||
easeInOutExpo: function (x, t, b, c, d) {
|
||||
if (t==0) return b;
|
||||
if (t==d) return b+c;
|
||||
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
|
||||
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
|
||||
},
|
||||
easeInCirc: function (x, t, b, c, d) {
|
||||
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
|
||||
},
|
||||
easeOutCirc: function (x, t, b, c, d) {
|
||||
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
|
||||
},
|
||||
easeInOutCirc: function (x, t, b, c, d) {
|
||||
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
|
||||
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
|
||||
},
|
||||
easeInElastic: function (x, t, b, c, d) {
|
||||
var s=1.70158;var p=0;var a=c;
|
||||
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
|
||||
if (a < Math.abs(c)) { a=c; var s=p/4; }
|
||||
else var s = p/(2*Math.PI) * Math.asin (c/a);
|
||||
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
|
||||
},
|
||||
easeOutElastic: function (x, t, b, c, d) {
|
||||
var s=1.70158;var p=0;var a=c;
|
||||
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
|
||||
if (a < Math.abs(c)) { a=c; var s=p/4; }
|
||||
else var s = p/(2*Math.PI) * Math.asin (c/a);
|
||||
return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
|
||||
},
|
||||
easeInOutElastic: function (x, t, b, c, d) {
|
||||
var s=1.70158;var p=0;var a=c;
|
||||
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
|
||||
if (a < Math.abs(c)) { a=c; var s=p/4; }
|
||||
else var s = p/(2*Math.PI) * Math.asin (c/a);
|
||||
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
|
||||
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
|
||||
},
|
||||
easeInBack: function (x, t, b, c, d, s) {
|
||||
if (s == undefined) s = 1.70158;
|
||||
return c*(t/=d)*t*((s+1)*t - s) + b;
|
||||
},
|
||||
easeOutBack: function (x, t, b, c, d, s) {
|
||||
if (s == undefined) s = 1.70158;
|
||||
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
|
||||
},
|
||||
easeInOutBack: function (x, t, b, c, d, s) {
|
||||
if (s == undefined) s = 1.70158;
|
||||
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
|
||||
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
|
||||
},
|
||||
easeInBounce: function (x, t, b, c, d) {
|
||||
return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b;
|
||||
},
|
||||
easeOutBounce: function (x, t, b, c, d) {
|
||||
if ((t/=d) < (1/2.75)) {
|
||||
return c*(7.5625*t*t) + b;
|
||||
} else if (t < (2/2.75)) {
|
||||
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
|
||||
} else if (t < (2.5/2.75)) {
|
||||
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
|
||||
} else {
|
||||
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
|
||||
}
|
||||
},
|
||||
easeInOutBounce: function (x, t, b, c, d) {
|
||||
if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
|
||||
return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
*
|
||||
* TERMS OF USE - EASING EQUATIONS
|
||||
*
|
||||
* Open source under the BSD License.
|
||||
*
|
||||
* Copyright © 2001 Robert Penner
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||
* provided with the distribution.
|
||||
*
|
||||
* Neither the name of the author nor the names of contributors may be used to endorse
|
||||
* or promote products derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
50
src/Ui/template/wrapper.html
Normal file
50
src/Ui/template/wrapper.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{title} - ZeroNet</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" href="/uimedia/all.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Fixed button -->
|
||||
<div class='fixbutton'>
|
||||
<div class='fixbutton-text'>0</div>
|
||||
<div class='fixbutton-burger'>☰</div>
|
||||
<a class='fixbutton-bg' href="/{homepage}"></a>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Notifications -->
|
||||
<div class='notifications'>
|
||||
<div class='notification template'><span class='notification-icon'>!</span> <span class='body'>Test notification</span><a class="close" href="#Close">×</a><div style="clear: both"></div></div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Loadingscreen -->
|
||||
<div class='loadingscreen'>
|
||||
<div class='loading-text console'>
|
||||
</div>
|
||||
<div class="flipper-container">
|
||||
<div class="flipper"> <div class="front"></div><div class="back"></div> </div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Site Iframe -->
|
||||
<iframe src='/media/{address}/{inner_path}#auth_key={auth_key}' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation"></iframe>
|
||||
|
||||
|
||||
<!-- Site info -->
|
||||
<script>address = "{address}"</script>
|
||||
<script>auth_key = "{auth_key}"</script>
|
||||
<script>inner_path = "{inner_path}"</script>
|
||||
<script>permissions = {permissions}</script>
|
||||
<script>show_loadingscreen = {show_loadingscreen}</script>
|
||||
<script type="text/javascript" src="/uimedia/all.js" asyc></script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue