From 46583c4248da579f897943d1c15535ea556b1558 Mon Sep 17 00:00:00 2001
From: wupg98 <106410199+wupg98@users.noreply.github.com>
Date: Mon, 4 Sep 2023 05:42:58 +0200
Subject: [PATCH] Add files via upload
We recovered Zeronet Enhanced/ZNE from here web.archive.org/https://github.com/zeronet-enhanced/ZeroNet/
---
plugins/MergerSite/MergerSitePlugin.py | 399 ++++
plugins/MergerSite/__init__.py | 1 +
plugins/MergerSite/languages/es.json | 5 +
plugins/MergerSite/languages/fr.json | 5 +
plugins/MergerSite/languages/hu.json | 5 +
plugins/MergerSite/languages/it.json | 5 +
plugins/MergerSite/languages/jp.json | 5 +
plugins/MergerSite/languages/pt-br.json | 5 +
plugins/MergerSite/languages/tr.json | 5 +
plugins/MergerSite/languages/zh-tw.json | 5 +
plugins/MergerSite/languages/zh.json | 5 +
plugins/Stats/StatsPlugin.py | 627 +++++++
plugins/Stats/__init__.py | 1 +
plugins/Stats/plugin_info.json | 5 +
plugins/Trayicon/TrayiconPlugin.py | 171 ++
plugins/Trayicon/__init__.py | 4 +
plugins/Trayicon/languages/es.json | 14 +
plugins/Trayicon/languages/fr.json | 14 +
plugins/Trayicon/languages/hu.json | 14 +
plugins/Trayicon/languages/it.json | 14 +
plugins/Trayicon/languages/jp.json | 14 +
plugins/Trayicon/languages/pl.json | 14 +
plugins/Trayicon/languages/pt-br.json | 14 +
plugins/Trayicon/languages/tr.json | 14 +
plugins/Trayicon/languages/zh-tw.json | 14 +
plugins/Trayicon/languages/zh.json | 14 +
plugins/Trayicon/lib/__init__.py | 0
plugins/Trayicon/lib/notificationicon.py | 730 ++++++++
plugins/Trayicon/lib/winfolders.py | 54 +
plugins/Trayicon/plugin_info.json | 5 +
plugins/Trayicon/trayicon.ico | Bin 0 -> 1150 bytes
.../UiPluginManager/UiPluginManagerPlugin.py | 221 +++
plugins/UiPluginManager/__init__.py | 1 +
.../media/css/PluginManager.css | 75 +
plugins/UiPluginManager/media/css/all.css | 129 ++
plugins/UiPluginManager/media/css/button.css | 12 +
plugins/UiPluginManager/media/css/fonts.css | 30 +
plugins/UiPluginManager/media/img/loading.gif | Bin 0 -> 723 bytes
.../media/js/PluginList.coffee | 132 ++
.../media/js/UiPluginManager.coffee | 71 +
plugins/UiPluginManager/media/js/all.js | 1606 +++++++++++++++++
.../UiPluginManager/media/js/lib/Class.coffee | 23 +
.../media/js/lib/Promise.coffee | 74 +
.../media/js/lib/Prototypes.coffee | 8 +
.../UiPluginManager/media/js/lib/maquette.js | 770 ++++++++
.../media/js/utils/Animation.coffee | 138 ++
.../media/js/utils/Dollar.coffee | 3 +
.../media/js/utils/ZeroFrame.coffee | 85 +
.../UiPluginManager/media/plugin_manager.html | 19 +
plugins/Zeroname/README.md | 55 +
plugins/Zeroname/SiteManagerPlugin.py | 69 +
plugins/Zeroname/__init__.py | 1 +
plugins/Zeroname/updater/zeroname_updater.py | 249 +++
plugins/__init__.py | 0
54 files changed, 5953 insertions(+)
create mode 100644 plugins/MergerSite/MergerSitePlugin.py
create mode 100644 plugins/MergerSite/__init__.py
create mode 100644 plugins/MergerSite/languages/es.json
create mode 100644 plugins/MergerSite/languages/fr.json
create mode 100644 plugins/MergerSite/languages/hu.json
create mode 100644 plugins/MergerSite/languages/it.json
create mode 100644 plugins/MergerSite/languages/jp.json
create mode 100644 plugins/MergerSite/languages/pt-br.json
create mode 100644 plugins/MergerSite/languages/tr.json
create mode 100644 plugins/MergerSite/languages/zh-tw.json
create mode 100644 plugins/MergerSite/languages/zh.json
create mode 100644 plugins/Stats/StatsPlugin.py
create mode 100644 plugins/Stats/__init__.py
create mode 100644 plugins/Stats/plugin_info.json
create mode 100644 plugins/Trayicon/TrayiconPlugin.py
create mode 100644 plugins/Trayicon/__init__.py
create mode 100644 plugins/Trayicon/languages/es.json
create mode 100644 plugins/Trayicon/languages/fr.json
create mode 100644 plugins/Trayicon/languages/hu.json
create mode 100644 plugins/Trayicon/languages/it.json
create mode 100644 plugins/Trayicon/languages/jp.json
create mode 100644 plugins/Trayicon/languages/pl.json
create mode 100644 plugins/Trayicon/languages/pt-br.json
create mode 100644 plugins/Trayicon/languages/tr.json
create mode 100644 plugins/Trayicon/languages/zh-tw.json
create mode 100644 plugins/Trayicon/languages/zh.json
create mode 100644 plugins/Trayicon/lib/__init__.py
create mode 100644 plugins/Trayicon/lib/notificationicon.py
create mode 100644 plugins/Trayicon/lib/winfolders.py
create mode 100644 plugins/Trayicon/plugin_info.json
create mode 100644 plugins/Trayicon/trayicon.ico
create mode 100644 plugins/UiPluginManager/UiPluginManagerPlugin.py
create mode 100644 plugins/UiPluginManager/__init__.py
create mode 100644 plugins/UiPluginManager/media/css/PluginManager.css
create mode 100644 plugins/UiPluginManager/media/css/all.css
create mode 100644 plugins/UiPluginManager/media/css/button.css
create mode 100644 plugins/UiPluginManager/media/css/fonts.css
create mode 100644 plugins/UiPluginManager/media/img/loading.gif
create mode 100644 plugins/UiPluginManager/media/js/PluginList.coffee
create mode 100644 plugins/UiPluginManager/media/js/UiPluginManager.coffee
create mode 100644 plugins/UiPluginManager/media/js/all.js
create mode 100644 plugins/UiPluginManager/media/js/lib/Class.coffee
create mode 100644 plugins/UiPluginManager/media/js/lib/Promise.coffee
create mode 100644 plugins/UiPluginManager/media/js/lib/Prototypes.coffee
create mode 100644 plugins/UiPluginManager/media/js/lib/maquette.js
create mode 100644 plugins/UiPluginManager/media/js/utils/Animation.coffee
create mode 100644 plugins/UiPluginManager/media/js/utils/Dollar.coffee
create mode 100644 plugins/UiPluginManager/media/js/utils/ZeroFrame.coffee
create mode 100644 plugins/UiPluginManager/media/plugin_manager.html
create mode 100644 plugins/Zeroname/README.md
create mode 100644 plugins/Zeroname/SiteManagerPlugin.py
create mode 100644 plugins/Zeroname/__init__.py
create mode 100644 plugins/Zeroname/updater/zeroname_updater.py
create mode 100644 plugins/__init__.py
diff --git a/plugins/MergerSite/MergerSitePlugin.py b/plugins/MergerSite/MergerSitePlugin.py
new file mode 100644
index 00000000..2dccc6de
--- /dev/null
+++ b/plugins/MergerSite/MergerSitePlugin.py
@@ -0,0 +1,399 @@
+import re
+import time
+import copy
+import os
+
+from Plugin import PluginManager
+from Translate import Translate
+from util import RateLimit
+from util import helper
+from util.Flag import flag
+from Debug import Debug
+try:
+ import OptionalManager.UiWebsocketPlugin # To make optioanlFileInfo merger sites compatible
+except Exception:
+ pass
+
+if "merger_db" not in locals().keys(): # To keep merger_sites between module reloads
+ merger_db = {} # Sites that allowed to list other sites {address: [type1, type2...]}
+ merged_db = {} # Sites that allowed to be merged to other sites {address: type, ...}
+ merged_to_merger = {} # {address: [site1, site2, ...]} cache
+ site_manager = None # Site manager for merger sites
+
+
+plugin_dir = os.path.dirname(__file__)
+
+if "_" not in locals():
+ _ = Translate(plugin_dir + "/languages/")
+
+
+# Check if the site has permission to this merger site
+def checkMergerPath(address, inner_path):
+ merged_match = re.match("^merged-(.*?)/([A-Za-z0-9]{26,35})/", inner_path)
+ if merged_match:
+ merger_type = merged_match.group(1)
+ # Check if merged site is allowed to include other sites
+ if merger_type in merger_db.get(address, []):
+ # Check if included site allows to include
+ merged_address = merged_match.group(2)
+ if merged_db.get(merged_address) == merger_type:
+ inner_path = re.sub("^merged-(.*?)/([A-Za-z0-9]{26,35})/", "", inner_path)
+ return merged_address, inner_path
+ else:
+ raise Exception(
+ "Merger site (%s) does not have permission for merged site: %s (%s)" %
+ (merger_type, merged_address, merged_db.get(merged_address))
+ )
+ else:
+ raise Exception("No merger (%s) permission to load: %s (%s not in %s)" % (
+ address, inner_path, merger_type, merger_db.get(address, []))
+ )
+ else:
+ raise Exception("Invalid merger path: %s" % inner_path)
+
+
+@PluginManager.registerTo("UiWebsocket")
+class UiWebsocketPlugin(object):
+ # Download new site
+ def actionMergerSiteAdd(self, to, addresses):
+ if type(addresses) != list:
+ # Single site add
+ addresses = [addresses]
+ # Check if the site has merger permission
+ merger_types = merger_db.get(self.site.address)
+ if not merger_types:
+ return self.response(to, {"error": "Not a merger site"})
+
+ if RateLimit.isAllowed(self.site.address + "-MergerSiteAdd", 10) and len(addresses) == 1:
+ # Without confirmation if only one site address and not called in last 10 sec
+ self.cbMergerSiteAdd(to, addresses)
+ else:
+ self.cmd(
+ "confirm",
+ [_["Add %s new site?"] % len(addresses), "Add"],
+ lambda res: self.cbMergerSiteAdd(to, addresses)
+ )
+ self.response(to, "ok")
+
+ # Callback of adding new site confirmation
+ def cbMergerSiteAdd(self, to, addresses):
+ added = 0
+ for address in addresses:
+ try:
+ site_manager.need(address)
+ added += 1
+ except Exception as err:
+ self.cmd("notification", ["error", _["Adding %s failed: %s"] % (address, err)])
+ if added:
+ self.cmd("notification", ["done", _["Added %s new site"] % added, 5000])
+ RateLimit.called(self.site.address + "-MergerSiteAdd")
+ site_manager.updateMergerSites()
+
+ # Delete a merged site
+ @flag.no_multiuser
+ def actionMergerSiteDelete(self, to, address):
+ site = self.server.sites.get(address)
+ if not site:
+ return self.response(to, {"error": "No site found: %s" % address})
+
+ merger_types = merger_db.get(self.site.address)
+ if not merger_types:
+ return self.response(to, {"error": "Not a merger site"})
+ if merged_db.get(address) not in merger_types:
+ return self.response(to, {"error": "Merged type (%s) not in %s" % (merged_db.get(address), merger_types)})
+
+ self.cmd("notification", ["done", _["Site deleted: %s "] % address, 5000])
+ self.response(to, "ok")
+
+ # Lists merged sites
+ def actionMergerSiteList(self, to, query_site_info=False):
+ merger_types = merger_db.get(self.site.address)
+ ret = {}
+ if not merger_types:
+ return self.response(to, {"error": "Not a merger site"})
+ for address, merged_type in merged_db.items():
+ if merged_type not in merger_types:
+ continue # Site not for us
+ if query_site_info:
+ site = self.server.sites.get(address)
+ ret[address] = self.formatSiteInfo(site, create_user=False)
+ else:
+ ret[address] = merged_type
+ self.response(to, ret)
+
+ def hasSitePermission(self, address, *args, **kwargs):
+ if super(UiWebsocketPlugin, self).hasSitePermission(address, *args, **kwargs):
+ return True
+ else:
+ if self.site.address in [merger_site.address for merger_site in merged_to_merger.get(address, [])]:
+ return True
+ else:
+ return False
+
+ # Add support merger sites for file commands
+ def mergerFuncWrapper(self, func_name, to, inner_path, *args, **kwargs):
+ if inner_path.startswith("merged-"):
+ merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)
+
+ # Set the same cert for merged site
+ merger_cert = self.user.getSiteData(self.site.address).get("cert")
+ if merger_cert and self.user.getSiteData(merged_address).get("cert") != merger_cert:
+ self.user.setCert(merged_address, merger_cert)
+
+ req_self = copy.copy(self)
+ req_self.site = self.server.sites.get(merged_address) # Change the site to the merged one
+
+ func = getattr(super(UiWebsocketPlugin, req_self), func_name)
+ return func(to, merged_inner_path, *args, **kwargs)
+ else:
+ func = getattr(super(UiWebsocketPlugin, self), func_name)
+ return func(to, inner_path, *args, **kwargs)
+
+ def actionFileList(self, to, inner_path, *args, **kwargs):
+ return self.mergerFuncWrapper("actionFileList", to, inner_path, *args, **kwargs)
+
+ def actionDirList(self, to, inner_path, *args, **kwargs):
+ return self.mergerFuncWrapper("actionDirList", to, inner_path, *args, **kwargs)
+
+ def actionFileGet(self, to, inner_path, *args, **kwargs):
+ return self.mergerFuncWrapper("actionFileGet", to, inner_path, *args, **kwargs)
+
+ def actionFileWrite(self, to, inner_path, *args, **kwargs):
+ return self.mergerFuncWrapper("actionFileWrite", to, inner_path, *args, **kwargs)
+
+ def actionFileDelete(self, to, inner_path, *args, **kwargs):
+ return self.mergerFuncWrapper("actionFileDelete", to, inner_path, *args, **kwargs)
+
+ def actionFileRules(self, to, inner_path, *args, **kwargs):
+ return self.mergerFuncWrapper("actionFileRules", to, inner_path, *args, **kwargs)
+
+ def actionFileNeed(self, to, inner_path, *args, **kwargs):
+ return self.mergerFuncWrapper("actionFileNeed", to, inner_path, *args, **kwargs)
+
+ def actionOptionalFileInfo(self, to, inner_path, *args, **kwargs):
+ return self.mergerFuncWrapper("actionOptionalFileInfo", to, inner_path, *args, **kwargs)
+
+ def actionOptionalFileDelete(self, to, inner_path, *args, **kwargs):
+ return self.mergerFuncWrapper("actionOptionalFileDelete", to, inner_path, *args, **kwargs)
+
+ def actionBigfileUploadInit(self, to, inner_path, *args, **kwargs):
+ back = self.mergerFuncWrapper("actionBigfileUploadInit", to, inner_path, *args, **kwargs)
+ if inner_path.startswith("merged-"):
+ merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)
+ back["inner_path"] = "merged-%s/%s/%s" % (merged_db[merged_address], merged_address, back["inner_path"])
+ return back
+
+ # Add support merger sites for file commands with privatekey parameter
+ def mergerFuncWrapperWithPrivatekey(self, func_name, to, privatekey, inner_path, *args, **kwargs):
+ func = getattr(super(UiWebsocketPlugin, self), func_name)
+ if inner_path.startswith("merged-"):
+ merged_address, merged_inner_path = checkMergerPath(self.site.address, inner_path)
+ merged_site = self.server.sites.get(merged_address)
+
+ # Set the same cert for merged site
+ merger_cert = self.user.getSiteData(self.site.address).get("cert")
+ if merger_cert:
+ self.user.setCert(merged_address, merger_cert)
+
+ site_before = self.site # Save to be able to change it back after we ran the command
+ self.site = merged_site # Change the site to the merged one
+ try:
+ back = func(to, privatekey, merged_inner_path, *args, **kwargs)
+ finally:
+ self.site = site_before # Change back to original site
+ return back
+ else:
+ return func(to, privatekey, inner_path, *args, **kwargs)
+
+ def actionSiteSign(self, to, privatekey=None, inner_path="content.json", *args, **kwargs):
+ return self.mergerFuncWrapperWithPrivatekey("actionSiteSign", to, privatekey, inner_path, *args, **kwargs)
+
+ def actionSitePublish(self, to, privatekey=None, inner_path="content.json", *args, **kwargs):
+ return self.mergerFuncWrapperWithPrivatekey("actionSitePublish", to, privatekey, inner_path, *args, **kwargs)
+
+ def actionPermissionAdd(self, to, permission):
+ super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission)
+ if permission.startswith("Merger"):
+ self.site.storage.rebuildDb()
+
+ def actionPermissionDetails(self, to, permission):
+ if not permission.startswith("Merger"):
+ return super(UiWebsocketPlugin, self).actionPermissionDetails(to, permission)
+
+ merger_type = permission.replace("Merger:", "")
+ if not re.match("^[A-Za-z0-9-]+$", merger_type):
+ raise Exception("Invalid merger_type: %s" % merger_type)
+ merged_sites = []
+ for address, merged_type in merged_db.items():
+ if merged_type != merger_type:
+ continue
+ site = self.server.sites.get(address)
+ try:
+ merged_sites.append(site.content_manager.contents.get("content.json").get("title", address))
+ except Exception:
+ merged_sites.append(address)
+
+ details = _["Read and write permissions to sites with merged type of %s "] % merger_type
+ details += _["(%s sites)"] % len(merged_sites)
+ details += "
%s
" % ", ".join(merged_sites)
+ self.response(to, details)
+
+
+@PluginManager.registerTo("UiRequest")
+class UiRequestPlugin(object):
+ # Allow to load merged site files using /merged-ZeroMe/address/file.jpg
+ def parsePath(self, path):
+ path_parts = super(UiRequestPlugin, self).parsePath(path)
+ if "merged-" not in path: # Optimization
+ return path_parts
+ path_parts["address"], path_parts["inner_path"] = checkMergerPath(path_parts["address"], path_parts["inner_path"])
+ return path_parts
+
+
+@PluginManager.registerTo("SiteStorage")
+class SiteStoragePlugin(object):
+ # Also rebuild from merged sites
+ def getDbFiles(self):
+ merger_types = merger_db.get(self.site.address)
+
+ # First return the site's own db files
+ for item in super(SiteStoragePlugin, self).getDbFiles():
+ yield item
+
+ # Not a merger site, that's all
+ if not merger_types:
+ return
+
+ merged_sites = [
+ site_manager.sites[address]
+ for address, merged_type in merged_db.items()
+ if merged_type in merger_types
+ ]
+ found = 0
+ for merged_site in merged_sites:
+ self.log.debug("Loading merged site: %s" % merged_site)
+ merged_type = merged_db[merged_site.address]
+ for content_inner_path, content in merged_site.content_manager.contents.items():
+ # content.json file itself
+ if merged_site.storage.isFile(content_inner_path): # Missing content.json file
+ merged_inner_path = "merged-%s/%s/%s" % (merged_type, merged_site.address, content_inner_path)
+ yield merged_inner_path, merged_site.storage.getPath(content_inner_path)
+ else:
+ merged_site.log.error("[MISSING] %s" % content_inner_path)
+ # Data files in content.json
+ content_inner_path_dir = helper.getDirname(content_inner_path) # Content.json dir relative to site
+ for file_relative_path in list(content.get("files", {}).keys()) + list(content.get("files_optional", {}).keys()):
+ if not file_relative_path.endswith(".json"):
+ continue # We only interesed in json files
+ file_inner_path = content_inner_path_dir + file_relative_path # File Relative to site dir
+ file_inner_path = file_inner_path.strip("/") # Strip leading /
+ if merged_site.storage.isFile(file_inner_path):
+ merged_inner_path = "merged-%s/%s/%s" % (merged_type, merged_site.address, file_inner_path)
+ yield merged_inner_path, merged_site.storage.getPath(file_inner_path)
+ else:
+ merged_site.log.error("[MISSING] %s" % file_inner_path)
+ found += 1
+ if found % 100 == 0:
+ time.sleep(0.001) # Context switch to avoid UI block
+
+ # Also notice merger sites on a merged site file change
+ def onUpdated(self, inner_path, file=None):
+ super(SiteStoragePlugin, self).onUpdated(inner_path, file)
+
+ merged_type = merged_db.get(self.site.address)
+
+ for merger_site in merged_to_merger.get(self.site.address, []):
+ if merger_site.address == self.site.address: # Avoid infinite loop
+ continue
+ virtual_path = "merged-%s/%s/%s" % (merged_type, self.site.address, inner_path)
+ if inner_path.endswith(".json"):
+ if file is not None:
+ merger_site.storage.onUpdated(virtual_path, file=file)
+ else:
+ merger_site.storage.onUpdated(virtual_path, file=self.open(inner_path))
+ else:
+ merger_site.storage.onUpdated(virtual_path)
+
+
+@PluginManager.registerTo("Site")
+class SitePlugin(object):
+ def fileDone(self, inner_path):
+ super(SitePlugin, self).fileDone(inner_path)
+
+ for merger_site in merged_to_merger.get(self.address, []):
+ if merger_site.address == self.address:
+ continue
+ for ws in merger_site.websockets:
+ ws.event("siteChanged", self, {"event": ["file_done", inner_path]})
+
+ def fileFailed(self, inner_path):
+ super(SitePlugin, self).fileFailed(inner_path)
+
+ for merger_site in merged_to_merger.get(self.address, []):
+ if merger_site.address == self.address:
+ continue
+ for ws in merger_site.websockets:
+ ws.event("siteChanged", self, {"event": ["file_failed", inner_path]})
+
+
+@PluginManager.registerTo("SiteManager")
+class SiteManagerPlugin(object):
+ # Update merger site for site types
+ def updateMergerSites(self):
+ global merger_db, merged_db, merged_to_merger, site_manager
+ s = time.time()
+ merger_db_new = {}
+ merged_db_new = {}
+ merged_to_merger_new = {}
+ site_manager = self
+ if not self.sites:
+ return
+ for site in self.sites.values():
+ # Update merged sites
+ try:
+ merged_type = site.content_manager.contents.get("content.json", {}).get("merged_type")
+ except Exception as err:
+ self.log.error("Error loading site %s: %s" % (site.address, Debug.formatException(err)))
+ continue
+ if merged_type:
+ merged_db_new[site.address] = merged_type
+
+ # Update merger sites
+ for permission in site.settings["permissions"]:
+ if not permission.startswith("Merger:"):
+ continue
+ if merged_type:
+ self.log.error(
+ "Removing permission %s from %s: Merger and merged at the same time." %
+ (permission, site.address)
+ )
+ site.settings["permissions"].remove(permission)
+ continue
+ merger_type = permission.replace("Merger:", "")
+ if site.address not in merger_db_new:
+ merger_db_new[site.address] = []
+ merger_db_new[site.address].append(merger_type)
+ site_manager.sites[site.address] = site
+
+ # Update merged to merger
+ if merged_type:
+ for merger_site in self.sites.values():
+ if "Merger:" + merged_type in merger_site.settings["permissions"]:
+ if site.address not in merged_to_merger_new:
+ merged_to_merger_new[site.address] = []
+ merged_to_merger_new[site.address].append(merger_site)
+
+ # Update globals
+ merger_db = merger_db_new
+ merged_db = merged_db_new
+ merged_to_merger = merged_to_merger_new
+
+ self.log.debug("Updated merger sites in %.3fs" % (time.time() - s))
+
+ def load(self, *args, **kwags):
+ super(SiteManagerPlugin, self).load(*args, **kwags)
+ self.updateMergerSites()
+
+ def saveDelayed(self, *args, **kwags):
+ super(SiteManagerPlugin, self).saveDelayed(*args, **kwags)
+ self.updateMergerSites()
diff --git a/plugins/MergerSite/__init__.py b/plugins/MergerSite/__init__.py
new file mode 100644
index 00000000..2cf54611
--- /dev/null
+++ b/plugins/MergerSite/__init__.py
@@ -0,0 +1 @@
+from . import MergerSitePlugin
\ No newline at end of file
diff --git a/plugins/MergerSite/languages/es.json b/plugins/MergerSite/languages/es.json
new file mode 100644
index 00000000..d554c3a9
--- /dev/null
+++ b/plugins/MergerSite/languages/es.json
@@ -0,0 +1,5 @@
+{
+ "Add %s new site?": "¿Agregar %s nuevo sitio?",
+ "Added %s new site": "Sitio %s agregado",
+ "Site deleted: %s ": "Sitio removido: %s "
+}
diff --git a/plugins/MergerSite/languages/fr.json b/plugins/MergerSite/languages/fr.json
new file mode 100644
index 00000000..9d59fde9
--- /dev/null
+++ b/plugins/MergerSite/languages/fr.json
@@ -0,0 +1,5 @@
+{
+ "Add %s new site?": "Ajouter le site %s ?",
+ "Added %s new site": "Site %s ajouté",
+ "Site deleted: %s ": "Site %s supprimé"
+}
diff --git a/plugins/MergerSite/languages/hu.json b/plugins/MergerSite/languages/hu.json
new file mode 100644
index 00000000..8e377aaa
--- /dev/null
+++ b/plugins/MergerSite/languages/hu.json
@@ -0,0 +1,5 @@
+{
+ "Add %s new site?": "Új oldal hozzáadása: %s ?",
+ "Added %s new site": "Új oldal hozzáadva: %s ",
+ "Site deleted: %s ": "Oldal törölve: %s "
+}
diff --git a/plugins/MergerSite/languages/it.json b/plugins/MergerSite/languages/it.json
new file mode 100644
index 00000000..d56c9817
--- /dev/null
+++ b/plugins/MergerSite/languages/it.json
@@ -0,0 +1,5 @@
+{
+ "Add %s new site?": "Aggiungere %s nuovo sito ?",
+ "Added %s new site": "Sito %s aggiunto",
+ "Site deleted: %s ": "Sito %s eliminato"
+}
diff --git a/plugins/MergerSite/languages/jp.json b/plugins/MergerSite/languages/jp.json
new file mode 100644
index 00000000..7216f268
--- /dev/null
+++ b/plugins/MergerSite/languages/jp.json
@@ -0,0 +1,5 @@
+{
+ "Add %s new site?": "サイト: %s を追加しますか?",
+ "Added %s new site": "サイト: %s を追加しました",
+ "Site deleted: %s ": "サイト: %s を削除しました"
+}
diff --git a/plugins/MergerSite/languages/pt-br.json b/plugins/MergerSite/languages/pt-br.json
new file mode 100644
index 00000000..cdc298cb
--- /dev/null
+++ b/plugins/MergerSite/languages/pt-br.json
@@ -0,0 +1,5 @@
+{
+ "Add %s new site?": "Adicionar %s novo site?",
+ "Added %s new site": "Site %s adicionado",
+ "Site deleted: %s ": "Site removido: %s "
+}
diff --git a/plugins/MergerSite/languages/tr.json b/plugins/MergerSite/languages/tr.json
new file mode 100644
index 00000000..5afb3942
--- /dev/null
+++ b/plugins/MergerSite/languages/tr.json
@@ -0,0 +1,5 @@
+{
+ "Add %s new site?": "%s sitesi eklensin mi?",
+ "Added %s new site": "%s sitesi eklendi",
+ "Site deleted: %s ": "%s sitesi silindi"
+}
diff --git a/plugins/MergerSite/languages/zh-tw.json b/plugins/MergerSite/languages/zh-tw.json
new file mode 100644
index 00000000..a0684e63
--- /dev/null
+++ b/plugins/MergerSite/languages/zh-tw.json
@@ -0,0 +1,5 @@
+{
+ "Add %s new site?": "添加新網站: %s ?",
+ "Added %s new site": "已添加到新網站:%s ",
+ "Site deleted: %s ": "網站已刪除:%s "
+}
diff --git a/plugins/MergerSite/languages/zh.json b/plugins/MergerSite/languages/zh.json
new file mode 100644
index 00000000..127044e6
--- /dev/null
+++ b/plugins/MergerSite/languages/zh.json
@@ -0,0 +1,5 @@
+{
+ "Add %s new site?": "添加新站点: %s ?",
+ "Added %s new site": "已添加到新站点:%s ",
+ "Site deleted: %s ": "站点已删除:%s "
+}
diff --git a/plugins/Stats/StatsPlugin.py b/plugins/Stats/StatsPlugin.py
new file mode 100644
index 00000000..32fd06c5
--- /dev/null
+++ b/plugins/Stats/StatsPlugin.py
@@ -0,0 +1,627 @@
+import time
+import html
+import os
+import json
+import sys
+import itertools
+
+from Plugin import PluginManager
+from Config import config
+from util import helper
+from Debug import Debug
+from Db import Db
+
+
+@PluginManager.registerTo("UiRequest")
+class UiRequestPlugin(object):
+
+ def formatTableRow(self, row, class_name=""):
+ back = []
+ for format, val in row:
+ if val is None:
+ formatted = "n/a"
+ elif format == "since":
+ if val:
+ formatted = "%.0f" % (time.time() - val)
+ else:
+ formatted = "n/a"
+ else:
+ formatted = format % val
+ back.append("%s " % formatted)
+ return "%s " % (class_name, "".join(back))
+
+ def getObjSize(self, obj, hpy=None):
+ if hpy:
+ return float(hpy.iso(obj).domisize) / 1024
+ else:
+ return 0
+
+ def renderHead(self):
+ import main
+ from Crypt import CryptConnection
+
+ # Memory
+ yield "rev%s | " % config.rev
+ yield "%s | " % main.file_server.ip_external_list
+ yield "Port: %s | " % main.file_server.port
+ yield "Network: %s | " % main.file_server.supported_ip_types
+ yield "Opened: %s | " % main.file_server.port_opened
+ yield "Crypt: %s, TLSv1.3: %s | " % (CryptConnection.manager.crypt_supported, CryptConnection.ssl.HAS_TLSv1_3)
+ yield "In: %.2fMB, Out: %.2fMB | " % (
+ float(main.file_server.bytes_recv) / 1024 / 1024,
+ float(main.file_server.bytes_sent) / 1024 / 1024
+ )
+ yield "Peerid: %s | " % main.file_server.peer_id
+ yield "Time: %.2fs | " % main.file_server.getTimecorrection()
+ yield "Blocks: %s" % Debug.num_block
+
+ try:
+ import psutil
+ process = psutil.Process(os.getpid())
+ mem = process.get_memory_info()[0] / float(2 ** 20)
+ yield "Mem: %.2fMB | " % mem
+ yield "Threads: %s | " % len(process.threads())
+ yield "CPU: usr %.2fs sys %.2fs | " % process.cpu_times()
+ yield "Files: %s | " % len(process.open_files())
+ yield "Sockets: %s | " % len(process.connections())
+ yield "Calc size on off "
+ except Exception:
+ pass
+ yield " "
+
+ def renderConnectionsTable(self):
+ import main
+
+ # Connections
+ yield "Connections (%s, total made: %s, in: %s, out: %s): " % (
+ len(main.file_server.connections), main.file_server.last_connection_id,
+ main.file_server.num_incoming, main.file_server.num_outgoing
+ )
+ yield " id type ip open crypt ping "
+ yield "buff bad idle open delay cpu out in last sent "
+ yield "wait version time sites "
+ for connection in main.file_server.connections:
+ if "cipher" in dir(connection.sock):
+ cipher = connection.sock.cipher()[0]
+ tls_version = connection.sock.version()
+ else:
+ cipher = connection.crypt
+ tls_version = ""
+ if "time" in connection.handshake and connection.last_ping_delay:
+ time_correction = connection.handshake["time"] - connection.handshake_time - connection.last_ping_delay
+ else:
+ time_correction = 0.0
+ yield self.formatTableRow([
+ ("%3d", connection.id),
+ ("%s", connection.type),
+ ("%s:%s", (connection.ip, connection.port)),
+ ("%s", connection.handshake.get("port_opened")),
+ ("%s ", (cipher, tls_version, connection.crypt)),
+ ("%6.3f", connection.last_ping_delay),
+ ("%s", connection.incomplete_buff_recv),
+ ("%s", connection.bad_actions),
+ ("since", max(connection.last_send_time, connection.last_recv_time)),
+ ("since", connection.start_time),
+ ("%.3f", max(-1, connection.last_sent_time - connection.last_send_time)),
+ ("%.3f", connection.cpu_time),
+ ("%.0fk", connection.bytes_sent / 1024),
+ ("%.0fk", connection.bytes_recv / 1024),
+ ("%s ", (connection.last_cmd_recv, connection.last_cmd_sent)),
+ ("%s", list(connection.waiting_requests.keys())),
+ ("%s r%s", (connection.handshake.get("version"), connection.handshake.get("rev", "?"))),
+ ("%.2fs", time_correction),
+ ("%s", connection.sites)
+ ])
+ yield "
"
+
+ def renderTrackers(self):
+ # Trackers
+ yield "Trackers: "
+ yield " address request successive errors last_request "
+ from Site import SiteAnnouncer # importing at the top of the file breaks plugins
+ for tracker_address, tracker_stat in sorted(SiteAnnouncer.global_stats.items()):
+ yield self.formatTableRow([
+ ("%s", tracker_address),
+ ("%s", tracker_stat["num_request"]),
+ ("%s", tracker_stat["num_error"]),
+ ("%.0f min ago", min(999, (time.time() - tracker_stat["time_request"]) / 60))
+ ])
+ yield "
"
+
+ if "AnnounceShare" in PluginManager.plugin_manager.plugin_names:
+ yield "Shared trackers: "
+ yield " address added found latency successive errors last_success "
+ from AnnounceShare import AnnounceSharePlugin
+ for tracker_address, tracker_stat in sorted(AnnounceSharePlugin.tracker_storage.getTrackers().items()):
+ yield self.formatTableRow([
+ ("%s", tracker_address),
+ ("%.0f min ago", min(999, (time.time() - tracker_stat["time_added"]) / 60)),
+ ("%.0f min ago", min(999, (time.time() - tracker_stat.get("time_found", 0)) / 60)),
+ ("%.3fs", tracker_stat["latency"]),
+ ("%s", tracker_stat["num_error"]),
+ ("%.0f min ago", min(999, (time.time() - tracker_stat["time_success"]) / 60)),
+ ])
+ yield "
"
+
+ def renderTor(self):
+ import main
+ yield "Tor hidden services (status: %s): " % main.file_server.tor_manager.status
+ for site_address, onion in list(main.file_server.tor_manager.site_onions.items()):
+ yield "- %-34s: %s " % (site_address, onion)
+
+ def renderDbStats(self):
+ yield "Db : "
+ for db in Db.opened_dbs:
+ tables = [row["name"] for row in db.execute("SELECT name FROM sqlite_master WHERE type = 'table'").fetchall()]
+ table_rows = {}
+ for table in tables:
+ table_rows[table] = db.execute("SELECT COUNT(*) AS c FROM %s" % table).fetchone()["c"]
+ db_size = os.path.getsize(db.db_path) / 1024.0 / 1024.0
+ yield "- %.3fs: %s %.3fMB, table rows: %s " % (
+ time.time() - db.last_query_time, db.db_path, db_size, json.dumps(table_rows, sort_keys=True)
+ )
+
+ def renderSites(self):
+ yield "Sites :"
+ yield ""
+ yield "address connected peers content.json out in "
+ for site in list(self.server.sites.values()):
+ yield self.formatTableRow([
+ (
+ """%s """,
+ (site.address, site.address)
+ ),
+ ("%s", [peer.connection.id for peer in list(site.peers.values()) if peer.connection and peer.connection.connected]),
+ ("%s/%s/%s", (
+ len([peer for peer in list(site.peers.values()) if peer.connection and peer.connection.connected]),
+ len(site.getConnectablePeers(100)),
+ len(site.peers)
+ )),
+ ("%s (loaded: %s)", (
+ len(site.content_manager.contents),
+ len([key for key, val in dict(site.content_manager.contents).items() if val])
+ )),
+ ("%.0fk", site.settings.get("bytes_sent", 0) / 1024),
+ ("%.0fk", site.settings.get("bytes_recv", 0) / 1024),
+ ], "serving-%s" % site.settings["serving"])
+ yield "" % site.address
+ for key, peer in list(site.peers.items()):
+ if peer.time_found:
+ time_found = int(time.time() - peer.time_found) / 60
+ else:
+ time_found = "--"
+ if peer.connection:
+ connection_id = peer.connection.id
+ else:
+ connection_id = None
+ if site.content_manager.has_optional_files:
+ yield "Optional files: %4s " % len(peer.hashfield)
+ time_added = (time.time() - peer.time_added) / (60 * 60 * 24)
+ yield "(#%4s, rep: %2s, err: %s, found: %.1fs min, add: %.1f day) %30s - " % (connection_id, peer.reputation, peer.connection_error, time_found, time_added, key)
+ yield " "
+ yield "
"
+
+ def renderBigfiles(self):
+ yield "Big files : "
+ for site in list(self.server.sites.values()):
+ if not site.settings.get("has_bigfile"):
+ continue
+ bigfiles = {}
+ yield """%s """ % (site.address, site.address)
+ for peer in list(site.peers.values()):
+ if not peer.time_piecefields_updated:
+ continue
+ for sha512, piecefield in peer.piecefields.items():
+ if sha512 not in bigfiles:
+ bigfiles[sha512] = []
+ bigfiles[sha512].append(peer)
+
+ yield "" % site.address
+ for sha512, peers in bigfiles.items():
+ yield "
- " + sha512 + " (hash id: %s)
" % site.content_manager.hashfield.getHashId(sha512)
+ yield "
"
+ for peer in peers:
+ yield "" + peer.key + " " + peer.piecefields[sha512].tostring() + " "
+ yield "
"
+ yield "
"
+
+ def renderRequests(self):
+ import main
+ yield ""
+ yield "
Sent commands :
"
+ yield "
"
+ for stat_key, stat in sorted(main.file_server.stat_sent.items(), key=lambda i: i[1]["bytes"], reverse=True):
+ yield "%s x %s = %.0fkB " % (stat_key, stat["num"], stat["bytes"] / 1024)
+ yield "
"
+ yield "
"
+
+ yield ""
+ yield "
Received commands :
"
+ yield "
"
+ for stat_key, stat in sorted(main.file_server.stat_recv.items(), key=lambda i: i[1]["bytes"], reverse=True):
+ yield "%s x %s = %.0fkB " % (stat_key, stat["num"], stat["bytes"] / 1024)
+ yield "
"
+ yield "
"
+ yield "
"
+
+ def renderMemory(self):
+ import gc
+ from Ui import UiRequest
+
+ hpy = None
+ if self.get.get("size") == "1": # Calc obj size
+ try:
+ import guppy
+ hpy = guppy.hpy()
+ except Exception:
+ pass
+ self.sendHeader()
+
+ # Object types
+
+ obj_count = {}
+ for obj in gc.get_objects():
+ obj_type = str(type(obj))
+ if obj_type not in obj_count:
+ obj_count[obj_type] = [0, 0]
+ obj_count[obj_type][0] += 1 # Count
+ obj_count[obj_type][1] += float(sys.getsizeof(obj)) / 1024 # Size
+
+ yield "Objects in memory (types: %s, total: %s, %.2fkb): " % (
+ len(obj_count),
+ sum([stat[0] for stat in list(obj_count.values())]),
+ sum([stat[1] for stat in list(obj_count.values())])
+ )
+
+ for obj, stat in sorted(list(obj_count.items()), key=lambda x: x[1][0], reverse=True): # Sorted by count
+ yield " - %.1fkb = %s x %s " % (stat[1], stat[0], obj, html.escape(obj))
+
+ # Classes
+
+ class_count = {}
+ for obj in gc.get_objects():
+ obj_type = str(type(obj))
+ if obj_type != "":
+ continue
+ class_name = obj.__class__.__name__
+ if class_name not in class_count:
+ class_count[class_name] = [0, 0]
+ class_count[class_name][0] += 1 # Count
+ class_count[class_name][1] += float(sys.getsizeof(obj)) / 1024 # Size
+
+ yield "Classes in memory (types: %s, total: %s, %.2fkb): " % (
+ len(class_count),
+ sum([stat[0] for stat in list(class_count.values())]),
+ sum([stat[1] for stat in list(class_count.values())])
+ )
+
+ for obj, stat in sorted(list(class_count.items()), key=lambda x: x[1][0], reverse=True): # Sorted by count
+ yield " - %.1fkb = %s x %s " % (stat[1], stat[0], obj, html.escape(obj))
+
+ from greenlet import greenlet
+ objs = [obj for obj in gc.get_objects() if isinstance(obj, greenlet)]
+ yield " Greenlets (%s): " % len(objs)
+ for obj in objs:
+ yield " - %.1fkb: %s " % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
+
+ from Worker import Worker
+ objs = [obj for obj in gc.get_objects() if isinstance(obj, Worker)]
+ yield " Workers (%s): " % len(objs)
+ for obj in objs:
+ yield " - %.1fkb: %s " % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
+
+ from Connection import Connection
+ objs = [obj for obj in gc.get_objects() if isinstance(obj, Connection)]
+ yield " Connections (%s): " % len(objs)
+ for obj in objs:
+ yield " - %.1fkb: %s " % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
+
+ from socket import socket
+ objs = [obj for obj in gc.get_objects() if isinstance(obj, socket)]
+ yield " Sockets (%s): " % len(objs)
+ for obj in objs:
+ yield " - %.1fkb: %s " % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
+
+ from msgpack import Unpacker
+ objs = [obj for obj in gc.get_objects() if isinstance(obj, Unpacker)]
+ yield " Msgpack unpacker (%s): " % len(objs)
+ for obj in objs:
+ yield " - %.1fkb: %s " % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
+
+ from Site.Site import Site
+ objs = [obj for obj in gc.get_objects() if isinstance(obj, Site)]
+ yield " Sites (%s): " % len(objs)
+ for obj in objs:
+ yield " - %.1fkb: %s " % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
+
+ objs = [obj for obj in gc.get_objects() if isinstance(obj, self.server.log.__class__)]
+ yield " Loggers (%s): " % len(objs)
+ for obj in objs:
+ yield " - %.1fkb: %s " % (self.getObjSize(obj, hpy), html.escape(repr(obj.name)))
+
+ objs = [obj for obj in gc.get_objects() if isinstance(obj, UiRequest)]
+ yield " UiRequests (%s): " % len(objs)
+ for obj in objs:
+ yield " - %.1fkb: %s " % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
+
+ from Peer import Peer
+ objs = [obj for obj in gc.get_objects() if isinstance(obj, Peer)]
+ yield " Peers (%s): " % len(objs)
+ for obj in objs:
+ yield " - %.1fkb: %s " % (self.getObjSize(obj, hpy), html.escape(repr(obj)))
+
+ objs = [(key, val) for key, val in sys.modules.items() if val is not None]
+ objs.sort()
+ yield " Modules (%s): " % len(objs)
+ for module_name, module in objs:
+ yield " - %.3fkb: %s %s " % (self.getObjSize(module, hpy), module_name, html.escape(repr(module)))
+
+ # /Stats entry point
+ @helper.encodeResponse
+ def actionStats(self):
+ import gc
+
+ self.sendHeader()
+
+ if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
+ yield "This function is disabled on this proxy"
+ return
+
+ s = time.time()
+
+ # Style
+ yield """
+
+ """
+
+ renderers = [
+ self.renderHead(),
+ self.renderConnectionsTable(),
+ self.renderTrackers(),
+ self.renderTor(),
+ self.renderDbStats(),
+ self.renderSites(),
+ self.renderBigfiles(),
+ self.renderRequests()
+
+ ]
+
+ for part in itertools.chain(*renderers):
+ yield part
+
+ if config.debug:
+ for part in self.renderMemory():
+ yield part
+
+ gc.collect() # Implicit grabage collection
+ yield "Done in %.1f" % (time.time() - s)
+
+ @helper.encodeResponse
+ def actionDumpobj(self):
+
+ import gc
+ import sys
+
+ self.sendHeader()
+
+ if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
+ yield "This function is disabled on this proxy"
+ return
+
+ # No more if not in debug mode
+ if not config.debug:
+ yield "Not in debug mode"
+ return
+
+ class_filter = self.get.get("class")
+
+ yield """
+
+ """
+
+ objs = gc.get_objects()
+ for obj in objs:
+ obj_type = str(type(obj))
+ if obj_type != "" or obj.__class__.__name__ != class_filter:
+ continue
+ yield "%.1fkb %s... " % (float(sys.getsizeof(obj)) / 1024, html.escape(str(obj)))
+ for attr in dir(obj):
+ yield "- %s: %s " % (attr, html.escape(str(getattr(obj, attr))))
+ yield " "
+
+ gc.collect() # Implicit grabage collection
+
+ @helper.encodeResponse
+ def actionListobj(self):
+
+ import gc
+ import sys
+
+ self.sendHeader()
+
+ if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
+ yield "This function is disabled on this proxy"
+ return
+
+ # No more if not in debug mode
+ if not config.debug:
+ yield "Not in debug mode"
+ return
+
+ type_filter = self.get.get("type")
+
+ yield """
+
+ """
+
+ yield "Listing all %s objects in memory... " % html.escape(type_filter)
+
+ ref_count = {}
+ objs = gc.get_objects()
+ for obj in objs:
+ obj_type = str(type(obj))
+ if obj_type != type_filter:
+ continue
+ refs = [
+ ref for ref in gc.get_referrers(obj)
+ if hasattr(ref, "__class__") and
+ ref.__class__.__name__ not in ["list", "dict", "function", "type", "frame", "WeakSet", "tuple"]
+ ]
+ if not refs:
+ continue
+ try:
+ yield "%.1fkb %s ... " % (
+ float(sys.getsizeof(obj)) / 1024, html.escape(str(obj)), html.escape(str(obj)[0:100].ljust(100))
+ )
+ except Exception:
+ continue
+ for ref in refs:
+ yield " ["
+ if "object at" in str(ref) or len(str(ref)) > 100:
+ yield str(ref.__class__.__name__)
+ else:
+ yield str(ref.__class__.__name__) + ":" + html.escape(str(ref))
+ yield "] "
+ ref_type = ref.__class__.__name__
+ if ref_type not in ref_count:
+ ref_count[ref_type] = [0, 0]
+ ref_count[ref_type][0] += 1 # Count
+ ref_count[ref_type][1] += float(sys.getsizeof(obj)) / 1024 # Size
+ yield " "
+
+ yield " Object referrer (total: %s, %.2fkb): " % (len(ref_count), sum([stat[1] for stat in list(ref_count.values())]))
+
+ for obj, stat in sorted(list(ref_count.items()), key=lambda x: x[1][0], reverse=True)[0:30]: # Sorted by count
+ yield " - %.1fkb = %s x %s " % (stat[1], stat[0], html.escape(str(obj)))
+
+ gc.collect() # Implicit grabage collection
+
+ @helper.encodeResponse
+ def actionGcCollect(self):
+ import gc
+ self.sendHeader()
+ yield str(gc.collect())
+
+ # /About entry point
+ @helper.encodeResponse
+ def actionEnv(self):
+ import main
+
+ self.sendHeader()
+
+ yield """
+
+ """
+
+ if "Multiuser" in PluginManager.plugin_manager.plugin_names and not config.multiuser_local:
+ yield "This function is disabled on this proxy"
+ return
+
+ yield from main.actions.testEnv(format="html")
+
+
+@PluginManager.registerTo("Actions")
+class ActionsPlugin:
+ def formatTable(self, *rows, format="text"):
+ if format == "html":
+ return self.formatTableHtml(*rows)
+ else:
+ return self.formatTableText(*rows)
+
+ def formatHead(self, title, format="text"):
+ if format == "html":
+ return "%s " % title
+ else:
+ return "\n* %s\n" % title
+
+ def formatTableHtml(self, *rows):
+ yield ""
+ for row in rows:
+ yield ""
+ for col in row:
+ yield "%s " % html.escape(str(col))
+ yield " "
+ yield "
"
+
+ def formatTableText(self, *rows):
+ for row in rows:
+ yield " "
+ for col in row:
+ yield " " + str(col)
+ yield "\n"
+
+ def testEnv(self, format="text"):
+ import gevent
+ import msgpack
+ import pkg_resources
+ import importlib
+ import coincurve
+ import sqlite3
+ from Crypt import CryptBitcoin
+
+ yield "\n"
+
+ yield from self.formatTable(
+ ["ZeroNet version:", "%s rev%s" % (config.version, config.rev)],
+ ["Python:", "%s" % sys.version],
+ ["Platform:", "%s" % sys.platform],
+ ["Crypt verify lib:", "%s" % CryptBitcoin.lib_verify_best],
+ ["OpenSSL:", "%s" % CryptBitcoin.sslcrypto.ecc.get_backend()],
+ ["Libsecp256k1:", "%s" % type(coincurve._libsecp256k1.lib).__name__],
+ ["SQLite:", "%s, API: %s" % (sqlite3.sqlite_version, sqlite3.version)],
+ format=format
+ )
+
+
+ yield self.formatHead("Libraries:")
+ rows = []
+ for lib_name in ["gevent", "greenlet", "msgpack", "base58", "merkletools", "rsa", "socks", "pyasn1", "gevent_ws", "websocket", "maxminddb"]:
+ try:
+ module = importlib.import_module(lib_name)
+ if "__version__" in dir(module):
+ version = module.__version__
+ elif "version" in dir(module):
+ version = module.version
+ else:
+ version = "unknown version"
+
+ if type(version) is tuple:
+ version = ".".join(map(str, version))
+
+ rows.append(["- %s:" % lib_name, version, "at " + module.__file__])
+ except Exception as err:
+ rows.append(["! Error importing %s:", repr(err)])
+
+ """
+ try:
+ yield " - %s " % html.escape(repr(pkg_resources.get_distribution(lib_name)))
+ except Exception as err:
+ yield " ! %s " % html.escape(repr(err))
+ """
+
+ yield from self.formatTable(*rows, format=format)
+
+ yield self.formatHead("Library config:", format=format)
+
+ yield from self.formatTable(
+ ["- gevent:", gevent.config.loop.__module__],
+ ["- msgpack unpacker:", msgpack.Unpacker.__module__],
+ format=format
+ )
diff --git a/plugins/Stats/__init__.py b/plugins/Stats/__init__.py
new file mode 100644
index 00000000..791fb6e0
--- /dev/null
+++ b/plugins/Stats/__init__.py
@@ -0,0 +1 @@
+from . import StatsPlugin
\ No newline at end of file
diff --git a/plugins/Stats/plugin_info.json b/plugins/Stats/plugin_info.json
new file mode 100644
index 00000000..1f401a4f
--- /dev/null
+++ b/plugins/Stats/plugin_info.json
@@ -0,0 +1,5 @@
+{
+ "name": "Stats",
+ "description": "/Stats and /Benchmark pages.",
+ "default": "enabled"
+}
\ No newline at end of file
diff --git a/plugins/Trayicon/TrayiconPlugin.py b/plugins/Trayicon/TrayiconPlugin.py
new file mode 100644
index 00000000..2dc3a5c6
--- /dev/null
+++ b/plugins/Trayicon/TrayiconPlugin.py
@@ -0,0 +1,171 @@
+import os
+import sys
+import atexit
+
+from Plugin import PluginManager
+from Config import config
+from Translate import Translate
+
+allow_reload = False # No source reload supported in this plugin
+
+
+plugin_dir = os.path.dirname(__file__)
+
+if "_" not in locals():
+ _ = Translate(plugin_dir + "/languages/")
+
+
+@PluginManager.registerTo("Actions")
+class ActionsPlugin(object):
+
+ def main(self):
+ global notificationicon, winfolders
+ from .lib import notificationicon, winfolders
+ import gevent.threadpool
+ import main
+
+ self.main = main
+
+ icon = notificationicon.NotificationIcon(
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), 'trayicon.ico'),
+ "ZeroNet %s" % config.version
+ )
+ self.icon = icon
+
+ self.console = False
+
+ @atexit.register
+ def hideIcon():
+ try:
+ icon.die()
+ except Exception as err:
+ print("Error removing trayicon: %s" % err)
+
+ ui_ip = config.ui_ip if config.ui_ip != "*" else "127.0.0.1"
+
+ if ":" in ui_ip:
+ ui_ip = "[" + ui_ip + "]"
+
+ icon.items = [
+ (self.titleIp, False),
+ (self.titleConnections, False),
+ (self.titleTransfer, False),
+ (self.titleConsole, self.toggleConsole),
+ (self.titleAutorun, self.toggleAutorun),
+ "--",
+ (_["ZeroNet Twitter"], lambda: self.opensite("https://twitter.com/HelloZeroNet")),
+ (_["ZeroNet Reddit"], lambda: self.opensite("http://www.reddit.com/r/zeronet/")),
+ (_["ZeroNet Github"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet")),
+ (_["Report bug/request feature"], lambda: self.opensite("https://github.com/HelloZeroNet/ZeroNet/issues")),
+ "--",
+ (_["!Open ZeroNet"], lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage))),
+ "--",
+ (_["Quit"], self.quit),
+ ]
+
+ if not notificationicon.hasConsole():
+ del icon.items[3]
+
+ icon.clicked = lambda: self.opensite("http://%s:%s/%s" % (ui_ip, config.ui_port, config.homepage))
+ self.quit_servers_event = gevent.threadpool.ThreadResult(
+ lambda res: gevent.spawn_later(0.1, self.quitServers), gevent.threadpool.get_hub(), lambda: True
+ ) # Fix gevent thread switch error
+ gevent.threadpool.start_new_thread(icon._run, ()) # Start in real thread (not gevent compatible)
+ super(ActionsPlugin, self).main()
+ icon._die = True
+
+ def quit(self):
+ self.icon.die()
+ self.quit_servers_event.set(True)
+
+ def quitServers(self):
+ self.main.ui_server.stop()
+ self.main.file_server.stop()
+
+ def opensite(self, url):
+ import webbrowser
+ webbrowser.open(url, new=0)
+
+ def titleIp(self):
+ title = "!IP: %s " % ", ".join(self.main.file_server.ip_external_list)
+ if any(self.main.file_server.port_opened):
+ title += _["(active)"]
+ else:
+ title += _["(passive)"]
+ return title
+
+ def titleConnections(self):
+ title = _["Connections: %s"] % len(self.main.file_server.connections)
+ return title
+
+ def titleTransfer(self):
+ title = _["Received: %.2f MB | Sent: %.2f MB"] % (
+ float(self.main.file_server.bytes_recv) / 1024 / 1024,
+ float(self.main.file_server.bytes_sent) / 1024 / 1024
+ )
+ return title
+
+ def titleConsole(self):
+ translate = _["Show console window"]
+ if self.console:
+ return "+" + translate
+ else:
+ return translate
+
+ def toggleConsole(self):
+ if self.console:
+ notificationicon.hideConsole()
+ self.console = False
+ else:
+ notificationicon.showConsole()
+ self.console = True
+
+ def getAutorunPath(self):
+ return "%s\\zeronet.cmd" % winfolders.get(winfolders.STARTUP)
+
+ def formatAutorun(self):
+ args = sys.argv[:]
+
+ if not getattr(sys, 'frozen', False): # Not frozen
+ args.insert(0, sys.executable)
+ cwd = os.getcwd()
+ else:
+ cwd = os.path.dirname(sys.executable)
+
+ ignored_args = [
+ "--open_browser", "default_browser",
+ "--dist_type", "bundle_win64"
+ ]
+
+ if sys.platform == 'win32':
+ args = ['"%s"' % arg for arg in args if arg and arg not in ignored_args]
+ cmd = " ".join(args)
+
+ # Dont open browser on autorun
+ cmd = cmd.replace("start.py", "zeronet.py").strip()
+ cmd += ' --open_browser ""'
+
+ return "\r\n".join([
+ '@echo off',
+ 'chcp 65001 > nul',
+ 'set PYTHONIOENCODING=utf-8',
+ 'cd /D \"%s\"' % cwd,
+ 'start "" %s' % cmd
+ ])
+
+ def isAutorunEnabled(self):
+ path = self.getAutorunPath()
+ return os.path.isfile(path) and open(path, "rb").read().decode("utf8") == self.formatAutorun()
+
+ def titleAutorun(self):
+ translate = _["Start ZeroNet when Windows starts"]
+ if self.isAutorunEnabled():
+ return "+" + translate
+ else:
+ return translate
+
+ def toggleAutorun(self):
+ if self.isAutorunEnabled():
+ os.unlink(self.getAutorunPath())
+ else:
+ open(self.getAutorunPath(), "wb").write(self.formatAutorun().encode("utf8"))
diff --git a/plugins/Trayicon/__init__.py b/plugins/Trayicon/__init__.py
new file mode 100644
index 00000000..918f76dc
--- /dev/null
+++ b/plugins/Trayicon/__init__.py
@@ -0,0 +1,4 @@
+import sys
+
+if sys.platform == 'win32':
+ from . import TrayiconPlugin
\ No newline at end of file
diff --git a/plugins/Trayicon/languages/es.json b/plugins/Trayicon/languages/es.json
new file mode 100644
index 00000000..6710c3c5
--- /dev/null
+++ b/plugins/Trayicon/languages/es.json
@@ -0,0 +1,14 @@
+{
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "Reportar fallo/sugerir característica",
+ "!Open ZeroNet": "!Abrir ZeroNet",
+ "Quit": "Sair",
+ "(active)": "(activo)",
+ "(passive)": "(pasivo)",
+ "Connections: %s": "Conecciones: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "Recibido: %.2f MB | Enviado: %.2f MB",
+ "Show console window": "Mostrar consola",
+ "Start ZeroNet when Windows starts": "Iniciar Zeronet cuando inicie Windows"
+}
diff --git a/plugins/Trayicon/languages/fr.json b/plugins/Trayicon/languages/fr.json
new file mode 100644
index 00000000..a6fca769
--- /dev/null
+++ b/plugins/Trayicon/languages/fr.json
@@ -0,0 +1,14 @@
+{
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "Rapport d'erreur/Demander une fonctionnalité",
+ "!Open ZeroNet": "!Ouvrir ZeroNet",
+ "Quit": "Quitter",
+ "(active)": "(actif)",
+ "(passive)": "(passif)",
+ "Connections: %s": "Connexions: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "Reçu: %.2f MB | Envoyé: %.2f MB",
+ "Show console window": "Afficher la console",
+ "Start ZeroNet when Windows starts": "Lancer ZeroNet au démarrage de Windows"
+}
diff --git a/plugins/Trayicon/languages/hu.json b/plugins/Trayicon/languages/hu.json
new file mode 100644
index 00000000..56fef23a
--- /dev/null
+++ b/plugins/Trayicon/languages/hu.json
@@ -0,0 +1,14 @@
+{
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "Hiba bejelentés/ötletek",
+ "!Open ZeroNet": "!ZeroNet megnyitása",
+ "Quit": "Kilépés",
+ "(active)": "(aktív)",
+ "(passive)": "(passive)",
+ "Connections: %s": "Kapcsolatok: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "Fogadott: %.2f MB | Küldött: %.2f MB",
+ "Show console window": "Parancssor mutatása",
+ "Start ZeroNet when Windows starts": "ZeroNet indítása a Windows-al együtt"
+}
diff --git a/plugins/Trayicon/languages/it.json b/plugins/Trayicon/languages/it.json
new file mode 100644
index 00000000..efbd6cfa
--- /dev/null
+++ b/plugins/Trayicon/languages/it.json
@@ -0,0 +1,14 @@
+{
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "Segnala bug/richiesta di una funzione",
+ "!Open ZeroNet": "!Apri ZeroNet",
+ "Quit": "Chiudi",
+ "(active)": "(attivo)",
+ "(passive)": "(passivo)",
+ "Connections: %s": "Connessioni: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "Ricevuto: %.2f MB | Inviato: %.2f MB",
+ "Show console window": "Mostra finestra console",
+ "Start ZeroNet when Windows starts": "Avvia ZeroNet all'avvio di Windows"
+}
diff --git a/plugins/Trayicon/languages/jp.json b/plugins/Trayicon/languages/jp.json
new file mode 100644
index 00000000..aa28457b
--- /dev/null
+++ b/plugins/Trayicon/languages/jp.json
@@ -0,0 +1,14 @@
+ {
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "バグ報告/要望",
+ "!Open ZeroNet": "!ZeroNetをブラウザで開く",
+ "Quit": "閉じる",
+ "(active)": "(アクティブ)",
+ "(passive)": "(パッシブ)",
+ "Connections: %s": "接続数: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "受信: %.2f MB | 送信: %.2f MB",
+ "Show console window": "コンソールを表示",
+ "Start ZeroNet when Windows starts": "Windows起動時にZeroNetも起動する"
+}
diff --git a/plugins/Trayicon/languages/pl.json b/plugins/Trayicon/languages/pl.json
new file mode 100644
index 00000000..84c14796
--- /dev/null
+++ b/plugins/Trayicon/languages/pl.json
@@ -0,0 +1,14 @@
+{
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "Zgłoś błąd / propozycję",
+ "!Open ZeroNet": "!Otwórz ZeroNet",
+ "Quit": "Zamknij",
+ "(active)": "(aktywny)",
+ "(passive)": "(pasywny)",
+ "Connections: %s": "Połączenia: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "Odebrano: %.2f MB | Wysłano: %.2f MB",
+ "Show console window": "Pokaż okno konsoli",
+ "Start ZeroNet when Windows starts": "Uruchom ZeroNet podczas startu Windows"
+}
diff --git a/plugins/Trayicon/languages/pt-br.json b/plugins/Trayicon/languages/pt-br.json
new file mode 100644
index 00000000..5e86493a
--- /dev/null
+++ b/plugins/Trayicon/languages/pt-br.json
@@ -0,0 +1,14 @@
+{
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "Reportar bug/sugerir recurso",
+ "!Open ZeroNet": "!Abrir ZeroNet",
+ "Quit": "Sair",
+ "(active)": "(ativo)",
+ "(passive)": "(passivo)",
+ "Connections: %s": "Conexões: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "Recebido: %.2f MB | Enviado: %.2f MB",
+ "Show console window": "Mostrar console",
+ "Start ZeroNet when Windows starts": "Iniciar o ZeroNet quando o Windows for iniciado"
+}
diff --git a/plugins/Trayicon/languages/tr.json b/plugins/Trayicon/languages/tr.json
new file mode 100644
index 00000000..077b8ddd
--- /dev/null
+++ b/plugins/Trayicon/languages/tr.json
@@ -0,0 +1,14 @@
+{
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "Hata bildir/geliştirme taleb et",
+ "!Open ZeroNet": "!ZeroNet'i Aç",
+ "Quit": "Kapat",
+ "(active)": "(aktif)",
+ "(passive)": "(pasif)",
+ "Connections: %s": "Bağlantı sayısı: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "Gelen: %.2f MB | Gönderilen: %.2f MB",
+ "Show console window": "Konsolu aç",
+ "Start ZeroNet when Windows starts": "ZeroNet'i açılışta otomatik başlat"
+}
diff --git a/plugins/Trayicon/languages/zh-tw.json b/plugins/Trayicon/languages/zh-tw.json
new file mode 100644
index 00000000..2189033e
--- /dev/null
+++ b/plugins/Trayicon/languages/zh-tw.json
@@ -0,0 +1,14 @@
+{
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "回饋问题/請求功能",
+ "!Open ZeroNet": "!開啟 ZeroNet",
+ "Quit": "退出",
+ "(active)": "(主動模式)",
+ "(passive)": "(被動模式)",
+ "Connections: %s": "連線數: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "已收到: %.2f MB | 已傳送: %.2f MB",
+ "Show console window": "顯示控制臺窗體",
+ "Start ZeroNet when Windows starts": "在 Windows 啟動時執行 ZeroNet"
+}
diff --git a/plugins/Trayicon/languages/zh.json b/plugins/Trayicon/languages/zh.json
new file mode 100644
index 00000000..29b73305
--- /dev/null
+++ b/plugins/Trayicon/languages/zh.json
@@ -0,0 +1,14 @@
+{
+ "ZeroNet Twitter": "ZeroNet Twitter",
+ "ZeroNet Reddit": "ZeroNet Reddit",
+ "ZeroNet Github": "ZeroNet Github",
+ "Report bug/request feature": "反馈问题/请求功能",
+ "!Open ZeroNet": "!打开 ZeroNet",
+ "Quit": "退出",
+ "(active)": "(主动模式)",
+ "(passive)": "(被动模式)",
+ "Connections: %s": "连接数: %s",
+ "Received: %.2f MB | Sent: %.2f MB": "已接收: %.2f MB | 已发送: %.2f MB",
+ "Show console window": "显示控制台窗口",
+ "Start ZeroNet when Windows starts": "在 Windows 启动时运行 ZeroNet"
+}
diff --git a/plugins/Trayicon/lib/__init__.py b/plugins/Trayicon/lib/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/plugins/Trayicon/lib/notificationicon.py b/plugins/Trayicon/lib/notificationicon.py
new file mode 100644
index 00000000..8b913f83
--- /dev/null
+++ b/plugins/Trayicon/lib/notificationicon.py
@@ -0,0 +1,730 @@
+# Pure ctypes windows taskbar notification icon
+# via https://gist.github.com/jasonbot/5759510
+# Modified for ZeroNet
+
+import ctypes
+import ctypes.wintypes
+import os
+import uuid
+import time
+import gevent
+import threading
+try:
+ from queue import Empty as queue_Empty # Python 3
+except ImportError:
+ from Queue import Empty as queue_Empty # Python 2
+
+__all__ = ['NotificationIcon']
+
+# Create popup menu
+
+CreatePopupMenu = ctypes.windll.user32.CreatePopupMenu
+CreatePopupMenu.restype = ctypes.wintypes.HMENU
+CreatePopupMenu.argtypes = []
+
+MF_BYCOMMAND = 0x0
+MF_BYPOSITION = 0x400
+
+MF_BITMAP = 0x4
+MF_CHECKED = 0x8
+MF_DISABLED = 0x2
+MF_ENABLED = 0x0
+MF_GRAYED = 0x1
+MF_MENUBARBREAK = 0x20
+MF_MENUBREAK = 0x40
+MF_OWNERDRAW = 0x100
+MF_POPUP = 0x10
+MF_SEPARATOR = 0x800
+MF_STRING = 0x0
+MF_UNCHECKED = 0x0
+
+InsertMenu = ctypes.windll.user32.InsertMenuW
+InsertMenu.restype = ctypes.wintypes.BOOL
+InsertMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.LPCWSTR]
+
+AppendMenu = ctypes.windll.user32.AppendMenuW
+AppendMenu.restype = ctypes.wintypes.BOOL
+AppendMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.LPCWSTR]
+
+SetMenuDefaultItem = ctypes.windll.user32.SetMenuDefaultItem
+SetMenuDefaultItem.restype = ctypes.wintypes.BOOL
+SetMenuDefaultItem.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT]
+
+class POINT(ctypes.Structure):
+ _fields_ = [ ('x', ctypes.wintypes.LONG),
+ ('y', ctypes.wintypes.LONG)]
+
+GetCursorPos = ctypes.windll.user32.GetCursorPos
+GetCursorPos.argtypes = [ctypes.POINTER(POINT)]
+
+SetForegroundWindow = ctypes.windll.user32.SetForegroundWindow
+SetForegroundWindow.argtypes = [ctypes.wintypes.HWND]
+
+TPM_LEFTALIGN = 0x0
+TPM_CENTERALIGN = 0x4
+TPM_RIGHTALIGN = 0x8
+
+TPM_TOPALIGN = 0x0
+TPM_VCENTERALIGN = 0x10
+TPM_BOTTOMALIGN = 0x20
+
+TPM_NONOTIFY = 0x80
+TPM_RETURNCMD = 0x100
+
+TPM_LEFTBUTTON = 0x0
+TPM_RIGHTBUTTON = 0x2
+
+TPM_HORNEGANIMATION = 0x800
+TPM_HORPOSANIMATION = 0x400
+TPM_NOANIMATION = 0x4000
+TPM_VERNEGANIMATION = 0x2000
+TPM_VERPOSANIMATION = 0x1000
+
+TrackPopupMenu = ctypes.windll.user32.TrackPopupMenu
+TrackPopupMenu.restype = ctypes.wintypes.BOOL
+TrackPopupMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.wintypes.HWND, ctypes.c_void_p]
+
+PostMessage = ctypes.windll.user32.PostMessageW
+PostMessage.restype = ctypes.wintypes.BOOL
+PostMessage.argtypes = [ctypes.wintypes.HWND, ctypes.wintypes.UINT, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM]
+
+DestroyMenu = ctypes.windll.user32.DestroyMenu
+DestroyMenu.restype = ctypes.wintypes.BOOL
+DestroyMenu.argtypes = [ctypes.wintypes.HMENU]
+
+# Create notification icon
+
+GUID = ctypes.c_ubyte * 16
+
+class TimeoutVersionUnion(ctypes.Union):
+ _fields_ = [('uTimeout', ctypes.wintypes.UINT),
+ ('uVersion', ctypes.wintypes.UINT),]
+
+NIS_HIDDEN = 0x1
+NIS_SHAREDICON = 0x2
+
+class NOTIFYICONDATA(ctypes.Structure):
+ def __init__(self, *args, **kwargs):
+ super(NOTIFYICONDATA, self).__init__(*args, **kwargs)
+ self.cbSize = ctypes.sizeof(self)
+ _fields_ = [
+ ('cbSize', ctypes.wintypes.DWORD),
+ ('hWnd', ctypes.wintypes.HWND),
+ ('uID', ctypes.wintypes.UINT),
+ ('uFlags', ctypes.wintypes.UINT),
+ ('uCallbackMessage', ctypes.wintypes.UINT),
+ ('hIcon', ctypes.wintypes.HICON),
+ ('szTip', ctypes.wintypes.WCHAR * 64),
+ ('dwState', ctypes.wintypes.DWORD),
+ ('dwStateMask', ctypes.wintypes.DWORD),
+ ('szInfo', ctypes.wintypes.WCHAR * 256),
+ ('union', TimeoutVersionUnion),
+ ('szInfoTitle', ctypes.wintypes.WCHAR * 64),
+ ('dwInfoFlags', ctypes.wintypes.DWORD),
+ ('guidItem', GUID),
+ ('hBalloonIcon', ctypes.wintypes.HICON),
+ ]
+
+NIM_ADD = 0
+NIM_MODIFY = 1
+NIM_DELETE = 2
+NIM_SETFOCUS = 3
+NIM_SETVERSION = 4
+
+NIF_MESSAGE = 1
+NIF_ICON = 2
+NIF_TIP = 4
+NIF_STATE = 8
+NIF_INFO = 16
+NIF_GUID = 32
+NIF_REALTIME = 64
+NIF_SHOWTIP = 128
+
+NIIF_NONE = 0
+NIIF_INFO = 1
+NIIF_WARNING = 2
+NIIF_ERROR = 3
+NIIF_USER = 4
+
+NOTIFYICON_VERSION = 3
+NOTIFYICON_VERSION_4 = 4
+
+Shell_NotifyIcon = ctypes.windll.shell32.Shell_NotifyIconW
+Shell_NotifyIcon.restype = ctypes.wintypes.BOOL
+Shell_NotifyIcon.argtypes = [ctypes.wintypes.DWORD, ctypes.POINTER(NOTIFYICONDATA)]
+
+# Load icon/image
+
+IMAGE_BITMAP = 0
+IMAGE_ICON = 1
+IMAGE_CURSOR = 2
+
+LR_CREATEDIBSECTION = 0x00002000
+LR_DEFAULTCOLOR = 0x00000000
+LR_DEFAULTSIZE = 0x00000040
+LR_LOADFROMFILE = 0x00000010
+LR_LOADMAP3DCOLORS = 0x00001000
+LR_LOADTRANSPARENT = 0x00000020
+LR_MONOCHROME = 0x00000001
+LR_SHARED = 0x00008000
+LR_VGACOLOR = 0x00000080
+
+OIC_SAMPLE = 32512
+OIC_HAND = 32513
+OIC_QUES = 32514
+OIC_BANG = 32515
+OIC_NOTE = 32516
+OIC_WINLOGO = 32517
+OIC_WARNING = OIC_BANG
+OIC_ERROR = OIC_HAND
+OIC_INFORMATION = OIC_NOTE
+
+LoadImage = ctypes.windll.user32.LoadImageW
+LoadImage.restype = ctypes.wintypes.HANDLE
+LoadImage.argtypes = [ctypes.wintypes.HINSTANCE, ctypes.wintypes.LPCWSTR, ctypes.wintypes.UINT, ctypes.c_int, ctypes.c_int, ctypes.wintypes.UINT]
+
+# CreateWindow call
+
+WNDPROC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.wintypes.HWND, ctypes.c_uint, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM)
+DefWindowProc = ctypes.windll.user32.DefWindowProcW
+DefWindowProc.restype = ctypes.c_int
+DefWindowProc.argtypes = [ctypes.wintypes.HWND, ctypes.c_uint, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM]
+
+WS_OVERLAPPED = 0x00000000
+WS_POPUP = 0x80000000
+WS_CHILD = 0x40000000
+WS_MINIMIZE = 0x20000000
+WS_VISIBLE = 0x10000000
+WS_DISABLED = 0x08000000
+WS_CLIPSIBLINGS = 0x04000000
+WS_CLIPCHILDREN = 0x02000000
+WS_MAXIMIZE = 0x01000000
+WS_CAPTION = 0x00C00000
+WS_BORDER = 0x00800000
+WS_DLGFRAME = 0x00400000
+WS_VSCROLL = 0x00200000
+WS_HSCROLL = 0x00100000
+WS_SYSMENU = 0x00080000
+WS_THICKFRAME = 0x00040000
+WS_GROUP = 0x00020000
+WS_TABSTOP = 0x00010000
+
+WS_MINIMIZEBOX = 0x00020000
+WS_MAXIMIZEBOX = 0x00010000
+
+WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED |
+ WS_CAPTION |
+ WS_SYSMENU |
+ WS_THICKFRAME |
+ WS_MINIMIZEBOX |
+ WS_MAXIMIZEBOX)
+
+SM_XVIRTUALSCREEN = 76
+SM_YVIRTUALSCREEN = 77
+SM_CXVIRTUALSCREEN = 78
+SM_CYVIRTUALSCREEN = 79
+SM_CMONITORS = 80
+SM_SAMEDISPLAYFORMAT = 81
+
+WM_NULL = 0x0000
+WM_CREATE = 0x0001
+WM_DESTROY = 0x0002
+WM_MOVE = 0x0003
+WM_SIZE = 0x0005
+WM_ACTIVATE = 0x0006
+WM_SETFOCUS = 0x0007
+WM_KILLFOCUS = 0x0008
+WM_ENABLE = 0x000A
+WM_SETREDRAW = 0x000B
+WM_SETTEXT = 0x000C
+WM_GETTEXT = 0x000D
+WM_GETTEXTLENGTH = 0x000E
+WM_PAINT = 0x000F
+WM_CLOSE = 0x0010
+WM_QUERYENDSESSION = 0x0011
+WM_QUIT = 0x0012
+WM_QUERYOPEN = 0x0013
+WM_ERASEBKGND = 0x0014
+WM_SYSCOLORCHANGE = 0x0015
+WM_ENDSESSION = 0x0016
+WM_SHOWWINDOW = 0x0018
+WM_CTLCOLOR = 0x0019
+WM_WININICHANGE = 0x001A
+WM_SETTINGCHANGE = 0x001A
+WM_DEVMODECHANGE = 0x001B
+WM_ACTIVATEAPP = 0x001C
+WM_FONTCHANGE = 0x001D
+WM_TIMECHANGE = 0x001E
+WM_CANCELMODE = 0x001F
+WM_SETCURSOR = 0x0020
+WM_MOUSEACTIVATE = 0x0021
+WM_CHILDACTIVATE = 0x0022
+WM_QUEUESYNC = 0x0023
+WM_GETMINMAXINFO = 0x0024
+WM_PAINTICON = 0x0026
+WM_ICONERASEBKGND = 0x0027
+WM_NEXTDLGCTL = 0x0028
+WM_SPOOLERSTATUS = 0x002A
+WM_DRAWITEM = 0x002B
+WM_MEASUREITEM = 0x002C
+WM_DELETEITEM = 0x002D
+WM_VKEYTOITEM = 0x002E
+WM_CHARTOITEM = 0x002F
+WM_SETFONT = 0x0030
+WM_GETFONT = 0x0031
+WM_SETHOTKEY = 0x0032
+WM_GETHOTKEY = 0x0033
+WM_QUERYDRAGICON = 0x0037
+WM_COMPAREITEM = 0x0039
+WM_GETOBJECT = 0x003D
+WM_COMPACTING = 0x0041
+WM_COMMNOTIFY = 0x0044
+WM_WINDOWPOSCHANGING = 0x0046
+WM_WINDOWPOSCHANGED = 0x0047
+WM_POWER = 0x0048
+WM_COPYDATA = 0x004A
+WM_CANCELJOURNAL = 0x004B
+WM_NOTIFY = 0x004E
+WM_INPUTLANGCHANGEREQUEST = 0x0050
+WM_INPUTLANGCHANGE = 0x0051
+WM_TCARD = 0x0052
+WM_HELP = 0x0053
+WM_USERCHANGED = 0x0054
+WM_NOTIFYFORMAT = 0x0055
+WM_CONTEXTMENU = 0x007B
+WM_STYLECHANGING = 0x007C
+WM_STYLECHANGED = 0x007D
+WM_DISPLAYCHANGE = 0x007E
+WM_GETICON = 0x007F
+WM_SETICON = 0x0080
+WM_NCCREATE = 0x0081
+WM_NCDESTROY = 0x0082
+WM_NCCALCSIZE = 0x0083
+WM_NCHITTEST = 0x0084
+WM_NCPAINT = 0x0085
+WM_NCACTIVATE = 0x0086
+WM_GETDLGCODE = 0x0087
+WM_SYNCPAINT = 0x0088
+WM_NCMOUSEMOVE = 0x00A0
+WM_NCLBUTTONDOWN = 0x00A1
+WM_NCLBUTTONUP = 0x00A2
+WM_NCLBUTTONDBLCLK = 0x00A3
+WM_NCRBUTTONDOWN = 0x00A4
+WM_NCRBUTTONUP = 0x00A5
+WM_NCRBUTTONDBLCLK = 0x00A6
+WM_NCMBUTTONDOWN = 0x00A7
+WM_NCMBUTTONUP = 0x00A8
+WM_NCMBUTTONDBLCLK = 0x00A9
+WM_KEYDOWN = 0x0100
+WM_KEYUP = 0x0101
+WM_CHAR = 0x0102
+WM_DEADCHAR = 0x0103
+WM_SYSKEYDOWN = 0x0104
+WM_SYSKEYUP = 0x0105
+WM_SYSCHAR = 0x0106
+WM_SYSDEADCHAR = 0x0107
+WM_KEYLAST = 0x0108
+WM_IME_STARTCOMPOSITION = 0x010D
+WM_IME_ENDCOMPOSITION = 0x010E
+WM_IME_COMPOSITION = 0x010F
+WM_IME_KEYLAST = 0x010F
+WM_INITDIALOG = 0x0110
+WM_COMMAND = 0x0111
+WM_SYSCOMMAND = 0x0112
+WM_TIMER = 0x0113
+WM_HSCROLL = 0x0114
+WM_VSCROLL = 0x0115
+WM_INITMENU = 0x0116
+WM_INITMENUPOPUP = 0x0117
+WM_MENUSELECT = 0x011F
+WM_MENUCHAR = 0x0120
+WM_ENTERIDLE = 0x0121
+WM_MENURBUTTONUP = 0x0122
+WM_MENUDRAG = 0x0123
+WM_MENUGETOBJECT = 0x0124
+WM_UNINITMENUPOPUP = 0x0125
+WM_MENUCOMMAND = 0x0126
+WM_CTLCOLORMSGBOX = 0x0132
+WM_CTLCOLOREDIT = 0x0133
+WM_CTLCOLORLISTBOX = 0x0134
+WM_CTLCOLORBTN = 0x0135
+WM_CTLCOLORDLG = 0x0136
+WM_CTLCOLORSCROLLBAR = 0x0137
+WM_CTLCOLORSTATIC = 0x0138
+WM_MOUSEMOVE = 0x0200
+WM_LBUTTONDOWN = 0x0201
+WM_LBUTTONUP = 0x0202
+WM_LBUTTONDBLCLK = 0x0203
+WM_RBUTTONDOWN = 0x0204
+WM_RBUTTONUP = 0x0205
+WM_RBUTTONDBLCLK = 0x0206
+WM_MBUTTONDOWN = 0x0207
+WM_MBUTTONUP = 0x0208
+WM_MBUTTONDBLCLK = 0x0209
+WM_MOUSEWHEEL = 0x020A
+WM_PARENTNOTIFY = 0x0210
+WM_ENTERMENULOOP = 0x0211
+WM_EXITMENULOOP = 0x0212
+WM_NEXTMENU = 0x0213
+WM_SIZING = 0x0214
+WM_CAPTURECHANGED = 0x0215
+WM_MOVING = 0x0216
+WM_DEVICECHANGE = 0x0219
+WM_MDICREATE = 0x0220
+WM_MDIDESTROY = 0x0221
+WM_MDIACTIVATE = 0x0222
+WM_MDIRESTORE = 0x0223
+WM_MDINEXT = 0x0224
+WM_MDIMAXIMIZE = 0x0225
+WM_MDITILE = 0x0226
+WM_MDICASCADE = 0x0227
+WM_MDIICONARRANGE = 0x0228
+WM_MDIGETACTIVE = 0x0229
+WM_MDISETMENU = 0x0230
+WM_ENTERSIZEMOVE = 0x0231
+WM_EXITSIZEMOVE = 0x0232
+WM_DROPFILES = 0x0233
+WM_MDIREFRESHMENU = 0x0234
+WM_IME_SETCONTEXT = 0x0281
+WM_IME_NOTIFY = 0x0282
+WM_IME_CONTROL = 0x0283
+WM_IME_COMPOSITIONFULL = 0x0284
+WM_IME_SELECT = 0x0285
+WM_IME_CHAR = 0x0286
+WM_IME_REQUEST = 0x0288
+WM_IME_KEYDOWN = 0x0290
+WM_IME_KEYUP = 0x0291
+WM_MOUSEHOVER = 0x02A1
+WM_MOUSELEAVE = 0x02A3
+WM_CUT = 0x0300
+WM_COPY = 0x0301
+WM_PASTE = 0x0302
+WM_CLEAR = 0x0303
+WM_UNDO = 0x0304
+WM_RENDERFORMAT = 0x0305
+WM_RENDERALLFORMATS = 0x0306
+WM_DESTROYCLIPBOARD = 0x0307
+WM_DRAWCLIPBOARD = 0x0308
+WM_PAINTCLIPBOARD = 0x0309
+WM_VSCROLLCLIPBOARD = 0x030A
+WM_SIZECLIPBOARD = 0x030B
+WM_ASKCBFORMATNAME = 0x030C
+WM_CHANGECBCHAIN = 0x030D
+WM_HSCROLLCLIPBOARD = 0x030E
+WM_QUERYNEWPALETTE = 0x030F
+WM_PALETTEISCHANGING = 0x0310
+WM_PALETTECHANGED = 0x0311
+WM_HOTKEY = 0x0312
+WM_PRINT = 0x0317
+WM_PRINTCLIENT = 0x0318
+WM_HANDHELDFIRST = 0x0358
+WM_HANDHELDLAST = 0x035F
+WM_AFXFIRST = 0x0360
+WM_AFXLAST = 0x037F
+WM_PENWINFIRST = 0x0380
+WM_PENWINLAST = 0x038F
+WM_APP = 0x8000
+WM_USER = 0x0400
+WM_REFLECT = WM_USER + 0x1c00
+
+class WNDCLASSEX(ctypes.Structure):
+ def __init__(self, *args, **kwargs):
+ super(WNDCLASSEX, self).__init__(*args, **kwargs)
+ self.cbSize = ctypes.sizeof(self)
+ _fields_ = [("cbSize", ctypes.c_uint),
+ ("style", ctypes.c_uint),
+ ("lpfnWndProc", WNDPROC),
+ ("cbClsExtra", ctypes.c_int),
+ ("cbWndExtra", ctypes.c_int),
+ ("hInstance", ctypes.wintypes.HANDLE),
+ ("hIcon", ctypes.wintypes.HANDLE),
+ ("hCursor", ctypes.wintypes.HANDLE),
+ ("hBrush", ctypes.wintypes.HANDLE),
+ ("lpszMenuName", ctypes.wintypes.LPCWSTR),
+ ("lpszClassName", ctypes.wintypes.LPCWSTR),
+ ("hIconSm", ctypes.wintypes.HANDLE)]
+
+ShowWindow = ctypes.windll.user32.ShowWindow
+ShowWindow.argtypes = [ctypes.wintypes.HWND, ctypes.c_int]
+
+def GenerateDummyWindow(callback, uid):
+ newclass = WNDCLASSEX()
+ newclass.lpfnWndProc = callback
+ newclass.lpszClassName = uid.replace("-", "")
+ ATOM = ctypes.windll.user32.RegisterClassExW(ctypes.byref(newclass))
+ hwnd = ctypes.windll.user32.CreateWindowExW(0, newclass.lpszClassName, None, WS_POPUP, 0, 0, 0, 0, 0, 0, 0, 0)
+ return hwnd
+
+# Message loop calls
+
+TIMERCALLBACK = ctypes.WINFUNCTYPE(None,
+ ctypes.wintypes.HWND,
+ ctypes.wintypes.UINT,
+ ctypes.POINTER(ctypes.wintypes.UINT),
+ ctypes.wintypes.DWORD)
+
+SetTimer = ctypes.windll.user32.SetTimer
+SetTimer.restype = ctypes.POINTER(ctypes.wintypes.UINT)
+SetTimer.argtypes = [ctypes.wintypes.HWND,
+ ctypes.POINTER(ctypes.wintypes.UINT),
+ ctypes.wintypes.UINT,
+ TIMERCALLBACK]
+
+KillTimer = ctypes.windll.user32.KillTimer
+KillTimer.restype = ctypes.wintypes.BOOL
+KillTimer.argtypes = [ctypes.wintypes.HWND,
+ ctypes.POINTER(ctypes.wintypes.UINT)]
+
+class MSG(ctypes.Structure):
+ _fields_ = [ ('HWND', ctypes.wintypes.HWND),
+ ('message', ctypes.wintypes.UINT),
+ ('wParam', ctypes.wintypes.WPARAM),
+ ('lParam', ctypes.wintypes.LPARAM),
+ ('time', ctypes.wintypes.DWORD),
+ ('pt', POINT)]
+
+GetMessage = ctypes.windll.user32.GetMessageW
+GetMessage.restype = ctypes.wintypes.BOOL
+GetMessage.argtypes = [ctypes.POINTER(MSG), ctypes.wintypes.HWND, ctypes.wintypes.UINT, ctypes.wintypes.UINT]
+
+TranslateMessage = ctypes.windll.user32.TranslateMessage
+TranslateMessage.restype = ctypes.wintypes.ULONG
+TranslateMessage.argtypes = [ctypes.POINTER(MSG)]
+
+DispatchMessage = ctypes.windll.user32.DispatchMessageW
+DispatchMessage.restype = ctypes.wintypes.ULONG
+DispatchMessage.argtypes = [ctypes.POINTER(MSG)]
+
+def LoadIcon(iconfilename, small=False):
+ return LoadImage(0,
+ str(iconfilename),
+ IMAGE_ICON,
+ 16 if small else 0,
+ 16 if small else 0,
+ LR_LOADFROMFILE)
+
+
+class NotificationIcon(object):
+ def __init__(self, iconfilename, tooltip=None):
+ assert os.path.isfile(str(iconfilename)), "{} doesn't exist".format(iconfilename)
+ self._iconfile = str(iconfilename)
+ self._hicon = LoadIcon(self._iconfile, True)
+ assert self._hicon, "Failed to load {}".format(iconfilename)
+ #self._pumpqueue = Queue.Queue()
+ self._die = False
+ self._timerid = None
+ self._uid = uuid.uuid4()
+ self._tooltip = str(tooltip) if tooltip else ''
+ #self._thread = threading.Thread(target=self._run)
+ #self._thread.start()
+ self._info_bubble = None
+ self.items = []
+
+
+ def _bubble(self, iconinfo):
+ if self._info_bubble:
+ info_bubble = self._info_bubble
+ self._info_bubble = None
+ message = str(self._info_bubble)
+ iconinfo.uFlags |= NIF_INFO
+ iconinfo.szInfo = message
+ iconinfo.szInfoTitle = message
+ iconinfo.dwInfoFlags = NIIF_INFO
+ iconinfo.union.uTimeout = 10000
+ Shell_NotifyIcon(NIM_MODIFY, ctypes.pointer(iconinfo))
+
+
+ def _run(self):
+ self.WM_TASKBARCREATED = ctypes.windll.user32.RegisterWindowMessageW('TaskbarCreated')
+
+ self._windowproc = WNDPROC(self._callback)
+ self._hwnd = GenerateDummyWindow(self._windowproc, str(self._uid))
+
+ iconinfo = NOTIFYICONDATA()
+ iconinfo.hWnd = self._hwnd
+ iconinfo.uID = 100
+ iconinfo.uFlags = NIF_ICON | NIF_SHOWTIP | NIF_MESSAGE | (NIF_TIP if self._tooltip else 0)
+ iconinfo.uCallbackMessage = WM_MENUCOMMAND
+ iconinfo.hIcon = self._hicon
+ iconinfo.szTip = self._tooltip
+
+ Shell_NotifyIcon(NIM_ADD, ctypes.pointer(iconinfo))
+
+ self.iconinfo = iconinfo
+
+ PostMessage(self._hwnd, WM_NULL, 0, 0)
+
+ message = MSG()
+ last_time = -1
+ ret = None
+ while not self._die:
+ try:
+ ret = GetMessage(ctypes.pointer(message), 0, 0, 0)
+ TranslateMessage(ctypes.pointer(message))
+ DispatchMessage(ctypes.pointer(message))
+ except Exception as err:
+ # print "NotificationIcon error", err, message
+ message = MSG()
+ time.sleep(0.125)
+ print("Icon thread stopped, removing icon (hicon: %s, hwnd: %s)..." % (self._hicon, self._hwnd))
+
+ Shell_NotifyIcon(NIM_DELETE, ctypes.cast(ctypes.pointer(iconinfo), ctypes.POINTER(NOTIFYICONDATA)))
+ ctypes.windll.user32.DestroyWindow(self._hwnd)
+ ctypes.windll.user32.DestroyIcon.argtypes = [ctypes.wintypes.HICON]
+ ctypes.windll.user32.DestroyIcon(self._hicon)
+
+
+ def _menu(self):
+ if not hasattr(self, 'items'):
+ return
+
+ menu = CreatePopupMenu()
+ func = None
+
+ try:
+ iidx = 1000
+ defaultitem = -1
+ item_map = {}
+ for fs in self.items:
+ iidx += 1
+ if isinstance(fs, str):
+ if fs and not fs.strip('-_='):
+ AppendMenu(menu, MF_SEPARATOR, iidx, fs)
+ else:
+ AppendMenu(menu, MF_STRING | MF_GRAYED, iidx, fs)
+ elif isinstance(fs, tuple):
+ if callable(fs[0]):
+ itemstring = fs[0]()
+ else:
+ itemstring = str(fs[0])
+ flags = MF_STRING
+ if itemstring.startswith("!"):
+ itemstring = itemstring[1:]
+ defaultitem = iidx
+ if itemstring.startswith("+"):
+ itemstring = itemstring[1:]
+ flags = flags | MF_CHECKED
+ itemcallable = fs[1]
+ item_map[iidx] = itemcallable
+ if itemcallable is False:
+ flags = flags | MF_DISABLED
+ elif not callable(itemcallable):
+ flags = flags | MF_GRAYED
+ AppendMenu(menu, flags, iidx, itemstring)
+
+ if defaultitem != -1:
+ SetMenuDefaultItem(menu, defaultitem, 0)
+
+ pos = POINT()
+ GetCursorPos(ctypes.pointer(pos))
+
+ PostMessage(self._hwnd, WM_NULL, 0, 0)
+
+ SetForegroundWindow(self._hwnd)
+
+ ti = TrackPopupMenu(menu, TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, pos.x, pos.y, 0, self._hwnd, None)
+
+ if ti in item_map:
+ func = item_map[ti]
+
+ PostMessage(self._hwnd, WM_NULL, 0, 0)
+ finally:
+ DestroyMenu(menu)
+ if func: func()
+
+
+ def clicked(self):
+ self._menu()
+
+
+
+ def _callback(self, hWnd, msg, wParam, lParam):
+ # Check if the main thread is still alive
+ if msg == WM_TIMER:
+ if not any(thread.getName() == 'MainThread' and thread.isAlive()
+ for thread in threading.enumerate()):
+ self._die = True
+ elif msg == WM_MENUCOMMAND and lParam == WM_LBUTTONUP:
+ self.clicked()
+ elif msg == WM_MENUCOMMAND and lParam == WM_RBUTTONUP:
+ self._menu()
+ elif msg == self.WM_TASKBARCREATED: # Explorer restarted, add the icon again.
+ Shell_NotifyIcon(NIM_ADD, ctypes.pointer(self.iconinfo))
+ else:
+ return DefWindowProc(hWnd, msg, wParam, lParam)
+ return 1
+
+
+ def die(self):
+ self._die = True
+ PostMessage(self._hwnd, WM_NULL, 0, 0)
+ time.sleep(0.2)
+ try:
+ Shell_NotifyIcon(NIM_DELETE, self.iconinfo)
+ except Exception as err:
+ print("Icon remove error", err)
+ ctypes.windll.user32.DestroyWindow(self._hwnd)
+ ctypes.windll.user32.DestroyIcon(self._hicon)
+
+
+ def pump(self):
+ try:
+ while not self._pumpqueue.empty():
+ callable = self._pumpqueue.get(False)
+ callable()
+ except queue_Empty:
+ pass
+
+
+ def announce(self, text):
+ self._info_bubble = text
+
+
+def hideConsole():
+ ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
+
+def showConsole():
+ ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 1)
+
+def hasConsole():
+ return ctypes.windll.kernel32.GetConsoleWindow() != 0
+
+if __name__ == "__main__":
+ import time
+
+ def greet():
+ ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
+ print("Hello")
+
+ def quit():
+ ni._die = True
+
+ def announce():
+ ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 1)
+ ni.announce("Hello there")
+
+ def clicked():
+ ni.announce("Hello")
+
+ def dynamicTitle():
+ return "!The time is: %s" % time.time()
+
+ ni = NotificationIcon(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../trayicon.ico'), "ZeroNet 0.2.9")
+ ni.items = [
+ (dynamicTitle, False),
+ ('Hello', greet),
+ ('Title', False),
+ ('!Default', greet),
+ ('+Popup bubble', announce),
+ 'Nothing',
+ '--',
+ ('Quit', quit)
+ ]
+ ni.clicked = clicked
+ import atexit
+
+ @atexit.register
+ def goodbye():
+ print("You are now leaving the Python sector.")
+
+ ni._run()
diff --git a/plugins/Trayicon/lib/winfolders.py b/plugins/Trayicon/lib/winfolders.py
new file mode 100644
index 00000000..75437c19
--- /dev/null
+++ b/plugins/Trayicon/lib/winfolders.py
@@ -0,0 +1,54 @@
+''' Get windows special folders without pythonwin
+ Example:
+ import specialfolders
+ start_programs = specialfolders.get(specialfolders.PROGRAMS)
+
+Code is public domain, do with it what you will.
+
+Luke Pinner - Environment.gov.au, 2010 February 10
+'''
+
+#Imports use _syntax to mask them from autocomplete IDE's
+import ctypes as _ctypes
+from ctypes import create_unicode_buffer as _cub
+from ctypes.wintypes import HWND as _HWND, HANDLE as _HANDLE,DWORD as _DWORD,LPCWSTR as _LPCWSTR,MAX_PATH as _MAX_PATH
+_SHGetFolderPath = _ctypes.windll.shell32.SHGetFolderPathW
+
+#public special folder constants
+DESKTOP= 0
+PROGRAMS= 2
+MYDOCUMENTS= 5
+FAVORITES= 6
+STARTUP= 7
+RECENT= 8
+SENDTO= 9
+STARTMENU= 11
+MYMUSIC= 13
+MYVIDEOS= 14
+NETHOOD= 19
+FONTS= 20
+TEMPLATES= 21
+ALLUSERSSTARTMENU= 22
+ALLUSERSPROGRAMS= 23
+ALLUSERSSTARTUP= 24
+ALLUSERSDESKTOP= 25
+APPLICATIONDATA= 26
+PRINTHOOD= 27
+LOCALSETTINGSAPPLICATIONDATA= 28
+ALLUSERSFAVORITES= 31
+LOCALSETTINGSTEMPORARYINTERNETFILES=32
+COOKIES= 33
+LOCALSETTINGSHISTORY= 34
+ALLUSERSAPPLICATIONDATA= 35
+
+def get(intFolder):
+ _SHGetFolderPath.argtypes = [_HWND, _ctypes.c_int, _HANDLE, _DWORD, _LPCWSTR]
+ auPathBuffer = _cub(_MAX_PATH)
+ exit_code=_SHGetFolderPath(0, intFolder, 0, 0, auPathBuffer)
+ return auPathBuffer.value
+
+
+if __name__ == "__main__":
+ import os
+ print(get(STARTUP))
+ open(get(STARTUP)+"\\zeronet.cmd", "w").write("cd /D %s\r\nzeronet.py" % os.getcwd())
\ No newline at end of file
diff --git a/plugins/Trayicon/plugin_info.json b/plugins/Trayicon/plugin_info.json
new file mode 100644
index 00000000..d74fa6c1
--- /dev/null
+++ b/plugins/Trayicon/plugin_info.json
@@ -0,0 +1,5 @@
+{
+ "name": "Trayicon",
+ "description": "Icon for system tray. (Windows only)",
+ "default": "enabled"
+}
\ No newline at end of file
diff --git a/plugins/Trayicon/trayicon.ico b/plugins/Trayicon/trayicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..086172254e3534b532ddc8a82c49c60bc6e036f2
GIT binary patch
literal 1150
zcmb7DziV1i6h3JS5!yl^kTt~+)5VgKq3i3~KOi6!iqN5UkU;HFDKwjOjEfCsaENq}
z7D|hwNU13lM+9qEv<2`OW)pA&%fMCbmc!wA7>Pt)G@H$QtycRM3Wc6S>;i7i
z3Bv5w0)c?HR4RR#UN*B>EXMtQ{}XVW0Q0k(E1S)}8;{55O4@F>OTAuCSN>=;`khLp
z_8?>~R%1L(@j9JOHJMDlb-UeJRn&c(zgdD9&D+qenP#abMJ{t9aCOyWLae^KR1VbeLQUY<3;KlW(+8C>-*vN{6qR
z&*u*<@AsNbYjxQp>+Bi4hwOj!k2MCz>{<5i-2RYou have to restart the client to load the plugin", "Restart"],
+ lambda res: self.actionServerShutdown(to, restart=True)
+ )
+
+ self.response(to, "ok")
+
+ @flag.no_multiuser
+ def actionPluginAddRequest(self, to, inner_path):
+ self.pluginAction("add_request", self.site.address, inner_path)
+ plugin_info = self.site.storage.loadJson(inner_path + "/plugin_info.json")
+ warning = "Warning! Plugins has the same permissions as the ZeroNet client. "
+ warning += "Do not install it if you don't trust the developer. "
+
+ self.cmd(
+ "confirm",
+ ["Install new plugin: %s? %s" % (plugin_info["name"], warning), "Trust & Install"],
+ lambda res: self.doPluginAdd(to, inner_path, res)
+ )
+
+ @flag.admin
+ @flag.no_multiuser
+ def actionPluginRemove(self, to, address, inner_path):
+ self.pluginAction("remove", address, inner_path)
+ PluginManager.plugin_manager.saveConfig()
+ return "ok"
+
+ @flag.admin
+ @flag.no_multiuser
+ def actionPluginUpdate(self, to, address, inner_path):
+ self.pluginAction("update", address, inner_path)
+ PluginManager.plugin_manager.saveConfig()
+ PluginManager.plugin_manager.plugins_updated["%s/%s" % (address, inner_path)] = True
+ return "ok"
diff --git a/plugins/UiPluginManager/__init__.py b/plugins/UiPluginManager/__init__.py
new file mode 100644
index 00000000..d29ae44b
--- /dev/null
+++ b/plugins/UiPluginManager/__init__.py
@@ -0,0 +1 @@
+from . import UiPluginManagerPlugin
diff --git a/plugins/UiPluginManager/media/css/PluginManager.css b/plugins/UiPluginManager/media/css/PluginManager.css
new file mode 100644
index 00000000..30f36717
--- /dev/null
+++ b/plugins/UiPluginManager/media/css/PluginManager.css
@@ -0,0 +1,75 @@
+body { background-color: #EDF2F5; font-family: Roboto, 'Segoe UI', Arial, 'Helvetica Neue'; margin: 0px; padding: 0px; backface-visibility: hidden; }
+h1, h2, h3, h4 { font-family: 'Roboto', Arial, sans-serif; font-weight: 200; font-size: 30px; margin: 0px; padding: 0px }
+h1 { background: linear-gradient(33deg,#af3bff,#0d99c9); color: white; padding: 16px 30px; }
+h2 { margin-top: 10px; }
+h3 { font-weight: normal }
+h4 { font-size: 19px; font-weight: lighter; margin-right: 100px; margin-top: 30px; }
+a { color: #9760F9 }
+a:hover { text-decoration: none }
+
+.link { background-color: transparent; outline: 5px solid transparent; transition: all 0.3s }
+.link:active { background-color: #EFEFEF; outline: 5px solid #EFEFEF; transition: none }
+
+.content { max-width: 800px; margin: auto; background-color: white; padding: 60px 20px; box-sizing: border-box; padding-bottom: 150px; }
+.section { margin: 0px 10%; }
+.plugins { font-size: 19px; margin-top: 25px; margin-bottom: 75px; }
+.plugin { transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); position: relative; padding-bottom: 20px; padding-top: 10px; }
+.plugin.hidden { opacity: 0; height: 0px; padding: 0px; }
+.plugin .title { display: inline-block; line-height: 36px; }
+.plugin .title h3 { font-size: 20px; font-weight: lighter; margin-right: 100px; }
+.plugin .title .version { font-size: 70%; margin-left: 5px; }
+.plugin .title .version .version-latest { color: #2ecc71; font-weight: normal; }
+.plugin .title .version .version-missing { color: #ffa200; font-weight: normal; }
+.plugin .title .version .version-update { padding: 0px 15px; margin-left: 5px; line-height: 28px; }
+.plugin .description { font-size: 14px; color: #666; line-height: 24px; }
+.plugin .description .source { color: #999; font-size: 90%; }
+.plugin .description .source a { color: #666; }
+.plugin .value { display: inline-block; white-space: nowrap; }
+.plugin .value-right { right: 0px; position: absolute; }
+.plugin .value-fullwidth { width: 100% }
+.plugin .marker {
+ font-weight: bold; text-decoration: none; font-size: 25px; position: absolute; padding: 2px 15px; line-height: 32px;
+ opacity: 0; pointer-events: none; transition: all 0.6s; transform: scale(2); color: #9760F9;
+}
+.plugin .marker.visible { opacity: 1; pointer-events: all; transform: scale(1); }
+.plugin .marker.changed { color: #2ecc71; }
+.plugin .marker.pending { color: #ffa200; }
+
+
+.input-text, .input-select { padding: 8px 18px; border: 1px solid #CCC; border-radius: 3px; font-size: 17px; box-sizing: border-box; }
+.input-text:focus, .input-select:focus { border: 1px solid #3396ff; outline: none; }
+.input-textarea { overflow-x: auto; overflow-y: hidden; white-space: pre; line-height: 22px; }
+
+.input-select { width: initial; font-size: 14px; padding-right: 10px; padding-left: 10px; }
+
+.value-right .input-text { text-align: right; width: 100px; }
+.value-fullwidth .input-text { width: 100%; font-size: 14px; font-family: 'Segoe UI', Arial, 'Helvetica Neue'; }
+.value-fullwidth { margin-top: 10px; }
+
+/* Checkbox */
+.checkbox-skin { background-color: #CCC; width: 50px; height: 24px; border-radius: 15px; transition: all 0.3s ease-in-out; display: inline-block; }
+.checkbox-skin:before {
+ content: ""; position: relative; width: 20px; background-color: white; height: 20px; display: block; border-radius: 100%; margin-top: 2px; margin-left: 2px;
+ transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86);
+}
+.checkbox { font-size: 14px; font-weight: normal; display: inline-block; cursor: pointer; margin-top: 5px; }
+.checkbox .title { display: inline; line-height: 30px; vertical-align: 4px; margin-left: 11px }
+.checkbox.checked .checkbox-skin:before { margin-left: 27px; }
+.checkbox.checked .checkbox-skin { background-color: #2ECC71 }
+
+/* Bottom */
+
+.bottom {
+ width: 100%; text-align: center; background-color: #ffffffde; padding: 25px; bottom: -120px; -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
+ transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); position: fixed; backface-visibility: hidden; box-sizing: border-box;
+}
+.bottom-content { max-width: 750px; width: 100%; margin: 0px auto; }
+.bottom .button { float: right; }
+.bottom.visible { bottom: 0px; box-shadow: 0px 0px 35px #dcdcdc; }
+.bottom .title { padding: 10px 10px; color: #363636; float: left; text-transform: uppercase; letter-spacing: 1px; }
+.bottom .title:before { content: "•"; display: inline-block; color: #2ecc71; font-size: 31px; vertical-align: -7px; margin-right: 8px; line-height: 25px; }
+.bottom-restart .title:before { color: #ffa200; }
+
+.animate { transition: all 0.3s ease-out !important; }
+.animate-back { transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; }
+.animate-inout { transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; }
\ No newline at end of file
diff --git a/plugins/UiPluginManager/media/css/all.css b/plugins/UiPluginManager/media/css/all.css
new file mode 100644
index 00000000..ba72fa0d
--- /dev/null
+++ b/plugins/UiPluginManager/media/css/all.css
@@ -0,0 +1,129 @@
+
+/* ---- PluginManager.css ---- */
+
+
+body { background-color: #EDF2F5; font-family: Roboto, 'Segoe UI', Arial, 'Helvetica Neue'; margin: 0px; padding: 0px; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; }
+h1, h2, h3, h4 { font-family: 'Roboto', Arial, sans-serif; font-weight: 200; font-size: 30px; margin: 0px; padding: 0px }
+h1 { background: -webkit-linear-gradient(33deg,#af3bff,#0d99c9);background: -moz-linear-gradient(33deg,#af3bff,#0d99c9);background: -o-linear-gradient(33deg,#af3bff,#0d99c9);background: -ms-linear-gradient(33deg,#af3bff,#0d99c9);background: linear-gradient(33deg,#af3bff,#0d99c9); color: white; padding: 16px 30px; }
+h2 { margin-top: 10px; }
+h3 { font-weight: normal }
+h4 { font-size: 19px; font-weight: lighter; margin-right: 100px; margin-top: 30px; }
+a { color: #9760F9 }
+a:hover { text-decoration: none }
+
+.link { background-color: transparent; outline: 5px solid transparent; -webkit-transition: all 0.3s ; -moz-transition: all 0.3s ; -o-transition: all 0.3s ; -ms-transition: all 0.3s ; transition: all 0.3s }
+.link:active { background-color: #EFEFEF; outline: 5px solid #EFEFEF; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none }
+
+.content { max-width: 800px; margin: auto; background-color: white; padding: 60px 20px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; padding-bottom: 150px; }
+.section { margin: 0px 10%; }
+.plugins { font-size: 19px; margin-top: 25px; margin-bottom: 75px; }
+.plugin { -webkit-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -moz-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -o-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -ms-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1) ; position: relative; padding-bottom: 20px; padding-top: 10px; }
+.plugin.hidden { opacity: 0; height: 0px; padding: 0px; }
+.plugin .title { display: inline-block; line-height: 36px; }
+.plugin .title h3 { font-size: 20px; font-weight: lighter; margin-right: 100px; }
+.plugin .title .version { font-size: 70%; margin-left: 5px; }
+.plugin .title .version .version-latest { color: #2ecc71; font-weight: normal; }
+.plugin .title .version .version-missing { color: #ffa200; font-weight: normal; }
+.plugin .title .version .version-update { padding: 0px 15px; margin-left: 5px; line-height: 28px; }
+.plugin .description { font-size: 14px; color: #666; line-height: 24px; }
+.plugin .description .source { color: #999; font-size: 90%; }
+.plugin .description .source a { color: #666; }
+.plugin .value { display: inline-block; white-space: nowrap; }
+.plugin .value-right { right: 0px; position: absolute; }
+.plugin .value-fullwidth { width: 100% }
+.plugin .marker {
+ font-weight: bold; text-decoration: none; font-size: 25px; position: absolute; padding: 2px 15px; line-height: 32px;
+ opacity: 0; pointer-events: none; -webkit-transition: all 0.6s; -moz-transition: all 0.6s; -o-transition: all 0.6s; -ms-transition: all 0.6s; transition: all 0.6s ; -webkit-transform: scale(2); -moz-transform: scale(2); -o-transform: scale(2); -ms-transform: scale(2); transform: scale(2) ; color: #9760F9;
+}
+.plugin .marker.visible { opacity: 1; pointer-events: all; -webkit-transform: scale(1); -moz-transform: scale(1); -o-transform: scale(1); -ms-transform: scale(1); transform: scale(1) ; }
+.plugin .marker.changed { color: #2ecc71; }
+.plugin .marker.pending { color: #ffa200; }
+
+
+.input-text, .input-select { padding: 8px 18px; border: 1px solid #CCC; -webkit-border-radius: 3px; -moz-border-radius: 3px; -o-border-radius: 3px; -ms-border-radius: 3px; border-radius: 3px ; font-size: 17px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ; }
+.input-text:focus, .input-select:focus { border: 1px solid #3396ff; outline: none; }
+.input-textarea { overflow-x: auto; overflow-y: hidden; white-space: pre; line-height: 22px; }
+
+.input-select { width: initial; font-size: 14px; padding-right: 10px; padding-left: 10px; }
+
+.value-right .input-text { text-align: right; width: 100px; }
+.value-fullwidth .input-text { width: 100%; font-size: 14px; font-family: 'Segoe UI', Arial, 'Helvetica Neue'; }
+.value-fullwidth { margin-top: 10px; }
+
+/* Checkbox */
+.checkbox-skin { background-color: #CCC; width: 50px; height: 24px; -webkit-border-radius: 15px; -moz-border-radius: 15px; -o-border-radius: 15px; -ms-border-radius: 15px; border-radius: 15px ; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; -ms-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out ; display: inline-block; }
+.checkbox-skin:before {
+ content: ""; position: relative; width: 20px; background-color: white; height: 20px; display: block; -webkit-border-radius: 100%; -moz-border-radius: 100%; -o-border-radius: 100%; -ms-border-radius: 100%; border-radius: 100% ; margin-top: 2px; margin-left: 2px;
+ -webkit-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -moz-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -o-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); -ms-transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86); transition: all 0.5s cubic-bezier(0.785, 0.135, 0.15, 0.86) ;
+}
+.checkbox { font-size: 14px; font-weight: normal; display: inline-block; cursor: pointer; margin-top: 5px; }
+.checkbox .title { display: inline; line-height: 30px; vertical-align: 4px; margin-left: 11px }
+.checkbox.checked .checkbox-skin:before { margin-left: 27px; }
+.checkbox.checked .checkbox-skin { background-color: #2ECC71 }
+
+/* Bottom */
+
+.bottom {
+ width: 100%; text-align: center; background-color: #ffffffde; padding: 25px; bottom: -120px; -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
+ -webkit-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -moz-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -o-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); -ms-transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1); transition: all 0.8s cubic-bezier(0.86, 0, 0.07, 1) ; position: fixed; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box ;
+}
+.bottom-content { max-width: 750px; width: 100%; margin: 0px auto; }
+.bottom .button { float: right; }
+.bottom.visible { bottom: 0px; -webkit-box-shadow: 0px 0px 35px #dcdcdc; -moz-box-shadow: 0px 0px 35px #dcdcdc; -o-box-shadow: 0px 0px 35px #dcdcdc; -ms-box-shadow: 0px 0px 35px #dcdcdc; box-shadow: 0px 0px 35px #dcdcdc ; }
+.bottom .title { padding: 10px 10px; color: #363636; float: left; text-transform: uppercase; letter-spacing: 1px; }
+.bottom .title:before { content: "•"; display: inline-block; color: #2ecc71; font-size: 31px; vertical-align: -7px; margin-right: 8px; line-height: 25px; }
+.bottom-restart .title:before { color: #ffa200; }
+
+.animate { -webkit-transition: all 0.3s ease-out !important; -moz-transition: all 0.3s ease-out !important; -o-transition: all 0.3s ease-out !important; -ms-transition: all 0.3s ease-out !important; transition: all 0.3s ease-out !important ; }
+.animate-back { -webkit-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; -moz-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; -o-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; -ms-transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important; transition: all 1s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important ; }
+.animate-inout { -webkit-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; -moz-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; -o-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; -ms-transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important; transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1) !important ; }
+
+/* ---- button.css ---- */
+
+
+/* Button */
+.button {
+ background-color: #FFDC00; color: black; padding: 10px 20px; display: inline-block; background-position: left center;
+ -webkit-border-radius: 2px; -moz-border-radius: 2px; -o-border-radius: 2px; -ms-border-radius: 2px; border-radius: 2px ; border-bottom: 2px solid #E8BE29; -webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; -ms-transition: all 0.5s ease-out; transition: all 0.5s ease-out ; text-decoration: none;
+}
+.button:hover { border-color: white; border-bottom: 2px solid #BD960C; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none ; background-color: #FDEB07 }
+.button:active { position: relative; top: 1px }
+.button.loading {
+ color: rgba(0,0,0,0); background: #999 url(../img/loading.gif) no-repeat center center;
+ -webkit-transition: all 0.5s ease-out ; -moz-transition: all 0.5s ease-out ; -o-transition: all 0.5s ease-out ; -ms-transition: all 0.5s ease-out ; transition: all 0.5s ease-out ; pointer-events: none; border-bottom: 2px solid #666
+}
+.button.disabled { color: #DDD; background-color: #999; pointer-events: none; border-bottom: 2px solid #666 }
+
+/* ---- fonts.css ---- */
+
+
+/* Base64 encoder: http://www.motobit.com/util/base64-decoder-encoder.asp */
+/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 21, 2015 */
+
+
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 400;
+ src:
+ local('Roboto'),
+ url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');
+}
+
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: bold;
+ src:
+ local('Roboto Medium'),
+ url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'),
+}
+
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 200;
+ src:
+ local('Roboto Light'),
+ url(data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAEScABMAAAAAdFQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABqAAAABwAAAAcXzC5yUdERUYAAAHEAAAAHgAAACAAzgAER1BPUwAAAeQAAAVxAAANIkezYOlHU1VCAAAHWAAAACwAAAAwuP+4/k9TLzIAAAeEAAAAVgAAAGC3ouDrY21hcAAAB9wAAAG+AAACioYHy/VjdnQgAAAJnAAAADQAAAA0CnAOGGZwZ20AAAnQAAABsQAAAmVTtC+nZ2FzcAAAC4QAAAAIAAAACAAAABBnbHlmAAALjAAAMaIAAFTUMXgLR2hlYWQAAD0wAAAAMQAAADYBsFYkaGhlYQAAPWQAAAAfAAAAJA7cBhlobXR4AAA9hAAAAeEAAAKEbjk+b2xvY2EAAD9oAAABNgAAAUQwY0cibWF4cAAAQKAAAAAgAAAAIAG+AZluYW1lAABAwAAAAZAAAANoT6qDDHBvc3QAAEJQAAABjAAAAktoPRGfcHJlcAAAQ9wAAAC2AAABI0qzIoZ3ZWJmAABElAAAAAYAAAAGVU1R3QAAAAEAAAAAzD2izwAAAADE8BEuAAAAAM4DBct42mNgZGBg4ANiCQYQYGJgBMIFQMwC5jEAAAsqANMAAHjapZZ5bNRFFMff79dtd7u03UNsORWwKYhWGwFLsRBiGuSKkdIDsBg0kRCVGq6GcpSEFINKghzlMDFBVBITNRpDJEGCBlBBRSEQIQYJyLHd/pA78a99fn6zy3ZbykJxXr7zm3nz5s2b7xy/EUtE/FIiY8SuGDe5SvLeeHlhvfQRD3pRFbc9tWy9/ur8evG5JQOP2Hxt8ds7xLJrjO1AmYxUyiyZLQtlpayRmOWx/FbQGmSVWM9aVdZs6z1rk/WZFbU9dtgutIeCsVivND1dsWSG9JAMKZOeMkrCUi756MI6AN0g3Se1ellm6GlqOXpBxuoNmYXGlgn6D/qo9JOA5ksIFOoBKY79K6V4qtC/ZJy2yXNgPJgIKkEVqMbPNHpO14jUgXr6LcK+gbbFoBEsoX0pWE55Bd8W/G8BW9WNboZ+b/KPyWslDy5K9biU6TkZpY6U6ymiLdUv0Vyi9jvt1boT+x9lTmyXzNUhaHKIcqyEaDkLfw8YTQBNDpo2NHmsVjZtrl2u/kZLmDlHaT0BJ1HTZ45+gbdfTSznJVOK4WQkWAAWgiYQQB/EVzAxYhheIvASgZcIvETgJGK8NfDdgN1GsAlsBllYO1g7WDtYO1g7WDrMcAK+a2UA6xci+kp0i0EjWA4s2nMZO6DNrE4zDDbDYDMMNptIHSJ1iNQhUodI3R4DafGzG8JSKEUyRB6VJ+RJGSbDZQSrWsb+KJfR7OAJ8rxUM/Z0xq6Tl6Re3iTyjUS9WezsQ+7e9L7j24G//uznFl2th/WAOrqPNelG0hq5z6Srk6Ub4Kau0Mv6qe7W7ZQPsxIhPcgeX3sPns6DCDjYSX/9rj3/7ka8bbeNGQXHE/UzyZb3Naqtt/W+FAepZ1J3mVOWPoW7ipYzFE8hSiE3Erfcabyo/I+kF7TVzPBMiq6VU3Wr/FGy9F2y1MD5aLfeG7ukh3SKztOQHtOldxmvgTW/3uWKBeLrqifdSuxbPeNypiOTPb/StfqBbgBrYCOIKkifoH6ou3S//oxFky4jLzLWvTSoV/RrU96pR/UY36Mdx9VzerNDbA+b/M8UzXE97TKTYCcvdY079Fxl8v2duY3vJb3Y3lvbjK+QWdMjScujKb226ze6V0+AH9gHId3G3ghxPk5yZs+m2BVzo4j+otuYZ3wX5ibGa4uP3R5tYufcaU32pGm7er+ninU2ffVaVz47Mt+tHXstTVvae0Cv3PeYTjqG4n5v927ukWDyTnDucuZXdXEerpqzcsc10D9M3nKnmNPFnZ6n7nOlY/RxrdBhYDA7yovKyx/Mq5N0vr6l67EIaA4ne4k5369QP6Kvpd4r8RRjZ+hP4PPkPrp4i832qOJ/AP1E1+ke7uE9nPDWJJ+Jrx4Cu92zEZtr6m93h6H2O7CDtjENA6eSpZOdzwL/84C8m3g93kuyeVN44C/L1LyIT7J5D3gNqz0SVjloc7lZuAc7/RfC3NHu/+dBU8tP6vORAnN/90poeoM+5H3vIaYsM3omo/oYwfVdgLgpk6+vWxvGSuQWfkuMV4v5+Q1TAaIMIr2ZVYhyIWLzCipijKGIT4qRPvIU4uNFNJz8aaQvL6NSeBqJ+HkjlcHUKCRHnkEKeDGVw9dopJdUIBkyTsbD80TEIy/IFKKoRLJkKpIpVYhHahCvTEPyeGVNJ7oXkX68tuooz0SCvLrqiXCezCeSBbz//bIIyZAGxCOLpRGfS2QpHpYhPlmOZEkT4pcVSJ6sk/XM1325WdKC5JsXnCVbZCtlG75djiSFI9uwkwE37hv6Md6G2cx+NJYVzKs3MxtPlJOQ/sxtqjzEO7FaBpk5PMIMZtKznvgGm/hKiKsJPjcw3oj/AIgWgIQAAAB42mNgZGBg4GLQYdBjYHJx8wlh4MtJLMljkGBgAYoz/P8PJBAsIAAAnsoHa3jaY2BmvsGow8DKwMI6i9WYgYFRHkIzX2RIY2JgYABhCHjAwPQ/gEEhGshUAPHd8/PTgRTvAwa2tH9pDAwcSUzBCgyM8/0ZGRhYrFg3gNUxAQCExA4aAAB42mNgYGBmgGAZBkYgycDYAuQxgvksjBlAOozBgYGVQQzI4mWoY1jAsJhhKcNKhtUM6xi2MOxg2M1wkOEkw1mGywzXGG4x3GF4yPCS4S3DZ4ZvDL8Y/jAGMhYyHWO6xXRHgUtBREFKQU5BTUFfwUohXmGNotIDhv//QTYCzVUAmrsIaO4KoLlriTA3gLEAai6DgoCChIIM2FxLJHMZ/3/9//j/of8H/x/4v+//3v97/m//v+X/pv9r/y/7v/j/vP9z/s/8P+P/lP+9/7v+t/5v/t/wv/6/zn++v7v+Lv+77EHzg7oH1Q+qHhQ/yH6Q9MDu/qf7tQoLIOFDC8DIxgA3nJEJSDChKwBGEQsrGzsHJxc3Dy8fv4CgkLCIqJi4hKSUtIysnLyCopKyiqqauoamlraOrp6+gaGRsYmpmbmFpZW1ja2dvYOjk7OLq5u7h6eXt4+vn39AYFBwSGhYeERkVHRMbFx8QiLIlnyGopJSiIVlQFwOYlQwMFQyVDEwVDMwJKeABLLS52enQZ2ViumVjNyZSWDGxEnTpk+eAmbOmz0HRE2dASTyGBgKgFQhEBcDcUMTkGjMARIAqVuf0QAAAAAEOgWvAGYAqABiAGUAZwBoAGkAagBrAHUApABcAHgAZQBsAHIAeAB8AHAAegBaAEQFEXjaXVG7TltBEN0NDwOBxNggOdoUs5mQxnuhBQnE1Y1iZDuF5QhpN3KRi3EBH0CBRA3arxmgoaRImwYhF0h8Qj4hEjNriKI0Ozuzc86ZM0vKkap36WvPU+ckkMLdBs02/U5ItbMA96Tr642MtIMHWmxm9Mp1+/4LBpvRlDtqAOU9bykPGU07gVq0p/7R/AqG+/wf8zsYtDTT9NQ6CekhBOabcUuD7xnNussP+oLV4WIwMKSYpuIuP6ZS/rc052rLsLWR0byDMxH5yTRAU2ttBJr+1CHV83EUS5DLprE2mJiy/iQTwYXJdFVTtcz42sFdsrPoYIMqzYEH2MNWeQweDg8mFNK3JMosDRH2YqvECBGTHAo55dzJ/qRA+UgSxrxJSjvjhrUGxpHXwKA2T7P/PJtNbW8dwvhZHMF3vxlLOvjIhtoYEWI7YimACURCRlX5hhrPvSwG5FL7z0CUgOXxj3+dCLTu2EQ8l7V1DjFWCHp+29zyy4q7VrnOi0J3b6pqqNIpzftezr7HA54eC8NBY8Gbz/v+SoH6PCyuNGgOBEN6N3r/orXqiKu8Fz6yJ9O/sVoAAAAAAQAB//8AD3jarXwHfBRl+v/7TtuWLbMlm54smwIJJLBLCKGJCOqJgIp6NBEiiUgNiCb0IgiIFU9FkKCABKXNbAIqcoAUC3Y9I6ioh5yaE8RT9CeQHf7P885sCgS4/+/zE7OZzO7O+z79+5QZwpG+hHBjxNsIT0wkX6WkoEfEJCScDKmS+FWPCM/BIVF5PC3i6YhJSmzoEaF4PiwH5KyAHOjLZWiZdIU2Vrzt7Ka+wvsELkmqCKHtRYVdt4BE4FyeSoX6iMiRPKqYCxShTiEh1eSsV7iQaqF5RBWp7FaE4o6dwoVhHy+H5apHH6iorqZf85805OM15wrd6edSAhGJjfSCa1KSp0jhWk4gFiFPMYeoEleg0DpVcNXXii6SBCcFl2qieaoVztjYGdUOS3XslExxjbAHX+fyZYFqoTQgdCfnvz6snaPcl/AK611DiLAGaEgm6fRmEkkCGiK++MRwOBwxARkRsy0OjmsJTTLZ82o4OSU10x9WiaO+xutPSM70h2pFgb3Fu9LS8S1RrK+RLFY7vEWVjAIlqU5NdNUrifomza76iMlszavpbRIsQI9LjYezPjjri8ezPg+c9blUG5yNc9WrAZqndEna2etfp3OJL8+6s9e3p514oCS5argkkwfWZa8SvsIiNZZEMxzEu2qs8TYPXqrG7ouDD7jYq8xevfiKn/Gzz8C3Eti34JrJseukxK6Tip+pSYt9Mh3P871dHI9EumTkQkpqWnr+Bf8pvZNABJ7CgCcAP2Eef8K+IB/wBfigB3+K4K1rqGuwVk/bDRoziHaDl3/9z2ByXjs1YMwA7S14uY92G6y9SVfeQV8bRZ/X2M8o7bo7tDK6En/gPKggqTzfkY9Kj5AO5CkSyQMJKm1BDub6SJ6IPM3LteRFZBCm4g2rKZb6iJyCp2W3BbQ0v0Bx1KnpoKIko05WOXe9ku5SZWB7bkj1guDahhSvSzXDicSQmuWsV/3uerUAxCOngyrHFSteucYmprTJ9BcrZrcSLCZqiii7txPq8CdkwVngQlHYGx8OdSnsnJ2TTws7dykClUyjThrsnB1sI/m88f406vNKJl+wMJ9W8uWHHvvblsd3fPT225vLtu3l+PLnH//bs0ve+PCtj5TS7afoc5L63KqKSQ9f3WfnS2vfcxw65Pr+gLhi96r7py7r3e+V6g1vOXb/3fYxWNCk8z+JC8WDxI7aDdzpTh7S+aN2ctRHBOCImuCor+2amSfY89SucCjb2KHsqKdKjwKF1KkOYIHDpXp13UWFzYDDfDjMd6md4bAtaGlP+O11yO4am5ACRlCsds6HP1Iz89LgD6J27SS71ZT04mI1QYaj1LRiZArwIRyKT6VeKdgmu4gxqCfVGeKhfpp1mfcnrZ43d/Vzc+ZXjbprxNDRJcOG3VXLvXVDtJjOgTeqVsMbo0v0N0qE/gPmbt06d8CcLVvmDJk1a8iAIXPmDGmQhakdzz26euCcrVvnDIy9NXD4jJnDCHiz4ed/El4DvrUhHUlPUkEiKegVMpBx2VJ9xIqM684Di3oxFgVBeYK6eXeCw04utSsc2kGT7C7VB4fxcr16FfxGPmy3ChnZHWRkks8OTHInprZjTOqeLbt3EJM9MbVDZ11rOne5ijJ1ATaAdjgp7QUeDdTEbwrmOGgjV4rgUzkmB/WAHhXBRxiPhj+x1HnzwMiqx18adtsa+lynLpP+0u81bumM2w7d9/Hpyk1rR2y7VisRTVzBtEEPXXW12q3TPSPLJtN7K98YYxvz4l+rNq+dOWzB1TO09OuUMfM+/+th8ZGBt9ZFZlVffw09JpqEzJEruEN9Hr1pYYeSroPGLgAbnCb0IceY387WvbbhsqkiXeCvkVGN3nmauSxb6EOt7+3XThK05Ye1TtxEaSiRiYdQxc0YbAWr87AveQpdpCidSpzsc7mBDdnkYRq/SUp64vDhJ5KkLdoJrqeTjud6l9C/3B39Vdvu1bZHfx1/7RiuM17brXWivza/Nl+n2puu3cUtF7q4nKJwPIHLE1PQ/fiRow8nSS/TeO3EZkmrKOPc9EYv/QvnK7u2JLpXe8qpPRx9bwzbdyo3m78B4oiD3EMgpIKzoQVUcbL9cyB7EczExZy5kp1EIQjnv0NUQvPfQfd+ovP+TPTqDoW4FMdeQaEuhdvLqZwjP58qDnSmVBU58Dc20BQeY6jE/IrIh/ksv+gx2WiOJzWD3iiMNdO+Aa3mm9vq3rvtiHBr6Uw6VVs2t/Re7YuraCft4560PWH77U+WC52EHRBlbyEKKVBMYZXa6hUxBMJD70is4DQpwUPKo6OEsGutY3EcdFwIRSxWfM9igo9ZLXhoJZZY5AW3D6EdXL0clPvTyHT6utZvOjetnH6i5ZdrafSYvofBmkadZBfoTBbuATXG2kxjQDJoUwKSKxY3qszgfhXj4Iv+6pe1E/p1OnHdOBe3Biy3DV5HpVI9/lBFKAAW59XyXtREwB7G3nyd6Ddct9JS/G41vHQk6+G77WIIxl7feICXQAny3nr2o18CsUv10vXr8ftp5x/g/s0wkEwAMiHwgVX1z/lpmKZxoyZEX5gtdTjzKcNMi8G3BA2f3I1EbLiQLMW8MTqVFN3vOpv8LjAi1fCwqk0oRlZ4ZJc7HHInUhcXbMN59PAi695x8ekjR/44feTw/1SqGzZsU6qrt3KFtB9NpCHtA+0H7XXte+0j2omavv799Dd0/Lf/+c+3QMeu82e4DWItyKI7iQjo7zjcEeVcGXsLEO8wsQjACidslkeBC9SiGzNoMxMRMjcLRL6L/rtSNN865Gw/sRvyaDJgLBloToKjiAMptgHFaCRqPF8fiWdXi09CLUvWAZPMABPYpSrBcpIHPyDZQdU8Eh56HLByCrzrSZTdEd5mLQamqDbgj+IsVuLliEQ8xSzIZBvO00T9oI6FNOYefcHJ4h+f7Dr2zGJtMsf93FBJjy6c+OzDGzZPFjw7Gg7vqPyfFVo3sXQEl/rUOyOWrH91JdIx9vxP/GmgIxe0JtIW6RCBDrEtbkkEZkRSkCQvkORlCMObYMmrtce1TYGQakfR5unuACID51L8iDcS4DihADEFnEKUgRBDyXIp6fiuDMdyAaKTiJzOMEscEN4ewYcfYgegjrYsdsQB4FBJVnGxYpeVNgBJ3GpienFL5JEHxsMOGPU5jYxhyCPYJnMsV/7Gs6u27nhp2bI161eueLimnBP/3L3/h3nTliw+d3CP9jNdJC1TXnj62SfL1sxesvbFxdLLx+p23729fc5rc/Z9fQR1ux/IuT/YgpU4yRASscS0qJbYLJwdgDoAZ6lekQAYuwoUS50SF0LlVvhQxMxciFkCJloYPLagN5FRuWyoXLRY4WTFwVSMhmVAkqBnkJjkmPpxax44frwi+h2XKoVpeV++oSGrVHuclpfyvbiJzD9sBZszw77SyX4SSW2UW2qj3FwoN4+tvsaR6jLn1fptqS4Qmd9WzxC8s64myUkceSoHcRxFlOSMAXPmyx1O9OVOh+7Lr9p8ZjH6clFxuhTXXjBixbN351UP/tkVztpqvA6PJy8CrxkPZTwUlEBli4nizacRl8erw2aqmtHTpxYrSaABbtRsB8g3QsxJxRfIFERpyvEgpO5Fi7q4fV5wBtlbufHVy9a+8MITDz8ZGH0ztz+6rkvRwik7jx/9uvYXOl168rkDO9cdHDrMxadOjp4JdeH58+TwUe3PdwjzTyuAV+nMVnPIXSSSgNxKi/knG19f685MQIjoFoE5bZk+J6OrCinJLmSK6gPmtIPfgWTQUMHkTmAampkGGupzAgS0uYE4c7EiyIoJqZE7E9BEvykfAI2UCgYKbo0RQoqak7mCpn3cf3lxenH5wLWf9dg55cDx3w+8o52r3Pv08m0vV03fHuBS6OQG2qtNRklGWsP78weO1H498rn2I23f8PGv/3pxW92cu5guDAAdRV2II51JxIwaik5bJWie9gLFXIfpaixFg8CnOlAHiRk2zRfr0cNKeVOwyE08A/jXT5zNtVXacqn5C/GGsjLtx+gebemMGXQq91dqIoglxwA/7cBPPwlCjnw/ifiQo8nAUQuu2wE4mhPwWYCjObiFjoyjCcBRCR1AJhwkuNQ04KcbDnPxXBwwuBOcyM0ENGnhfckBJ2MxMlx1E3ACObLq5OF3B7caJxXrULKoGZJkNi+AzTfnsKfZ8ZiqRfcuPvn3Xf956N5FL2hnP/hEi1bse27FgbefXnGg3ZYli7aqCxdvpgvm72nXVrl/10cfv36/2rbdnnkHPv3kwGNr1z360JYtXMH8Vavmz6l+HnVqKPjNfxk6BejIGot5LAJkAQcS0qw8cCBBatIpbz0qFIQ/JRBSTV5dp5LRFdhZymV18LpmyVb9XAK6BzUL9Yz4dKIJi5BeAkaRU5RGWQKBuJkzcLNO7FByftenmnb6i4Grr4vvu2jwhgOFNZPe+m3W5uULtmVtX/XIK/zuozRXO6md1QZHtfq09DEZKV9/uHzEGOr9cuOxRSUrP/zytG47GCSCQldWD+nQhCYYIEAsYUbSADshlAAvyBCFpRFR8PCzculSwBX83xBbcARhTo7QDWKyhXQiEROgalXCC1ljAEkxh7D8IeH1CljR4AK0ZMOXcYCY0pbGMJOwAq+u28IMfgn/EVydgFf1UZPPT30D+O7RlRMmcGX099F0xhztlxQpRTs9B/fzFN3Af85vYvQl6UjLqlNnZdQZxKCNUPh5iu/TsJvvQzeMG0dXjRunrzkL1nxHX7OokBYV5lBYeRZXOWFCdAk/YMYs6k4GL+CcqT04mvH0ZjCi65nupJFJJJKMPE2xx9CDrSV6SNfRg5uhB4CiSnIIzaU2zUu6C3lKXCOkYElsXBLoCh8PhuKRVYsLHW18CjpaKe4C8OCgviB42Bh4MAWRqzfzdRtq3l00o1dyBc29Y8JdS+bcD1GHtlkmlLy4+9DmxR9PLRwx6oG7byt/Ztq8h5fed279ypVAzwytu/S5+DAJk2vIFhJxYrXCElaLxHolLaR0KlBzHfXK1QWqD35lFqg8Aq++zCRyIOfO0X2sBMlEP70ydNW+s1P11KGnS+m1FzzLGSVpL6lJSu7ZC+swtPGIhZYcsCCVtgWaA3Jvi4WXM3PzOxV2w+KF5FZNbZAJzlz4TId88NVXFwE7EhINdrhJIIPwEsYYI/3s4mauO8xLzJ70D3AkAMd++EQGofobPWiRh/n3GW76Ga2gi+lS2Vr3wcB75MLnyh5Y4vGf2Dhyaj+OD1lvKnr0RZtbU7Sntb9rI2QPnUhvHlLbK733B3dqC7VRXLHr1lG3P9KZFmQM7PigQr+mGzlJS9WGHNb2lQ0fNfqXgxoNFxZx0X0LR515iy6i27R22jxtkdahfbB/u470Nzp11au3T4UMlsvwJ/0M8oCsXvgG4oEJMqH2us0qfJgFhVrJTCi4JQlxQFwBy21UipHAigVMAPdBPsB7AkAo124KlzXr6Wjp07u5G7WvJVE5exN9WhvHUcg9WBzYA+ssZvmhH9Ycb3gHJ3hBFn8y0Av62XLMCwaYyJ3o/kMAJJje2pz1NaLNYwYDgPMpYHagyG0o/slCKlH9TpYioi+ECJuhY3JIxJojvayA7uUDhbGDPfSl76JzJy7aEP2HNo/Oe+HV6jXaRDqoasurivaBqOzZW74hI+HQwv2flK557IGNpcsWP7RMt+WFENs2g22mkrGGZXqAHk8yg+jxgKsYaIgDPBwn4Lk4CxppGiPNBSS4WPVTsYQYDDaF1HQslrhA+4TkYqRClRJRIeM8cMqUoFeNXODVBUj9UZ+4VOp1o4KF/RLEM7KQ5v72I3V5uPKEd17d88MPe1495C/nPNrP3/+m1XGjT9J4OvqPb6Tte7XDP5z6t3Zk1+vSl+fonehnUD7vg3wsxEM6GtKxxqTjwdDsjdUiFKsLUQHzIz7dfcug+FgzCAB3SU/amSBXq6mNjtDWa79DutXxMPVrP36ufSQq2nNa/evaj1pVKc3/Yfdxms94iesPhfVt5DpjdUtsdQF0Q9RVUeSZKuJGYmk4S9EtgFQUa0jPx40kXE/A9Z89/FMNx7i/R6/hg6JSFj1aFl1fShrXHcXo7q2ve/GaJj3itLamsaDtggX38C801HEHoj1wsbfujt6ur7Uc9OUD0JcMrKmlxfSlFSWpTUhMQ5DJ8uFAK/qCkNMUisQzVYuHNIvZga46aaA6yTKzhwRQHCW5WI2DNNFAmy3Uxyfr6iODMchMg5bTwj9+ohYfNzlp364Dp7T3n3g3S5tNz3XSogc17XVuCMjUQW/9aZe0fLt2/Gvtt+PaVzd3pLPKomevm0mHNfG0nsnyKsOjmHSPoojhWivPuGptkqSN9UcUm15lFljDpFGG2IAJQ64DTK3ge1RUNBwQleit3OazN3FV0RJ9PUi+6M2sBhFoJsPG2gVcDX/ExiseqUT/pH/3FsBmKnzXg3rnaMyNHI25kYVdCpTfHctcWQ5k05Vfz1UcwGsL5CiKu3l+AithZpmTXdj5Fq5843OLNlee3PV+xVS6TKpat32F4Dl38q2fxpXtNcd49jPzjzGeWZp4xtsZz3j0jM7G8ggXwooaUXm7nlFQPaNACsE5+y0U4nQQ2PYW13MxF93ALeIejT7/NrCvhKsSo8XRgMhtiQ421jbB2mIsAuBKBg+lGA8jPNN6XrTEKphMOL49lRwY9dntTfYkdYRryeQ241qmuHAjJbGKJkvsdUaa9AKkKhPGSMUs13BinB0jskmv92F1JcLbHCwKM9ooaoQnhwapySPvWc35JS6xqsIqRb8bHD0u2WA7msiBhjzAzebOakIDjS6Jzm7SzVNMN6+9SDebKyRoo2Dszo7ixt1xLGszG1tSeUtsQ0WootQk76nku0ugowchAJ5Lo8I/z94kHKfnUsG/zgLb//7Cupc5VveyXLHuJdj0uhf4/5ivzSAeNF83+Fssgvlm0Y6UUIF20d7VGs4T7cPK+o8+O3nqHx/9iK4/kY7U1mo/nNS+19bTETTpZ+1bmn7q1AmaoX17QsfvyJu/sfqFh/Rp7g3B/9dabEwHLS1DgS2E0cCJBV4jGqgem9wy8AYDibQp1v7+r3Pn/qUtoHNqt9du1xaISv3efT9G13H7X1n28Gv6Pmadby86gFcesOebSURGXvljvEpDXrVhG/DCBrwuNcngVRBLE17Muh2yjbWjZEiMABXIumalyaBOzVjo5Ux+UxbDaZdg5MTSs4O1P7s/cP0lubleOzP4RP8zqakXs5Qju4CfH4nbALsHSamhbS5d29QgsDQxmbE0EVmayShKAoqSQ0qSnvmlM/SuiCE1C9UgSTfzOFmRgapEomMd5uqV4EVYB6BBvN8Hfp41jZqJYBc9+e+zD85YXJGRNSMrbcsqbSy9++CO7a9oD4nb3j847ZXcNtsWLu07oU1C5oJrFz24KjqJ+3PN4sdXge1gLl8JculAyluv/2GTUU2BUJYi47mUhJYdxvbNOoytNBTN7bGmZ5ODLK/FJmKNw5fVvtUWYmY45AdCfaaWLUQhKKG7HcNN0jZv+Sxy9NQf1HP4nw89yE/6UN12cMc3P/2ufXf0i7VVdIX08voVsyue6dZj77rqT2ZP3yqK0vJdz02b9GTXHu9Vb/2AThp3SEJ/0QFk+BjDx2C1UvN6icKHWEor1aHuR0RWmRUBFEQk1naVsILXlBFiL6CDUKLZKrFScnaHeAPzR9Ws14b+skjPhlTJ8L2KtdFd8lgkdOHFWPUD3SWkLljsZaVwiDONAQfLGtWVX6m1xyq0o//+QTtGP+O/bMja+e6h1/H3zw1R3Q8i7v+Q4Z6AUakkHBs1QKzDAI1KLLGiT5j6w0WI9zMW0B2pkJ9uXxD95xTwcdeOHi3shFBKSTH4fewD+EitXuNRnGF2yQjFAACXjWekUEjVqUuNww4hyl7P4t7485erWVufuBTfXofe/9m5r+rkcaOUmO9Q5L2q2XdGVEzwxuyfb8FqIsSQGpfs9ORF4LVZQbGGM7tklv3t4Exmp0v2NXXlKaxthGziQ8fKvDiQmE6RRP9VFAmlOUETDRbPpJb2UhHtPIV2LpQKqGmG9tAU7bVsKUvbMRXIP/EN/VbwnjvxT/wFvv6OZ589t07nb3fgr8LiTLZh+eYwKwYbcUbPpjiMI4KVxREL1f8PWmh3elpLfoI+S1c9oaXQ049pt2m3c8e4D6LLuUnRUDSNWxCdA2sEYI2dsIYZEbupUYY8LGApUEx1DKFbEambWPQCivUDpBfWooirltG9dP+y6MkKUWn4nG/XMCZ6gkvWaYDEQBjPdCQ/FstjeJXn65sUxaRXqAE0G425cCENYBEk4LuTH9bwBv9xwzp+9gjh57K/noszcMI67W16UpoHdlXIKimA7LGSQvlYnajW5CV2IQ9RDphX7C8+FDMpgB5BOexbR2/45BPtbdOrZWe8ZXDdjucf4MVYP4q07EeBkIMd7+NG3ScqZz6FzxLYQ3+2h15EMRXoRl2A2J/twVQHy9VK+sKSS6VghRTs3RXbjClW8fFB+AcEHfj0U9pf2/6JdKLsz+uxvsQd4RoY/xp7YwbLYC8sfQYt4wfQvGE0d9qBNCntDfjC59F29Pi4cVqKzid6fhU/lWXQSc2wGR40IywM7oXyUxoeK2XfuUPYSfeLB4hA2hC9AcELxIWdRZFxFnLyOAG0Qt9IUdgTvINbeeg+cY+o/YHx927AxG8LAyFq5ZMTemarJIUjAVw9xwoZLhbizBDA+PYBD+JSLNIUMPPGgm2mS7Ghp2cTAECvG09hDTcipOaGQiFI0zGtVzsatn/tb/2Z7SfnC0rqXlFNij8jKAl7d+799XcLs/IEV01iQpInT0l11aSkJoO5w59N5h6Bc8zqExJTUmM1n8SURnvPtLNBFTUNgEnEE8hhzTI+AJbnx1zJLEdszni9xNM5s3usQVYAJt+5iFXAwL36IZAWNp85KITP3E35r0499eDsFydxk6Ztr/nC7pwdZ+3x9uyqbRXTx89/s/1/1u2nGU/XPjht4ZzhVJKkqcNG7Xg5eqJ4QmHRTe1uK9+4dMjk6SOPLWOYZzXEAUlKAE1JJ6MN7GVHhvsA+EjI8BQ8YH01iWJczWAMd+uJgOyqV9wuNQHnwPTujOpG2OPSywh2JDkF3Z2LN0CrzDoNst4zyTF5jPowIiDJtLqyy8Zp+7/66o2KzYV2ue2a+1dXPb969rNZUkK0cvhd2jta1Peb9s2dQ9fRjJGTfzzg+5Dys0Yz3RsNuvMO051RRNeYeNDX+ECsSBkRkBYnYAQnS3edNqRFRz8eoMXjUhNBL+JCaqqM5V0GfRKxACIEWHEuHg7NqcYEjbslDEDMg4Ew7Pf6vCbIvbjRv34Zuf9ebvy2uVurNygVO8ZxlbPXH/0PZ849QTveU7ZOEqUFq878PXfvn0umS5L4aEkpLWDymAx0fGrI404dr+vhGeUhxOQhMHkI5pbyMARhsoGux6SR4EYSnKBvVhmU0ZBGnMko6rBCImYROc0L9LKepU/+8sCUDUUV46xdXr5335eVq6umrcpr9/T0qjX0vI/ytGjUEG7BmR9X3z6CBn478OPYEbRh5H1a9ENGxwig4yOQRzzQMYxEvEiCXTJISMWqm8UrxKpuGc1LPIlG+oO7T7QirLZ7/Swtk1WXjLKw2FGhZEMWhE0rBXz61rH+2YZ4/AHdnEZQ2+63jkeFfVXlVV3DPV+f/67223yOm7Hh0UW1NFr0Iw01fFKW+sofvbrd0rs/bU8nimmP7H4X9KkPEFEjdSB+ciuJxDOrwPgjWQAk4WykHFaJCGoDWCyhQIlnExo+rJWEmk0URuJ9TP8QkSVixJLQJVjYvsN6W6ixAacjtT41654M9A06E8JtSsZSTtMq+cMlVesiVstdkmlWeVVJQ1v+MNMTrT9fB/xNJXlkmlEFDIBmmGFzOpPbmpkb9GIVtT1jcBrsL83FsE9mKMZuNl1WoHYAbqcR3XL9co0g25ONyToTcDwZ0htA/2pbe/OKIFOeIr3a0HqnJ6ZIRw/eu7HIUfrDBwOVPum9H7256oWijeX7j1Y+DyqVm/PM9Kq1hkqVjthy7h8f/5odKM0I7Fi75JahtM2v++vH3UH/GFmpNXygx6YqCEtfgI14yAAD41jDuq9yoq9yNvkqb6N9cyE0cZvhp7CCYvMw1ACmTQy8GfNO4HmD+kyHSa6q7FJbuemVymUzZr6YA27ontET/vFNtJRbrTw7f3xUYrq+BTaVCfthc76x/BWVBAOl0KIB5dQbUM7GBhQsiQ2oLRUVFUK3c2+K5Rs34jXPP6L1p3lwTSdQ2ZUwsaI0BQvAFZdCMc5hT99VoMp2PTMG2ODSpeoOGfVRXpdJrCKUje2Te+2urr6hYyqefzStkAoV2shS0TqzUnjy3MTq7VZTeqxHtQZ4jHNljlhdFOtCIs6X8XYiYvA11Ud4OyvNMFZfuj4ktlofWlM5hy5/mNMG0a/5pVr/h6SEhpH0gKglRF8VOWf0P7CHJr6mkEbo0XppbUuFlHDmR/jOCsgH5oJdZGGuyHCLKwXrQGgWqCJKXBjtRPGB4Wazi2Xp2pHlYkUPVuJng6hY+lRzcDJE1w8lVQZ1UVLQgBVZVuN86IsCLSoyfqY+/guUyNtcoVaMt3XeUjmrOrPT9gVbdlU+MmfZCjed/tjsuU+lCd1q7hxbOXPq/O//E13KTX/7xa1LTElStIKbfuCl+ROj5pjuHwH6Wuh+I3VoAJfXeo9BjE2+SPf9F+n+OFtndbryauWyeXPWBIVufx8z8fPj0Ync8p0rF02K2pnu48xmAuznorkq+v83V8X8OEllXWNS1KIsAhjm8BEqaecOf6Gdrdz9cvWevRs37ubiAqdwsupU4BftQ9rpl13ncZoq8Bo6TaOes1obJYiwN4ylQ4kBa6T6ZuyCWApJQCwAybrtcC5WJGyOaWRO5xpgGrt0AabxGJxrxDSJtCWmKXV22cRAzdRNXdqtmrZ63fqq6c9ka6PELzYOK4lhmttvin7IbRtadmK/7wMq3DtC9/Gj+A+M/d9pZOm4/yYfnwKZg63gAgwA4kaY29K/IxW2RixglplbbwULFGGJs3UsMLm6S9zYiqINkxgWKH+2fbtn7m3EAnfcvuZsNpc/6FbEAj+V/pVzD52infsw5q+554EOF+RcTd5R76vHxYGKyI2tBsizcNrHjf4jjsTuWQAO+3TLMuUwxbzHWVA10Z/ncA2d8kS60K02bky5SSiX5k6O+mC9SYA9VsN6Hci8S9SL6GXrRaT1epHPD7gKC0YOI+80p8vuWjFODuI0mJIlKwmx+hFx+BpH0HUXHBtBb71+xMr1RZ0Bz5vUygVPz16377WPN78yvoyb/My8Bx6Y8tIbe7+sfbN8PKXtpPvGTb35xqmZuQ/NmbVp2O3zAd4PXTjlxv4lWXlPzVtcPXLoDInxPPv8T9wUcRDgl9tIxIM8iItBF1GHLqbm0CXWYYpvHC6Nt7SELtgMRHBAZMWpAxhZnwdrhruyC+Xs16f//POA3qlFme602/OmzgX4Qn3aTyXRq8YNFaWhdsfjz3FvwP5Wgow+F7rpfgwtUy+3SmZjk1iE8l5QhFLsrDDJ/BirQ8msKoklFSqx2kqzqlRRI6rNXlm5eNaStRmV46ydlcpN++hb3L3RZW9unjGe5869qd55N8aN9uBX98N+mtWl6JXrUu1n0dyglE2zZ2mlo4RuDZ/NncvnnXsTvno1IeIBuJ6PfGPMHjmcEIfwojXUhH2GVktT3sbS1L6bfj7dSmnqtxPvtihNWUS9NNXzvVND9XmEOEiD94qKHSead+7bd/IelsuaXDVmkwVy2cbSFfzZLJeFc5jLbufMFptew4J8treVM8HfjmaVLCO51YtYBjc8wI3Yq1FcCF4961A7Kfz93d93ljocnKUdLPulQOp44m6hWzTrjTe4L6NZb77JfXnuTe74669HU4ArIeB/LfCrZd2K/nd1qxCdqz3xCA3SrEe1J+ich7X3tPe4HM6jXUt3Rk9Gj9D3tTCsEQTMfIjJxJiVh2tjh9UeVmVEyfEFyHwgTW4uaJAz0yID4F5Fg4tou2yJXveglpv74HxfD4cjrjBu4MhAMSjAT/P5p88lTlppEcdw4uS/Lme2iDc3bGG61aKehU6IN/139axh3MPRJbwzOoXbM4SfeffQhoVGPauvNoFbKfUkaeRGAuZc63eQRCGPzQhBbLMU1JrZCTajk8wwKHYvIM3NYJT6gZ8ebPpTGY3b4lZFux4OWABjdo23gsQK+ya9rt/3/imrXkmae9/wO+4YXjEv9ZVVU7j0sQ/OPL7pVNGgdoceOz5pbVbOuonHHjuYe1PRyZePzVjK9hrRfqV+ViNLIS1bpa569mOUy8ByI6Xar9LuM33Y9yxA450xGtMKaolOo79AjQcaHQW1ziYa+TrFqvep3QaNfhIbbIjHqKc43KrVzWjsRRmJOkkoXpbH+1g+L5kscytH3nXXyPvmJu14rryionzVK9qu3IOPHStfmxlcO+X44++0G1R0atPxGYvHLp1x7OWTRbo8HqPVQj3vIYnkJoLo3GKtR73iUb+SGLHGXWnM3IHmZCyuJyKIZJNQFuylk0S2W1XywG8eQrTdmCbEEKjHE7+edLHk0fdY1cy/Pjn0qvHFAyaUrJ0+5IkhvSd2HXQP/eKBHTfcWByeV+Kcv+u6QV0Kp4/R9zjjvI3/TswmQTJDr5UoaWE1XqyPBJj7D2QY5RK8OcEJpwWWUQniRRWTDL1vns6yGoyWRgklSa5HKWAJJT0D6MEyl15CqbHaEpP1yFjY2d3yfqymKko8uyUrm5vxwd8rq97l+cYyynhO+MdTlbvf58y5R2hOwldfyu+tblZIWbrP/d1xP80BGvH+wo7sXqJn9fuI1FRIlxJDEQnTeAdfX0toimTPU9xhVn/1hmpsKZIZKAyy+1Nk7DwzdMATnLfgUyzoOxUfYoM2QHCbAoULs5QfFC0ePh3fhgVML346Ppl9Wkfe7no1E6ck0KoTEXmrksMAvWGeybTxjjScKQbJmnBmPtyLFuZc867tH5HXd/F8+dLK2U/Y6D7talM4n6cNg63XXmviFpTRtu/Vf7hV+ttSZY12uEwZv693aanz+0ol1kNaDvYWjxUCR7M6fa1LdhA7G4BzIYIM1Xp97ARAAy+vQwM/wiGkzc7GHSN2NppgtwFhUijiYJmfwwV/eUMMKtsdsVq/r0WtH0jx6bUNcGX4r8MyWk03LtOK6b3acPqiNrxCv8GQThWVaAfu06hctq1M20mvhV86jl8revgs437XHiTWNVeJnWEWvS/WOOeJVeYErNizRjqWzOGvxn5YGBnrW7uVtt0ielbDf1jhHn/+J/EP8QDEHj8g1FV6/FedDmPa0QcHmQwx4gGrvGWCidSG8yyZkAiH4WxemN3wWIAW0oXtIs5F8vTRxwT9Zj2lrUvN18dqO8Jf6SGlowtxbq3EPqkW4e19bWX3DovTx2emhPXx7TzZvV2Kc6eTjrrR6C1kvQnf7NiYMW7NksBLjKdVtC3NoVXaaO0L7bBWchudSAVK6WRtuaZpDdqTNGnHM09uELjhk8ZNmjVz8vgJwznhxSef2cEdod2pot2kHdQOaANphPbQ6rW5dD71Ux/E3PnatorNn1c9JU2ZVD2/cuGLE6ZJT1d9xmQ2k6zle/ObiASZIU65YqA2fs2kOfdoJ6j3HkfsgEv10JnaTG0WnWkcXHB/EWlx9xCoNSkDmf1qyCxEuuNM50VSqwWQgPPNeNdlJyahToD0lbah2sTu7I3ExvstL5BXCCQUDikhFxNLu/YA/FPBVwfbhkJKagux4S2YRSHIA1BsGXh7oTsV9D8HhNcJpwKDxUpYrgUREnxT6Y43GFxGjpfoo+fRRBq7naTMkOYakOYRXZqTIAPj6CQmzai2HKTLPVn1l759e5gtZVbhxqG7tg8aP+Le568kzehA/pY5M/relZY4rn/Xtn18Lt/NuV1uvUF7ju65+frb9L7xNGEXPSK+CRJor1tiLblEj0flMfByen6fTMN+ftqHT/Jn4PtWSWvAa5VoA+hKuKoTpz5MDP7H1SvOWIBnd6uY6motumgsLpU37s5m96dIRL8P2CTrFVU9ySoKG/OWJcNmDh6bekfcoNFVT2qrenYv7mCe29syaPDwiUw/F4B+DojpZxE6Kh/Dk/BrAfVqJ+6hOdqRTxqP1tKFdJG2yKMtajzQ50vZHKspnc2xui47ySoX6Gltq5OsvAf4c9E4axEyrPlMKyU68/SZmaGwLq56xclF+UqTi+6LJhcpbqjZ+GL0XX0vxhCj5DOkiLw8BC8FsBeBmEkWiYgYaSQG7ywFiljHCj7YDjaLLKE31MFGAecdwqveUWlc7sxPxoAcr88tmTqzulIG6dnq5FKgtcpSm9g90YKN3RN9heElRuelJ5joZNzgFeeYuC90dgjGvpONe7+DpKyVnWNJLCOspkL8CoRikMogIwVcS7oewdIZwKoN6n8Fm0hEXJWRjiTKCbYrkxiLepemcjbGwysSyeezgMnpsyMgbxmQRffWpkf8rU2PJBhZe8Tp9hUXtz5BwqTRcozkLRTARcMkYodG/eON/YA/gMwukZRcvCMcZ4kPqx5gOD4dIqn59tCX+3QW+9ica22i/ldi09YRo8djrcwpXWLjMR632PtnyNaLtz4/hjtYv1v8GvQbrI/8j37Xl+IP6zO6mdb6iKux490uzRXreHdi2w/A9gMXd7wDLtxtREjKwY435nq+kBq6oOOdkC8oSXtF1Y8db1+zjrfPVRPv8+uPpEhMSvBgB8vfrEoA51jH2xefmKR3vP0J8YmNHe+A0fFOtgFscaVltu+AsEXxymp+AWt+411C3mSj+W33tNL8zr5s55uFkWbtb6m+ttX29x9MaZp64NP3tNYA52+OKRGv9ytBFtivzCQjrtSxzGqtY5ltdCy3Y8cyI/i/7VkyIi/XuDzHqLtk95K+0sw3PwuBVhPfbumb6X/lm5/VfbOwm13uXB/sT5HYcxoSxKMX+uYWVf/L+2bjeRVXKPwzb9B69Z+2ZX75cj0AbkPMJ+v7PdDok8c223EqeohAGO9tUjJCzQj4v/HKlyYu5jFap68L88iXJe+s7kbw/jespYKMPSQB51YvUU1NvEQ1NSnml2WvHwzyv6qoMslcWFa9k6nlRcVV/iddDryxT5x594MkFly4Ux+KIhEyUDuO6TRtPCW28RovT/A24cYEr4mKmuQ4C7yVoL+VUFCbrOd92GdKwCKXLOm3J1yRtJhcLqBuIvPlFxEn9GZSiMX9UUzHAiSHXN8qYmnbmlW0M6xiByKWNsFsfYRYzcy64uQ18xTBInilwUtH91/qFvG/l/1KzU9w2uEpVw7zNiqCvCQq6E7EsB/JcjFtLSz+8rShxbdC26XtozltrdvISy3puqyxfN6Sphhm6A+YwU9ScSb/YhST1hqKSTesZTugmITEFKQnTlaTki8HaAwqWuKa61vs/mKUMLL5jpntCFbxNMHKYjr2dC5h5RmXsPKAse9asPKkNGPbDtz25c2huRguMIlvW1JwsW2ktGA6Jc8Lx7l3xTqIRHns2Scie76YLOjBCJJH0UvMYLTWWKlfv3eosCgMiXCO6fnvSr4vr94gHPcd/dbNxiTA920SltKz4iesDnAjwYK3XgxWfAW1vJFGJsQy/CQ9wzfSd3wmDoZudxz4BwuPrPBByg6JZVO11dfsKUh6dN5017V9S0b3u65kYGF2VjiclV0otu83Gk6MGHFdTudw27aFXZDWMuEUdx5ipAd3BdhMEtmwBi/G+vO1Hj2t9TAx1Vr1cgJrbeHUGc9G59i8EClWeZeRM+q7aioAI2gqmzD46vWF+X1umnTLDSu7FPQW6e33Tbq+yDtk2qRru1y+jvK/f+9FbqvwHST7PPCddRv4en2ItmnqFb7yotCL21qG87FLuK3i3it+fonY1fj8cCFEZfZco8Zn1MSeakTY4Dt7Ro2o3x7Dvu0J877hk6+7SghtpV21t7fq+7zMdS7zrJvhV1VMhi923FGjvW9c53wHKlH+v76Onz3+bnjnijGfUut7+zS8LwP2wpmNZ+z1YRZw0RP2dNoU0cUqKDbjLiCDTEWS2egGu+k0RnK4kfB5zYg3WKCvab/8msYt7bHH+RlrGqRgeUUqVqzslqiWz/ZDJm1vxiiDXTgT0oX+Qd3/V2vqrDTWDFeO2di5cswhmrN9m/YpfAde0Z/jPS93s+cJYSWmn1EREczhMD4KQBUtoVCzpwvFxZ4uZJSJ8UkHism4w87beBegAQXwZ9dSKi8l55euZ//pOjGBrKUNrIYUIFQxxVyYTZ8XN8cEJ+jCYrXPCReVPOE6pXCd31teR+FCxqWarkPxOkapqrSVyhTb002Asd4TD4KHhXwyBwnOMB6dptjCqszjhGItoTlWO8Na2PpIxmcpshP4GEUeM8YaR44VeyHtC5TcOpWTsP4JMvImABdTc7F+lIodjvhQJJc9zSWXWLAThLVRlGOHZg9pseNDWuzGQ1p+nfzGNL197WAPabFjr3rn6bq951j6aXPVxEFamKe4XDVOlwPST/izWfoJ5zD9hICGqactzulq1o/OYNVWfbQyiOOV5ILxSvavecbVk9700ksvUedXxZN7W7pM6br5bS4YPYo/724qLu9s6XJf96+0U5yvbGNZ1mkadDnHuTw/vpUDf3rePCHLY50u2uZ3jx6HRvHPCNew+3X8pFKvjELOh0+w1MMR3/iAL3zWjtnpgfScRSapzng+W+t38qArAA2o9evRy+/C2bpaZ1P0ciG6tdoNPBVgD+iB7M0D/+Aohw/yJnkUnbfiBtpx5CZp65C/SM+HX5TE8f36ae3pP7T2XKI2lFZHf6BzqTaPPka1qUyPEPh1Zc/UIJ3kgIzH597+f+LPPhMAAHjaY2BkYGAAYqY1CuLx/DZfGeQ5GEDgHDPraRj9v/efIdsr9gQgl4OBCSQKAP2qCgwAAAB42mNgZGDgSPq7Fkgy/O/9f4rtFQNQBAUsBACcywcFAHjaNZJNSFRRGIafc853Z2rTohZu+lGiAknINv1trKZFP0ZWmxorNf8ycVqMkDpQlJQLIxCCEjWzRCmScBEExmyCpEXRrqBlizLJKGpr771Ni4f3fOec7573e7l+kcwKwP0s8ZYxf4Qr9of9luNytECXLZJ19eT9VQb9IKtDC+usn8NugBP+ENXuK1OhivX2mJvqmRM50S4OiBlxV9SKZnHKzTLsntNhZdrr445tohAmqEsfpdeWKbffFKMK+qMaijYiRlX3MBRNU/SVfLQ2jkdrtb+DYmpJZzOiiYL9kp6nEGXk4Z3eeklVdJYpW6I8Xcku+8Ie+0SFzXPOfeNh2MI2KeEktSGP8wc5Y7W0WZ5ReWqU5mwD9f4B+6xb6zxj7j1P3eflW+E79+N1ukyzaV9kkz71+Beq19Dlp9msejgssDW1ir3S7WKjOO0fkXGvmJWujHq5HWdvWc0/pNxfUxWKTKRauBgm6YszTnXQ6mvI615TGOdaktNIksebePYEzZrMG88g326eeyVfMcMxSU6qk3uxt0uMy8OTUKA1PIN0g/Ioqe/W//BB7P4Hi9IeabvO5Ok/0Q0mU9cZcJ36T2IayfpmcUHU6a0K5uI+30inaIm/adUcsx802E74C0holcIAAAB42mNgYNCBwjCGPsYCxj9MM5iNmMOYW5g3sXCx+LAUsPSxrGM5xirE6sC6hM2ErYFdjL2NfR+HA8cWjjucPJwqnG6ccZzHuPq4DnHrcE/ivsTDx+PCs4PnAy8fbxDvBN5tfGx8TnxT+G7w2/AvEZAT8BPoEtgkaCWYIzhH8JTgNyEeIRuhOKEKoRnCQcLbRKRE6kTuieqJrhH9IiYnFie2QGyXuJZ4kfgBCQWJFok9knaSfZLXJP9JTZM6Ic0ibSTdIb1E+peMDxDuk3WQXSJ7Ra5OboHcOvks+Qny5+Q/KegplCjMU/ilmKO4RUlA6Zqyk3KO8hEVE5UOlW+qKarn1NTUOtQ2qf1Td8EBg9QT1PPU29TnqR9Sf6bBoeGkUaOxTeODxgdNEU0rIPymFaeVBQDd1FqqAAAAAQAAAKEARAAFAAAAAAACAAEAAgAWAAABAAFRAAAAAHjadVLLSsNQED1Jq9IaRYuULoMLV22aVhGJIBVfWIoLLRbETfqyxT4kjYh7P8OvcVV/QvwUT26mNSlKuJMzcydnzswEQAZfSEBLpgAc8YRYg0EvxDrSqApOwEZdcBI5vAleQh7vgpcZnwpeQQXfglMwNFPwKra0vGADO1pF8Bruta7gddS1D8EbMPSs4E2k9W3BGeT0Gc8UWf1U8Cds/Q7nGGMEHybacPl2iVqMPeEVHvp4QE/dXjA2pjdAh16ZPZZorxlr8vg8tXn2LNdhZjTDjOQ4wmLj4N+cW9byMKEfaDRZ0eKxVe092sO5kt0YRyHCEefuk81UPfpkdtlzB0O+PTwyNkZ3oVMr5sVvgikNccIqnuL1aV2lM6wZaPcZD7QHelqMjOh3WNXEM3Fb5QRaemqqx5y6y7zQi3+TZ2RxHmWqsFWXPr90UOTzoh6LPL9cFvM96i5SeZRzwkgNl+zhDFe4oS0I5997/W9PDXI1ObvZn1RSHA3ptMpeBypq0wb7drivfdoy8XyDP0JQfA542m3Ou0+TcRTG8e+hpTcol9JSoCqKIiqI71taCqJCtS3ekIsWARVoUmxrgDaFd2hiTEx0AXVkZ1Q3Edlw0cHEwcEBBv1XlNLfAAnP8slzknNyKGM//56R5Kisg5SJCRNmyrFgxYYdBxVU4qSKamqoxUUdbjzU46WBRprwcYzjnKCZk5yihdOcoZWztHGO81ygnQ4u0sklNHT8dBEgSDcheujlMn1c4SrX6GeAMNe5QYQoMQa5yS1uc4e7DHGPYUYYZYz7PCDOOA+ZYJIpHvGYJ0wzwywJMfOK16zxjlXeSzkrvOUvH/jBHD/5RYrfpMmQY5kCz3nBS7GIVWxiZ4c/7IpDKqRSnFIl1VIjteKSOnGLR+rFyyc2+MIW3/jMJt/5KA1s81UapYk34rOk5gu5tG41FjOapkVKhjVlxDmcNhZTibyxMJ8wlp3ZQy1+qBkHW3Hfv3dQqSv9yi5lQBlUditDyh5lrzJcUld3dd3xNJMy8nPJxFK6NPLHSgZj5qiRzxZLdO+P/+/adfZ42j3OKRLCQBAF0Bkm+0JWE0Ex6LkCksTEUKikiuIGWCwYcHABOEQHReE5BYcJHWjG9fst/n/w/gj8zGpwlk3H+aXtKks1M4jbGvIVHod2ApZaNwyELEGoBRiyvItipL4wEcaUYMnyyUy+ZWQbn9ab4CDsF8FFODeCh3CvBB/hnQgBwq8IISL4V40RofyBQ0TTUkwj7OhEtUMmyHSjGSOTuWY2rI32PdNJPiQZL3TSQq4+STRSagAAAAFR3VVMAAA=) format('woff');
+}
\ No newline at end of file
diff --git a/plugins/UiPluginManager/media/css/button.css b/plugins/UiPluginManager/media/css/button.css
new file mode 100644
index 00000000..9f46d478
--- /dev/null
+++ b/plugins/UiPluginManager/media/css/button.css
@@ -0,0 +1,12 @@
+/* Button */
+.button {
+ background-color: #FFDC00; color: black; padding: 10px 20px; display: inline-block; background-position: left center;
+ border-radius: 2px; border-bottom: 2px solid #E8BE29; transition: all 0.5s ease-out; text-decoration: none;
+}
+.button:hover { border-color: white; border-bottom: 2px solid #BD960C; transition: none ; background-color: #FDEB07 }
+.button:active { position: relative; top: 1px }
+.button.loading {
+ color: rgba(0,0,0,0); background: #999 url(../img/loading.gif) no-repeat center center;
+ transition: all 0.5s ease-out ; pointer-events: none; border-bottom: 2px solid #666
+}
+.button.disabled { color: #DDD; background-color: #999; pointer-events: none; border-bottom: 2px solid #666 }
\ No newline at end of file
diff --git a/plugins/UiPluginManager/media/css/fonts.css b/plugins/UiPluginManager/media/css/fonts.css
new file mode 100644
index 00000000..f5576c5a
--- /dev/null
+++ b/plugins/UiPluginManager/media/css/fonts.css
@@ -0,0 +1,30 @@
+/* Base64 encoder: http://www.motobit.com/util/base64-decoder-encoder.asp */
+/* Generated by Font Squirrel (http://www.fontsquirrel.com) on January 21, 2015 */
+
+
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 400;
+ src:
+ local('Roboto'),
+ url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');
+}
+
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: bold;
+ src:
+ local('Roboto Medium'),
+ url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'),
+}
+
+@font-face {
+ font-family: 'Roboto';
+ font-style: normal;
+ font-weight: 200;
+ src:
+ local('Roboto Light'),
+ url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');
+}
\ No newline at end of file
diff --git a/plugins/UiPluginManager/media/img/loading.gif b/plugins/UiPluginManager/media/img/loading.gif
new file mode 100644
index 0000000000000000000000000000000000000000..27d0aa8108b0800f9cddcf613f787347d9981e05
GIT binary patch
literal 723
zcmZ?wbhEHb6ky0iXa9k4z>53@HBR_Hzvhc6JPKHPSO+W(0~W{*!Vp
zN=+!e)X>LZSp~!n_rkGVK%h
z9k_L9<(o^(d!N7A`+9eTzQ!EZMr*-N2_|eB&45;SC+a-zP~lXP;z?eTv`FKm^!Y8l
zuZ^S*OlLmOv^VZNyYQx
zE1Q1d^QD!~t!MErXFkzlm$bqCmuUZ)iN%&IQkAQ(b??%e8>EQMBqK<8T-y}!%q4L0
z4v$MoL7}cEx5PfOihDclHe=f1_`ny+jJ+qGonTF#=e6?cS1GK1Glv+XQW)E^VpGzx
z%$u!=(=#3~+Lk*jmQUf$-=^(}f)AMWru(Y&&oE(%*JUs>JH24vgCGuUPSS^%^#tgi
z6`S6zDw0tR+QR$5bp7w`G6mDQzjYm%RoE)?D^8cegv~i}{SvGJM6wyypd
+ @plugins = plugins
+
+ savePluginStatus: (plugin, is_enabled) =>
+ Page.cmd "pluginConfigSet", [plugin.source, plugin.inner_path, "enabled", is_enabled], (res) =>
+ if res == "ok"
+ Page.updatePlugins()
+ else
+ Page.cmd "wrapperNotification", ["error", res.error]
+
+ Page.projector.scheduleRender()
+
+ handleCheckboxChange: (e) =>
+ node = e.currentTarget
+ plugin = node["data-plugin"]
+ node.classList.toggle("checked")
+ value = node.classList.contains("checked")
+
+ @savePluginStatus(plugin, value)
+
+ handleResetClick: (e) =>
+ node = e.currentTarget
+ plugin = node["data-plugin"]
+
+ @savePluginStatus(plugin, null)
+
+ handleUpdateClick: (e) =>
+ node = e.currentTarget
+ plugin = node["data-plugin"]
+ node.classList.add("loading")
+
+ Page.cmd "pluginUpdate", [plugin.source, plugin.inner_path], (res) =>
+ if res == "ok"
+ Page.cmd "wrapperNotification", ["done", "Plugin #{plugin.name} updated to latest version"]
+ Page.updatePlugins()
+ else
+ Page.cmd "wrapperNotification", ["error", res.error]
+ node.classList.remove("loading")
+
+ return false
+
+ handleDeleteClick: (e) =>
+ node = e.currentTarget
+ plugin = node["data-plugin"]
+ if plugin.loaded
+ Page.cmd "wrapperNotification", ["info", "You can only delete plugin that are not currently active"]
+ return false
+
+ node.classList.add("loading")
+
+ Page.cmd "wrapperConfirm", ["Delete #{plugin.name} plugin?", "Delete"], (res) =>
+ if not res
+ node.classList.remove("loading")
+ return false
+
+ Page.cmd "pluginRemove", [plugin.source, plugin.inner_path], (res) =>
+ if res == "ok"
+ Page.cmd "wrapperNotification", ["done", "Plugin #{plugin.name} deleted"]
+ Page.updatePlugins()
+ else
+ Page.cmd "wrapperNotification", ["error", res.error]
+ node.classList.remove("loading")
+
+ return false
+
+ render: ->
+ h("div.plugins", @plugins.map (plugin) =>
+ if not plugin.info
+ return
+ descr = plugin.info.description
+ plugin.info.default ?= "enabled"
+ if plugin.info.default
+ descr += " (default: #{plugin.info.default})"
+
+ tag_version = ""
+ tag_source = ""
+ tag_delete = ""
+ if plugin.source != "builtin"
+ tag_update = ""
+ if plugin.site_info?.rev
+ if plugin.site_info.rev > plugin.info.rev
+ tag_update = h("a.version-update.button",
+ {href: "#Update+plugin", onclick: @handleUpdateClick, "data-plugin": plugin},
+ "Update to rev#{plugin.site_info.rev}"
+ )
+
+ else
+ tag_update = h("span.version-missing", "(unable to get latest vesion: update site missing)")
+
+ tag_version = h("span.version",[
+ "rev#{plugin.info.rev} ",
+ tag_update,
+ ])
+
+ tag_source = h("div.source",[
+ "Source: ",
+ h("a", {"href": "/#{plugin.source}", "target": "_top"}, if plugin.site_title then plugin.site_title else plugin.source),
+ " /" + plugin.inner_path
+ ])
+
+ tag_delete = h("a.delete", {"href": "#Delete+plugin", onclick: @handleDeleteClick, "data-plugin": plugin}, "Delete plugin")
+
+
+ enabled_default = plugin.info.default == "enabled"
+ if plugin.enabled != plugin.loaded or plugin.updated
+ marker_title = "Change pending"
+ is_pending = true
+ else
+ marker_title = "Changed from default status (click to reset to #{plugin.info.default})"
+ is_pending = false
+
+ is_changed = plugin.enabled != enabled_default and plugin.owner == "builtin"
+
+ h("div.plugin", {key: plugin.name}, [
+ h("div.title", [
+ h("h3", [plugin.name, tag_version]),
+ h("div.description", [descr, tag_source, tag_delete]),
+ ])
+ h("div.value.value-right",
+ h("div.checkbox", {onclick: @handleCheckboxChange, "data-plugin": plugin, classes: {checked: plugin.enabled}}, h("div.checkbox-skin"))
+ h("a.marker", {
+ href: "#Reset", title: marker_title,
+ onclick: @handleResetClick, "data-plugin": plugin,
+ classes: {visible: is_pending or is_changed, pending: is_pending}
+ }, "\u2022")
+ )
+ ])
+ )
+
+
+window.PluginList = PluginList
\ No newline at end of file
diff --git a/plugins/UiPluginManager/media/js/UiPluginManager.coffee b/plugins/UiPluginManager/media/js/UiPluginManager.coffee
new file mode 100644
index 00000000..6a0adee5
--- /dev/null
+++ b/plugins/UiPluginManager/media/js/UiPluginManager.coffee
@@ -0,0 +1,71 @@
+window.h = maquette.h
+
+class UiPluginManager extends ZeroFrame
+ init: ->
+ @plugin_list_builtin = new PluginList()
+ @plugin_list_custom = new PluginList()
+ @plugins_changed = null
+ @need_restart = null
+ @
+
+ onOpenWebsocket: =>
+ @cmd("wrapperSetTitle", "Plugin manager - ZeroNet")
+ @cmd "serverInfo", {}, (server_info) =>
+ @server_info = server_info
+ @updatePlugins()
+
+ updatePlugins: (cb) =>
+ @cmd "pluginList", [], (res) =>
+ @plugins_changed = (item for item in res.plugins when item.enabled != item.loaded or item.updated)
+
+ plugins_builtin = (item for item in res.plugins when item.source == "builtin")
+ @plugin_list_builtin.plugins = plugins_builtin.sort (a, b) ->
+ return a.name.localeCompare(b.name)
+
+ plugins_custom = (item for item in res.plugins when item.source != "builtin")
+ @plugin_list_custom.plugins = plugins_custom.sort (a, b) ->
+ return a.name.localeCompare(b.name)
+
+ @projector.scheduleRender()
+ cb?()
+
+ createProjector: =>
+ @projector = maquette.createProjector()
+ @projector.replace($("#content"), @render)
+ @projector.replace($("#bottom-restart"), @renderBottomRestart)
+
+ render: =>
+ if not @plugin_list_builtin.plugins
+ return h("div.content")
+
+ h("div.content", [
+ h("div.section", [
+ if @plugin_list_custom.plugins?.length
+ [
+ h("h2", "Installed third-party plugins"),
+ @plugin_list_custom.render()
+ ]
+ h("h2", "Built-in plugins")
+ @plugin_list_builtin.render()
+ ])
+ ])
+
+ handleRestartClick: =>
+ @restart_loading = true
+ setTimeout ( =>
+ Page.cmd("serverShutdown", {restart: true})
+ ), 300
+ Page.projector.scheduleRender()
+ return false
+
+ renderBottomRestart: =>
+ h("div.bottom.bottom-restart", {classes: {visible: @plugins_changed?.length}}, h("div.bottom-content", [
+ h("div.title", "Some plugins status has been changed"),
+ h("a.button.button-submit.button-restart",
+ {href: "#Restart", classes: {loading: @restart_loading}, onclick: @handleRestartClick},
+ "Restart ZeroNet client"
+ )
+ ]))
+
+window.Page = new UiPluginManager()
+window.Page.createProjector()
diff --git a/plugins/UiPluginManager/media/js/all.js b/plugins/UiPluginManager/media/js/all.js
new file mode 100644
index 00000000..25632862
--- /dev/null
+++ b/plugins/UiPluginManager/media/js/all.js
@@ -0,0 +1,1606 @@
+
+/* ---- lib/Class.coffee ---- */
+
+
+(function() {
+ var Class,
+ slice = [].slice;
+
+ Class = (function() {
+ function Class() {}
+
+ Class.prototype.trace = true;
+
+ Class.prototype.log = function() {
+ var args;
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ if (!this.trace) {
+ return;
+ }
+ if (typeof console === 'undefined') {
+ return;
+ }
+ args.unshift("[" + this.constructor.name + "]");
+ console.log.apply(console, args);
+ return this;
+ };
+
+ Class.prototype.logStart = function() {
+ var args, name;
+ name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
+ if (!this.trace) {
+ return;
+ }
+ this.logtimers || (this.logtimers = {});
+ this.logtimers[name] = +(new Date);
+ if (args.length > 0) {
+ this.log.apply(this, ["" + name].concat(slice.call(args), ["(started)"]));
+ }
+ return this;
+ };
+
+ Class.prototype.logEnd = function() {
+ var args, ms, name;
+ name = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
+ ms = +(new Date) - this.logtimers[name];
+ this.log.apply(this, ["" + name].concat(slice.call(args), ["(Done in " + ms + "ms)"]));
+ return this;
+ };
+
+ return Class;
+
+ })();
+
+ window.Class = Class;
+
+}).call(this);
+
+
+/* ---- lib/Promise.coffee ---- */
+
+
+(function() {
+ var Promise,
+ slice = [].slice;
+
+ Promise = (function() {
+ Promise.when = function() {
+ var args, fn, i, len, num_uncompleted, promise, task, task_id, tasks;
+ tasks = 1 <= arguments.length ? slice.call(arguments, 0) : [];
+ num_uncompleted = tasks.length;
+ args = new Array(num_uncompleted);
+ promise = new Promise();
+ fn = function(task_id) {
+ return task.then(function() {
+ args[task_id] = Array.prototype.slice.call(arguments);
+ num_uncompleted--;
+ if (num_uncompleted === 0) {
+ return promise.complete.apply(promise, args);
+ }
+ });
+ };
+ for (task_id = i = 0, len = tasks.length; i < len; task_id = ++i) {
+ task = tasks[task_id];
+ fn(task_id);
+ }
+ return promise;
+ };
+
+ function Promise() {
+ this.resolved = false;
+ this.end_promise = null;
+ this.result = null;
+ this.callbacks = [];
+ }
+
+ Promise.prototype.resolve = function() {
+ var back, callback, i, len, ref;
+ if (this.resolved) {
+ return false;
+ }
+ this.resolved = true;
+ this.data = arguments;
+ if (!arguments.length) {
+ this.data = [true];
+ }
+ this.result = this.data[0];
+ ref = this.callbacks;
+ for (i = 0, len = ref.length; i < len; i++) {
+ callback = ref[i];
+ back = callback.apply(callback, this.data);
+ }
+ if (this.end_promise) {
+ return this.end_promise.resolve(back);
+ }
+ };
+
+ Promise.prototype.fail = function() {
+ return this.resolve(false);
+ };
+
+ Promise.prototype.then = function(callback) {
+ if (this.resolved === true) {
+ callback.apply(callback, this.data);
+ return;
+ }
+ this.callbacks.push(callback);
+ return this.end_promise = new Promise();
+ };
+
+ return Promise;
+
+ })();
+
+ window.Promise = Promise;
+
+
+ /*
+ s = Date.now()
+ log = (text) ->
+ console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
+
+ log "Started"
+
+ cmd = (query) ->
+ p = new Promise()
+ setTimeout ( ->
+ p.resolve query+" Result"
+ ), 100
+ return p
+
+ back = cmd("SELECT * FROM message").then (res) ->
+ log res
+ return "Return from query"
+ .then (res) ->
+ log "Back then", res
+
+ log "Query started", back
+ */
+
+}).call(this);
+
+
+/* ---- lib/Prototypes.coffee ---- */
+
+
+(function() {
+ String.prototype.startsWith = function(s) {
+ return this.slice(0, s.length) === s;
+ };
+
+ String.prototype.endsWith = function(s) {
+ return s === '' || this.slice(-s.length) === s;
+ };
+
+ String.prototype.repeat = function(count) {
+ return new Array(count + 1).join(this);
+ };
+
+ window.isEmpty = function(obj) {
+ var key;
+ for (key in obj) {
+ return false;
+ }
+ return true;
+ };
+
+}).call(this);
+
+
+/* ---- lib/maquette.js ---- */
+
+
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['exports'], factory);
+ } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
+ // CommonJS
+ factory(exports);
+ } else {
+ // Browser globals
+ factory(root.maquette = {});
+ }
+}(this, function (exports) {
+ 'use strict';
+ ;
+ ;
+ ;
+ ;
+ var NAMESPACE_W3 = 'http://www.w3.org/';
+ var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';
+ var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';
+ // Utilities
+ var emptyArray = [];
+ var extend = function (base, overrides) {
+ var result = {};
+ Object.keys(base).forEach(function (key) {
+ result[key] = base[key];
+ });
+ if (overrides) {
+ Object.keys(overrides).forEach(function (key) {
+ result[key] = overrides[key];
+ });
+ }
+ return result;
+ };
+ // Hyperscript helper functions
+ var same = function (vnode1, vnode2) {
+ if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {
+ return false;
+ }
+ if (vnode1.properties && vnode2.properties) {
+ if (vnode1.properties.key !== vnode2.properties.key) {
+ return false;
+ }
+ return vnode1.properties.bind === vnode2.properties.bind;
+ }
+ return !vnode1.properties && !vnode2.properties;
+ };
+ var toTextVNode = function (data) {
+ return {
+ vnodeSelector: '',
+ properties: undefined,
+ children: undefined,
+ text: data.toString(),
+ domNode: null
+ };
+ };
+ var appendChildren = function (parentSelector, insertions, main) {
+ for (var i = 0; i < insertions.length; i++) {
+ var item = insertions[i];
+ if (Array.isArray(item)) {
+ appendChildren(parentSelector, item, main);
+ } else {
+ if (item !== null && item !== undefined) {
+ if (!item.hasOwnProperty('vnodeSelector')) {
+ item = toTextVNode(item);
+ }
+ main.push(item);
+ }
+ }
+ }
+ };
+ // Render helper functions
+ var missingTransition = function () {
+ throw new Error('Provide a transitions object to the projectionOptions to do animations');
+ };
+ var DEFAULT_PROJECTION_OPTIONS = {
+ namespace: undefined,
+ eventHandlerInterceptor: undefined,
+ styleApplyer: function (domNode, styleName, value) {
+ // Provides a hook to add vendor prefixes for browsers that still need it.
+ domNode.style[styleName] = value;
+ },
+ transitions: {
+ enter: missingTransition,
+ exit: missingTransition
+ }
+ };
+ var applyDefaultProjectionOptions = function (projectorOptions) {
+ return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);
+ };
+ var checkStyleValue = function (styleValue) {
+ if (typeof styleValue !== 'string') {
+ throw new Error('Style values must be strings');
+ }
+ };
+ var setProperties = function (domNode, properties, projectionOptions) {
+ if (!properties) {
+ return;
+ }
+ var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;
+ var propNames = Object.keys(properties);
+ var propCount = propNames.length;
+ for (var i = 0; i < propCount; i++) {
+ var propName = propNames[i];
+ /* tslint:disable:no-var-keyword: edge case */
+ var propValue = properties[propName];
+ /* tslint:enable:no-var-keyword */
+ if (propName === 'className') {
+ throw new Error('Property "className" is not supported, use "class".');
+ } else if (propName === 'class') {
+ if (domNode.className) {
+ // May happen if classes is specified before class
+ domNode.className += ' ' + propValue;
+ } else {
+ domNode.className = propValue;
+ }
+ } else if (propName === 'classes') {
+ // object with string keys and boolean values
+ var classNames = Object.keys(propValue);
+ var classNameCount = classNames.length;
+ for (var j = 0; j < classNameCount; j++) {
+ var className = classNames[j];
+ if (propValue[className]) {
+ domNode.classList.add(className);
+ }
+ }
+ } else if (propName === 'styles') {
+ // object with string keys and string (!) values
+ var styleNames = Object.keys(propValue);
+ var styleCount = styleNames.length;
+ for (var j = 0; j < styleCount; j++) {
+ var styleName = styleNames[j];
+ var styleValue = propValue[styleName];
+ if (styleValue) {
+ checkStyleValue(styleValue);
+ projectionOptions.styleApplyer(domNode, styleName, styleValue);
+ }
+ }
+ } else if (propName === 'key') {
+ continue;
+ } else if (propValue === null || propValue === undefined) {
+ continue;
+ } else {
+ var type = typeof propValue;
+ if (type === 'function') {
+ if (propName.lastIndexOf('on', 0) === 0) {
+ if (eventHandlerInterceptor) {
+ propValue = eventHandlerInterceptor(propName, propValue, domNode, properties); // intercept eventhandlers
+ }
+ if (propName === 'oninput') {
+ (function () {
+ // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput
+ var oldPropValue = propValue;
+ propValue = function (evt) {
+ evt.target['oninput-value'] = evt.target.value;
+ // may be HTMLTextAreaElement as well
+ oldPropValue.apply(this, [evt]);
+ };
+ }());
+ }
+ domNode[propName] = propValue;
+ }
+ } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {
+ if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
+ domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
+ } else {
+ domNode.setAttribute(propName, propValue);
+ }
+ } else {
+ domNode[propName] = propValue;
+ }
+ }
+ }
+ };
+ var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {
+ if (!properties) {
+ return;
+ }
+ var propertiesUpdated = false;
+ var propNames = Object.keys(properties);
+ var propCount = propNames.length;
+ for (var i = 0; i < propCount; i++) {
+ var propName = propNames[i];
+ // assuming that properties will be nullified instead of missing is by design
+ var propValue = properties[propName];
+ var previousValue = previousProperties[propName];
+ if (propName === 'class') {
+ if (previousValue !== propValue) {
+ throw new Error('"class" property may not be updated. Use the "classes" property for conditional css classes.');
+ }
+ } else if (propName === 'classes') {
+ var classList = domNode.classList;
+ var classNames = Object.keys(propValue);
+ var classNameCount = classNames.length;
+ for (var j = 0; j < classNameCount; j++) {
+ var className = classNames[j];
+ var on = !!propValue[className];
+ var previousOn = !!previousValue[className];
+ if (on === previousOn) {
+ continue;
+ }
+ propertiesUpdated = true;
+ if (on) {
+ classList.add(className);
+ } else {
+ classList.remove(className);
+ }
+ }
+ } else if (propName === 'styles') {
+ var styleNames = Object.keys(propValue);
+ var styleCount = styleNames.length;
+ for (var j = 0; j < styleCount; j++) {
+ var styleName = styleNames[j];
+ var newStyleValue = propValue[styleName];
+ var oldStyleValue = previousValue[styleName];
+ if (newStyleValue === oldStyleValue) {
+ continue;
+ }
+ propertiesUpdated = true;
+ if (newStyleValue) {
+ checkStyleValue(newStyleValue);
+ projectionOptions.styleApplyer(domNode, styleName, newStyleValue);
+ } else {
+ projectionOptions.styleApplyer(domNode, styleName, '');
+ }
+ }
+ } else {
+ if (!propValue && typeof previousValue === 'string') {
+ propValue = '';
+ }
+ if (propName === 'value') {
+ if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {
+ domNode[propName] = propValue;
+ // Reset the value, even if the virtual DOM did not change
+ domNode['oninput-value'] = undefined;
+ }
+ // else do not update the domNode, otherwise the cursor position would be changed
+ if (propValue !== previousValue) {
+ propertiesUpdated = true;
+ }
+ } else if (propValue !== previousValue) {
+ var type = typeof propValue;
+ if (type === 'function') {
+ throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');
+ }
+ if (type === 'string' && propName !== 'innerHTML') {
+ if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
+ domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
+ } else {
+ domNode.setAttribute(propName, propValue);
+ }
+ } else {
+ if (domNode[propName] !== propValue) {
+ domNode[propName] = propValue;
+ }
+ }
+ propertiesUpdated = true;
+ }
+ }
+ }
+ return propertiesUpdated;
+ };
+ var findIndexOfChild = function (children, sameAs, start) {
+ if (sameAs.vnodeSelector !== '') {
+ // Never scan for text-nodes
+ for (var i = start; i < children.length; i++) {
+ if (same(children[i], sameAs)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ };
+ var nodeAdded = function (vNode, transitions) {
+ if (vNode.properties) {
+ var enterAnimation = vNode.properties.enterAnimation;
+ if (enterAnimation) {
+ if (typeof enterAnimation === 'function') {
+ enterAnimation(vNode.domNode, vNode.properties);
+ } else {
+ transitions.enter(vNode.domNode, vNode.properties, enterAnimation);
+ }
+ }
+ }
+ };
+ var nodeToRemove = function (vNode, transitions) {
+ var domNode = vNode.domNode;
+ if (vNode.properties) {
+ var exitAnimation = vNode.properties.exitAnimation;
+ if (exitAnimation) {
+ domNode.style.pointerEvents = 'none';
+ var removeDomNode = function () {
+ if (domNode.parentNode) {
+ domNode.parentNode.removeChild(domNode);
+ }
+ };
+ if (typeof exitAnimation === 'function') {
+ exitAnimation(domNode, removeDomNode, vNode.properties);
+ return;
+ } else {
+ transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);
+ return;
+ }
+ }
+ }
+ if (domNode.parentNode) {
+ domNode.parentNode.removeChild(domNode);
+ }
+ };
+ var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {
+ var childNode = childNodes[indexToCheck];
+ if (childNode.vnodeSelector === '') {
+ return; // Text nodes need not be distinguishable
+ }
+ var properties = childNode.properties;
+ var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;
+ if (!key) {
+ for (var i = 0; i < childNodes.length; i++) {
+ if (i !== indexToCheck) {
+ var node = childNodes[i];
+ if (same(node, childNode)) {
+ if (operation === 'added') {
+ throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');
+ } else {
+ throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');
+ }
+ }
+ }
+ }
+ }
+ };
+ var createDom;
+ var updateDom;
+ var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {
+ if (oldChildren === newChildren) {
+ return false;
+ }
+ oldChildren = oldChildren || emptyArray;
+ newChildren = newChildren || emptyArray;
+ var oldChildrenLength = oldChildren.length;
+ var newChildrenLength = newChildren.length;
+ var transitions = projectionOptions.transitions;
+ var oldIndex = 0;
+ var newIndex = 0;
+ var i;
+ var textUpdated = false;
+ while (newIndex < newChildrenLength) {
+ var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;
+ var newChild = newChildren[newIndex];
+ if (oldChild !== undefined && same(oldChild, newChild)) {
+ textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;
+ oldIndex++;
+ } else {
+ var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);
+ if (findOldIndex >= 0) {
+ // Remove preceding missing children
+ for (i = oldIndex; i < findOldIndex; i++) {
+ nodeToRemove(oldChildren[i], transitions);
+ checkDistinguishable(oldChildren, i, vnode, 'removed');
+ }
+ textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;
+ oldIndex = findOldIndex + 1;
+ } else {
+ // New child
+ createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);
+ nodeAdded(newChild, transitions);
+ checkDistinguishable(newChildren, newIndex, vnode, 'added');
+ }
+ }
+ newIndex++;
+ }
+ if (oldChildrenLength > oldIndex) {
+ // Remove child fragments
+ for (i = oldIndex; i < oldChildrenLength; i++) {
+ nodeToRemove(oldChildren[i], transitions);
+ checkDistinguishable(oldChildren, i, vnode, 'removed');
+ }
+ }
+ return textUpdated;
+ };
+ var addChildren = function (domNode, children, projectionOptions) {
+ if (!children) {
+ return;
+ }
+ for (var i = 0; i < children.length; i++) {
+ createDom(children[i], domNode, undefined, projectionOptions);
+ }
+ };
+ var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {
+ addChildren(domNode, vnode.children, projectionOptions);
+ // children before properties, needed for value property of .
+ if (vnode.text) {
+ domNode.textContent = vnode.text;
+ }
+ setProperties(domNode, vnode.properties, projectionOptions);
+ if (vnode.properties && vnode.properties.afterCreate) {
+ vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);
+ }
+ };
+ createDom = function (vnode, parentNode, insertBefore, projectionOptions) {
+ var domNode, i, c, start = 0, type, found;
+ var vnodeSelector = vnode.vnodeSelector;
+ if (vnodeSelector === '') {
+ domNode = vnode.domNode = document.createTextNode(vnode.text);
+ if (insertBefore !== undefined) {
+ parentNode.insertBefore(domNode, insertBefore);
+ } else {
+ parentNode.appendChild(domNode);
+ }
+ } else {
+ for (i = 0; i <= vnodeSelector.length; ++i) {
+ c = vnodeSelector.charAt(i);
+ if (i === vnodeSelector.length || c === '.' || c === '#') {
+ type = vnodeSelector.charAt(start - 1);
+ found = vnodeSelector.slice(start, i);
+ if (type === '.') {
+ domNode.classList.add(found);
+ } else if (type === '#') {
+ domNode.id = found;
+ } else {
+ if (found === 'svg') {
+ projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });
+ }
+ if (projectionOptions.namespace !== undefined) {
+ domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);
+ } else {
+ domNode = vnode.domNode = document.createElement(found);
+ }
+ if (insertBefore !== undefined) {
+ parentNode.insertBefore(domNode, insertBefore);
+ } else {
+ parentNode.appendChild(domNode);
+ }
+ }
+ start = i + 1;
+ }
+ }
+ initPropertiesAndChildren(domNode, vnode, projectionOptions);
+ }
+ };
+ updateDom = function (previous, vnode, projectionOptions) {
+ var domNode = previous.domNode;
+ var textUpdated = false;
+ if (previous === vnode) {
+ return false; // By contract, VNode objects may not be modified anymore after passing them to maquette
+ }
+ var updated = false;
+ if (vnode.vnodeSelector === '') {
+ if (vnode.text !== previous.text) {
+ var newVNode = document.createTextNode(vnode.text);
+ domNode.parentNode.replaceChild(newVNode, domNode);
+ vnode.domNode = newVNode;
+ textUpdated = true;
+ return textUpdated;
+ }
+ } else {
+ if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {
+ projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });
+ }
+ if (previous.text !== vnode.text) {
+ updated = true;
+ if (vnode.text === undefined) {
+ domNode.removeChild(domNode.firstChild); // the only textnode presumably
+ } else {
+ domNode.textContent = vnode.text;
+ }
+ }
+ updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;
+ updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;
+ if (vnode.properties && vnode.properties.afterUpdate) {
+ vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);
+ }
+ }
+ if (updated && vnode.properties && vnode.properties.updateAnimation) {
+ vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);
+ }
+ vnode.domNode = previous.domNode;
+ return textUpdated;
+ };
+ var createProjection = function (vnode, projectionOptions) {
+ return {
+ update: function (updatedVnode) {
+ if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {
+ throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');
+ }
+ updateDom(vnode, updatedVnode, projectionOptions);
+ vnode = updatedVnode;
+ },
+ domNode: vnode.domNode
+ };
+ };
+ ;
+ // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.
+ exports.h = function (selector) {
+ var properties = arguments[1];
+ if (typeof selector !== 'string') {
+ throw new Error();
+ }
+ var childIndex = 1;
+ if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {
+ childIndex = 2;
+ } else {
+ // Optional properties argument was omitted
+ properties = undefined;
+ }
+ var text = undefined;
+ var children = undefined;
+ var argsLength = arguments.length;
+ // Recognize a common special case where there is only a single text node
+ if (argsLength === childIndex + 1) {
+ var onlyChild = arguments[childIndex];
+ if (typeof onlyChild === 'string') {
+ text = onlyChild;
+ } else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') {
+ text = onlyChild[0];
+ }
+ }
+ if (text === undefined) {
+ children = [];
+ for (; childIndex < arguments.length; childIndex++) {
+ var child = arguments[childIndex];
+ if (child === null || child === undefined) {
+ continue;
+ } else if (Array.isArray(child)) {
+ appendChildren(selector, child, children);
+ } else if (child.hasOwnProperty('vnodeSelector')) {
+ children.push(child);
+ } else {
+ children.push(toTextVNode(child));
+ }
+ }
+ }
+ return {
+ vnodeSelector: selector,
+ properties: properties,
+ children: children,
+ text: text === '' ? undefined : text,
+ domNode: null
+ };
+ };
+ /**
+ * Contains simple low-level utility functions to manipulate the real DOM.
+ */
+ exports.dom = {
+ /**
+ * Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in
+ * its [[Projection.domNode|domNode]] property.
+ * This is a low-level method. Users wil typically use a [[Projector]] instead.
+ * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]
+ * objects may only be rendered once.
+ * @param projectionOptions - Options to be used to create and update the projection.
+ * @returns The [[Projection]] which also contains the DOM Node that was created.
+ */
+ create: function (vnode, projectionOptions) {
+ projectionOptions = applyDefaultProjectionOptions(projectionOptions);
+ createDom(vnode, document.createElement('div'), undefined, projectionOptions);
+ return createProjection(vnode, projectionOptions);
+ },
+ /**
+ * Appends a new childnode to the DOM which is generated from a [[VNode]].
+ * This is a low-level method. Users wil typically use a [[Projector]] instead.
+ * @param parentNode - The parent node for the new childNode.
+ * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]
+ * objects may only be rendered once.
+ * @param projectionOptions - Options to be used to create and update the [[Projection]].
+ * @returns The [[Projection]] that was created.
+ */
+ append: function (parentNode, vnode, projectionOptions) {
+ projectionOptions = applyDefaultProjectionOptions(projectionOptions);
+ createDom(vnode, parentNode, undefined, projectionOptions);
+ return createProjection(vnode, projectionOptions);
+ },
+ /**
+ * Inserts a new DOM node which is generated from a [[VNode]].
+ * This is a low-level method. Users wil typically use a [[Projector]] instead.
+ * @param beforeNode - The node that the DOM Node is inserted before.
+ * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function.
+ * NOTE: [[VNode]] objects may only be rendered once.
+ * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].
+ * @returns The [[Projection]] that was created.
+ */
+ insertBefore: function (beforeNode, vnode, projectionOptions) {
+ projectionOptions = applyDefaultProjectionOptions(projectionOptions);
+ createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions);
+ return createProjection(vnode, projectionOptions);
+ },
+ /**
+ * Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node.
+ * This means that the virtual DOM and the real DOM will have one overlapping element.
+ * Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided.
+ * This is a low-level method. Users wil typically use a [[Projector]] instead.
+ * @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved.
+ * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects
+ * may only be rendered once.
+ * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].
+ * @returns The [[Projection]] that was created.
+ */
+ merge: function (element, vnode, projectionOptions) {
+ projectionOptions = applyDefaultProjectionOptions(projectionOptions);
+ vnode.domNode = element;
+ initPropertiesAndChildren(element, vnode, projectionOptions);
+ return createProjection(vnode, projectionOptions);
+ }
+ };
+ /**
+ * Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees.
+ * In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem.
+ * For more information, see [[CalculationCache]].
+ *
+ * @param The type of the value that is cached.
+ */
+ exports.createCache = function () {
+ var cachedInputs = undefined;
+ var cachedOutcome = undefined;
+ var result = {
+ invalidate: function () {
+ cachedOutcome = undefined;
+ cachedInputs = undefined;
+ },
+ result: function (inputs, calculation) {
+ if (cachedInputs) {
+ for (var i = 0; i < inputs.length; i++) {
+ if (cachedInputs[i] !== inputs[i]) {
+ cachedOutcome = undefined;
+ }
+ }
+ }
+ if (!cachedOutcome) {
+ cachedOutcome = calculation();
+ cachedInputs = inputs;
+ }
+ return cachedOutcome;
+ }
+ };
+ return result;
+ };
+ /**
+ * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.
+ * See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}.
+ *
+ * @param The type of source items. A database-record for instance.
+ * @param The type of target items. A [[Component]] for instance.
+ * @param getSourceKey `function(source)` that must return a key to identify each source object. The result must either be a string or a number.
+ * @param createResult `function(source, index)` that must create a new result object from a given source. This function is identical
+ * to the `callback` argument in `Array.map(callback)`.
+ * @param updateResult `function(source, target, index)` that updates a result to an updated source.
+ */
+ exports.createMapping = function (getSourceKey, createResult, updateResult) {
+ var keys = [];
+ var results = [];
+ return {
+ results: results,
+ map: function (newSources) {
+ var newKeys = newSources.map(getSourceKey);
+ var oldTargets = results.slice();
+ var oldIndex = 0;
+ for (var i = 0; i < newSources.length; i++) {
+ var source = newSources[i];
+ var sourceKey = newKeys[i];
+ if (sourceKey === keys[oldIndex]) {
+ results[i] = oldTargets[oldIndex];
+ updateResult(source, oldTargets[oldIndex], i);
+ oldIndex++;
+ } else {
+ var found = false;
+ for (var j = 1; j < keys.length; j++) {
+ var searchIndex = (oldIndex + j) % keys.length;
+ if (keys[searchIndex] === sourceKey) {
+ results[i] = oldTargets[searchIndex];
+ updateResult(newSources[i], oldTargets[searchIndex], i);
+ oldIndex = searchIndex + 1;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ results[i] = createResult(source, i);
+ }
+ }
+ }
+ results.length = newSources.length;
+ keys = newKeys;
+ }
+ };
+ };
+ /**
+ * Creates a [[Projector]] instance using the provided projectionOptions.
+ *
+ * For more information, see [[Projector]].
+ *
+ * @param projectionOptions Options that influence how the DOM is rendered and updated.
+ */
+ exports.createProjector = function (projectorOptions) {
+ var projector;
+ var projectionOptions = applyDefaultProjectionOptions(projectorOptions);
+ projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) {
+ return function () {
+ // intercept function calls (event handlers) to do a render afterwards.
+ projector.scheduleRender();
+ return eventHandler.apply(properties.bind || this, arguments);
+ };
+ };
+ var renderCompleted = true;
+ var scheduled;
+ var stopped = false;
+ var projections = [];
+ var renderFunctions = [];
+ // matches the projections array
+ var doRender = function () {
+ scheduled = undefined;
+ if (!renderCompleted) {
+ return; // The last render threw an error, it should be logged in the browser console.
+ }
+ renderCompleted = false;
+ for (var i = 0; i < projections.length; i++) {
+ var updatedVnode = renderFunctions[i]();
+ projections[i].update(updatedVnode);
+ }
+ renderCompleted = true;
+ };
+ projector = {
+ scheduleRender: function () {
+ if (!scheduled && !stopped) {
+ scheduled = requestAnimationFrame(doRender);
+ }
+ },
+ stop: function () {
+ if (scheduled) {
+ cancelAnimationFrame(scheduled);
+ scheduled = undefined;
+ }
+ stopped = true;
+ },
+ resume: function () {
+ stopped = false;
+ renderCompleted = true;
+ projector.scheduleRender();
+ },
+ append: function (parentNode, renderMaquetteFunction) {
+ projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions));
+ renderFunctions.push(renderMaquetteFunction);
+ },
+ insertBefore: function (beforeNode, renderMaquetteFunction) {
+ projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions));
+ renderFunctions.push(renderMaquetteFunction);
+ },
+ merge: function (domNode, renderMaquetteFunction) {
+ projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions));
+ renderFunctions.push(renderMaquetteFunction);
+ },
+ replace: function (domNode, renderMaquetteFunction) {
+ var vnode = renderMaquetteFunction();
+ createDom(vnode, domNode.parentNode, domNode, projectionOptions);
+ domNode.parentNode.removeChild(domNode);
+ projections.push(createProjection(vnode, projectionOptions));
+ renderFunctions.push(renderMaquetteFunction);
+ },
+ detach: function (renderMaquetteFunction) {
+ for (var i = 0; i < renderFunctions.length; i++) {
+ if (renderFunctions[i] === renderMaquetteFunction) {
+ renderFunctions.splice(i, 1);
+ return projections.splice(i, 1)[0];
+ }
+ }
+ throw new Error('renderMaquetteFunction was not found');
+ }
+ };
+ return projector;
+ };
+}));
+
+
+/* ---- utils/Animation.coffee ---- */
+
+
+(function() {
+ var Animation;
+
+ Animation = (function() {
+ function Animation() {}
+
+ Animation.prototype.slideDown = function(elem, props) {
+ var cstyle, h, margin_bottom, margin_top, padding_bottom, padding_top, transition;
+ if (elem.offsetTop > 2000) {
+ return;
+ }
+ h = elem.offsetHeight;
+ cstyle = window.getComputedStyle(elem);
+ margin_top = cstyle.marginTop;
+ margin_bottom = cstyle.marginBottom;
+ padding_top = cstyle.paddingTop;
+ padding_bottom = cstyle.paddingBottom;
+ transition = cstyle.transition;
+ elem.style.boxSizing = "border-box";
+ elem.style.overflow = "hidden";
+ elem.style.transform = "scale(0.6)";
+ elem.style.opacity = "0";
+ elem.style.height = "0px";
+ elem.style.marginTop = "0px";
+ elem.style.marginBottom = "0px";
+ elem.style.paddingTop = "0px";
+ elem.style.paddingBottom = "0px";
+ elem.style.transition = "none";
+ setTimeout((function() {
+ elem.className += " animate-inout";
+ elem.style.height = h + "px";
+ elem.style.transform = "scale(1)";
+ elem.style.opacity = "1";
+ elem.style.marginTop = margin_top;
+ elem.style.marginBottom = margin_bottom;
+ elem.style.paddingTop = padding_top;
+ return elem.style.paddingBottom = padding_bottom;
+ }), 1);
+ return elem.addEventListener("transitionend", function() {
+ elem.classList.remove("animate-inout");
+ elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null;
+ elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null;
+ elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null;
+ return elem.removeEventListener("transitionend", arguments.callee, false);
+ });
+ };
+
+ Animation.prototype.slideUp = function(elem, remove_func, props) {
+ if (elem.offsetTop > 1000) {
+ return remove_func();
+ }
+ elem.className += " animate-back";
+ elem.style.boxSizing = "border-box";
+ elem.style.height = elem.offsetHeight + "px";
+ elem.style.overflow = "hidden";
+ elem.style.transform = "scale(1)";
+ elem.style.opacity = "1";
+ elem.style.pointerEvents = "none";
+ setTimeout((function() {
+ elem.style.height = "0px";
+ elem.style.marginTop = "0px";
+ elem.style.marginBottom = "0px";
+ elem.style.paddingTop = "0px";
+ elem.style.paddingBottom = "0px";
+ elem.style.transform = "scale(0.8)";
+ elem.style.borderTopWidth = "0px";
+ elem.style.borderBottomWidth = "0px";
+ return elem.style.opacity = "0";
+ }), 1);
+ return elem.addEventListener("transitionend", function(e) {
+ if (e.propertyName === "opacity" || e.elapsedTime >= 0.6) {
+ elem.removeEventListener("transitionend", arguments.callee, false);
+ return remove_func();
+ }
+ });
+ };
+
+ Animation.prototype.slideUpInout = function(elem, remove_func, props) {
+ elem.className += " animate-inout";
+ elem.style.boxSizing = "border-box";
+ elem.style.height = elem.offsetHeight + "px";
+ elem.style.overflow = "hidden";
+ elem.style.transform = "scale(1)";
+ elem.style.opacity = "1";
+ elem.style.pointerEvents = "none";
+ setTimeout((function() {
+ elem.style.height = "0px";
+ elem.style.marginTop = "0px";
+ elem.style.marginBottom = "0px";
+ elem.style.paddingTop = "0px";
+ elem.style.paddingBottom = "0px";
+ elem.style.transform = "scale(0.8)";
+ elem.style.borderTopWidth = "0px";
+ elem.style.borderBottomWidth = "0px";
+ return elem.style.opacity = "0";
+ }), 1);
+ return elem.addEventListener("transitionend", function(e) {
+ if (e.propertyName === "opacity" || e.elapsedTime >= 0.6) {
+ elem.removeEventListener("transitionend", arguments.callee, false);
+ return remove_func();
+ }
+ });
+ };
+
+ Animation.prototype.showRight = function(elem, props) {
+ elem.className += " animate";
+ elem.style.opacity = 0;
+ elem.style.transform = "TranslateX(-20px) Scale(1.01)";
+ setTimeout((function() {
+ elem.style.opacity = 1;
+ return elem.style.transform = "TranslateX(0px) Scale(1)";
+ }), 1);
+ return elem.addEventListener("transitionend", function() {
+ elem.classList.remove("animate");
+ return elem.style.transform = elem.style.opacity = null;
+ });
+ };
+
+ Animation.prototype.show = function(elem, props) {
+ var delay, ref;
+ delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1;
+ elem.style.opacity = 0;
+ setTimeout((function() {
+ return elem.className += " animate";
+ }), 1);
+ setTimeout((function() {
+ return elem.style.opacity = 1;
+ }), delay);
+ return elem.addEventListener("transitionend", function() {
+ elem.classList.remove("animate");
+ elem.style.opacity = null;
+ return elem.removeEventListener("transitionend", arguments.callee, false);
+ });
+ };
+
+ Animation.prototype.hide = function(elem, remove_func, props) {
+ var delay, ref;
+ delay = ((ref = arguments[arguments.length - 2]) != null ? ref.delay : void 0) * 1000 || 1;
+ elem.className += " animate";
+ setTimeout((function() {
+ return elem.style.opacity = 0;
+ }), delay);
+ return elem.addEventListener("transitionend", function(e) {
+ if (e.propertyName === "opacity") {
+ return remove_func();
+ }
+ });
+ };
+
+ Animation.prototype.addVisibleClass = function(elem, props) {
+ return setTimeout(function() {
+ return elem.classList.add("visible");
+ });
+ };
+
+ return Animation;
+
+ })();
+
+ window.Animation = new Animation();
+
+}).call(this);
+
+
+/* ---- utils/Dollar.coffee ---- */
+
+
+(function() {
+ window.$ = function(selector) {
+ if (selector.startsWith("#")) {
+ return document.getElementById(selector.replace("#", ""));
+ }
+ };
+
+}).call(this);
+
+
+/* ---- utils/ZeroFrame.coffee ---- */
+
+
+(function() {
+ var ZeroFrame,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+ ZeroFrame = (function(superClass) {
+ extend(ZeroFrame, superClass);
+
+ function ZeroFrame(url) {
+ this.onCloseWebsocket = bind(this.onCloseWebsocket, this);
+ this.onOpenWebsocket = bind(this.onOpenWebsocket, this);
+ this.onRequest = bind(this.onRequest, this);
+ this.onMessage = bind(this.onMessage, this);
+ this.url = url;
+ this.waiting_cb = {};
+ this.wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1");
+ this.connect();
+ this.next_message_id = 1;
+ this.history_state = {};
+ this.init();
+ }
+
+ ZeroFrame.prototype.init = function() {
+ return this;
+ };
+
+ ZeroFrame.prototype.connect = function() {
+ this.target = window.parent;
+ window.addEventListener("message", this.onMessage, false);
+ this.cmd("innerReady");
+ window.addEventListener("beforeunload", (function(_this) {
+ return function(e) {
+ _this.log("save scrollTop", window.pageYOffset);
+ _this.history_state["scrollTop"] = window.pageYOffset;
+ return _this.cmd("wrapperReplaceState", [_this.history_state, null]);
+ };
+ })(this));
+ return this.cmd("wrapperGetState", [], (function(_this) {
+ return function(state) {
+ if (state != null) {
+ _this.history_state = state;
+ }
+ _this.log("restore scrollTop", state, window.pageYOffset);
+ if (window.pageYOffset === 0 && state) {
+ return window.scroll(window.pageXOffset, state.scrollTop);
+ }
+ };
+ })(this));
+ };
+
+ ZeroFrame.prototype.onMessage = function(e) {
+ var cmd, message;
+ message = e.data;
+ cmd = message.cmd;
+ if (cmd === "response") {
+ if (this.waiting_cb[message.to] != null) {
+ return this.waiting_cb[message.to](message.result);
+ } else {
+ return this.log("Websocket callback not found:", message);
+ }
+ } else if (cmd === "wrapperReady") {
+ return this.cmd("innerReady");
+ } else if (cmd === "ping") {
+ return this.response(message.id, "pong");
+ } else if (cmd === "wrapperOpenedWebsocket") {
+ return this.onOpenWebsocket();
+ } else if (cmd === "wrapperClosedWebsocket") {
+ return this.onCloseWebsocket();
+ } else {
+ return this.onRequest(cmd, message.params);
+ }
+ };
+
+ ZeroFrame.prototype.onRequest = function(cmd, message) {
+ return this.log("Unknown request", message);
+ };
+
+ ZeroFrame.prototype.response = function(to, result) {
+ return this.send({
+ "cmd": "response",
+ "to": to,
+ "result": result
+ });
+ };
+
+ ZeroFrame.prototype.cmd = function(cmd, params, cb) {
+ if (params == null) {
+ params = {};
+ }
+ if (cb == null) {
+ cb = null;
+ }
+ return this.send({
+ "cmd": cmd,
+ "params": params
+ }, cb);
+ };
+
+ ZeroFrame.prototype.send = function(message, cb) {
+ if (cb == null) {
+ cb = null;
+ }
+ message.wrapper_nonce = this.wrapper_nonce;
+ message.id = this.next_message_id;
+ this.next_message_id += 1;
+ this.target.postMessage(message, "*");
+ if (cb) {
+ return this.waiting_cb[message.id] = cb;
+ }
+ };
+
+ ZeroFrame.prototype.onOpenWebsocket = function() {
+ return this.log("Websocket open");
+ };
+
+ ZeroFrame.prototype.onCloseWebsocket = function() {
+ return this.log("Websocket close");
+ };
+
+ return ZeroFrame;
+
+ })(Class);
+
+ window.ZeroFrame = ZeroFrame;
+
+}).call(this);
+
+
+/* ---- PluginList.coffee ---- */
+
+
+(function() {
+ var PluginList,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+ PluginList = (function(superClass) {
+ extend(PluginList, superClass);
+
+ function PluginList(plugins) {
+ this.handleDeleteClick = bind(this.handleDeleteClick, this);
+ this.handleUpdateClick = bind(this.handleUpdateClick, this);
+ this.handleResetClick = bind(this.handleResetClick, this);
+ this.handleCheckboxChange = bind(this.handleCheckboxChange, this);
+ this.savePluginStatus = bind(this.savePluginStatus, this);
+ this.plugins = plugins;
+ }
+
+ PluginList.prototype.savePluginStatus = function(plugin, is_enabled) {
+ Page.cmd("pluginConfigSet", [plugin.source, plugin.inner_path, "enabled", is_enabled], (function(_this) {
+ return function(res) {
+ if (res === "ok") {
+ return Page.updatePlugins();
+ } else {
+ return Page.cmd("wrapperNotification", ["error", res.error]);
+ }
+ };
+ })(this));
+ return Page.projector.scheduleRender();
+ };
+
+ PluginList.prototype.handleCheckboxChange = function(e) {
+ var node, plugin, value;
+ node = e.currentTarget;
+ plugin = node["data-plugin"];
+ node.classList.toggle("checked");
+ value = node.classList.contains("checked");
+ return this.savePluginStatus(plugin, value);
+ };
+
+ PluginList.prototype.handleResetClick = function(e) {
+ var node, plugin;
+ node = e.currentTarget;
+ plugin = node["data-plugin"];
+ return this.savePluginStatus(plugin, null);
+ };
+
+ PluginList.prototype.handleUpdateClick = function(e) {
+ var node, plugin;
+ node = e.currentTarget;
+ plugin = node["data-plugin"];
+ node.classList.add("loading");
+ Page.cmd("pluginUpdate", [plugin.source, plugin.inner_path], (function(_this) {
+ return function(res) {
+ if (res === "ok") {
+ Page.cmd("wrapperNotification", ["done", "Plugin " + plugin.name + " updated to latest version"]);
+ Page.updatePlugins();
+ } else {
+ Page.cmd("wrapperNotification", ["error", res.error]);
+ }
+ return node.classList.remove("loading");
+ };
+ })(this));
+ return false;
+ };
+
+ PluginList.prototype.handleDeleteClick = function(e) {
+ var node, plugin;
+ node = e.currentTarget;
+ plugin = node["data-plugin"];
+ if (plugin.loaded) {
+ Page.cmd("wrapperNotification", ["info", "You can only delete plugin that are not currently active"]);
+ return false;
+ }
+ node.classList.add("loading");
+ Page.cmd("wrapperConfirm", ["Delete " + plugin.name + " plugin?", "Delete"], (function(_this) {
+ return function(res) {
+ if (!res) {
+ node.classList.remove("loading");
+ return false;
+ }
+ return Page.cmd("pluginRemove", [plugin.source, plugin.inner_path], function(res) {
+ if (res === "ok") {
+ Page.cmd("wrapperNotification", ["done", "Plugin " + plugin.name + " deleted"]);
+ Page.updatePlugins();
+ } else {
+ Page.cmd("wrapperNotification", ["error", res.error]);
+ }
+ return node.classList.remove("loading");
+ });
+ };
+ })(this));
+ return false;
+ };
+
+ PluginList.prototype.render = function() {
+ return h("div.plugins", this.plugins.map((function(_this) {
+ return function(plugin) {
+ var base, descr, enabled_default, is_changed, is_pending, marker_title, ref, tag_delete, tag_source, tag_update, tag_version;
+ if (!plugin.info) {
+ return;
+ }
+ descr = plugin.info.description;
+ if ((base = plugin.info)["default"] == null) {
+ base["default"] = "enabled";
+ }
+ if (plugin.info["default"]) {
+ descr += " (default: " + plugin.info["default"] + ")";
+ }
+ tag_version = "";
+ tag_source = "";
+ tag_delete = "";
+ if (plugin.source !== "builtin") {
+ tag_update = "";
+ if ((ref = plugin.site_info) != null ? ref.rev : void 0) {
+ if (plugin.site_info.rev > plugin.info.rev) {
+ tag_update = h("a.version-update.button", {
+ href: "#Update+plugin",
+ onclick: _this.handleUpdateClick,
+ "data-plugin": plugin
+ }, "Update to rev" + plugin.site_info.rev);
+ }
+ } else {
+ tag_update = h("span.version-missing", "(unable to get latest vesion: update site missing)");
+ }
+ tag_version = h("span.version", ["rev" + plugin.info.rev + " ", tag_update]);
+ tag_source = h("div.source", [
+ "Source: ", h("a", {
+ "href": "/" + plugin.source,
+ "target": "_top"
+ }, plugin.site_title ? plugin.site_title : plugin.source), " /" + plugin.inner_path
+ ]);
+ tag_delete = h("a.delete", {
+ "href": "#Delete+plugin",
+ onclick: _this.handleDeleteClick,
+ "data-plugin": plugin
+ }, "Delete plugin");
+ }
+ enabled_default = plugin.info["default"] === "enabled";
+ if (plugin.enabled !== plugin.loaded || plugin.updated) {
+ marker_title = "Change pending";
+ is_pending = true;
+ } else {
+ marker_title = "Changed from default status (click to reset to " + plugin.info["default"] + ")";
+ is_pending = false;
+ }
+ is_changed = plugin.enabled !== enabled_default && plugin.owner === "builtin";
+ return h("div.plugin", {
+ key: plugin.name
+ }, [
+ h("div.title", [h("h3", [plugin.name, tag_version]), h("div.description", [descr, tag_source, tag_delete])]), h("div.value.value-right", h("div.checkbox", {
+ onclick: _this.handleCheckboxChange,
+ "data-plugin": plugin,
+ classes: {
+ checked: plugin.enabled
+ }
+ }, h("div.checkbox-skin")), h("a.marker", {
+ href: "#Reset",
+ title: marker_title,
+ onclick: _this.handleResetClick,
+ "data-plugin": plugin,
+ classes: {
+ visible: is_pending || is_changed,
+ pending: is_pending
+ }
+ }, "\u2022"))
+ ]);
+ };
+ })(this)));
+ };
+
+ return PluginList;
+
+ })(Class);
+
+ window.PluginList = PluginList;
+
+}).call(this);
+
+
+/* ---- UiPluginManager.coffee ---- */
+
+
+(function() {
+ var UiPluginManager,
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ hasProp = {}.hasOwnProperty;
+
+ window.h = maquette.h;
+
+ UiPluginManager = (function(superClass) {
+ extend(UiPluginManager, superClass);
+
+ function UiPluginManager() {
+ this.renderBottomRestart = bind(this.renderBottomRestart, this);
+ this.handleRestartClick = bind(this.handleRestartClick, this);
+ this.render = bind(this.render, this);
+ this.createProjector = bind(this.createProjector, this);
+ this.updatePlugins = bind(this.updatePlugins, this);
+ this.onOpenWebsocket = bind(this.onOpenWebsocket, this);
+ return UiPluginManager.__super__.constructor.apply(this, arguments);
+ }
+
+ UiPluginManager.prototype.init = function() {
+ this.plugin_list_builtin = new PluginList();
+ this.plugin_list_custom = new PluginList();
+ this.plugins_changed = null;
+ this.need_restart = null;
+ return this;
+ };
+
+ UiPluginManager.prototype.onOpenWebsocket = function() {
+ this.cmd("wrapperSetTitle", "Plugin manager - ZeroNet");
+ this.cmd("serverInfo", {}, (function(_this) {
+ return function(server_info) {
+ return _this.server_info = server_info;
+ };
+ })(this));
+ return this.updatePlugins();
+ };
+
+ UiPluginManager.prototype.updatePlugins = function(cb) {
+ return this.cmd("pluginList", [], (function(_this) {
+ return function(res) {
+ var item, plugins_builtin, plugins_custom;
+ _this.plugins_changed = (function() {
+ var i, len, ref, results;
+ ref = res.plugins;
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ item = ref[i];
+ if (item.enabled !== item.loaded || item.updated) {
+ results.push(item);
+ }
+ }
+ return results;
+ })();
+ plugins_builtin = (function() {
+ var i, len, ref, results;
+ ref = res.plugins;
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ item = ref[i];
+ if (item.source === "builtin") {
+ results.push(item);
+ }
+ }
+ return results;
+ })();
+ _this.plugin_list_builtin.plugins = plugins_builtin.sort(function(a, b) {
+ return a.name.localeCompare(b.name);
+ });
+ plugins_custom = (function() {
+ var i, len, ref, results;
+ ref = res.plugins;
+ results = [];
+ for (i = 0, len = ref.length; i < len; i++) {
+ item = ref[i];
+ if (item.source !== "builtin") {
+ results.push(item);
+ }
+ }
+ return results;
+ })();
+ _this.plugin_list_custom.plugins = plugins_custom.sort(function(a, b) {
+ return a.name.localeCompare(b.name);
+ });
+ _this.projector.scheduleRender();
+ return typeof cb === "function" ? cb() : void 0;
+ };
+ })(this));
+ };
+
+ UiPluginManager.prototype.createProjector = function() {
+ this.projector = maquette.createProjector();
+ this.projector.replace($("#content"), this.render);
+ return this.projector.replace($("#bottom-restart"), this.renderBottomRestart);
+ };
+
+ UiPluginManager.prototype.render = function() {
+ var ref;
+ if (!this.plugin_list_builtin.plugins) {
+ return h("div.content");
+ }
+ return h("div.content", [h("div.section", [((ref = this.plugin_list_custom.plugins) != null ? ref.length : void 0) ? [h("h2", "Installed third-party plugins"), this.plugin_list_custom.render()] : void 0, h("h2", "Built-in plugins"), this.plugin_list_builtin.render()])]);
+ };
+
+ UiPluginManager.prototype.handleRestartClick = function() {
+ this.restart_loading = true;
+ setTimeout(((function(_this) {
+ return function() {
+ return Page.cmd("serverShutdown", {
+ restart: true
+ });
+ };
+ })(this)), 300);
+ Page.projector.scheduleRender();
+ return false;
+ };
+
+ UiPluginManager.prototype.renderBottomRestart = function() {
+ var ref;
+ return h("div.bottom.bottom-restart", {
+ classes: {
+ visible: (ref = this.plugins_changed) != null ? ref.length : void 0
+ }
+ }, h("div.bottom-content", [
+ h("div.title", "Some plugins status has been changed"), h("a.button.button-submit.button-restart", {
+ href: "#Restart",
+ classes: {
+ loading: this.restart_loading
+ },
+ onclick: this.handleRestartClick
+ }, "Restart ZeroNet client")
+ ]));
+ };
+
+ return UiPluginManager;
+
+ })(ZeroFrame);
+
+ window.Page = new UiPluginManager();
+
+ window.Page.createProjector();
+
+}).call(this);
diff --git a/plugins/UiPluginManager/media/js/lib/Class.coffee b/plugins/UiPluginManager/media/js/lib/Class.coffee
new file mode 100644
index 00000000..d62ab25c
--- /dev/null
+++ b/plugins/UiPluginManager/media/js/lib/Class.coffee
@@ -0,0 +1,23 @@
+class Class
+ trace: true
+
+ log: (args...) ->
+ return unless @trace
+ return if typeof console is 'undefined'
+ args.unshift("[#{@.constructor.name}]")
+ console.log(args...)
+ @
+
+ logStart: (name, args...) ->
+ return unless @trace
+ @logtimers or= {}
+ @logtimers[name] = +(new Date)
+ @log "#{name}", args..., "(started)" if args.length > 0
+ @
+
+ logEnd: (name, args...) ->
+ ms = +(new Date)-@logtimers[name]
+ @log "#{name}", args..., "(Done in #{ms}ms)"
+ @
+
+window.Class = Class
\ No newline at end of file
diff --git a/plugins/UiPluginManager/media/js/lib/Promise.coffee b/plugins/UiPluginManager/media/js/lib/Promise.coffee
new file mode 100644
index 00000000..136e3ec7
--- /dev/null
+++ b/plugins/UiPluginManager/media/js/lib/Promise.coffee
@@ -0,0 +1,74 @@
+# From: http://dev.bizo.com/2011/12/promises-in-javascriptcoffeescript.html
+
+class Promise
+ @when: (tasks...) ->
+ num_uncompleted = tasks.length
+ args = new Array(num_uncompleted)
+ promise = new Promise()
+
+ for task, task_id in tasks
+ ((task_id) ->
+ task.then(() ->
+ args[task_id] = Array.prototype.slice.call(arguments)
+ num_uncompleted--
+ promise.complete.apply(promise, args) if num_uncompleted == 0
+ )
+ )(task_id)
+
+ return promise
+
+ constructor: ->
+ @resolved = false
+ @end_promise = null
+ @result = null
+ @callbacks = []
+
+ resolve: ->
+ if @resolved
+ return false
+ @resolved = true
+ @data = arguments
+ if not arguments.length
+ @data = [true]
+ @result = @data[0]
+ for callback in @callbacks
+ back = callback.apply callback, @data
+ if @end_promise
+ @end_promise.resolve(back)
+
+ fail: ->
+ @resolve(false)
+
+ then: (callback) ->
+ if @resolved == true
+ callback.apply callback, @data
+ return
+
+ @callbacks.push callback
+
+ @end_promise = new Promise()
+
+window.Promise = Promise
+
+###
+s = Date.now()
+log = (text) ->
+ console.log Date.now()-s, Array.prototype.slice.call(arguments).join(", ")
+
+log "Started"
+
+cmd = (query) ->
+ p = new Promise()
+ setTimeout ( ->
+ p.resolve query+" Result"
+ ), 100
+ return p
+
+back = cmd("SELECT * FROM message").then (res) ->
+ log res
+ return "Return from query"
+.then (res) ->
+ log "Back then", res
+
+log "Query started", back
+###
\ No newline at end of file
diff --git a/plugins/UiPluginManager/media/js/lib/Prototypes.coffee b/plugins/UiPluginManager/media/js/lib/Prototypes.coffee
new file mode 100644
index 00000000..034add50
--- /dev/null
+++ b/plugins/UiPluginManager/media/js/lib/Prototypes.coffee
@@ -0,0 +1,8 @@
+String::startsWith = (s) -> @[...s.length] is s
+String::endsWith = (s) -> s is '' or @[-s.length..] is s
+String::repeat = (count) -> new Array( count + 1 ).join(@)
+
+window.isEmpty = (obj) ->
+ for key of obj
+ return false
+ return true
diff --git a/plugins/UiPluginManager/media/js/lib/maquette.js b/plugins/UiPluginManager/media/js/lib/maquette.js
new file mode 100644
index 00000000..84d14471
--- /dev/null
+++ b/plugins/UiPluginManager/media/js/lib/maquette.js
@@ -0,0 +1,770 @@
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['exports'], factory);
+ } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
+ // CommonJS
+ factory(exports);
+ } else {
+ // Browser globals
+ factory(root.maquette = {});
+ }
+}(this, function (exports) {
+ 'use strict';
+ ;
+ ;
+ ;
+ ;
+ var NAMESPACE_W3 = 'http://www.w3.org/';
+ var NAMESPACE_SVG = NAMESPACE_W3 + '2000/svg';
+ var NAMESPACE_XLINK = NAMESPACE_W3 + '1999/xlink';
+ // Utilities
+ var emptyArray = [];
+ var extend = function (base, overrides) {
+ var result = {};
+ Object.keys(base).forEach(function (key) {
+ result[key] = base[key];
+ });
+ if (overrides) {
+ Object.keys(overrides).forEach(function (key) {
+ result[key] = overrides[key];
+ });
+ }
+ return result;
+ };
+ // Hyperscript helper functions
+ var same = function (vnode1, vnode2) {
+ if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {
+ return false;
+ }
+ if (vnode1.properties && vnode2.properties) {
+ if (vnode1.properties.key !== vnode2.properties.key) {
+ return false;
+ }
+ return vnode1.properties.bind === vnode2.properties.bind;
+ }
+ return !vnode1.properties && !vnode2.properties;
+ };
+ var toTextVNode = function (data) {
+ return {
+ vnodeSelector: '',
+ properties: undefined,
+ children: undefined,
+ text: data.toString(),
+ domNode: null
+ };
+ };
+ var appendChildren = function (parentSelector, insertions, main) {
+ for (var i = 0; i < insertions.length; i++) {
+ var item = insertions[i];
+ if (Array.isArray(item)) {
+ appendChildren(parentSelector, item, main);
+ } else {
+ if (item !== null && item !== undefined) {
+ if (!item.hasOwnProperty('vnodeSelector')) {
+ item = toTextVNode(item);
+ }
+ main.push(item);
+ }
+ }
+ }
+ };
+ // Render helper functions
+ var missingTransition = function () {
+ throw new Error('Provide a transitions object to the projectionOptions to do animations');
+ };
+ var DEFAULT_PROJECTION_OPTIONS = {
+ namespace: undefined,
+ eventHandlerInterceptor: undefined,
+ styleApplyer: function (domNode, styleName, value) {
+ // Provides a hook to add vendor prefixes for browsers that still need it.
+ domNode.style[styleName] = value;
+ },
+ transitions: {
+ enter: missingTransition,
+ exit: missingTransition
+ }
+ };
+ var applyDefaultProjectionOptions = function (projectorOptions) {
+ return extend(DEFAULT_PROJECTION_OPTIONS, projectorOptions);
+ };
+ var checkStyleValue = function (styleValue) {
+ if (typeof styleValue !== 'string') {
+ throw new Error('Style values must be strings');
+ }
+ };
+ var setProperties = function (domNode, properties, projectionOptions) {
+ if (!properties) {
+ return;
+ }
+ var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;
+ var propNames = Object.keys(properties);
+ var propCount = propNames.length;
+ for (var i = 0; i < propCount; i++) {
+ var propName = propNames[i];
+ /* tslint:disable:no-var-keyword: edge case */
+ var propValue = properties[propName];
+ /* tslint:enable:no-var-keyword */
+ if (propName === 'className') {
+ throw new Error('Property "className" is not supported, use "class".');
+ } else if (propName === 'class') {
+ if (domNode.className) {
+ // May happen if classes is specified before class
+ domNode.className += ' ' + propValue;
+ } else {
+ domNode.className = propValue;
+ }
+ } else if (propName === 'classes') {
+ // object with string keys and boolean values
+ var classNames = Object.keys(propValue);
+ var classNameCount = classNames.length;
+ for (var j = 0; j < classNameCount; j++) {
+ var className = classNames[j];
+ if (propValue[className]) {
+ domNode.classList.add(className);
+ }
+ }
+ } else if (propName === 'styles') {
+ // object with string keys and string (!) values
+ var styleNames = Object.keys(propValue);
+ var styleCount = styleNames.length;
+ for (var j = 0; j < styleCount; j++) {
+ var styleName = styleNames[j];
+ var styleValue = propValue[styleName];
+ if (styleValue) {
+ checkStyleValue(styleValue);
+ projectionOptions.styleApplyer(domNode, styleName, styleValue);
+ }
+ }
+ } else if (propName === 'key') {
+ continue;
+ } else if (propValue === null || propValue === undefined) {
+ continue;
+ } else {
+ var type = typeof propValue;
+ if (type === 'function') {
+ if (propName.lastIndexOf('on', 0) === 0) {
+ if (eventHandlerInterceptor) {
+ propValue = eventHandlerInterceptor(propName, propValue, domNode, properties); // intercept eventhandlers
+ }
+ if (propName === 'oninput') {
+ (function () {
+ // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput
+ var oldPropValue = propValue;
+ propValue = function (evt) {
+ evt.target['oninput-value'] = evt.target.value;
+ // may be HTMLTextAreaElement as well
+ oldPropValue.apply(this, [evt]);
+ };
+ }());
+ }
+ domNode[propName] = propValue;
+ }
+ } else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {
+ if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
+ domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
+ } else {
+ domNode.setAttribute(propName, propValue);
+ }
+ } else {
+ domNode[propName] = propValue;
+ }
+ }
+ }
+ };
+ var updateProperties = function (domNode, previousProperties, properties, projectionOptions) {
+ if (!properties) {
+ return;
+ }
+ var propertiesUpdated = false;
+ var propNames = Object.keys(properties);
+ var propCount = propNames.length;
+ for (var i = 0; i < propCount; i++) {
+ var propName = propNames[i];
+ // assuming that properties will be nullified instead of missing is by design
+ var propValue = properties[propName];
+ var previousValue = previousProperties[propName];
+ if (propName === 'class') {
+ if (previousValue !== propValue) {
+ throw new Error('"class" property may not be updated. Use the "classes" property for conditional css classes.');
+ }
+ } else if (propName === 'classes') {
+ var classList = domNode.classList;
+ var classNames = Object.keys(propValue);
+ var classNameCount = classNames.length;
+ for (var j = 0; j < classNameCount; j++) {
+ var className = classNames[j];
+ var on = !!propValue[className];
+ var previousOn = !!previousValue[className];
+ if (on === previousOn) {
+ continue;
+ }
+ propertiesUpdated = true;
+ if (on) {
+ classList.add(className);
+ } else {
+ classList.remove(className);
+ }
+ }
+ } else if (propName === 'styles') {
+ var styleNames = Object.keys(propValue);
+ var styleCount = styleNames.length;
+ for (var j = 0; j < styleCount; j++) {
+ var styleName = styleNames[j];
+ var newStyleValue = propValue[styleName];
+ var oldStyleValue = previousValue[styleName];
+ if (newStyleValue === oldStyleValue) {
+ continue;
+ }
+ propertiesUpdated = true;
+ if (newStyleValue) {
+ checkStyleValue(newStyleValue);
+ projectionOptions.styleApplyer(domNode, styleName, newStyleValue);
+ } else {
+ projectionOptions.styleApplyer(domNode, styleName, '');
+ }
+ }
+ } else {
+ if (!propValue && typeof previousValue === 'string') {
+ propValue = '';
+ }
+ if (propName === 'value') {
+ if (domNode[propName] !== propValue && domNode['oninput-value'] !== propValue) {
+ domNode[propName] = propValue;
+ // Reset the value, even if the virtual DOM did not change
+ domNode['oninput-value'] = undefined;
+ }
+ // else do not update the domNode, otherwise the cursor position would be changed
+ if (propValue !== previousValue) {
+ propertiesUpdated = true;
+ }
+ } else if (propValue !== previousValue) {
+ var type = typeof propValue;
+ if (type === 'function') {
+ throw new Error('Functions may not be updated on subsequent renders (property: ' + propName + '). Hint: declare event handler functions outside the render() function.');
+ }
+ if (type === 'string' && propName !== 'innerHTML') {
+ if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
+ domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
+ } else {
+ domNode.setAttribute(propName, propValue);
+ }
+ } else {
+ if (domNode[propName] !== propValue) {
+ domNode[propName] = propValue;
+ }
+ }
+ propertiesUpdated = true;
+ }
+ }
+ }
+ return propertiesUpdated;
+ };
+ var findIndexOfChild = function (children, sameAs, start) {
+ if (sameAs.vnodeSelector !== '') {
+ // Never scan for text-nodes
+ for (var i = start; i < children.length; i++) {
+ if (same(children[i], sameAs)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ };
+ var nodeAdded = function (vNode, transitions) {
+ if (vNode.properties) {
+ var enterAnimation = vNode.properties.enterAnimation;
+ if (enterAnimation) {
+ if (typeof enterAnimation === 'function') {
+ enterAnimation(vNode.domNode, vNode.properties);
+ } else {
+ transitions.enter(vNode.domNode, vNode.properties, enterAnimation);
+ }
+ }
+ }
+ };
+ var nodeToRemove = function (vNode, transitions) {
+ var domNode = vNode.domNode;
+ if (vNode.properties) {
+ var exitAnimation = vNode.properties.exitAnimation;
+ if (exitAnimation) {
+ domNode.style.pointerEvents = 'none';
+ var removeDomNode = function () {
+ if (domNode.parentNode) {
+ domNode.parentNode.removeChild(domNode);
+ }
+ };
+ if (typeof exitAnimation === 'function') {
+ exitAnimation(domNode, removeDomNode, vNode.properties);
+ return;
+ } else {
+ transitions.exit(vNode.domNode, vNode.properties, exitAnimation, removeDomNode);
+ return;
+ }
+ }
+ }
+ if (domNode.parentNode) {
+ domNode.parentNode.removeChild(domNode);
+ }
+ };
+ var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {
+ var childNode = childNodes[indexToCheck];
+ if (childNode.vnodeSelector === '') {
+ return; // Text nodes need not be distinguishable
+ }
+ var properties = childNode.properties;
+ var key = properties ? properties.key === undefined ? properties.bind : properties.key : undefined;
+ if (!key) {
+ for (var i = 0; i < childNodes.length; i++) {
+ if (i !== indexToCheck) {
+ var node = childNodes[i];
+ if (same(node, childNode)) {
+ if (operation === 'added') {
+ throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'added, but there is now more than one. You must add unique key properties to make them distinguishable.');
+ } else {
+ throw new Error(parentVNode.vnodeSelector + ' had a ' + childNode.vnodeSelector + ' child ' + 'removed, but there were more than one. You must add unique key properties to make them distinguishable.');
+ }
+ }
+ }
+ }
+ }
+ };
+ var createDom;
+ var updateDom;
+ var updateChildren = function (vnode, domNode, oldChildren, newChildren, projectionOptions) {
+ if (oldChildren === newChildren) {
+ return false;
+ }
+ oldChildren = oldChildren || emptyArray;
+ newChildren = newChildren || emptyArray;
+ var oldChildrenLength = oldChildren.length;
+ var newChildrenLength = newChildren.length;
+ var transitions = projectionOptions.transitions;
+ var oldIndex = 0;
+ var newIndex = 0;
+ var i;
+ var textUpdated = false;
+ while (newIndex < newChildrenLength) {
+ var oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;
+ var newChild = newChildren[newIndex];
+ if (oldChild !== undefined && same(oldChild, newChild)) {
+ textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;
+ oldIndex++;
+ } else {
+ var findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);
+ if (findOldIndex >= 0) {
+ // Remove preceding missing children
+ for (i = oldIndex; i < findOldIndex; i++) {
+ nodeToRemove(oldChildren[i], transitions);
+ checkDistinguishable(oldChildren, i, vnode, 'removed');
+ }
+ textUpdated = updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;
+ oldIndex = findOldIndex + 1;
+ } else {
+ // New child
+ createDom(newChild, domNode, oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined, projectionOptions);
+ nodeAdded(newChild, transitions);
+ checkDistinguishable(newChildren, newIndex, vnode, 'added');
+ }
+ }
+ newIndex++;
+ }
+ if (oldChildrenLength > oldIndex) {
+ // Remove child fragments
+ for (i = oldIndex; i < oldChildrenLength; i++) {
+ nodeToRemove(oldChildren[i], transitions);
+ checkDistinguishable(oldChildren, i, vnode, 'removed');
+ }
+ }
+ return textUpdated;
+ };
+ var addChildren = function (domNode, children, projectionOptions) {
+ if (!children) {
+ return;
+ }
+ for (var i = 0; i < children.length; i++) {
+ createDom(children[i], domNode, undefined, projectionOptions);
+ }
+ };
+ var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {
+ addChildren(domNode, vnode.children, projectionOptions);
+ // children before properties, needed for value property of .
+ if (vnode.text) {
+ domNode.textContent = vnode.text;
+ }
+ setProperties(domNode, vnode.properties, projectionOptions);
+ if (vnode.properties && vnode.properties.afterCreate) {
+ vnode.properties.afterCreate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);
+ }
+ };
+ createDom = function (vnode, parentNode, insertBefore, projectionOptions) {
+ var domNode, i, c, start = 0, type, found;
+ var vnodeSelector = vnode.vnodeSelector;
+ if (vnodeSelector === '') {
+ domNode = vnode.domNode = document.createTextNode(vnode.text);
+ if (insertBefore !== undefined) {
+ parentNode.insertBefore(domNode, insertBefore);
+ } else {
+ parentNode.appendChild(domNode);
+ }
+ } else {
+ for (i = 0; i <= vnodeSelector.length; ++i) {
+ c = vnodeSelector.charAt(i);
+ if (i === vnodeSelector.length || c === '.' || c === '#') {
+ type = vnodeSelector.charAt(start - 1);
+ found = vnodeSelector.slice(start, i);
+ if (type === '.') {
+ domNode.classList.add(found);
+ } else if (type === '#') {
+ domNode.id = found;
+ } else {
+ if (found === 'svg') {
+ projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });
+ }
+ if (projectionOptions.namespace !== undefined) {
+ domNode = vnode.domNode = document.createElementNS(projectionOptions.namespace, found);
+ } else {
+ domNode = vnode.domNode = document.createElement(found);
+ }
+ if (insertBefore !== undefined) {
+ parentNode.insertBefore(domNode, insertBefore);
+ } else {
+ parentNode.appendChild(domNode);
+ }
+ }
+ start = i + 1;
+ }
+ }
+ initPropertiesAndChildren(domNode, vnode, projectionOptions);
+ }
+ };
+ updateDom = function (previous, vnode, projectionOptions) {
+ var domNode = previous.domNode;
+ var textUpdated = false;
+ if (previous === vnode) {
+ return false; // By contract, VNode objects may not be modified anymore after passing them to maquette
+ }
+ var updated = false;
+ if (vnode.vnodeSelector === '') {
+ if (vnode.text !== previous.text) {
+ var newVNode = document.createTextNode(vnode.text);
+ domNode.parentNode.replaceChild(newVNode, domNode);
+ vnode.domNode = newVNode;
+ textUpdated = true;
+ return textUpdated;
+ }
+ } else {
+ if (vnode.vnodeSelector.lastIndexOf('svg', 0) === 0) {
+ projectionOptions = extend(projectionOptions, { namespace: NAMESPACE_SVG });
+ }
+ if (previous.text !== vnode.text) {
+ updated = true;
+ if (vnode.text === undefined) {
+ domNode.removeChild(domNode.firstChild); // the only textnode presumably
+ } else {
+ domNode.textContent = vnode.text;
+ }
+ }
+ updated = updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) || updated;
+ updated = updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) || updated;
+ if (vnode.properties && vnode.properties.afterUpdate) {
+ vnode.properties.afterUpdate(domNode, projectionOptions, vnode.vnodeSelector, vnode.properties, vnode.children);
+ }
+ }
+ if (updated && vnode.properties && vnode.properties.updateAnimation) {
+ vnode.properties.updateAnimation(domNode, vnode.properties, previous.properties);
+ }
+ vnode.domNode = previous.domNode;
+ return textUpdated;
+ };
+ var createProjection = function (vnode, projectionOptions) {
+ return {
+ update: function (updatedVnode) {
+ if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {
+ throw new Error('The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)');
+ }
+ updateDom(vnode, updatedVnode, projectionOptions);
+ vnode = updatedVnode;
+ },
+ domNode: vnode.domNode
+ };
+ };
+ ;
+ // The other two parameters are not added here, because the Typescript compiler creates surrogate code for desctructuring 'children'.
+ exports.h = function (selector) {
+ var properties = arguments[1];
+ if (typeof selector !== 'string') {
+ throw new Error();
+ }
+ var childIndex = 1;
+ if (properties && !properties.hasOwnProperty('vnodeSelector') && !Array.isArray(properties) && typeof properties === 'object') {
+ childIndex = 2;
+ } else {
+ // Optional properties argument was omitted
+ properties = undefined;
+ }
+ var text = undefined;
+ var children = undefined;
+ var argsLength = arguments.length;
+ // Recognize a common special case where there is only a single text node
+ if (argsLength === childIndex + 1) {
+ var onlyChild = arguments[childIndex];
+ if (typeof onlyChild === 'string') {
+ text = onlyChild;
+ } else if (onlyChild !== undefined && onlyChild.length === 1 && typeof onlyChild[0] === 'string') {
+ text = onlyChild[0];
+ }
+ }
+ if (text === undefined) {
+ children = [];
+ for (; childIndex < arguments.length; childIndex++) {
+ var child = arguments[childIndex];
+ if (child === null || child === undefined) {
+ continue;
+ } else if (Array.isArray(child)) {
+ appendChildren(selector, child, children);
+ } else if (child.hasOwnProperty('vnodeSelector')) {
+ children.push(child);
+ } else {
+ children.push(toTextVNode(child));
+ }
+ }
+ }
+ return {
+ vnodeSelector: selector,
+ properties: properties,
+ children: children,
+ text: text === '' ? undefined : text,
+ domNode: null
+ };
+ };
+ /**
+ * Contains simple low-level utility functions to manipulate the real DOM.
+ */
+ exports.dom = {
+ /**
+ * Creates a real DOM tree from `vnode`. The [[Projection]] object returned will contain the resulting DOM Node in
+ * its [[Projection.domNode|domNode]] property.
+ * This is a low-level method. Users wil typically use a [[Projector]] instead.
+ * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]
+ * objects may only be rendered once.
+ * @param projectionOptions - Options to be used to create and update the projection.
+ * @returns The [[Projection]] which also contains the DOM Node that was created.
+ */
+ create: function (vnode, projectionOptions) {
+ projectionOptions = applyDefaultProjectionOptions(projectionOptions);
+ createDom(vnode, document.createElement('div'), undefined, projectionOptions);
+ return createProjection(vnode, projectionOptions);
+ },
+ /**
+ * Appends a new childnode to the DOM which is generated from a [[VNode]].
+ * This is a low-level method. Users wil typically use a [[Projector]] instead.
+ * @param parentNode - The parent node for the new childNode.
+ * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]]
+ * objects may only be rendered once.
+ * @param projectionOptions - Options to be used to create and update the [[Projection]].
+ * @returns The [[Projection]] that was created.
+ */
+ append: function (parentNode, vnode, projectionOptions) {
+ projectionOptions = applyDefaultProjectionOptions(projectionOptions);
+ createDom(vnode, parentNode, undefined, projectionOptions);
+ return createProjection(vnode, projectionOptions);
+ },
+ /**
+ * Inserts a new DOM node which is generated from a [[VNode]].
+ * This is a low-level method. Users wil typically use a [[Projector]] instead.
+ * @param beforeNode - The node that the DOM Node is inserted before.
+ * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function.
+ * NOTE: [[VNode]] objects may only be rendered once.
+ * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].
+ * @returns The [[Projection]] that was created.
+ */
+ insertBefore: function (beforeNode, vnode, projectionOptions) {
+ projectionOptions = applyDefaultProjectionOptions(projectionOptions);
+ createDom(vnode, beforeNode.parentNode, beforeNode, projectionOptions);
+ return createProjection(vnode, projectionOptions);
+ },
+ /**
+ * Merges a new DOM node which is generated from a [[VNode]] with an existing DOM Node.
+ * This means that the virtual DOM and the real DOM will have one overlapping element.
+ * Therefore the selector for the root [[VNode]] will be ignored, but its properties and children will be applied to the Element provided.
+ * This is a low-level method. Users wil typically use a [[Projector]] instead.
+ * @param domNode - The existing element to adopt as the root of the new virtual DOM. Existing attributes and childnodes are preserved.
+ * @param vnode - The root of the virtual DOM tree that was created using the [[h]] function. NOTE: [[VNode]] objects
+ * may only be rendered once.
+ * @param projectionOptions - Options to be used to create and update the projection, see [[createProjector]].
+ * @returns The [[Projection]] that was created.
+ */
+ merge: function (element, vnode, projectionOptions) {
+ projectionOptions = applyDefaultProjectionOptions(projectionOptions);
+ vnode.domNode = element;
+ initPropertiesAndChildren(element, vnode, projectionOptions);
+ return createProjection(vnode, projectionOptions);
+ }
+ };
+ /**
+ * Creates a [[CalculationCache]] object, useful for caching [[VNode]] trees.
+ * In practice, caching of [[VNode]] trees is not needed, because achieving 60 frames per second is almost never a problem.
+ * For more information, see [[CalculationCache]].
+ *
+ * @param The type of the value that is cached.
+ */
+ exports.createCache = function () {
+ var cachedInputs = undefined;
+ var cachedOutcome = undefined;
+ var result = {
+ invalidate: function () {
+ cachedOutcome = undefined;
+ cachedInputs = undefined;
+ },
+ result: function (inputs, calculation) {
+ if (cachedInputs) {
+ for (var i = 0; i < inputs.length; i++) {
+ if (cachedInputs[i] !== inputs[i]) {
+ cachedOutcome = undefined;
+ }
+ }
+ }
+ if (!cachedOutcome) {
+ cachedOutcome = calculation();
+ cachedInputs = inputs;
+ }
+ return cachedOutcome;
+ }
+ };
+ return result;
+ };
+ /**
+ * Creates a {@link Mapping} instance that keeps an array of result objects synchronized with an array of source objects.
+ * See {@link http://maquettejs.org/docs/arrays.html|Working with arrays}.
+ *
+ * @param The type of source items. A database-record for instance.
+ * @param The type of target items. A [[Component]] for instance.
+ * @param getSourceKey `function(source)` that must return a key to identify each source object. The result must either be a string or a number.
+ * @param createResult `function(source, index)` that must create a new result object from a given source. This function is identical
+ * to the `callback` argument in `Array.map(callback)`.
+ * @param updateResult `function(source, target, index)` that updates a result to an updated source.
+ */
+ exports.createMapping = function (getSourceKey, createResult, updateResult) {
+ var keys = [];
+ var results = [];
+ return {
+ results: results,
+ map: function (newSources) {
+ var newKeys = newSources.map(getSourceKey);
+ var oldTargets = results.slice();
+ var oldIndex = 0;
+ for (var i = 0; i < newSources.length; i++) {
+ var source = newSources[i];
+ var sourceKey = newKeys[i];
+ if (sourceKey === keys[oldIndex]) {
+ results[i] = oldTargets[oldIndex];
+ updateResult(source, oldTargets[oldIndex], i);
+ oldIndex++;
+ } else {
+ var found = false;
+ for (var j = 1; j < keys.length; j++) {
+ var searchIndex = (oldIndex + j) % keys.length;
+ if (keys[searchIndex] === sourceKey) {
+ results[i] = oldTargets[searchIndex];
+ updateResult(newSources[i], oldTargets[searchIndex], i);
+ oldIndex = searchIndex + 1;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ results[i] = createResult(source, i);
+ }
+ }
+ }
+ results.length = newSources.length;
+ keys = newKeys;
+ }
+ };
+ };
+ /**
+ * Creates a [[Projector]] instance using the provided projectionOptions.
+ *
+ * For more information, see [[Projector]].
+ *
+ * @param projectionOptions Options that influence how the DOM is rendered and updated.
+ */
+ exports.createProjector = function (projectorOptions) {
+ var projector;
+ var projectionOptions = applyDefaultProjectionOptions(projectorOptions);
+ projectionOptions.eventHandlerInterceptor = function (propertyName, eventHandler, domNode, properties) {
+ return function () {
+ // intercept function calls (event handlers) to do a render afterwards.
+ projector.scheduleRender();
+ return eventHandler.apply(properties.bind || this, arguments);
+ };
+ };
+ var renderCompleted = true;
+ var scheduled;
+ var stopped = false;
+ var projections = [];
+ var renderFunctions = [];
+ // matches the projections array
+ var doRender = function () {
+ scheduled = undefined;
+ if (!renderCompleted) {
+ return; // The last render threw an error, it should be logged in the browser console.
+ }
+ renderCompleted = false;
+ for (var i = 0; i < projections.length; i++) {
+ var updatedVnode = renderFunctions[i]();
+ projections[i].update(updatedVnode);
+ }
+ renderCompleted = true;
+ };
+ projector = {
+ scheduleRender: function () {
+ if (!scheduled && !stopped) {
+ scheduled = requestAnimationFrame(doRender);
+ }
+ },
+ stop: function () {
+ if (scheduled) {
+ cancelAnimationFrame(scheduled);
+ scheduled = undefined;
+ }
+ stopped = true;
+ },
+ resume: function () {
+ stopped = false;
+ renderCompleted = true;
+ projector.scheduleRender();
+ },
+ append: function (parentNode, renderMaquetteFunction) {
+ projections.push(exports.dom.append(parentNode, renderMaquetteFunction(), projectionOptions));
+ renderFunctions.push(renderMaquetteFunction);
+ },
+ insertBefore: function (beforeNode, renderMaquetteFunction) {
+ projections.push(exports.dom.insertBefore(beforeNode, renderMaquetteFunction(), projectionOptions));
+ renderFunctions.push(renderMaquetteFunction);
+ },
+ merge: function (domNode, renderMaquetteFunction) {
+ projections.push(exports.dom.merge(domNode, renderMaquetteFunction(), projectionOptions));
+ renderFunctions.push(renderMaquetteFunction);
+ },
+ replace: function (domNode, renderMaquetteFunction) {
+ var vnode = renderMaquetteFunction();
+ createDom(vnode, domNode.parentNode, domNode, projectionOptions);
+ domNode.parentNode.removeChild(domNode);
+ projections.push(createProjection(vnode, projectionOptions));
+ renderFunctions.push(renderMaquetteFunction);
+ },
+ detach: function (renderMaquetteFunction) {
+ for (var i = 0; i < renderFunctions.length; i++) {
+ if (renderFunctions[i] === renderMaquetteFunction) {
+ renderFunctions.splice(i, 1);
+ return projections.splice(i, 1)[0];
+ }
+ }
+ throw new Error('renderMaquetteFunction was not found');
+ }
+ };
+ return projector;
+ };
+}));
diff --git a/plugins/UiPluginManager/media/js/utils/Animation.coffee b/plugins/UiPluginManager/media/js/utils/Animation.coffee
new file mode 100644
index 00000000..271b88c1
--- /dev/null
+++ b/plugins/UiPluginManager/media/js/utils/Animation.coffee
@@ -0,0 +1,138 @@
+class Animation
+ slideDown: (elem, props) ->
+ if elem.offsetTop > 2000
+ return
+
+ h = elem.offsetHeight
+ cstyle = window.getComputedStyle(elem)
+ margin_top = cstyle.marginTop
+ margin_bottom = cstyle.marginBottom
+ padding_top = cstyle.paddingTop
+ padding_bottom = cstyle.paddingBottom
+ transition = cstyle.transition
+
+ elem.style.boxSizing = "border-box"
+ elem.style.overflow = "hidden"
+ elem.style.transform = "scale(0.6)"
+ elem.style.opacity = "0"
+ elem.style.height = "0px"
+ elem.style.marginTop = "0px"
+ elem.style.marginBottom = "0px"
+ elem.style.paddingTop = "0px"
+ elem.style.paddingBottom = "0px"
+ elem.style.transition = "none"
+
+ setTimeout (->
+ elem.className += " animate-inout"
+ elem.style.height = h+"px"
+ elem.style.transform = "scale(1)"
+ elem.style.opacity = "1"
+ elem.style.marginTop = margin_top
+ elem.style.marginBottom = margin_bottom
+ elem.style.paddingTop = padding_top
+ elem.style.paddingBottom = padding_bottom
+ ), 1
+
+ elem.addEventListener "transitionend", ->
+ elem.classList.remove("animate-inout")
+ elem.style.transition = elem.style.transform = elem.style.opacity = elem.style.height = null
+ elem.style.boxSizing = elem.style.marginTop = elem.style.marginBottom = null
+ elem.style.paddingTop = elem.style.paddingBottom = elem.style.overflow = null
+ elem.removeEventListener "transitionend", arguments.callee, false
+
+
+ slideUp: (elem, remove_func, props) ->
+ if elem.offsetTop > 1000
+ return remove_func()
+
+ elem.className += " animate-back"
+ elem.style.boxSizing = "border-box"
+ elem.style.height = elem.offsetHeight+"px"
+ elem.style.overflow = "hidden"
+ elem.style.transform = "scale(1)"
+ elem.style.opacity = "1"
+ elem.style.pointerEvents = "none"
+ setTimeout (->
+ elem.style.height = "0px"
+ elem.style.marginTop = "0px"
+ elem.style.marginBottom = "0px"
+ elem.style.paddingTop = "0px"
+ elem.style.paddingBottom = "0px"
+ elem.style.transform = "scale(0.8)"
+ elem.style.borderTopWidth = "0px"
+ elem.style.borderBottomWidth = "0px"
+ elem.style.opacity = "0"
+ ), 1
+ elem.addEventListener "transitionend", (e) ->
+ if e.propertyName == "opacity" or e.elapsedTime >= 0.6
+ elem.removeEventListener "transitionend", arguments.callee, false
+ remove_func()
+
+
+ slideUpInout: (elem, remove_func, props) ->
+ elem.className += " animate-inout"
+ elem.style.boxSizing = "border-box"
+ elem.style.height = elem.offsetHeight+"px"
+ elem.style.overflow = "hidden"
+ elem.style.transform = "scale(1)"
+ elem.style.opacity = "1"
+ elem.style.pointerEvents = "none"
+ setTimeout (->
+ elem.style.height = "0px"
+ elem.style.marginTop = "0px"
+ elem.style.marginBottom = "0px"
+ elem.style.paddingTop = "0px"
+ elem.style.paddingBottom = "0px"
+ elem.style.transform = "scale(0.8)"
+ elem.style.borderTopWidth = "0px"
+ elem.style.borderBottomWidth = "0px"
+ elem.style.opacity = "0"
+ ), 1
+ elem.addEventListener "transitionend", (e) ->
+ if e.propertyName == "opacity" or e.elapsedTime >= 0.6
+ elem.removeEventListener "transitionend", arguments.callee, false
+ remove_func()
+
+
+ showRight: (elem, props) ->
+ elem.className += " animate"
+ elem.style.opacity = 0
+ elem.style.transform = "TranslateX(-20px) Scale(1.01)"
+ setTimeout (->
+ elem.style.opacity = 1
+ elem.style.transform = "TranslateX(0px) Scale(1)"
+ ), 1
+ elem.addEventListener "transitionend", ->
+ elem.classList.remove("animate")
+ elem.style.transform = elem.style.opacity = null
+
+
+ show: (elem, props) ->
+ delay = arguments[arguments.length-2]?.delay*1000 or 1
+ elem.style.opacity = 0
+ setTimeout (->
+ elem.className += " animate"
+ ), 1
+ setTimeout (->
+ elem.style.opacity = 1
+ ), delay
+ elem.addEventListener "transitionend", ->
+ elem.classList.remove("animate")
+ elem.style.opacity = null
+ elem.removeEventListener "transitionend", arguments.callee, false
+
+ hide: (elem, remove_func, props) ->
+ delay = arguments[arguments.length-2]?.delay*1000 or 1
+ elem.className += " animate"
+ setTimeout (->
+ elem.style.opacity = 0
+ ), delay
+ elem.addEventListener "transitionend", (e) ->
+ if e.propertyName == "opacity"
+ remove_func()
+
+ addVisibleClass: (elem, props) ->
+ setTimeout ->
+ elem.classList.add("visible")
+
+window.Animation = new Animation()
\ No newline at end of file
diff --git a/plugins/UiPluginManager/media/js/utils/Dollar.coffee b/plugins/UiPluginManager/media/js/utils/Dollar.coffee
new file mode 100644
index 00000000..7f19f551
--- /dev/null
+++ b/plugins/UiPluginManager/media/js/utils/Dollar.coffee
@@ -0,0 +1,3 @@
+window.$ = (selector) ->
+ if selector.startsWith("#")
+ return document.getElementById(selector.replace("#", ""))
diff --git a/plugins/UiPluginManager/media/js/utils/ZeroFrame.coffee b/plugins/UiPluginManager/media/js/utils/ZeroFrame.coffee
new file mode 100644
index 00000000..11512d16
--- /dev/null
+++ b/plugins/UiPluginManager/media/js/utils/ZeroFrame.coffee
@@ -0,0 +1,85 @@
+class ZeroFrame extends Class
+ constructor: (url) ->
+ @url = url
+ @waiting_cb = {}
+ @wrapper_nonce = document.location.href.replace(/.*wrapper_nonce=([A-Za-z0-9]+).*/, "$1")
+ @connect()
+ @next_message_id = 1
+ @history_state = {}
+ @init()
+
+
+ init: ->
+ @
+
+
+ connect: ->
+ @target = window.parent
+ window.addEventListener("message", @onMessage, false)
+ @cmd("innerReady")
+
+ # Save scrollTop
+ window.addEventListener "beforeunload", (e) =>
+ @log "save scrollTop", window.pageYOffset
+ @history_state["scrollTop"] = window.pageYOffset
+ @cmd "wrapperReplaceState", [@history_state, null]
+
+ # Restore scrollTop
+ @cmd "wrapperGetState", [], (state) =>
+ @history_state = state if state?
+ @log "restore scrollTop", state, window.pageYOffset
+ if window.pageYOffset == 0 and state
+ window.scroll(window.pageXOffset, state.scrollTop)
+
+
+ onMessage: (e) =>
+ message = 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 == "wrapperReady" # Wrapper inited later
+ @cmd("innerReady")
+ else if cmd == "ping"
+ @response message.id, "pong"
+ else if cmd == "wrapperOpenedWebsocket"
+ @onOpenWebsocket()
+ else if cmd == "wrapperClosedWebsocket"
+ @onCloseWebsocket()
+ else
+ @onRequest cmd, message.params
+
+
+ onRequest: (cmd, message) =>
+ @log "Unknown request", 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) ->
+ message.wrapper_nonce = @wrapper_nonce
+ message.id = @next_message_id
+ @next_message_id += 1
+ @target.postMessage(message, "*")
+ if cb
+ @waiting_cb[message.id] = cb
+
+
+ onOpenWebsocket: =>
+ @log "Websocket open"
+
+
+ onCloseWebsocket: =>
+ @log "Websocket close"
+
+
+
+window.ZeroFrame = ZeroFrame
diff --git a/plugins/UiPluginManager/media/plugin_manager.html b/plugins/UiPluginManager/media/plugin_manager.html
new file mode 100644
index 00000000..321cbbb3
--- /dev/null
+++ b/plugins/UiPluginManager/media/plugin_manager.html
@@ -0,0 +1,19 @@
+
+
+
+
+ Settings - ZeroNet
+
+
+
+
+
+
+ZeroNet plugin manager
+
+
+
+
+
+
+