summaryrefslogtreecommitdiffstats
path: root/sca-cpp/trunk/hosting
diff options
context:
space:
mode:
authorjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2012-05-28 16:49:36 +0000
committerjsdelfino <jsdelfino@13f79535-47bb-0310-9956-ffa450edef68>2012-05-28 16:49:36 +0000
commita7a8f4f9c9bbbd3bd16605235440dec29f581ad7 (patch)
treef01ccb8694da3d6207302a09eac725094b243d3f /sca-cpp/trunk/hosting
parent7519724a171bb85246bb86bce453cbdd408691d9 (diff)
Improvements to the hosted composite management app. Simplify and optimize the Web UI a bit. Add test cases and fix some of the logic in the management components.
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1343316 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'sca-cpp/trunk/hosting')
-rw-r--r--sca-cpp/trunk/hosting/server/Makefile.am14
-rw-r--r--sca-cpp/trunk/hosting/server/accounts.py5
-rw-r--r--sca-cpp/trunk/hosting/server/apps.py74
-rw-r--r--sca-cpp/trunk/hosting/server/composites.py85
-rw-r--r--sca-cpp/trunk/hosting/server/dashboards.py63
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/account/index.html66
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/app/index.html279
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/cache-manifest.cmf2
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/clone/index.html87
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/config.js31
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/create/index.html70
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/delete/index.html127
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/graph/index.html865
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/home/index.html49
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/index.html214
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/login/index.html173
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/page/index.html572
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/proxy/public/oops/index.html171
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/config.js29
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html64
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html64
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html64
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html64
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/stats/index.html84
-rw-r--r--sca-cpp/trunk/hosting/server/htdocs/store/index.html79
-rw-r--r--sca-cpp/trunk/hosting/server/pages.py88
-rwxr-xr-xsca-cpp/trunk/hosting/server/server-test28
-rw-r--r--sca-cpp/trunk/hosting/server/server.composite10
-rwxr-xr-xsca-cpp/trunk/hosting/server/ssl-start40
-rwxr-xr-xsca-cpp/trunk/hosting/server/start9
-rw-r--r--sca-cpp/trunk/hosting/server/store.py86
-rwxr-xr-xsca-cpp/trunk/hosting/server/test.py296
-rw-r--r--sca-cpp/trunk/hosting/server/test/__init__.py17
-rw-r--r--sca-cpp/trunk/hosting/server/test/cache.py48
-rw-r--r--sca-cpp/trunk/hosting/server/test/property.py38
-rw-r--r--sca-cpp/trunk/hosting/server/test/reference.py38
-rw-r--r--sca-cpp/trunk/hosting/server/user.py7
37 files changed, 2671 insertions, 1429 deletions
diff --git a/sca-cpp/trunk/hosting/server/Makefile.am b/sca-cpp/trunk/hosting/server/Makefile.am
index d7a808c5e4..d140895dcd 100644
--- a/sca-cpp/trunk/hosting/server/Makefile.am
+++ b/sca-cpp/trunk/hosting/server/Makefile.am
@@ -20,16 +20,9 @@ if WANT_PYTHON
moddir = $(prefix)/hosting/server
dist_mod_SCRIPTS = start stop ssl-start mkapplinks config-backup data-backup
-BUILT_SOURCES = htdocs/config.js htdocs/public/config.js
-htdocs/config.js:
- touch htdocs/config.js
+not_minified = htdocs/public/iframe.html htdocs/create/index.html htdocs/page/index.html htdocs/login/index.html htdocs/public/notfound/index.html htdocs/public/oops/index.html htdocs/proxy/public/oops/index.html htdocs/graph/index.html htdocs/public/notauth/index.html htdocs/account/index.html htdocs/home/index.html htdocs/index.html htdocs/public/notyet/index.html htdocs/clone/index.html htdocs/delete/index.html htdocs/stats/index.html htdocs/app/index.html htdocs/store/index.html htdocs/config.js htdocs/public/config.js
-htdocs/public/config.js:
- touch htdocs/public/config.js
-
-not_minified = htdocs/public/iframe.html htdocs/create/index.html htdocs/page/index.html htdocs/login/index.html htdocs/public/notfound/index.html htdocs/public/oops/index.html htdocs/graph/index.html htdocs/public/notauth/index.html htdocs/account/index.html htdocs/home/index.html htdocs/index.html htdocs/public/notyet/index.html htdocs/clone/index.html htdocs/stats/index.html htdocs/app/index.html htdocs/store/index.html htdocs/config.js htdocs/public/config.js
-
-minified = htdocs/public/iframe-min.html htdocs/create/index-min.html htdocs/page/index-min.html htdocs/login/index-min.html htdocs/public/notfound/index-min.html htdocs/public/oops/index-min.html htdocs/graph/index-min.html htdocs/public/notauth/index-min.html htdocs/account/index-min.html htdocs/home/index-min.html htdocs/index-min.html htdocs/public/notyet/index-min.html htdocs/clone/index-min.html htdocs/stats/index-min.html htdocs/app/index-min.html htdocs/store/index-min.html htdocs/config-min.js htdocs/public/config-min.js
+minified = htdocs/public/iframe-min.html htdocs/create/index-min.html htdocs/page/index-min.html htdocs/login/index-min.html htdocs/public/notfound/index-min.html htdocs/public/oops/index-min.html htdocs/proxy/public/oops/index-min.html htdocs/graph/index-min.html htdocs/public/notauth/index-min.html htdocs/account/index-min.html htdocs/home/index-min.html htdocs/index-min.html htdocs/public/notyet/index-min.html htdocs/clone/index-min.html htdocs/delete/index-min.html htdocs/stats/index-min.html htdocs/app/index-min.html htdocs/store/index-min.html htdocs/config-min.js htdocs/public/config-min.js
resources = server.composite *.py htdocs/*.cmf htdocs/*.ico htdocs/home/*.png htdocs/app/*.cmf htdocs/home/*.b64 htdocs/*.txt htdocs/public/*.png htdocs/public/*.b64 data/palettes/*/palette.composite data/accounts/*/*.account data/apps/*/app.composite data/apps/*/app.stats data/apps/*/htdocs/app.html data/dashboards/*/user.apps data/store/*/store.apps ${not_minified} ${minified}
@@ -45,4 +38,7 @@ SUFFIXES = -min.html -min.js
CLEANFILES = ${minified}
+dist_noinst_SCRIPTS = server-test test.py
+TESTS = server-test
+
endif
diff --git a/sca-cpp/trunk/hosting/server/accounts.py b/sca-cpp/trunk/hosting/server/accounts.py
index 4415c69e62..3587f5fb65 100644
--- a/sca-cpp/trunk/hosting/server/accounts.py
+++ b/sca-cpp/trunk/hosting/server/accounts.py
@@ -16,17 +16,18 @@
# under the License.
# Accounts collection implementation
+from time import strftime
from util import *
# Convert a particular user id to an account id
def accountid(user):
- return ("accounts", user.id(), "user.account")
+ return ('accounts', user.get(()), 'user.account')
# Get the current user's account
def get(id, user, cache):
account = cache.get(accountid(user))
if isNil(account) or account is None:
- return ()
+ return (("'entry", ("'title", user.get(())), ("'id", user.get(())), ("'updated", strftime('%b %d, %Y'))),)
return account
# Update the user's account
diff --git a/sca-cpp/trunk/hosting/server/apps.py b/sca-cpp/trunk/hosting/server/apps.py
index 40d1da7dce..064701a7df 100644
--- a/sca-cpp/trunk/hosting/server/apps.py
+++ b/sca-cpp/trunk/hosting/server/apps.py
@@ -16,42 +16,96 @@
# under the License.
# App collection implementation
-import os
+from time import strftime
from util import *
+from sys import debug
# Convert an id to an app id
def appid(id):
return ("apps", car(id), "app.stats")
# Put an app into the apps db
-def put(id, app, cache, store, composites, pages):
- eid = cadr(caddr(car(app)))
- appentry = (("'entry", cadr(car(app)), ("'id", car(id))),)
+def put(id, app, user, cache, dashboard, store, composites, pages):
+ debug('apps.py::put::id', id)
+ debug('apps.py::put::app', app)
- # Update app in apps db
+ # Update an app
+ eid = cadr(assoc("'id", car(app)))
if car(id) == eid:
+ # Check app author
+ eapp = cache.get(appid(id));
+ if (not (isNil(eapp) or eapp is None)) and (cadr(assoc("'author", car(eapp))) != user.get(())):
+ debug('apps.py::put', 'different author', cadr(assoc("'author", car(eapp))))
+ return False
+
+ # Update the app in the apps db
+ appentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), assoc("'content", car(app))),)
+ debug('apps.py::put::appentry', appentry)
cache.put(appid(id), appentry)
+ dashboard.put(id, appentry)
+
+ # Create new page and composite if necessary
+ if isNil(eapp) or eapp is None:
+ comp = (("'entry", ("'title", car(id)), ("'id", car(id))),)
+ composites.put(id, comp);
+ page = (("'entry", ("'title", car(id)), ("'id", car(id))),)
+ pages.put(id, comp);
+ return True
return True
- # Clone an app's composite and page
+ # Check app author
+ eapp = cache.get(appid(id));
+ if (not (isNil(eapp) or eapp is None)) and (cadr(assoc("'author", car(eapp))) != user.get(())):
+ debug('apps.py::put', 'different author', cadr(assoc("'author", car(eapp))))
+ return False
+
+ # Clone an app
+ appentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), assoc("'content", car(app))),)
+ debug('apps.py::put::appentry', appentry)
cache.put(appid(id), appentry)
composites.put(id, composites.get((eid,)))
pages.put(id, pages.get((eid,)))
+ dashboard.put(id, appentry)
return True
# Get an app from the apps db
-def get(id, cache, store, composites, pages):
+def get(id, user, cache, dashboard, store, composites, pages):
+ debug('apps.py::get::id', id)
if isNil(id):
return (("'feed", ("'title", "Apps"), ("'id", "apps")),)
+
+ # Get the requested app
app = cache.get(appid(id));
if isNil(app) or app is None:
- return (("'entry", ("'title", car(id)), ("'id", car(id))),)
+ debug('apps.py::get', 'app not found', id)
+
+ # Return a default new app
+ return (("'entry", ("'title", car(id)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), ("'content", ("'stats", ("'description", '')))),)
+
+ # Return the app
+ debug('apps.py::get::app', app)
return app
# Delete an app from the apps db
-def delete(id, cache, store, composites, pages):
- cache.delete(appid(id))
+def delete(id, user, cache, dashboard, store, composites, pages):
+ debug('apps.py::delete::id', id)
+
+ # Get the requested app
+ app = cache.get(appid(id));
+ if isNil(app) or app is None:
+ debug('apps.py::delete', 'app not found', id)
+ return False
+
+ # Check app author
+ author = cadr(assoc("'author", car(app)))
+ if author != user.get(()):
+ debug('apps.py::delete', 'different author', author)
+ return False
+
+ # Delete the app, its composite and page
+ dashboard.delete(id)
composites.delete(id)
pages.delete(id)
+ cache.delete(appid(id))
return True
diff --git a/sca-cpp/trunk/hosting/server/composites.py b/sca-cpp/trunk/hosting/server/composites.py
index 2b59ebdab4..970bc98a5c 100644
--- a/sca-cpp/trunk/hosting/server/composites.py
+++ b/sca-cpp/trunk/hosting/server/composites.py
@@ -16,29 +16,82 @@
# under the License.
# App composites collection implementation
+from time import strftime
from util import *
+from sys import debug
-# Convert an id to an app id
-def appid(id):
+# Convert an id to a composite id
+def compid(id):
return ("apps", car(id), "app.composite")
-# Put an app into the apps db
-def put(id, app, cache):
- comp = cdr(cadddr(car(app)))
- cache.put(appid(id), comp)
- return True
+# Put a composite into the composite db
+def put(id, comp, user, cache, apps):
+ debug('composites.py::put::id', id)
+ debug('composites.py::put::comp', comp)
-# Get an app from the apps db
-def get(id, cache):
+ # Get the requested app
+ app = apps.get(id);
+ if isNil(app) or app is None:
+ debug('composites.py::put', 'app not found', id)
+ return False
+
+ # Check app author
+ author = cadr(assoc("'author", car(app)))
+ if author != user.get(()):
+ debug('composites.py::put', 'different author', author)
+ return False
+
+ # Update the composite in the composite db
+ compentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), assoc("'content", car(comp))),)
+ debug('composites.py::put::compentry', compentry)
+ return cache.put(compid(id), compentry)
+
+# Get a composite from the composite db
+def get(id, user, cache, apps):
+ debug('composites.py::get::id', id)
if isNil(id):
return (("'feed", ("'title", "Composites"), ("'id", "composites")),)
- app = cache.get(appid(id));
+
+ # Get the requested app
+ app = apps.get(id)
+ if isNil(app) or app is None:
+ debug('composites.py::get', 'app not found', id)
+
+ # Return a default new composite
+ return (("'entry", ("'title", car(id)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y'))),)
+
+ # Get the requested composite
+ comp = cache.get(compid(id));
+ if isNil(comp) or comp is None:
+ debug('composites.py::get', 'composite not found', id)
+
+ # Return a default new composite
+ return (("'entry", ("'title", car(id)), ("'id", car(id)), assoc("'author", car(app)), assoc("'updated", car(app))),)
+
+ # Return the composite
+ def updated(u):
+ return assoc("'updated", car(app)) if isNil(u) or u is None else u
+
+ compentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), assoc("'author", car(app)), updated(assoc("'updated", car(comp))), assoc("'content", car(comp))),)
+ debug('composites.py::get::compentry', compentry)
+ return compentry
+
+# Delete a composite from the composite db
+def delete(id, user, cache, apps):
+ debug('composites.py::delete::id', id)
+
+ # Get the requested app
+ app = apps.get(id);
if isNil(app) or app is None:
- return (("'entry", ("'title", car(id)), ("'id", car(id))),)
- return (("'entry", ("'title", car(id)), ("'id", car(id)), ("'content", car(app))),)
+ debug('composites.py::delete', 'app not found', id)
+ return False
+
+ # Check app author
+ author = cadr(assoc("'author", car(app)))
+ if author != user.get(()):
+ debug('composites.py::delete', 'different author', author)
+ return False
-# Delete an app from the apps db
-def delete(id, cache):
- cache.delete(appid(id))
- return True
+ # Delete the composite
+ return cache.delete(compid(id))
diff --git a/sca-cpp/trunk/hosting/server/dashboards.py b/sca-cpp/trunk/hosting/server/dashboards.py
index c7ea066d4e..d6281d0454 100644
--- a/sca-cpp/trunk/hosting/server/dashboards.py
+++ b/sca-cpp/trunk/hosting/server/dashboards.py
@@ -17,68 +17,89 @@
# Dashboards collection implementation
from util import *
+from sys import debug
# Convert a particular user id to a dashboard id
def dashboardid(user):
- return ("dashboards", user.id(), "user.apps")
+ return ("dashboards", user.get(()), "user.apps")
# Get a dashboard from the cache
def getdashboard(id, cache):
+ debug('dashboards.py::getdashboard::id', id)
val = cache.get(id)
if isNil(val) or val is None:
return ()
- return cdddr(car(val))
+ dashboard = cdddr(car(val))
+ if not isNil(dashboard) and isList(car(cadr(car(dashboard)))):
+ # Expand list of entries
+ edashboard = tuple(map(lambda e: cons("'entry", e), cadr(car(dashboard))))
+ debug('dashboards.py::getdashboard::edashboard', edashboard)
+ return edashboard
+
+ debug('dashboards.py::getdashboard::dashboard', dashboard)
+ return dashboard
# Put a dashboard into the cache
def putdashboard(id, dashboard, cache):
+ debug('dashboards.py::putdashboard::id', id)
+ debug('dashboards.py::putdashboard::dashboard', dashboard)
val = ((("'feed", ("'title", "Your Apps"), ("'id", cadr(id))) + dashboard),)
+ return cache.put(id, val)
# Put an app into the user's dashboard
def put(id, app, user, cache, apps):
- def putapp(app, dashboard):
+ debug('dashboards.py::put::id', id)
+ debug('dashboards.py::put::app', app)
+
+ def putapp(id, app, dashboard):
if isNil(dashboard):
return app
- if cadr(caddr(car(app))) == cadr(caddr(car(dashboard))):
+ if car(id) == cadr(assoc("'id", car(dashboard))):
return cons(car(app), cdr(dashboard))
- return cons(car(dashboard), putapp(app, cdr(dashboard)))
+ return cons(car(dashboard), putapp(id, app, cdr(dashboard)))
- appentry = (("'entry", cadr(car(app)), ("'id", car(id))),)
- dashboard = putapp(appentry, getdashboard(dashboardid(user), cache))
- putdashboard(dashboardid(user), dashboard, cache)
+ appentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), assoc("'updated", car(app)), assoc("'content", car(app))),)
+ debug('dashboards.py::put::appentry', appentry)
- # Update app in app repository
- apps.put(id, app);
- return True
+ dashboard = putapp(id, appentry, getdashboard(dashboardid(user), cache))
+ return putdashboard(dashboardid(user), dashboard, cache)
# Get apps from the user's dashboard
def get(id, user, cache, apps):
+ debug('dashboards.py::get::id', id)
+
def findapp(id, dashboard):
if isNil(dashboard):
return None
- if car(id) == cadr(caddr(car(dashboard))):
+ if car(id) == cadr(assoc("'id", car(dashboard))):
return (car(dashboard),)
return findapp(id, cdr(dashboard))
if isNil(id):
- return ((("'feed", ("'title", "Your Apps"), ("'id", user.id())) + getdashboard(dashboardid(user), cache)),)
- return findapp(id, getdashboard(dashboardid(user), cache))
+ dashboard = ((("'feed", ("'title", "Your Apps"), ("'id", user.get(()))) + getdashboard(dashboardid(user), cache)),)
+ debug('dashboards.py::get::dashboard', dashboard)
+ return dashboard
+
+ app = findapp(id, getdashboard(dashboardid(user), cache))
+ debug('dashboards.py::get::app', app)
+ return app
# Delete apps from the user's dashboard
def delete(id, user, cache, apps):
+ debug('dashboards.py::delete::id', id)
if isNil(id):
return cache.delete(dashboardid(user))
def deleteapp(id, dashboard):
if isNil(dashboard):
return ()
- if car(id) == cadr(caddr(car(dashboard))):
+ if car(id) == cadr(assoc("'id", car(dashboard))):
return cdr(dashboard)
return cons(car(dashboard), deleteapp(id, cdr(dashboard)))
- dashboard = deleteapp(id, getdashboard(dashboardid(user), cache))
- putdashboard(dashboardid(user), dashboard, cache)
-
- # Delete app from app repository
- apps.delete(id);
- return True
+ dashboard = getdashboard(dashboardid(user), cache)
+ deleted = deleteapp(id, dashboard)
+ if deleted == dashboard:
+ return False
+ return putdashboard(dashboardid(user), deleted, cache)
diff --git a/sca-cpp/trunk/hosting/server/htdocs/account/index.html b/sca-cpp/trunk/hosting/server/htdocs/account/index.html
index 291f6dea1c..dce34d2ef3 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/account/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/account/index.html
@@ -17,29 +17,18 @@
* specific language governing permissions and limitations
* under the License.
-->
-<div id="bodydiv" class="bodydiv">
+<div id="bodydiv" class="body">
-<table style="width: 100%;">
-<tr>
-<td><h2><span id="h1"></span><span id="userNameHeader"></span></h2></td>
-<td style="vertical-align: middle; text-align: right;"><span id="status" style="font-weight: bold; color: #808080;"></span></td>
-</tr>
-</table>
-
-<table style="width: 100%;">
-<tr>
-<th class="thl thr" style="padding-top: 4px; padding-bottom: 4px; padding-left: 2px; padding-right: 2px; ">Account</th>
-</tr>
-</table>
+<div class="viewform">
<form id="userForm">
<table style="width: 100%;">
<tr><tr><td><b>Photo:</b></td></tr>
<tr><td><img id="userimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
<tr><tr><td style="padding-top: 6px;"><b>Name:</b></td></tr>
-<tr><td><input type="text" id="userTitle" size="30" placeholder="Enter your name" style="width: 300px;"/></td></tr>
+<tr><td><input type="text" id="userTitle" class="flatentry" size="30" placeholder="Enter your name" style="width: 300px;"/></td></tr>
<tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr>
-<tr><td><textarea id="userDescription" cols="40" rows="3" placeholder="Enter a short description of yourself" style="width: 300px;"></textarea></td></tr>
+<tr><td><textarea id="userDescription" class="flatentry" cols="40" rows="3" placeholder="Enter a short description of yourself" style="width: 300px;"></textarea></td></tr>
</table>
<br/>
@@ -50,11 +39,11 @@
</table>
<table>
-<tr><td style="padding-right: 2px;"><input type="text" id="sched1" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service1" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="sched2" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service2" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="sched3" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service3" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="sched4" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service4" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="sched5" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service5" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="sched1" class="flatentry" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service1" class="flatentry" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="sched2" class="flatentry" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service2" class="flatentry" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="sched3" class="flatentry" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service3" class="flatentry" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="sched4" class="flatentry" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service4" class="flatentry" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="sched5" class="flatentry" size="10" placeholder="Schedule" style="width: 80px;"/></td><td><input type="text" id="service5" class="flatentry" size="2048" placeholder="Service URL" style="width: 200px;"/></td></tr>
</table>
<br/>
@@ -65,28 +54,31 @@
</table>
<table>
-<tr><td style="padding-right: 2px;"><input type="text" id="name1" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value1" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="name2" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value2" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="name3" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value3" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="name4" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value4" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="name5" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value5" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="name6" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value6" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="name7" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value7" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="name8" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value8" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="name9" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value9" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
-<tr><td style="padding-right: 2px;"><input type="text" id="name10" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value10" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name1" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value1" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name2" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value2" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name3" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value3" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name4" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value4" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name5" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value5" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name6" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value6" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name7" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value7" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name8" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value8" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name9" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value9" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
+<tr><td style="padding-right: 2px;"><input type="text" id="name10" class="flatentry" size="10" placeholder="Key name" style="width: 80px;"/></td><td><input type="text" id="value10" class="flatentry" size="2048" placeholder="Key value" style="width: 200px;"/></td></tr>
</table>
</form>
+</div>
+
<script type="text/javascript">
// Init service references
var editWidget = sca.component("EditWidget");
-var user= sca.defun(sca.reference(editWidget, "user"), "id");
+var user= sca.defun(sca.reference(editWidget, "user"));
var accounts = sca.reference(editWidget, "accounts");
// Set page titles
document.title = ui.windowtitle(location.hostname) + ' - Account';
+$('viewhead').innerHTML = '<span class="smenu">' + username + '</span>';
// Set images
$('userimg').src = ui.b64img(appcache.get('/public/user.b64'));
@@ -94,30 +86,28 @@ $('userimg').src = ui.b64img(appcache.get('/public/user.b64'));
/**
* The current account entry and corresponding saved XML content.
*/
-var username;
var accountentry;
var savedaccountentryxml = '';
/**
* Get and display the user's account.
*/
-function getaccount(name) {
+function getaccount() {
showStatus('Loading');
return accounts.get(name, function(doc) {
// Stop now if we didn't get an account
if (doc == null) {
- showStatus('No data');
+ showError('App not available');
return false;
}
showStatus(defaultStatus());
- accountentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", name));
- username = cadr(assoc("'id", cdr(accountentry)));
- var title = cadr(assoc("'title", cdr(accountentry)));
+ accountentry = car(elementsToValues(atom.readATOMEntry(mklist(doc))));
+ //username = cadr(assoc("'id", cdr(accountentry)));
$('userNameHeader').innerHTML = username;
- $('userTitle').value = title;
+ $('userTitle').value = cadr(assoc("'title", cdr(accountentry)));
var content = cadr(assoc("'content", cdr(accountentry)));
var acct = isNil(content)? mklist() : cdr(content);
diff --git a/sca-cpp/trunk/hosting/server/htdocs/app/index.html b/sca-cpp/trunk/hosting/server/htdocs/app/index.html
index 19fa7488a5..30bd1d9999 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/app/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/app/index.html
@@ -37,28 +37,31 @@ appcache.get = function(uri) {
var u = h == -1? uri : uri.substring(0, h);
// Get resource from local storage first
- var item = localStorage.getItem(u);
+ var ls = window.lstorage || localStorage;
+ var item = null;
+ try { item = ls.getItem(u); } catch(e) {}
if (item != null && item != '')
return item;
// Get resource from network
var http = new XMLHttpRequest();
http.open("GET", u, false);
+ http.setRequestHeader("Accept", "*/*");
http.send(null);
if (http.status == 200) {
if (http.getResponseHeader("X-Login") != null) {
- if (log) log('http error', u, 'X-Login');
+ if (debug) debug('http error', u, 'X-Login');
// Redirect to login page if not signed in
document.location = '/login/';
return null;
} else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
- if (log) log('http error', u, 'No-Content');
+ if (debug) debug('http error', u, 'No-Content');
return null;
}
- localStorage.setItem(u, http.responseText);
+ try { ls.setItem(u, http.responseText); } catch(e) {}
return http.responseText;
}
- if (log) log('http error', u, http.status, http.statusText);
+ if (debug) debug('http error', u, http.status, http.statusText);
// Redirect to login page if not signed in
if (http.status == 403)
document.location = '/login/';
@@ -114,10 +117,17 @@ document.title = appname;
var contentdiv = $('content');
/**
+ * The main app composite and page definitions.
+ */
+var appcomposite = null;
+var apppage = null;
+
+/**
* Initialize the app HTTP clients.
*/
var appWidget = sca.component('AppWidget');
var pagecomp = sca.reference(appWidget, 'pages');
+var composcomp = sca.reference(appWidget, 'composites');
var startcomp = sca.httpclient('start', '/' + appname + '/start');
var stopcomp = sca.httpclient('stop', '/' + appname + '/stop');
var timercomp = sca.httpclient('timer', '/' + appname + '/timer');
@@ -138,27 +148,29 @@ var appresources = [
* Handle application cache events.
*/
applicationCache.addEventListener('checking', function(e) {
- //log('appcache checking', e);
+ //debug('appcache checking', e);
}, false);
applicationCache.addEventListener('error', function(e) {
- //log('appcache error', e);
+ //debug('appcache error', e);
}, false);
applicationCache.addEventListener('noupdate', function(e) {
- //log('appcache noupdate', e);
+ //debug('appcache noupdate', e);
}, false);
applicationCache.addEventListener('downloading', function(e) {
- //log('appcache downloading', e);
+ //debug('appcache downloading', e);
}, false);
applicationCache.addEventListener('progress', function(e) {
- //log('appcache progress', e);
+ //debug('appcache progress', e);
}, false);
applicationCache.addEventListener('updateready', function(e) {
- //log('appcache updateready', e);
- applicationCache.swapCache();
- //log('appcache swapped', e);
+ //debug('appcache updateready', e);
+ try {
+ applicationCache.swapCache();
+ } catch(e) {}
+ //debug('appcache swapped', e);
}, false);
applicationCache.addEventListener('cached', function(e) {
- //log('appcache cached', e);
+ //debug('appcache cached', e);
map(function(res) {
appcache.get(res[0]);
}, appresources);
@@ -168,13 +180,13 @@ applicationCache.addEventListener('cached', function(e) {
* Handle network offline/online events.
*/
window.addEventListener('offline', function(e) {
- //log('going offline');
+ //debug('going offline');
}, false);
window.addEventListener('online', function(e) {
- //log('going online');
+ //debug('going online');
}, false);
-//log(navigator.onLine? 'online' : 'offline');
+//debug(navigator.onLine? 'online' : 'offline');
/**
* Find a named value in a tree of elements. The value name is given
@@ -545,17 +557,24 @@ function initwidget(e) {
}
/**
+ * Return the component bound to a uri.
+ */
+function isbound(uri, comps) {
+ return !isNil(filter(function(comp) {
+ return !isNil(filter(function(svc) {
+ return !isNil(filter(function(b) {
+ return uri == scdl.uri(b);
+ }, scdl.bindings(svc)));
+ }, scdl.services(comp)));
+ }, comps));
+}
+
+/**
* Get app data from the main app page component.
*/
-function getpagedata(appname) {
+function getappdata(appname, page, compos) {
try {
- // Display component data on the page
- function displaypage(doc) {
- updatepage(docdata(doc));
- return true;
- }
-
// Eval a component init script
function evalcompinit(doc) {
if (isNil(doc))
@@ -577,55 +596,104 @@ function getpagedata(appname) {
// Setup the widgets
map(setupwidget, filter(function(e) { return !isNil(e.id); }, nodeList(ui.elementByID(contentdiv, 'page').childNodes)));
+ // Get the app components
+ var comps = scdl.components(compos);
+
// Get the component app data
- startcomp.get(location.search, function(doc, e) {
- if (isNil(doc)) {
- log('error on get(start, ' + location.search + ')', e);
- return false;
- }
+ if (isbound("start", comps)) {
+ startcomp.get(location.search, function(doc, e) {
+ if (isNil(doc)) {
+ debug('error on get(start, ' + location.search + ')', e);
+ return false;
+ }
- // Display data on the page
- displaypage(doc);
- });
+ // Display data on the page
+ updatepage(docdata(doc));
+ });
+ }
// Get and eval the optional timer, animation and location watch setup scripts
- timercomp.get('setup', function(doc, e) {
- if (isNil(doc)) {
- log('error on get(timer, setup)', e);
- return false;
- }
+ if (isbound("timer", comps)) {
+ timercomp.get('setup', function(doc, e) {
+ if (isNil(doc)) {
+ debug('error on get(timer, setup)', e);
+ return false;
+ }
- // Evaluate the component init expression
- return evalcompinit(doc);
- });
- animationcomp.get('setup', function(doc, e) {
- if (isNil(doc)) {
- log('error on get(animation, setup)', e);
- return false;
- }
+ // Evaluate the component init expression
+ return evalcompinit(doc);
+ });
+ }
- // Evaluate the component init expression
- return evalcompinit(doc);
- });
- locationcomp.get('setup', function(doc, e) {
- if (isNil(doc)) {
- log('error on get(location, setup)', e);
- return false;
- }
+ if (isbound("animation", comps)) {
+ animationcomp.get('setup', function(doc, e) {
+ if (isNil(doc)) {
+ debug('error on get(animation, setup)', e);
+ return false;
+ }
- // Evaluate the component init expression
- return evalcompinit(doc);
- });
+ // Evaluate the component init expression
+ return evalcompinit(doc);
+ });
+ }
+
+ if (isbound("location", comps)) {
+ locationcomp.get('setup', function(doc, e) {
+ if (isNil(doc)) {
+ debug('error on get(location, setup)', e);
+ return false;
+ }
+
+ // Evaluate the component init expression
+ return evalcompinit(doc);
+ });
+ }
return true;
} catch(e) {
- log('error in getpagedata()', e);
+ debug('error in getappdata()', e);
return true;
}
}
/**
+ * Return the page in an ATOM entry.
+ */
+function atompage(doc) {
+ var entry = atom.readATOMEntry(mklist(doc));
+ if (isNil(entry))
+ return mklist();
+ var content = namedElementChild("'content", car(entry));
+ if (content == null)
+ return mklist();
+ return elementChildren(content);
+}
+
+/**
+ * Get the app page.
+ */
+function getapppage(appname, compos) {
+ pagecomp.get(appname, function(doc, e) {
+ //debug('page get');
+ if (isNil(doc)) {
+ debug('error in getapppage', e);
+ return false;
+ }
+
+ // Set the app HTML page into the content div
+ var page = atompage(doc);
+ contentdiv.innerHTML = writeStrings(writeXML(page, false));
+ apppage = page;
+
+ // Merge in the app data
+ if (!isNil(appcomposite))
+ getappdata(appname, apppage, appcomposite);
+});
+
+}
+
+/**
* Build a query string from the values of the page's input fields.
*/
function compquery() {
@@ -658,7 +726,7 @@ function buttonClickHandler(id, appname) {
var uri = compquery();
return sca.component(id, appname).get(uri, function(doc, e) {
if (isNil(doc)) {
- log('error on get(button, ' + uri + ')', e);
+ debug('error on get(button, ' + uri + ')', e);
return false;
}
@@ -666,7 +734,7 @@ function buttonClickHandler(id, appname) {
updatepage(docdata(doc));
});
} catch(e) {
- log('error in buttonClickHandler()', e);
+ debug('error in buttonClickHandler()', e);
return true;
}
}
@@ -679,7 +747,7 @@ function intervalHandler() {
var uri = compquery();
return timercomp.get(uri, function(doc, e) {
if (isNil(doc)) {
- log('error on get(timer, ' + uri + ')', e);
+ debug('error on get(timer, ' + uri + ')', e);
return false;
}
@@ -687,7 +755,7 @@ function intervalHandler() {
updatepage(docdata(doc));
});
} catch(e) {
- log('error in intervalHandler()', e);
+ debug('error in intervalHandler()', e);
return true;
}
}
@@ -700,7 +768,7 @@ function setupIntervalHandler(msec) {
try {
return setInterval(intervalHandler, msec);
} catch(e) {
- log('error in setupIntervalHandler()', e);
+ debug('error in setupIntervalHandler()', e);
return true;
}
}
@@ -753,7 +821,7 @@ function animationHandler() {
var uri = compquery();
return animationcomp.get(uri, function(doc, e) {
if (isNil(doc)) {
- log('error on get(animation, ' + uri + ')', e);
+ debug('error on get(animation, ' + uri + ')', e);
return false;
}
@@ -769,7 +837,7 @@ function animationHandler() {
return applyAnimation();
} catch(e) {
- log('error in animationHandler()', e);
+ debug('error in animationHandler()', e);
return true;
}
}
@@ -783,7 +851,7 @@ function setupAnimationHandler(msec, loop) {
try {
return setInterval(animationHandler, msec);
} catch(e) {
- log('error in setupAnimationHandler()', e);
+ debug('error in setupAnimationHandler()', e);
return true;
}
}
@@ -800,7 +868,7 @@ function locationHandler(pos) {
var uri = compquery();
return locationcomp.get(uri, function(doc, e) {
if (isNil(doc)) {
- log('error on get(location, ' + uri + ')', e);
+ debug('error on get(location, ' + uri + ')', e);
return false;
}
@@ -813,7 +881,7 @@ function locationHandler(pos) {
}
function locationErrorHandler(e) {
- log('location error', e);
+ debug('location error', e);
if (!isNil(locationWatch)) {
try {
navigator.geolocation.clearWatch(locationWatch);
@@ -833,7 +901,7 @@ function setupLocationHandler() {
try {
locationWatch = navigator.geolocation.watchPosition(locationHandler, locationErrorHandler);
} catch(e) {
- log('error in installLocationHandler()', e);
+ debug('error in installLocationHandler()', e);
}
return true;
}
@@ -844,9 +912,21 @@ function setupLocationHandler() {
}
/**
- * Return the page in an ATOM entry.
+ * Handle orientation change.
*/
-function atompage(doc) {
+document.body.onorientationchange = function(e) {
+ //debug('onorientationchange');
+
+ // Scroll to the top and hide the address bar
+ window.scrollTo(0, 0);
+
+ return true;
+};
+
+/**
+ * Return the composite in an ATOM entry.
+ */
+function atomcomposite(doc) {
var entry = atom.readATOMEntry(mklist(doc));
if (isNil(entry))
return mklist();
@@ -856,46 +936,51 @@ function atompage(doc) {
return elementChildren(content);
}
-// Load the app page
-pagecomp.get(appname, function(doc, e) {
- //log('page get');
- if (isNil(doc)) {
- log('error getting app page', e);
- return false;
- }
-
- // Set the app HTML page into the content div
- //log('page', doc);
- var el = atompage(doc);
- contentdiv.innerHTML = writeStrings(writeXML(el, false));
-
- // Merge in the app data
- getpagedata(appname);
-});
-
/**
- * Handle orientation change.
+ * Get the app composite.
*/
-document.body.onorientationchange = function(e) {
- //log('onorientationchange');
+function getappcomposite(appname) {
+ return composcomp.get(appname, function(doc, e) {
+ //debug('page get');
+ if (isNil(doc)) {
+ debug('error in getappcomposite', e);
+ return false;
+ }
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
+ var compos = atomcomposite(doc);
+ if (isNil(compos)) {
- return true;
-};
+ // Create a default empty composite if necessary
+ var x = '<composite xmlns="http://docs.oasis-open.org/ns/opencsa/sca/200912" ' +
+ 'targetNamespace="http://app" name="app"></composite>';
+ compos = readXML(mklist(x));
+ }
+ appcomposite = compos;
+
+ // Merge in the app data
+ if (!isNil(apppage))
+ getappdata(appname, apppage, appcomposite);
+ });
+}
/**
* Document load post processing.
*/
function onload() {
- //log('onload');
+ //debug('onload');
+
+ // Scroll to the top and hide the address bar
+ window.scrollTo(0, 0);
// Show the page
document.body.style.visibility = 'visible';
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
+ // Initialize the app composite
+ getappcomposite(appname);
+
+ // Initialize the app page
+ getapppage(appname);
+
return true;
}
diff --git a/sca-cpp/trunk/hosting/server/htdocs/cache-manifest.cmf b/sca-cpp/trunk/hosting/server/htdocs/cache-manifest.cmf
index cb76f773a3..e2c44def89 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/cache-manifest.cmf
+++ b/sca-cpp/trunk/hosting/server/htdocs/cache-manifest.cmf
@@ -1,6 +1,6 @@
CACHE MANIFEST
-# Version 5
+# Version 6
# App resources
/
diff --git a/sca-cpp/trunk/hosting/server/htdocs/clone/index.html b/sca-cpp/trunk/hosting/server/htdocs/clone/index.html
index 3642634ed6..f5557efcd3 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/clone/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/clone/index.html
@@ -17,59 +17,45 @@
* specific language governing permissions and limitations
* under the License.
-->
-<div id="bodydiv" class="bodydiv">
+<div id="bodydiv" class="body">
-<table style="width: 100%;">
-<tr>
-<td><h2><span id="h1"></span><span id="appNameHeader"></span></h2></td>
-<td style="vertical-align: middle; text-align: right;"><span id="status" style="font-weight: bold; color: #808080;"></span></td>
-</tr>
-</table>
-
-<table style="width: 100%;">
-<tr>
-<th id="th" class="thl thr" style="padding-top: 4px; padding-bottom:4px;">Clone this App</th>
-</tr>
-</table>
+<div class="viewform">
<form id="cloneAppForm">
<table style="width: 100%;">
<tr><td><b>New App Name:</b></td></tr>
-<tr><td><input type="text" id="appName" size="15" autocapitalize="off" placeholder="Your app name"/></td></tr>
-<tr><tr><td style="padding-top: 6px;"><b>App Icon:</b></td></tr>
+<tr><td><input type="text" id="appName" class="flatentry" size="15" autocapitalize="off" placeholder="Your app name"/></td></tr>
+<tr><tr><td style="padding-top: 6px;"><b>Icon:</b></td></tr>
<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
-<tr><tr><td style="padding-top: 6px;"><b>Sharing:</b></td></tr>
-<tr><td><input type="checkbox" value="shared"/><span>Shared</span></td></tr>
-<tr><tr><td style="padding-top: 6px;"><b>App Title:</b></td></tr>
-<tr><td><input type="text" id="appTitle" size="30" placeholder="Enter the title of your app" style="width: 300px;"/></td></tr>
+<tr><tr><td style="padding-top: 6px;"><b>Title:</b></td></tr>
+<tr><td><input type="text" id="appTitle" class="flatentry" size="30" placeholder="Enter the title of your app" style="width: 300px;"/></td></tr>
<tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr>
-<tr><td><textarea id="appDescription" cols="40" rows="3" placeholder="Enter a short description of your app" style="width: 300px;"></textarea></td></tr>
+<tr><td><textarea id="appDescription" class="flatentry" cols="40" rows="3" placeholder="Enter a short description of your app" style="width: 300px;"></textarea></td></tr>
<tr><td>
-<input id="cloneAppOKButton" type="submit" class="graybutton" style="font-weight: bold;" value="Clone" title="Clone the app"/>
+<input id="cloneAppOKButton" type="submit" class="graybutton bluebutton" style="font-weight: bold;" value="Clone" title="Clone the app"/>
<input id="cloneAppCancelButton" type="button" class="graybutton" value="Cancel"/>
</td></tr>
</table>
</form>
+</div>
+
<script type="text/javascript">
// Get the app name
var appname = ui.fragmentParams(location)['app'];
// Set page titles
-var tclone = isNil(config.clone)? 'Clone' : config.clone;
-document.title = ui.windowtitle(location.hostname) + ' - ' + tclone + ' - ' + appname;
-$('appNameHeader').innerHTML = '<a href=\"/' + appname + '/\" target=\"' + '_blank' + '\">' + appname + '</a>';
-$('th').innerHTML = tclone + ' this App';
-$('cloneAppOKButton').value = tclone;
-$('cloneAppOKButton').title = tclone + ' this app';
+document.title = ui.windowtitle(location.hostname) + ' - ' + config.clone + ' - ' + appname;
+$('viewhead').innerHTML = '<span class="smenu">' + config.clone + ' ' + appname + '</span>';
+$('cloneAppOKButton').value = config.clone;
+$('cloneAppOKButton').title = config.clone + ' this app';
// Set images
$('appimg').src = ui.b64img(appcache.get('/public/app.b64'));
// Init service references
var editWidget = sca.component("EditWidget");
-var dashboards = sca.reference(editWidget, "dashboards");
var apps = sca.reference(editWidget, "apps");
/**
@@ -90,45 +76,58 @@ function getapp(name) {
// Stop now if we didn't get the app
if (doc == null) {
- showStatus('No data');
+ showError('App not available');
return false;
}
showStatus(defaultStatus());
appentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", name));
- var title = cadr(assoc("'title", cdr(appentry)));
- $('appTitle').value = title;
- $('appDescription').innerHTML = '';
+ $('appTitle').value = cadr(assoc("'title", cdr(appentry)));
+ var content = cadr(assoc("'content", cdr(appentry)));
+ var description = assoc("'description", content);
+ $('appDescription').value = isNil(description) || isNil(cadr(description))? '' : cadr(description);
savedappentryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry))));
return true;
});
}
/**
- * Clone an app.
+ * Save an app.
*/
-$('cloneAppForm').onsubmit = function() {
- var name = $('appName').value;
- if (name == '')
- return false;
+function save(name, entryxml) {
showStatus('Saving');
-
- // Clone the app
- var title = $('appTitle').value;
- var app = mklist(mklist("'entry", mklist("'title", title != ''? title : name), mklist("'id", appname)));
- var entry = atom.writeATOMEntry(valuesToElements(app));
- dashboards.put(name, car(entry), function(e) {
+ savedappentryxml = entryxml;
+ apps.put(name, savedappentryxml, function(e) {
if (e) {
showStatus('Local copy');
return false;
}
- showStatus(defaultStatus());
+ showStatus('Saved');
// Open it in the page editor
ui.navigate('/#view=page&app=' + name, '_view');
return false;
});
return false;
+}
+
+/**
+ * Clone an app.
+ */
+$('cloneAppForm').onsubmit = function() {
+ var name = $('appName').value;
+ if (name == '') {
+ showError('Missing app name');
+ return false;
+ }
+ showStatus('Saving');
+
+ // Clone the app
+ var title = $('appTitle').value;
+ var description = $('appDescription').value;
+ appentry = mklist("'entry", mklist("'title", title != ''? title : name), mklist("'id", appname), mklist("'content", mklist("'stats", mklist("'description", description))));
+ var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry))));
+ return save(name, entryxml);
};
/**
diff --git a/sca-cpp/trunk/hosting/server/htdocs/config.js b/sca-cpp/trunk/hosting/server/htdocs/config.js
new file mode 100644
index 0000000000..de1d1410cd
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/config.js
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+if (isNil(config))
+ config = {};
+
+/**
+ * UI configuration.
+ */
+config.windowtitle = 'App Builder'
+config.pagetitle = '<span style="font-weight: bold;">App Builder</span>';
+config.hometitle = '<br/><span style="font-weight: bold;">Create SCA Composite Apps</span><br/><br/>';
+config.clone = 'Clone';
+config.logic = 'Logic';
+
diff --git a/sca-cpp/trunk/hosting/server/htdocs/create/index.html b/sca-cpp/trunk/hosting/server/htdocs/create/index.html
index 6097053cd6..825587627b 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/create/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/create/index.html
@@ -17,68 +17,55 @@
* specific language governing permissions and limitations
* under the License.
-->
-<div id="bodydiv" class="bodydiv">
+<div id="bodydiv" class="body">
-<table style="width: 100%;">
-<tr>
-<td><h2><span id="h1"></span></h2></td>
-<td style="vertical-align: middle; text-align: right;"><span id="status" style="font-weight: bold; color: #808080;"></span></td>
-</tr>
-</table>
-
-<table style="width: 100%;">
-<tr>
-<th class="thl thr" style="padding-top: 4px; padding-bottom:4px;">Create an App</th>
-</tr>
-</table>
+<div class="viewform">
<form id="createAppForm">
<table style="width: 100%;">
<tr><td><b>App Name:</b></td></tr>
-<tr><td><input type="text" id="appName" size="15" autocapitalize="off" placeholder="Your app name"/></td></tr>
+<tr><td><input type="text" id="appName" class="flatentry" size="15" autocapitalize="off" placeholder="Your app name"/></td></tr>
<tr><tr><td style="padding-top: 6px;"><b>App Icon:</b></td></tr>
<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
-<tr><tr><td style="padding-top: 6px;"><b>Sharing:</b></td></tr>
-<tr><td><input type="checkbox" value="shared"/><span>Shared</span></td></tr>
<tr><tr><td style="padding-top: 6px;"><b>App Title:</b></td></tr>
-<tr><td><input type="text" id="appTitle" size="30" placeholder="Enter the title of your app" style="width: 300px;"/></td></tr>
+<tr><td><input type="text" id="appTitle" class="flatentry" size="30" placeholder="Enter the title of your app" style="width: 300px;"/></td></tr>
<tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr>
-<tr><td><textarea id="appDescription" cols="40" rows="3" placeholder="Enter a short description of your app" style="width: 300px;"></textarea></td></tr>
+<tr><td><textarea id="appDescription" class="flatentry" cols="40" rows="3" placeholder="Enter a short description of your app" style="width: 300px;"></textarea></td></tr>
<tr><td>
-<input id="createAppOKButton" type="submit" class="graybutton" style="font-weight: bold;" value="Create" title="Create the app"/>
+<input id="createAppOKButton" type="submit" class="graybutton bluebutton" style="font-weight: bold;" value="Create" title="Create the app"/>
<input id="createAppCancelButton" type="button" class="graybutton" value="Cancel"/>
</td></tr>
</table>
</form>
+</div>
+
<script type="text/javascript">
// Set page titles
document.title = ui.windowtitle(location.hostname) + ' - Create App';
-$('h1').innerHTML = ui.hometitle(location.hostname);
+$('viewhead').innerHTML = '<span class="smenu">Create an App</span>';
// Set images
$('appimg').src = ui.b64img(appcache.get('/public/app.b64'));
// Init service references
var editWidget = sca.component("EditWidget");
-var dashboards = sca.reference(editWidget, "dashboards");
var apps = sca.reference(editWidget, "apps");
/**
- * Create an app.
+ * The current app entry and corresponding saved XML content.
*/
-$('createAppForm').onsubmit = function() {
- var name = $('appName').value;
- if (name == '')
- return false;
- showStatus('Saving');
+var appentry;
+var savedappentryxml = '';
- // Clone the '.new' app template
- var title = $('appTitle').value;
- var app = mklist(mklist("'entry", mklist("'title", title != ''? title : name), mklist("'id", 'new')));
- var entry = atom.writeATOMEntry(valuesToElements(app));
- dashboards.put(name, car(entry), function(e) {
+/**
+ * Save an app.
+ */
+function save(name, entryxml) {
+ showStatus('Saving');
+ savedappentryxml = entryxml;
+ apps.put(name, savedappentryxml, function(e) {
if (e) {
showStatus('Local copy');
return false;
@@ -90,6 +77,25 @@ $('createAppForm').onsubmit = function() {
return false;
});
return false;
+}
+
+/**
+ * Create an app.
+ */
+$('createAppForm').onsubmit = function() {
+ var name = $('appName').value;
+ if (name == '') {
+ showError('Missing app name');
+ return false;
+ }
+ showStatus('Modified');
+
+ // Clone the 'new' app template
+ var title = $('appTitle').value;
+ var description = $('appDescription').value;
+ appentry = mklist("'entry", mklist("'title", title != ''? title : name), mklist("'id", 'new'), mklist("'content", mklist("'stats", mklist("'description", description))));
+ var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry))));
+ return save(name, entryxml);
};
/**
diff --git a/sca-cpp/trunk/hosting/server/htdocs/delete/index.html b/sca-cpp/trunk/hosting/server/htdocs/delete/index.html
new file mode 100644
index 0000000000..75869a4f28
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/delete/index.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<div id="bodydiv" class="body">
+
+<div class="viewform">
+
+<form id="deleteAppForm">
+<table style="width: 100%;">
+<tr><tr><td style="padding-top: 6px;"><b>App Icon:</b></td></tr>
+<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
+<tr><tr><td style="padding-top: 6px;"><b>App Title:</b></td></tr>
+<tr><td><input type="text" id="appTitle" class="flatentry" size="30" readonly="readonly" style="width: 300px;"/></td></tr>
+<tr><tr><td style="padding-top: 6px;"><b>Author:</b></td></tr>
+<tr><td><span id="appAuthor"></span></td></tr>
+<tr><tr><td style="padding-top: 6px;"><b>Updated:</b></td></tr>
+<tr><td><span id="appUpdated"></span></td></tr>
+<tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr>
+<tr><td><textarea id="appDescription" class="flatentry" cols="40" rows="3" readonly="readonly" style="width: 300px;"></textarea></td></tr>
+<tr><td>
+<input id="deleteAppOKButton" type="submit" class="graybutton bluebutton" style="font-weight: bold;" value="Delete" title="Delete the app"/>
+<input id="deleteAppCancelButton" type="button" class="graybutton" value="Cancel"/>
+</td></tr>
+</table>
+</form>
+
+</div>
+
+<script type="text/javascript">
+
+// Get the app name
+var appname = ui.fragmentParams(location)['app'];
+
+// Set page titles
+document.title = ui.windowtitle(location.hostname) + ' - ' + 'Delete' + ' - ' + appname;
+$('viewhead').innerHTML = '<span class="smenu">Delete ' + appname + '</span>';
+
+// Set images
+$('appimg').src = ui.b64img(appcache.get('/public/app.b64'));
+
+// Init service references
+var editWidget = sca.component("EditWidget");
+var apps = sca.reference(editWidget, "apps");
+
+/**
+ * The current app entry and corresponding saved XML content.
+ */
+var appentry;
+
+/**
+ * Get and display an app.
+ */
+function getapp(name) {
+ if (isNil(name))
+ return false;
+ showStatus('Loading');
+
+ return apps.get(name, function(doc) {
+
+ // Stop now if we didn't get the app
+ if (doc == null) {
+ showError('App not available');
+ return false;
+ }
+ showStatus(defaultStatus());
+
+ appentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", name));
+ $('appTitle').value = cadr(assoc("'title", cdr(appentry)));
+ $('appAuthor').innerHTML = cadr(assoc("'author", cdr(appentry)));
+ $('appUpdated').innerHTML = cadr(assoc("'updated", cdr(appentry)));
+ var content = cadr(assoc("'content", cdr(appentry)));
+ var description = assoc("'description", content);
+ $('appDescription').value = isNil(description) || isNil(cadr(description))? '' : cadr(description);
+ return true;
+ });
+}
+
+/**
+ * Delete an app.
+ */
+$('deleteAppForm').onsubmit = function() {
+ showStatus('Deleting');
+
+ // Delete the app
+ apps.del(appname, function(e) {
+ if (e) {
+ showStatus('Local copy');
+ return false;
+ }
+ showStatus(defaultStatus());
+
+ // Return to the app store
+ ui.navigate('/#view=store', '_view');
+ return false;
+ });
+ return false;
+};
+
+/**
+ * Cancel cloning an app.
+ */
+$('deleteAppCancelButton').onclick = function() {
+ history.back();
+};
+
+// Get the current app
+getapp(appname);
+
+</script>
+
+</div>
diff --git a/sca-cpp/trunk/hosting/server/htdocs/graph/index.html b/sca-cpp/trunk/hosting/server/htdocs/graph/index.html
index 34b7ead90e..6f73aec5ec 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/graph/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/graph/index.html
@@ -17,42 +17,11 @@
* specific language governing permissions and limitations
* under the License.
-->
-<div id="bodydiv" class="bodydiv" style="overflow: visible;">
+<div id="bodydiv" class="body">
-<table style="width: 100%;">
-<tr>
-<td><h2><span id="appNameHeader"></span></h2></td>
-<td style="vertical-align: middle; text-align: right; padding-right: 8px;"><span id="status" style="font-weight: bold; color: #808080;"></span></td>
-</tr>
-</table>
-
-<table id="compValueBackground" style="width: 2500px; position: absolute; top: 59px; left: 0px; z-index: -1;">
-<tr>
-<th class="thr thl"><span style="display: inline-block; padding-top: 0px; padding-bottom: 0px; height: 24px;"></span></th>
-</tr>
-</table>
-
-<table id="compValueTable" style="width: 100%;">
-<tr>
-<td class="thl thr" style="text-align: right; padding-right: 2px; vertical-align: top;">
-<span id="deleteCompButton" title="Delete a component" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">-</span>
-
-<span id="copyCompButton" title="Copy a component" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">c</span>
-
-<span id="addCompButton" title="Add a component" class="graybutton" style="font-weight: bold; font-size: 16px; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">+</span>
-
-<span id="playCompButton" title="View component value" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">&gt;</span>
-</td>
-
-<td class="thl thr" style="padding-left: 2px; padding-right: 2px; vertical-align: top; width: 100%">
-<input id="compValue" type="text" value="" title="Component value" autocapitalize="off" placeholder="Value" style="position: relative; visibility: hidden; width: 100%;"/>
-</td>
-</tr>
-</table>
-
-<div id="contentdiv" style="margin-top: 4px; width: 2500px;">
-<div id="playdiv" style="position: relative; top: 0x; left: 0px; right: 0px; width: 2500px; height: 5000px; visibility: hidden">
-</div>
+<div id="contentdiv" class="viewcontent" style="width: 2500px;">
+<div id="graphdiv" class="graphdiv" style="top: 0px; left: -2500px; width: 5000px; height: 5000px;"></div>
+<div id="playdiv" style="position: absolute; top: 0x; left: 0px; width: 2500px; height: 5000px; visibility: hidden"></div>
</div>
<script type="text/javascript">
@@ -68,36 +37,35 @@ if (isNil(appname)) {
ispalette = true;
}
-// Set page titles
-document.title = ui.windowtitle(location.hostname) + ' - ' + (isNil(config.compose)? 'Composition' : config.compose) + ' - ' + appname;
-$('appNameHeader').innerHTML = '<a href=\"/' + appname + '/\" target=\"' + '_blank' + '\">' + appname + '</a>';
+// Set page title
+document.title = ui.windowtitle(location.hostname) + ' - ' + config.logic + ' - ' + appname;
+
+// Set header div
+$('viewhead').innerHTML = '<span id="appTitle" class="cmenu">' + appname + '</span>' +
+'<input type="button" id="deleteCompButton" title="Delete a component" class="graybutton redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" disabled="true" value="-"/>' +
+'<span style="position: absolute; top: 0px; left: 45px; right: 115px; padding: 0px; background: transparent;"><input id="compValue" type="text" value="" class="flatentry" title="Component value" autocapitalize="off" placeholder="Value" style="position: absolute; left: 0px; top: 4px; width: 100%; visibility: hidden;" readonly="readonly"/></span>' +
+'<input type="button" id="playCompButton" title="View component value" class="graybutton plusminus" style="position: absolute; top: 4px; right: 75px;" disabled="true" value="&gt;"/>' +
+'<input type="button" id="copyCompButton" title="Copy a component" class="graybutton bluebutton" style="position: absolute; top: 4px; right: 40px;" disabled="true" value="C"/>' +
+'<input type="button" id="addCompButton" title="Add a component" class="graybutton bluebutton plusminus" style="position: absolute; top: 4px; right: 5px;" disabled="true" value="+"/>';
+
+/**
+ * Track the current app composite, author, and saved XML content.
+ */
+var author = '';
+var editable = false;
+var composite;
+var savedcomposxml = '';
/**
* Component value field, add, delete and play buttons.
*/
var cvalue = $('compValue');
+var atitle = $('appTitle');
var cadd = $('addCompButton');
var cdelete = $('deleteCompButton');
var ccopy = $('copyCompButton');
var cplay = $('playCompButton');
-// Position background divs
-var cvbackground = $('compValueBackground');
-var cvtable = $('compValueTable');
-cvbackground.style.top = ui.pixpos(cvtable.offsetTop);
-
-/**
- * Adjust component value field size.
- */
-function resizeFields() {
- cvalue.style.width = '0px';
- cvalue.style.width = ui.pixpos(cvalue.parentNode.clientWidth - 18);
- return true;
-}
-
-resizeFields();
-window.onresize = resizeFields;
-
// Init componnent references
var editWidget = sca.component("EditWidget");
var palettes = sca.reference(editWidget, "palettes");
@@ -107,7 +75,7 @@ var composites = sca.reference(editWidget, ispalette? "palettes" : "composites")
//rconsole = sca.defun(sca.reference(editWidget, "log"), "log");
/**
- * SVG composite rendering functions.
+ * Composite rendering functions.
*/
var graph = {};
@@ -128,7 +96,7 @@ graph.colors.purple = '#800080';
graph.colors.red = '#ff0000';
graph.colors.white = '#ffffff';
graph.colors.yellow = '#ffff00';
-graph.colors.link = '#598edd';
+graph.colors.link = '#357ae8';
graph.colors.orange1 = '#ffd666';
graph.colors.green1 = '#bbe082';
@@ -173,51 +141,36 @@ graph.buttoncy = 23;
graph.curvsz = 4;
graph.tabsz = 2;
graph.titlex = 4;
-graph.titley = 11;
-graph.titlew = ui.isMobile()? -2 : 0;
+graph.titley = 0;
/**
- * SVG rendering functions.
+ * Make a composite graph editor.
*/
-
-graph.svgns='http://www.w3.org/2000/svg';
-
-/**
- * Make an SVG graph.
- */
-graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
-
- // Create a div element to host the graph
- var div = document.createElement('div');
- div.id = 'svgdiv';
- div.style.position = 'absolute';
- div.style.left = ui.pixpos(pos.xpos() + cdiv.offsetLeft);
- div.style.top = ui.pixpos(pos.ypos() + cdiv.offsetTop);
- cdiv.appendChild(div);
-
- // Create SVG element
- var svg = document.createElementNS(graph.svgns, 'svg');
- svg.style.height = ui.pixpos(5000);
- svg.style.width = ui.pixpos(5000);
- div.appendChild(svg);
+graph.mkedit = function(graphdiv, pos, atitle, cvalue, cadd, ccopy, cdelete, onchange, onselect) {
// Track element dragging and selection
graph.dragging = null;
graph.dragged = false;
- graph.moverenderer = null;
graph.selected = null;
- cvalue.disabled = true;
+ cvalue.readOnly = true;
cvalue.style.visibility = 'hidden';
+ atitle.style.visibility = 'visible';
ccopy.disabled = true;
cdelete.disabled = true;
+ cadd.disabled = !editable;
+
+ // Register event listeners
+ graph.oncomposchange = onchange;
+ graph.oncompselect = onselect;
/**
* Find the first draggable element in a hierarchy of elements.
*/
function draggable(n) {
- if (n == div || n == svg || n == null)
+ //debug('draggable', n);
+ if (n == graphdiv || n == null)
return null;
- if (n.nodeName == 'g' && !isNil(n.id) && n.id != '')
+ if (n.className == 'g' && !isNil(n.id) && n.id != '')
return n;
return draggable(n.parentNode);
}
@@ -255,27 +208,14 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
};
if (!ui.isMobile()) {
- div.onmousedown = function(e) {
- //log('onmousedown');
- var suspend = svg.suspendRedraw(10);
- var r = onmousedown(e);
- svg.unsuspendRedraw(suspend);
- return r;
+ graphdiv.onmousedown = function(e) {
+ //debug('onmousedown');
+ return onmousedown(e);
}
} else {
- div.ontouchstart = function(e) {
- //log('ontouchstart');
-
- // Clear current move renderer if it's running
- if (!isNil(graph.moverenderer)) {
- clearInterval(graph.moverenderer);
- graph.moverenderer = null;
- }
-
- var suspend = svg.suspendRedraw(10);
- var r = onmousedown(e);
- svg.unsuspendRedraw(suspend);
- return r;
+ graphdiv.ontouchstart = function(e) {
+ //debug('ontouchstart');
+ return onmousedown(e);
}
}
@@ -298,28 +238,28 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
return true;
}
- if (graph.dragging.parentNode == svg && graph.dragging.id.substring(0, 8) != 'palette:') {
+ if (graph.dragging.parentNode == graphdiv && graph.dragging.id.substring(0, 8) != 'palette:') {
// Add new dragged component to the composite
if (isNil(graph.dragging.compos)) {
- var compos = scdl.composite(svg.compos);
+ var compos = scdl.composite(graphdiv.compos);
setElement(compos, graph.sortcompos(graph.addcomps(mklist(graph.dragging.comp), compos)));
- graph.dragging.compos = svg.compos;
+ graph.dragging.compos = graphdiv.compos;
}
// Update component position
- setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.abspos(graph.dragging, svg)));
+ setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.abspos(graph.draggingg)));
// Wire component to neighboring reference
if (!isNil(graph.dragging.svcpos)) {
- var compos = scdl.composite(svg.compos);
- setElement(compos, graph.sortcompos(graph.clonerefs(graph.wire(graph.dragging, compos, svg))));
+ var compos = scdl.composite(graphdiv.compos);
+ setElement(compos, graph.sortcompos(graph.clonerefs(graph.wire(graph.dragging, compos, graphdiv))));
}
// Snap top level component position to grid
- if (graph.dragging.parentNode == svg) {
+ if (graph.dragging.parentNode == graphdiv) {
var gpos = graph.relpos(graph.dragging);
- setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.mkpath().pos(graph.gridsnap(gpos.xpos()), graph.gridsnap(gpos.ypos()))));
+ setElement(graph.dragging.comp, graph.movecomp(graph.dragging.comp, graph.mkpath().pos(graph.gridsnap(gpos.x), graph.gridsnap(gpos.y))));
}
}
@@ -328,45 +268,32 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
graph.dragged = false;
// Refresh the composite
- //log('onmouseup refresh');
- var nodes = graph.refresh(svg);
+ //debug('onmouseup refresh');
+ var nodes = graph.refresh(graphdiv);
- // Reselected the previously selected component
+ // Reselect the previously selected component
if (!isNil(graph.selected)) {
graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes);
- graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+ graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete);
// Trigger component select event
- svg.oncompselect(graph.selected);
+ graph.oncompselect(graph.selected);
}
// Trigger composite change event
- svg.oncomposchange(false);
+ graph.oncomposchange(false);
return true;
};
if (!ui.isMobile()) {
- div.onmouseup = function(e) {
- //log('onmouseup');
- var suspend = svg.suspendRedraw(10);
- var r = onmouseup(e);
- svg.unsuspendRedraw(suspend);
- return r;
+ graphdiv.onmouseup = function(e) {
+ //debug('onmouseup');
+ return onmouseup(e);
}
} else {
- div.ontouchend = function(e) {
- //log('ontouchend');
-
- // Clear current move renderer if it's running
- if (!isNil(graph.moverenderer)) {
- clearInterval(graph.moverenderer);
- graph.moverenderer = null;
- }
-
- var suspend = svg.suspendRedraw(10);
- var r = onmouseup(e);
- svg.unsuspendRedraw(suspend);
- return r;
+ graphdiv.ontouchend = function(e) {
+ //debug('ontouchend');
+ return onmouseup(e);
}
}
@@ -374,7 +301,7 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
* Handle a mouse or touch click event.
*/
function onclick(e) {
- //log('onclick logic');
+ //debug('onclick logic');
// Find selected component
var selected = draggable(e.target);
@@ -382,82 +309,78 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
if (graph.selected != null) {
// Reset current selection
- graph.compselect(graph.selected, false, cvalue, ccopy, cdelete);
+ graph.compselect(graph.selected, false, atitle, cvalue, ccopy, cdelete);
graph.selected = null;
// Trigger component select event
- svg.oncompselect(null);
+ graph.oncompselect(null);
}
// Dismiss the palette
- if (e.target == div || e.target == svg && ui.numpos(div.style.left) != (graph.palcx * -1))
- div.style.left = ui.pixpos(graph.palcx * -1);
+ if (e.target == graphdiv && ui.numpos(graphdiv.style.left) != (graph.palcx * -1))
+ graphdiv.style.left = ui.pixpos(graph.palcx * -1);
return true;
}
- // Ignore multiple click events
+
+ // Ignore duplicate click events
if (selected == graph.selected)
return true;
- if (selected.id.substring(0, 8) == 'palette:' && ui.numpos(div.style.left) != 0)
+ if (selected.id.substring(0, 8) == 'palette:' && ui.numpos(graphdiv.style.left) != 0)
return true;
// Deselect previously selected component
- graph.compselect(graph.selected, false, cvalue, ccopy, cdelete);
+ graph.compselect(graph.selected, false, atitle, cvalue, ccopy, cdelete);
// Clone component from the palette
if (selected.id.substring(0, 8) == 'palette:') {
- var compos = scdl.composite(svg.compos);
- var comp = graph.clonepalette(selected, compos, svg);
+ var compos = scdl.composite(graphdiv.compos);
+ var comp = graph.clonepalette(selected, compos);
setElement(compos, graph.sortcompos(graph.addcomps(mklist(comp), compos)));
// Move into the editing area and hide the palette
- div.style.left = ui.pixpos(graph.palcx * -1);
+ graphdiv.style.left = ui.pixpos(graph.palcx * -1);
// Refresh the composite
- //log('onclick refresh');
- var nodes = graph.refresh(svg);
+ //debug('onclick refresh');
+ var nodes = graph.refresh(graphdiv);
// Reselect the previously selected component
graph.selected = graph.findcompnode(scdl.name(comp), nodes);
- graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+ graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete);
// Trigger component select event
- svg.oncompselect(graph.selected);
+ graph.oncompselect(graph.selected);
// Trigger composite change event
- svg.oncomposchange(true);
+ graph.oncomposchange(true);
} else {
graph.selected = selected;
// Select the component
- graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+ graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete);
// Trigger component select event
- svg.oncompselect(graph.selected);
+ graph.oncompselect(graph.selected);
}
- //log('comp selected');
+ //debug('comp selected');
e.preventDefault();
return true;
}
if (!ui.isMobile()) {
- div.onclick = function(e) {
- //log('div onclick');
- var suspend = svg.suspendRedraw(10);
- var r = onclick(e);
- svg.unsuspendRedraw(suspend);
- return r;
+ graphdiv.onclick = function(e) {
+ //debug('onclick');
+ return onclick(e);
}
- svg.onclick = function(e) {
- //log('svg onclick');
- var suspend = svg.suspendRedraw(10);
- var r = onclick(e);
- svg.unsuspendRedraw(suspend);
- return r;
+ } else {
+ graphdiv.onclick = function(e) {
+ //debug('onclick');
+ return onclick(e);
}
}
@@ -476,18 +399,18 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
graph.dragged = true;
// Cut wire to component
- if (graph.dragging.parentNode != svg) {
- var compos = scdl.composite(svg.compos);
- setElement(compos, graph.sortcompos(graph.cutwire(graph.dragging, compos, svg)));
+ if (graph.dragging.parentNode != graphdiv) {
+ var compos = scdl.composite(graphdiv.compos);
+ setElement(compos, graph.sortcompos(graph.cutwire(graph.dragging, compos, graphdiv)));
// Bring component to the top
- graph.bringtotop(graph.dragging, svg);
+ graph.bringtotop(graph.dragging, graphdiv);
}
// Calculate new position of dragged element
var gpos = graph.relpos(graph.dragging);
- var newX = gpos.xpos() + (graph.moveX - graph.dragX);
- var newY = gpos.ypos() + (graph.moveY - graph.dragY);
+ var newX = gpos.x + (graph.moveX - graph.dragX);
+ var newY = gpos.y + (graph.moveY - graph.dragY);
if (newX >= graph.palcx)
graph.dragX = graph.moveX
else
@@ -497,9 +420,6 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
else
newY = 0;
- // Detach child elements to speedup rendering
- graph.compoutline(graph.dragging, true);
-
// Move the dragged element
graph.move(graph.dragging, graph.mkpath().pos(newX, newY));
@@ -508,20 +428,17 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
if (!ui.isMobile()) {
window.onmousemove = function(e) {
- //log('onmousemove');
+ //debug('onmousemove');
// Remember mouse position
graph.moveX = e.screenX;
graph.moveY = e.screenY;
- var suspend = svg.suspendRedraw(10);
- var r = onmousemove(e);
- svg.unsuspendRedraw(suspend);
- return r;
+ return onmousemove(e);
}
} else {
- div.ontouchmove = function(e) {
- //log('ontouchmove');
+ graphdiv.ontouchmove = function(e) {
+ //debug('ontouchmove');
// Remember touch position
var pos = e.touches[0];
@@ -532,15 +449,7 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
if (graph.moveX == graph.dragX && graph.moveY == graph.dragY)
return true;
- // Start async move renderer
- if (graph.moverenderer == null) {
- graph.moverenderer = setInterval(function() {
- var suspend = svg.suspendRedraw(10);
- onmousemove(e);
- svg.unsuspendRedraw(suspend);
- }, 10);
- }
- return true;
+ return onmousemove(e);
}
}
@@ -550,29 +459,29 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
function onvaluechange() {
if (graph.selected == null)
return false;
- if (g.parentNode.style.visibility == 'hidden')
+ if (graphdiv.parentNode.style.visibility == 'hidden')
return false;
// Change component name and refactor references to it
function changename() {
- var compos = scdl.composite(svg.compos);
+ var compos = scdl.composite(graphdiv.compos);
cvalue.value = graph.ucid(cvalue.value, compos, false);
graph.selected.id = cvalue.value;
setElement(compos, graph.sortcompos(graph.renamecomp(graph.selected.comp, compos, cvalue.value)));
// Refresh the composite
- //log('onchangename refresh');
- var nodes = graph.refresh(svg);
+ //debug('onchangename refresh');
+ var nodes = graph.refresh(graphdiv);
// Reselected the previously selected component
graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes);
- graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+ graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete);
// Trigger component select event
- svg.oncompselect(graph.selected);
+ graph.oncompselect(graph.selected);
// Trigger composite change event
- svg.oncomposchange(true);
+ graph.oncomposchange(true);
return false;
}
@@ -580,23 +489,24 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
function changeprop() {
graph.setproperty(graph.selected.comp, cvalue.value);
var hasprop = graph.hasproperty(graph.selected.comp);
- cvalue.disabled = hasprop? false : true;
+ cvalue.readOnly = (hasprop? false : true) || !editable;
cvalue.style.visibility = hasprop? 'visible' : 'hidden';
+ atitle.style.visibility = hasprop? 'hidden' : 'visible';
cvalue.value = graph.property(graph.selected.comp);
// Refresh the composite
- //log('onchangeprop refresh');
- var nodes = graph.refresh(svg);
+ //debug('onchangeprop refresh');
+ var nodes = graph.refresh(graphdiv);
// Reselected the previously selected component
graph.selected = graph.findcompnode(scdl.name(graph.selected.comp), nodes);
- graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+ graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete);
// Trigger component select event
- svg.oncompselect(graph.selected);
+ graph.oncompselect(graph.selected);
// Trigger composite change event
- svg.oncomposchange(true);
+ graph.oncomposchange(true);
return false;
}
@@ -604,10 +514,7 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
};
cvalue.onchange = function() {
- var suspend = svg.suspendRedraw(10);
- var r = onvaluechange();
- svg.unsuspendRedraw(suspend);
- return r;
+ return onvaluechange();
}
// Handle delete event
@@ -617,33 +524,30 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
if (graph.selected.id.substring(0, 8) != 'palette:') {
// Remove selected component
- var compos = scdl.composite(svg.compos);
+ var compos = scdl.composite(graphdiv.compos);
if (isNil(graph.selected.compos))
- setElement(compos, graph.sortcompos(graph.cutwire(graph.selected, compos, svg)));
+ setElement(compos, graph.sortcompos(graph.cutwire(graph.selected, compos, graphdiv)));
setElement(compos, graph.sortcompos(graph.clonerefs(graph.gcollect(graph.removecomp(graph.selected.comp, compos)))));
// Reset current selection
- graph.compselect(graph.selected, false, cvalue, ccopy, cdelete);
+ graph.compselect(graph.selected, false, atitle, cvalue, ccopy, cdelete);
graph.selected = null;
// Refresh the composite
- //log('ondelete refresh');
- graph.refresh(svg);
+ //debug('ondelete refresh');
+ graph.refresh(graphdiv);
// Trigger component select event
- svg.oncompselect(null);
+ graph.oncompselect(null);
// Trigger composite change event
- svg.oncomposchange(true);
+ graph.oncomposchange(true);
}
return false;
};
cdelete.onclick = function() {
- var suspend = svg.suspendRedraw(10);
- var r = ondeleteclick();
- svg.unsuspendRedraw(suspend);
- return r;
+ return ondeleteclick();
};
// Handle copy event
@@ -654,51 +558,50 @@ graph.mkgraph = function(cdiv, pos, cvalue, cadd, ccopy, cdelete) {
return false;
// Clone the selected component
- var compos = scdl.composite(svg.compos);
- var comps = graph.clonecomp(graph.selected, compos, svg);
+ var compos = scdl.composite(graphdiv.compos);
+ var comps = graph.clonecomp(graph.selected, compos);
setElement(compos, graph.sortcompos(graph.addcomps(comps, compos)));
// Refresh the composite
- //log('onclick refresh');
- var nodes = graph.refresh(svg);
+ //debug('oncopyclick refresh');
+ var nodes = graph.refresh(graphdiv);
// Select the component clone
graph.selected = graph.findcompnode(scdl.name(car(comps)), nodes);
- graph.compselect(graph.selected, true, cvalue, ccopy, cdelete);
+ graph.compselect(graph.selected, true, atitle, cvalue, ccopy, cdelete);
// Trigger component select event
- svg.oncompselect(graph.selected);
+ graph.oncompselect(graph.selected);
// Trigger composite change event
- svg.oncomposchange(true);
+ graph.oncomposchange(true);
return false;
};
ccopy.onclick = function() {
- var suspend = svg.suspendRedraw(10);
- var r = oncopyclick();
- svg.unsuspendRedraw(suspend);
- return r;
+ return oncopyclick();
};
// Handle add event
cadd.onclick = function() {
// Show the palette
- div.style.left = ui.pixpos(0);
+ graphdiv.style.left = ui.pixpos(0);
return false;
};
// Create a hidden SVG element to help compute the width
// of component and reference titles
- graph.svgtitles = document.createElementNS(graph.svgns, 'svg');
- graph.svgtitles.style.visibility = 'hidden';
- graph.svgtitles.style.height = ui.pixpos(0);
- graph.svgtitles.style.width = ui.pixpos(0);
- div.appendChild(graph.svgtitles);
-
- return svg;
+ graph.offtitles = document.createElement('span');
+ graph.offtitles.style.visibility = 'hidden';
+ graph.offtitles.position = 'absolute';
+ graph.offtitles.top = -500;
+ graph.offtitles.width = 500;
+ graph.offtitles.height = 50;
+ graphdiv.appendChild(graph.offtitles);
+
+ return graphdiv;
};
/**
@@ -708,12 +611,6 @@ graph.Point = function(x, y) {
this.x = x;
this.y = y;
};
-graph.Point.prototype.xpos = function() {
- return this.x;
-};
-graph.Point.prototype.ypos = function() {
- return this.y;
-};
graph.mkpoint = function(x, y) {
return new graph.Point(x, y);
@@ -723,21 +620,25 @@ graph.mkpoint = function(x, y) {
* Path class.
*/
graph.Path = function() {
- this.path = '';
this.x = 0;
this.y = 0;
+ this.xmin = null;
+ this.xmax = null;
+ this.xmin = -8;
+ this.ymax = null;
+ this.draw = function(ctx) {
+ return ctx;
+ };
}
graph.Path.prototype.pos = function(x, y) {
this.x = x;
this.y = y;
+ if (this.xmin == null || x < this.xmin) this.xmin = x;
+ if (this.xmax == null || x > this.xmax) this.xmax = x;
+ if (this.ymin == null || y < this.ymin) this.ymin = y;
+ if (this.ymax == null || y > this.ymax) this.ymax = y;
return this;
};
-graph.Path.prototype.xpos = function() {
- return this.x;
-};
-graph.Path.prototype.ypos = function() {
- return this.y;
-};
graph.Path.prototype.rmove = function(x, y) {
return this.move(this.x + x, this.y + y);
};
@@ -747,44 +648,111 @@ graph.Path.prototype.rline = function(x, y) {
graph.Path.prototype.rcurve = function(x1, y1, x, y) {
return this.curve(this.x + x1, this.y + y1, this.x + x1 + x, this.y + y1 + y);
};
-graph.Path.prototype.str = function() {
- return this.path;
-};
graph.Path.prototype.clone = function() {
- return graph.mkpath().pos(this.xpos(), this.ypos());
+ return graph.mkpath().pos(this.x, this.y);
};
graph.Path.prototype.move = function(x, y) {
- this.path += 'M' + x + ',' + y + ' ';
+ var d = this.draw;
+ this.draw = function(ctx) {
+ d(ctx);
+ ctx.moveTo(x, y);
+ return ctx;
+ };
return this.pos(x, y);
};
graph.Path.prototype.line = function(x, y) {
- this.path += 'L' + x + ',' + y + ' ';
+ var d = this.draw;
+ this.draw = function(ctx) {
+ d(ctx);
+ ctx.lineTo(x, y);
+ return ctx;
+ };
return this.pos(x, y);
};
graph.Path.prototype.curve = function(x1, y1, x, y) {
- this.path += 'Q' + x1 + ',' + y1 + ' ' + x + ',' + y + ' ';
+ var d = this.draw;
+ this.draw = function(ctx) {
+ d(ctx);
+ ctx.quadraticCurveTo(x1, y1, x, y);
+ return ctx;
+ };
return this.pos(x, y);
};
graph.Path.prototype.end = function() {
- this.path += 'Z';
+ var d = this.draw;
+ this.draw = function(ctx) {
+ ctx.beginPath();
+ d(ctx);
+ ctx.fill();
+ ctx.beginPath();
+ d(ctx);
+ ctx.stroke();
+ };
return this;
};
+graph.Path.prototype.bounds = function() {
+ var width = this.xmin == null || this.xmax == null? 0 : this.xmax - this.xmin + 1;
+ var height = this.ymin == null || this.ymax == null? 0 : this.ymax - this.ymin + 1;
+ return graph.mkpath().pos(width, height);
+};
graph.mkpath = function() {
return new graph.Path();
};
/**
+ * Translate the position of an element.
+ */
+graph.translate = function(g, x, y) {
+ var t = 'translate(' + ui.pixpos(x) + ',' + ui.pixpos(y) + ')';
+ g.style.setProperty('-webkit-transform', t, null);
+ g.style.setProperty('-moz-transform', t, null);
+ g.style.setProperty('-o-transform', t, null);
+ g.style.setProperty('transform', t, null);
+ g.ctmx = x;
+ g.ctmy = y;
+ return g;
+};
+
+/**
+ * Apply a path to an element.
+ */
+graph.drawshape = function(g) {
+ // Set shape element size
+ var b = g.path.bounds();
+ g.width = b.x + 4;
+ g.height = b.y + 4;
+
+ // Get canvas context
+ var ctx = g.getContext('2d');
+ ctx.save();
+
+ // Apply translation
+ ctx.translate((g.path.xmin * -1) + 2, (g.path.ymin * -1) + 2);
+
+ // Draw the shape
+ ctx.fillStyle = g.fillStyle;
+ ctx.strokeStyle = !isNil(g.strokeStyle)? g.strokeStyle : graph.colors.gray;
+ ctx.lineWidth = !isNil(g.lineWidth)? g.lineWidth : 1;
+ g.path.draw(ctx);
+
+ // Reset canvas context
+ ctx.restore();
+ return g;
+}
+
+/**
* Return an element representing a title.
*/
graph.mktitle = function(t, x, y) {
- var title = document.createElementNS(graph.svgns, 'text');
- title.setAttribute('x', x);
- title.setAttribute('y', y);
- title.setAttribute('class', 'svgtitle');
- title.setAttribute('pointer-events', 'none');
+ var title = document.createElement('span');
+ title.className = 'gtitle';
+ title.style.left = ui.pixpos(x);
+ title.style.top = ui.pixpos(y);
title.appendChild(document.createTextNode(t));
- graph.svgtitles.appendChild(title);
+ graph.offtitles.appendChild(title);
+ title.style.width = ui.pixpos(title.clientWidth + 2);
+ title.style.height = ui.pixpos(title.clientHeight + 2);
return title;
};
@@ -808,34 +776,38 @@ graph.comptitlewidth = function(comp) {
var title = graph.comptitle(comp);
if (isNil(title))
return 0;
- return title.getBBox().width + graph.titlew;
+ return title.clientWidth;
};
/**
* Draw a component shape selection.
*/
-graph.compselect = function(g, s, cvalue, ccopy, cdelete) {
+graph.compselect = function(g, s, atitle, cvalue, ccopy, cdelete) {
if (isNil(g) || !s) {
cvalue.value = '';
- cvalue.disabled = true;
+ cvalue.readOnly = true;
cvalue.style.visibility = 'hidden';
+ atitle.style.visibility = 'visible';
ccopy.disabled = true;
cdelete.disabled = true;
if (isNil(g))
return true;
- g.shape.setAttribute('stroke', graph.colors.gray);
- g.shape.setAttribute('stroke-width', '1');
+ g.shape.strokeStyle = null;
+ g.shape.lineWidth = null;
+ graph.drawshape(g.shape);
return true;
}
cvalue.value = graph.hasproperty(g.comp)? graph.property(g.comp) : g.id;
- cvalue.disabled = false;
+ cvalue.readOnly = false || !editable;
cvalue.style.visibility = 'visible';
- ccopy.disabled = false;
- cdelete.disabled = false;
+ atitle.style.visibility = 'hidden';
+ ccopy.disabled = false || !editable;
+ cdelete.disabled = false || !editable;
- g.shape.setAttribute('stroke', graph.colors.link);
- g.shape.setAttribute('stroke-width', '2');
+ g.shape.strokeStyle = graph.colors.link;
+ g.shape.lineWidth = 2;
+ graph.drawshape(g.shape);
g.parentNode.appendChild(g);
return true;
};
@@ -847,84 +819,54 @@ graph.paletteselect = function(g, s) {
if (isNil(g))
return true;
if (!s) {
- g.shape.setAttribute('stroke', graph.colors.gray);
- g.shape.setAttribute('stroke-width', '1');
+ g.shape.strokeStyle = null;
+ g.shape.lineWidth = null;
+ graph.drawshape(g.shape);
return true;
}
- g.shape.setAttribute('stroke', graph.colors.link);
- g.shape.setAttribute('stroke-width', '2');
+ g.shape.strokeStyle = graph.colors.link;
+ g.shape.lineWidth = 2;
+ graph.drawshape(g.shape);
g.parentNode.appendChild(g);
return true;
};
/**
- * Draw a component outline for faster rendering.
- */
-graph.compoutline = function(g, s) {
- if (s == (isNil(g.outlined)? false : g.outlined))
- return true;
- g.outlined = s;
-
- if (s) {
- g.shape.setAttribute('fill', 'none');
- if (!isNil(g.title))
- g.removeChild(g.title);
- } else {
- g.shape.setAttribute('fill', graph.color(g.comp));
- if (!isNil(g.title))
- g.appendChild(g.title);
- }
-
- map(function(r) {
- var n = caddr(r);
- if (isNil(n))
- return r;
- graph.compoutline(n, s);
- return r;
- }, g.refpos);
- return true;
-};
-
-/**
* Return a node representing a component.
*/
-graph.compnode = function(comp, cassoc, pos, parentg) {
+graph.compnode = function(comp, cassoc, pos) {
+ //debug('compnode', graph.title(comp));
+
+ // Create the component shape
+ var shape = document.createElement('canvas');
+ shape.className = 'path';
+ shape.fillStyle = graph.color(comp);
+ shape.path = graph.comppath(comp, cassoc);
+ graph.drawshape(shape);
// Make the component title element
var title = graph.comptitle(comp);
- // Compute the path of the component shape
- var path = graph.comppath(comp, cassoc);
-
- // Create the main component shape
- var shape = document.createElementNS(graph.svgns, 'path');
- shape.setAttribute('d', path.str());
- shape.setAttribute('fill', graph.color(comp));
- //shape.setAttribute('fill-opacity', '0.6');
- shape.setAttribute('stroke', graph.colors.gray);
- shape.setAttribute('stroke-width', '1');
- shape.setAttribute('pointer-events', 'visible');
-
- // Create an svg group and add the shape and title to it
- var g = document.createElementNS(graph.svgns, 'g');
+ // Create a span group element and add the shape and title to it
+ var g = document.createElement('span');
+ g.className = 'g';
g.comp = comp;
g.id = scdl.name(comp);
- g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+ graph.translate(g, pos.x, pos.y);
g.pos = pos.clone();
g.appendChild(shape);
g.shape = shape;
+ g.style.width = ui.pixpos(shape.width);
+ g.style.height = ui.pixpos(shape.height);
if (!isNil(title)) {
+ title.style.left = ui.pixpos(shape.path.xmin * -1);
g.appendChild(title);
- g.title = title;
}
- // Store the the positions of the services and references
- g.refpos = reverse(path.refpos);
- g.svcpos = reverse(path.svcpos);
-
- // Handle onclick events
- g.onclick = parentg.onclick;
+ // Store the positions of the services and references
+ g.refpos = reverse(shape.path.refpos);
+ g.svcpos = reverse(shape.path.svcpos);
return g;
};
@@ -949,8 +891,9 @@ graph.findcompnode = function(name, nodes) {
* Return a graphical group.
*/
graph.mkgroup = function(pos) {
- var g = document.createElementNS(graph.svgns, 'g');
- g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+ var g = document.createElement('div');
+ g.className = 'g';
+ graph.translate(g, pos.x, pos.y);
g.pos = pos.clone();
return g;
};
@@ -960,24 +903,20 @@ graph.mkgroup = function(pos) {
*/
graph.mkbutton = function(t, pos) {
+ // Create the main button shape
+ var shape = document.createElement('canvas');
+ shape.className = 'path';
+ shape.fillStyle = graph.colors.lightgray1;
+ shape.path = graph.buttonpath();
+ graph.drawshape(shape);
+
// Make the button title
var title = graph.mktitle(t, graph.titlex, graph.titley);
- // Compute the path of the button shape
- var path = graph.buttonpath().str();
-
- // Create the main button shape
- var shape = document.createElementNS(graph.svgns, 'path');
- shape.setAttribute('d', path);
- shape.setAttribute('fill', graph.colors.lightgray1);
- //shape.setAttribute('fill-opacity', '0.6');
- shape.setAttribute('stroke', graph.colors.gray);
- shape.setAttribute('stroke-width', '1');
- shape.setAttribute('pointer-events', 'visible');
-
- // Create a group and add the button shape to it
- var g = document.createElementNS(graph.svgns, 'g');
- g.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
+ // Create a group and add the button shape and title to it
+ var g = document.createElement('span');
+ g.className = 'g';
+ graph.translate(g, pos.x, pos.y);
g.pos = pos.clone();
g.appendChild(shape);
g.appendChild(title);
@@ -989,33 +928,32 @@ graph.mkbutton = function(t, pos) {
};
/**
- * Return the relative position of a node.
+ * Return the position of a node relative to its parent.
*/
-graph.relpos = function(e) {
- var pmatrix = e.parentNode != null? e.parentNode.getCTM() : null;
- var matrix = e.getCTM();
- var curX = pmatrix != null? (Number(matrix.e) - Number(pmatrix.e)): Number(matrix.e);
- var curY = pmatrix != null? (Number(matrix.f) - Number(pmatrix.f)): Number(matrix.f);
+graph.relpos = function(g) {
+ var curX = g.ctmx? g.ctmx : 0;
+ var curY = g.ctmy? g.ctmy : 0;
return graph.mkpath().pos(curX, curY);
};
/**
* Move a node.
*/
-graph.move = function(e, pos) {
- e.setAttribute('transform', 'translate(' + pos.xpos() + ',' + pos.ypos() + ')');
- e.pos = pos.clone();
+graph.move = function(g, pos) {
+ g.pos = pos.clone();
+ graph.translate(g, g.pos.x, g.pos.y);
+ return g;
};
/**
* Return the absolute position of a component node.
*/
-graph.abspos = function(e, g) {
- if (e == g)
+graph.abspos = function(e) {
+ if (isNil(e) || e == graphdiv)
return graph.mkpath();
var gpos = graph.relpos(e);
- var pgpos = graph.abspos(e.parentNode, g);
- return graph.mkpath().pos(gpos.xpos() + pgpos.xpos(), gpos.ypos() + pgpos.ypos());
+ var pgpos = graph.abspos(e.parentNode);
+ return graph.mkpath().pos(gpos.x + pgpos.x, gpos.y + pgpos.y);
};
/**
@@ -1024,7 +962,7 @@ graph.abspos = function(e, g) {
graph.bringtotop = function(n, g) {
if (n == g)
return null;
- graph.move(n, graph.abspos(n, g));
+ graph.move(n, graph.abspos(n));
g.appendChild(n);
}
@@ -1211,12 +1149,12 @@ graph.rrefpath = function(ref, cassoc, path, maxheight) {
var height = graph.rrefheight(ref, cassoc);
// Record reference position in the path
- var xpos = path.xpos();
- var ypos = path.ypos();
+ var xpos = path.x;
+ var ypos = path.y;
path.refpos = cons(mklist(ref, graph.mkpath().pos(xpos, ypos + (graph.tabsz * 5))), path.refpos);
// Compute the reference path
- return path.rline(0,graph.tabsz * 2).rcurve(0,graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rline(0,graph.tabsz * 3).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz).line(path.xpos(), Math.min(ypos + height, maxheight));
+ return path.rline(0,graph.tabsz * 2).rcurve(0,graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rline(0,graph.tabsz * 3).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz/2.0).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz).line(path.x, Math.min(ypos + height, maxheight));
};
/**
@@ -1226,12 +1164,12 @@ graph.lsvcpath = function(svc, cassoc, path, minheight) {
var height = graph.tabsz * 8;
// Record service position in the path
- var xpos = path.xpos();
- var ypos = path.ypos();
+ var xpos = path.x;
+ var ypos = path.y;
path.svcpos = cons(mklist(svc, graph.mkpath().pos(xpos, ypos - (graph.tabsz * 6))), path.svcpos);
// Compute the service path
- return path.rline(0, -(graph.tabsz * 2)).rcurve(0,-graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rline(0,-(graph.tabsz * 3)).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz).line(path.xpos(), Math.max(ypos - height, minheight));
+ return path.rline(0, -(graph.tabsz * 2)).rcurve(0,-graph.tabsz,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,-graph.tabsz,0).rcurve(-graph.tabsz,0,0,-graph.tabsz/2.0).rline(0,-(graph.tabsz * 3)).rcurve(0,-graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,graph.tabsz/2.0).rcurve(0,graph.tabsz/2.0,graph.tabsz,0).rcurve(graph.tabsz,0,0,-graph.tabsz).line(path.x, Math.max(ypos - height, minheight));
};
/**
@@ -1260,17 +1198,17 @@ graph.comppath = function(comp, cassoc) {
// Render the references on the right side of the component
var rrefs = graph.rrefs(comp);
- path = path.line(width - graph.curvsz,path.ypos()).rcurve(graph.curvsz,0,0,graph.curvsz);
+ path = path.line(width - graph.curvsz,path.y).rcurve(graph.curvsz,0,0,graph.curvsz);
path = renderpath(rrefs, graph.rrefpath, cassoc, path, height - graph.curvsz);
// Render the references on the bottom side of the component
var boffset = graph.curvsz;
- path = path.line(path.xpos(),height - graph.curvsz).rcurve(0,graph.curvsz,graph.curvsz * -1,0).line(boffset, path.ypos());
+ path = path.line(path.x,height - graph.curvsz).rcurve(0,graph.curvsz,graph.curvsz * -1,0).line(boffset, path.y);
// Render the services on the left side of the component
var lsvcs = graph.lsvcs(comp);
var loffset = graph.curvsz + (length(lsvcs) * (graph.tabsz * 8));
- path = path.line(graph.curvsz,path.ypos()).rcurve(graph.curvsz * -1,0,0,graph.curvsz * -1).line(path.xpos(), loffset);
+ path = path.line(graph.curvsz,path.y).rcurve(graph.curvsz * -1,0,0,graph.curvsz * -1).line(path.x, loffset);
path = renderpath(lsvcs, graph.lsvcpath, cassoc, path, graph.curvsz);
// Close the component node path
@@ -1285,7 +1223,7 @@ graph.comppath = function(comp, cassoc) {
graph.comppos = function(comp, pos) {
var x = scdl.x(comp);
var y = scdl.y(comp);
- return graph.mkpath().pos(x != null? Number(x) + graph.palcx : pos.xpos(), y != null? Number(y) : pos.ypos());
+ return graph.mkpath().pos(x != null? Number(x) + graph.palcx : pos.x, y != null? Number(y) : pos.y);
};
/**
@@ -1293,9 +1231,9 @@ graph.comppos = function(comp, pos) {
*/
graph.buttonpath = function(t) {
var path = graph.mkpath().move(graph.curvsz,0);
- path = path.line(graph.buttoncx - graph.curvsz,path.ypos()).rcurve(graph.curvsz,0,0,graph.curvsz);
- path = path.line(path.xpos(),graph.buttoncy - graph.curvsz).rcurve(0,graph.curvsz,-graph.curvsz,0).line(graph.curvsz, path.ypos());
- path = path.line(graph.curvsz,path.ypos()).rcurve(-graph.curvsz,0,0,-graph.curvsz).line(path.xpos(), graph.curvsz);
+ path = path.line(graph.buttoncx - graph.curvsz,path.y).rcurve(graph.curvsz,0,0,graph.curvsz);
+ path = path.line(path.x,graph.buttoncy - graph.curvsz).rcurve(0,graph.curvsz,-graph.curvsz,0).line(graph.curvsz, path.y);
+ path = path.line(graph.curvsz,path.y).rcurve(-graph.curvsz,0,0,-graph.curvsz).line(path.x, graph.curvsz);
path = path.line(0,graph.curvsz).rcurve(0,-graph.curvsz,graph.curvsz,0);
return path.end();
};
@@ -1303,7 +1241,7 @@ graph.buttonpath = function(t) {
/**
* Render a SCDL composite into a list of component nodes.
*/
-graph.composite = function(compos, pos, aspalette, g) {
+graph.composite = function(compos, pos, aspalette) {
var name = scdl.name(scdl.composite(compos));
var comps = scdl.components(compos);
var cassoc = scdl.nameToElementAssoc(comps);
@@ -1355,7 +1293,7 @@ graph.composite = function(compos, pos, aspalette, g) {
}
// Compute the component shape
- var gcomp = graph.compnode(comp, cassoc, pos, g);
+ var gcomp = graph.compnode(comp, cassoc, pos);
// Render the components wired to the component references
var rrefs = graph.rrefs(comp);
@@ -1424,7 +1362,7 @@ graph.composite = function(compos, pos, aspalette, g) {
return map(function(r) {
r.id = 'palette:' + r.id;
var gpos = r.pos;
- graph.move(r, graph.mkpath().pos(gpos.xpos() - graph.palcx, gpos.ypos()));
+ graph.move(r, graph.mkpath().pos(gpos.x - graph.palcx, gpos.y));
return r;
}, rproms);
@@ -1470,7 +1408,7 @@ graph.ucid = function(prefix, compos1, compos2, clone) {
/**
* Clone a palette component node.
*/
-graph.clonepalette = function(e, compos, g) {
+graph.clonepalette = function(e, compos) {
// Clone the SCDL component and give it a unique name
var wcomp = append(mklist(element, "'component", mklist(attribute, "'name", graph.ucid(scdl.name(e.comp), compos, compos, true))),
@@ -1480,7 +1418,7 @@ graph.clonepalette = function(e, compos, g) {
var comp = car(scdl.components(mklist(rcompos)));
// Update component position
- setElement(comp, graph.movecomp(comp, graph.abspos(e, g).rmove(graph.palcx, 0)));
+ setElement(comp, graph.movecomp(comp, graph.abspos(e).rmove(graph.palcx, 0)));
return comp;
};
@@ -1492,7 +1430,7 @@ graph.movecomp = function(comp, pos) {
if (isNil(pos))
return append(mklist(element, "'component"),
filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp)));
- return append(mklist(element, "'component", mklist(attribute, "'x", '' + (pos.xpos() - graph.palcx)), mklist(attribute, "'y", '' + pos.ypos())),
+ return append(mklist(element, "'component", mklist(attribute, "'x", '' + (pos.x - graph.palcx)), mklist(attribute, "'y", '' + pos.y)),
filter(function(e) { return !(isAttribute(e) && (attributeName(e) == "'x" || attributeName(e) == "'y")); }, elementChildren(comp)));
};
@@ -1506,7 +1444,7 @@ graph.gridsnap = function(x) {
/**
* Clone a component node and all the components it references.
*/
-graph.clonecomp = function(e, compos, g) {
+graph.clonecomp = function(e, compos) {
// Write the component and the components it references to XML
function collectcomp(e) {
@@ -1546,7 +1484,7 @@ graph.clonecomp = function(e, compos, g) {
// Update the top component position
var comp = car(comps);
- setElement(comp, graph.movecomp(comp, graph.abspos(e, g).rmove(10, 10)));
+ setElement(comp, graph.movecomp(comp, graph.abspos(e).rmove(10, 10)));
return comps;
};
@@ -1731,7 +1669,7 @@ graph.wire = function(n, compos, g) {
// Compute position of the component's service node
var spos = cadr(car(n.svcpos));
- var aspos = graph.abspos(n, g).rmove(spos.xpos(), spos.ypos());
+ var aspos = graph.abspos(n).rmove(spos.x, spos.y);
/**
* Find closest unwired reference node among all the references
@@ -1754,9 +1692,9 @@ graph.wire = function(n, compos, g) {
return closerefs(npos, cdr(refs), spos, cref);
// Compute distance between service node and reference node
- var rpos = cadr(ref).clone().rmove(npos.xpos(), npos.ypos());
- var dx = Math.pow(rpos.xpos() - spos.xpos(), 2);
- var dy = Math.pow(rpos.ypos() - spos.ypos(), 2);
+ var rpos = cadr(ref).clone().rmove(npos.x, npos.y);
+ var dx = Math.pow(rpos.x - spos.x, 2);
+ var dy = Math.pow(rpos.y - spos.y, 2);
// Check for proximity threshold
var rdist = (dx < (graph.proxcx * graph.proxcx) && dy < (graph.proxcy * graph.proxcy))? Math.sqrt(dx + dy) : 25000000;
@@ -1774,7 +1712,7 @@ graph.wire = function(n, compos, g) {
return closecomprefs(cdr(nodes), spos, cref);
// Compute the component absolute position
- var npos = graph.abspos(node, g);
+ var npos = graph.abspos(node);
// Go through all the components and their references
return closecomprefs(append(nodeList(node.childNodes), cdr(nodes)), spos, closerefs(npos, node.refpos, spos, cref));
@@ -1800,13 +1738,10 @@ graph.wire = function(n, compos, g) {
/**
* Display a list of graphical nodes.
*/
-graph.display = function(nodes, g, svg) {
- var suspend = svg.suspendRedraw(10);
+graph.display = function(nodes, g) {
// Append the nodes to the graphical canvas
appendNodes(nodes, g);
-
- svg.unsuspendRedraw(suspend);
return nodes;
};
@@ -1824,13 +1759,13 @@ graph.hide = function(g) {
* Refresh a graph.
*/
graph.refresh = function(g) {
- //log('refresh');
+ //debug('refresh');
// Remove existing nodes from the graph
map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
// Redisplay the composite associated with the graph
- var nodes = graph.composite(g.compos, graph.mkpath().pos(graph.palcx,0), false, g);
+ var nodes = graph.composite(g.compos, graph.mkpath().pos(graph.palcx,0), false);
appendNodes(nodes, g);
return nodes;
};
@@ -1839,51 +1774,29 @@ graph.refresh = function(g) {
* Display and enable editing of a composite and the graphical
* nodes that represent it.
*/
-graph.edit = function(appname, compos, nodes, onchange, onselect, g) {
- var suspend = g.suspendRedraw(10);
+graph.edit = function(appname, compos, nodes, g) {
- // Store the appname and composite in the graphical canvas
- g.appname = appname;
+ // Store the composite elements, and sort them to allow for change detection later
g.compos = compos;
-
- // Sort the composite elements now to allow for change detection later
var scompos = scdl.composite(g.compos);
setElement(scompos, graph.sortcompos(scompos));
- // Store event listeners
- g.oncomposchange = onchange;
- g.oncompselect = onselect;
-
// Remove existing nodes from the graph
map(function(n) { if (!isNil(n.comp) && n.id.substr(0, 8) != 'palette:') { g.removeChild(n); } return n; }, nodeList(g.childNodes));
// Display the composite nodes
appendNodes(nodes, g);
-
- g.unsuspendRedraw(suspend);
return nodes;
};
/**
- * Track the current app composite and corresponding saved XML content.
- */
-var savedcomposxml = '';
-var composite;
-
-/**
* Track the composition graph, whether it's visible or not and the selected component.
*/
-var g;
-var gdiv;
-var bg;
var gvisible = true;
var gcomp = null;
var cdiv = $('contentdiv');
var pdiv = $('playdiv');
-
-// Position play div inside the content div
-pdiv.style.position = 'absolute';
-pdiv.style.top = cdiv.offsetTop + 'px';
+var graphdiv = $('graphdiv');
/**
* Track the palettes.
@@ -1893,20 +1806,7 @@ var spalette = 'control';
var bpalette = null;
/**
- * Return the composite in an ATOM entry.
- */
-function atomcomposite(doc) {
- var entry = atom.readATOMEntry(mklist(doc));
- if (isNil(entry))
- return mklist();
- var content = namedElementChild("'content", car(entry));
- if (content == null)
- return mklist();
- return elementChildren(content);
-}
-
-/**
- * Get and display an app.
+ * Get and display an application composite.
*/
function getapp(name, g) {
if (isNil(name))
@@ -1917,12 +1817,14 @@ function getapp(name, g) {
// Stop now if we didn't get a composite
if (doc == null) {
- showStatus('No data');
+ showError('App not available');
return false;
}
- showStatus(defaultStatus());
- composite = atomcomposite(doc);
+ // Get the composite from the ATOM entry
+ var composentry = car(atom.readATOMEntry(mklist(doc)));
+ var content = namedElementChild("'content", composentry);
+ composite = isNil(content)? mklist() : elementChildren(content);
if (isNil(composite)) {
// Create a default empty composite if necessary
@@ -1932,10 +1834,16 @@ function getapp(name, g) {
}
// Display the composite
- graph.edit(name, composite, graph.composite(composite, graph.mkpath().move(graph.palcx,0), false, g), oncomposchange, oncompselect, g);
+ graph.edit(name, composite, graph.composite(composite, graph.mkpath().move(graph.palcx,0), false, g), g);
// Track the saved composite XML
savedcomposxml = car(writeXML(composite, false));
+
+ // Enable author to edit the composite
+ author = elementValue(namedElementChild("'author", composentry));
+ editable = author == username;
+ cadd.disabled = !editable;
+ showStatus(editable? defaultStatus() : 'Read only');
return true;
});
}
@@ -1943,19 +1851,22 @@ function getapp(name, g) {
/**
* Display a palette. Get it from the server if needed.
*/
-function displaypalette(name, g, svg, palette, gpalettes) {
+function displaypalette(name, g, palette, gpalettes) {
if (isNil(name))
return;
if (isNil(gpalettes[name])) {
// Get the palette from the server
palettes.get(name, function(doc) {
- gpalettes[name] = graph.composite(atomcomposite(doc), graph.mkpath().move(2580,0), true, g);
- graph.display(gpalettes[name], g, svg);
+ var entry = car(atom.readATOMEntry(mklist(doc)));
+ var content = namedElementChild("'content", entry);
+ var compos = isNil(content)? mklist() : elementChildren(content);
+ gpalettes[name] = graph.composite(compos, graph.mkpath().move(2580,0), true, g);
+ graph.display(gpalettes[name], g);
});
return true;
}
- graph.display(gpalettes[name], g, svg);
+ graph.display(gpalettes[name], g);
return true;
}
@@ -1965,16 +1876,16 @@ function displaypalette(name, g, svg, palette, gpalettes) {
*/
function installpalette(name, pos, g, bg, palette, gpalettes) {
var b = graph.mkbutton(name, pos);
- graph.display(mklist(b), g, g);
+ graph.display(mklist(b), g);
b.onclick = function(e) {
// Swap the selected palette
graph.paletteselect(bpalette, false);
- displaypalette(spalette, bg, g, palette, gpalettes);
+ displaypalette(spalette, bg, palette, gpalettes);
bpalette = b;
graph.paletteselect(b, true);
spalette = name;
- return displaypalette(spalette, g, g, palette, gpalettes);
+ return displaypalette(spalette, g, palette, gpalettes);
};
if (name != spalette) {
@@ -1986,7 +1897,7 @@ function installpalette(name, pos, g, bg, palette, gpalettes) {
// Display the selected palette
graph.paletteselect(b, true);
- displaypalette(name, g, g, palette, gpalettes);
+ displaypalette(name, g, palette, gpalettes);
return b;
}
@@ -1998,8 +1909,8 @@ function save(savexml) {
showStatus('Saving');
savedcomposxml = savexml;
var entry = '<?xml version="1.0" encoding="UTF-8"?>\n' + '<entry xmlns="http://www.w3.org/2005/Atom">' +
- '<title type="text">' + appname + '</title><id>' + appname + '</id><content type="application/xml">' +
- savedcomposxml + '</content></entry>';
+ '<title type="text">' + appname + '</title><id>' + appname + '</id><author><email>' + author + '</email></author>' +
+ '<content type="application/xml">' + savedcomposxml + '</content></entry>';
composites.put(appname, entry, function(e) {
if (e) {
showStatus('Local copy');
@@ -2015,6 +1926,9 @@ function save(savexml) {
* Handle a composite change event.
*/
function oncomposchange(prop) {
+ if (!editable)
+ return false;
+
var newxml = car(writeXML(composite, false));
if (savedcomposxml == newxml)
return false;
@@ -2059,13 +1973,9 @@ function oncompselect(gsel) {
return true;
gcomp = gsel;
- function updateButton(b, v) {
- b.style.color = v? '#000000' : '#808080';
- }
-
- updateButton(cdelete, !isNil(gsel));
- updateButton(ccopy, !isNil(gsel));
- updateButton(cplay, !isNil(gsel));
+ cdelete.disabled = isNil(gsel) || !editable;
+ ccopy.disabled = isNil(gsel) || !editable;
+ cplay.disabled = isNil(gsel);
return true;
}
@@ -2078,7 +1988,7 @@ function showdata(gcomp) {
if (isNil(gcomp))
return true;
cvalue.value = complink(appname, gcomp.id);
- cplay.innerHTML = '&lt;';
+ cplay.value = '<';
gvisible = false;
pdiv.innerHTML = '';
pdiv.style.visibility = 'visible';
@@ -2115,7 +2025,7 @@ function showdata(gcomp) {
});
setTimeout(function() {
- gdiv.style.visibility = 'hidden'
+ graphdiv.style.visibility = 'hidden'
}, 0);
return true;
}
@@ -2126,10 +2036,10 @@ function showdata(gcomp) {
function showgraph(gcomp) {
if (gvisible)
return true;
- cplay.innerHTML = '&gt;';
- gdiv.style.visibility = 'visible'
+ cplay.value = '>';
+ graphdiv.style.visibility = 'visible'
gvisible = true;
- graph.compselect(gcomp, true, cvalue, ccopy, cdelete);
+ graph.compselect(gcomp, true, atitle, cvalue, ccopy, cdelete);
setTimeout(function() {
pdiv.style.visibility = 'hidden';
pdiv.innerHTML = '';
@@ -2149,29 +2059,28 @@ cplay.onclick = function() {
}
// Create editor graph area
-g = graph.mkgraph(cdiv, graph.mkpath().move(-2500,0), cvalue, cadd, ccopy, cdelete);
-gdiv = g.parentNode;
-bg = graph.mkgroup(graph.mkpath());
+graph.mkedit(graphdiv, graph.mkpath().move(-2500,0), atitle, cvalue, cadd, ccopy, cdelete, oncomposchange, oncompselect);
// Install the palettes
+var bg = graph.mkgroup(graph.mkpath());
var pos = graph.mkpath().move(0, 0);
-bpalette = installpalette('control', pos.rmove(5,2), g, bg, spalette, gpalettes);
-installpalette('values', pos.rmove(0,28), g, bg, spalette, gpalettes);
-installpalette('lists', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('transform', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('text', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('http', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('animation', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('talk', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('social', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('search', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('database', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('logic', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('math', pos.rmove(0, 28), g, bg, spalette, gpalettes);
-installpalette('python', pos.rmove(0, 28), g, bg, spalette, gpalettes);
+bpalette = installpalette('control', pos.rmove(5,2), graphdiv, bg, spalette, gpalettes);
+installpalette('values', pos.rmove(0,28), graphdiv, bg, spalette, gpalettes);
+installpalette('lists', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('transform', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('text', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('http', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('animation', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('talk', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('social', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('search', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('database', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('logic', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('math', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
+installpalette('python', pos.rmove(0, 28), graphdiv, bg, spalette, gpalettes);
// Get and display the current app
-getapp(appname, g);
+getapp(appname, graphdiv);
</script>
diff --git a/sca-cpp/trunk/hosting/server/htdocs/home/index.html b/sca-cpp/trunk/hosting/server/htdocs/home/index.html
index 6fb9b558bf..914eb1df00 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/home/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/home/index.html
@@ -17,25 +17,22 @@
* specific language governing permissions and limitations
* under the License.
-->
-<div id="bodydiv" class="bodydiv">
+<div id="bodydiv" class="body">
-<table style="width: 100%;">
-<tr>
-<td><h2><span id="h1"></span></h2></td>
-<td style="vertical-align: middle; text-align: right;"><span id="status" style="font-weight: bold; color: #808080;"></span></td>
-</tr>
-</table>
+<div class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;">
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
+<br/>
+<div id="hometitle" style="font-size: 28px;"></div>
+<br/>
-<div id="maintitle" style="font-size: 150%;"></div>
-
-<div id="maindiagram"><div id="diagram" style="width: 320px; height: 280px; padding: 0px; margin: 0px auto;"></div></div>
+<!--
+<div id="homeanimation" style="width: 320px; height: 280px; padding: 0px; margin: 0px auto;"></div>
+-->
-<input type="button" class="greenbutton" style="font-size: 150%; font-weight: bold; font-style: italic; padding: 10px;" id="getstarted" title="Get Started" value="Get Started"/>
+<input type="button" class="graybutton bluebutton" style="font-size: 21px; padding: 10px; height: 50px;" id="getstarted" title="Get Started" value="Get Started"/>
<br/><br/>
-<div>Requires Safari 5+, Chrome 11+, Firefox 4+, IE 9+</div>
+<div class="note">Requires Safari 5+, Chrome 11+, Firefox 4+, IE 9+</div>
</div>
@@ -43,23 +40,25 @@
// Set page titles
document.title = ui.windowtitle(location.hostname);
-$('h1').innerHTML = ui.hometitle(location.hostname);
+$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle + '</span>';
+$('hometitle').innerHTML = config.hometitle;
-$('maintitle').innerHTML = isNil(config.maintitle)? 'Simple App Builder' : config.maintitle;
$('getstarted').onclick = function() {
return ui.navigate('/#view=store', '_view');
};
-// Display the main diagram
-var diagram = $('diagram');
-diagram.style.background = 'url(\'' + ui.b64img(appcache.get('/home/home.b64')) + '\')';
-var bgpos = 0;
-setInterval(function() {
- bgpos = bgpos -280;
- if (bgpos == -2800)
- bgpos = 0;
- diagram.style.backgroundPosition = '0px ' + ui.pixpos(bgpos);
-}, 2000);
+// Display animation
+var anim = $('homeanimation');
+if (!isNil(anim)) {
+ anim.style.background = 'url(\'' + ui.b64img(appcache.get('/home/home.b64')) + '\')';
+ var bgpos = 0;
+ setInterval(function() {
+ bgpos = bgpos -280;
+ if (bgpos == -2800)
+ bgpos = 0;
+ anim.style.backgroundPosition = '0px ' + ui.pixpos(bgpos);
+ }, 2000);
+}
showStatus(defaultStatus());
diff --git a/sca-cpp/trunk/hosting/server/htdocs/index.html b/sca-cpp/trunk/hosting/server/htdocs/index.html
index 468461cedc..b46141919d 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/index.html
@@ -37,28 +37,31 @@ appcache.get = function(uri) {
var u = h == -1? uri : uri.substring(0, h);
// Get resource from local storage first
- var item = localStorage.getItem(u);
+ var ls = window.lstorage || localStorage;
+ var item = null;
+ try { item = ls.getItem(u); } catch(e) {}
if (item != null && item != '')
return item;
// Get resource from network
var http = new XMLHttpRequest();
http.open("GET", u, false);
+ http.setRequestHeader("Accept", "*/*");
http.send(null);
if (http.status == 200) {
if (http.getResponseHeader("X-Login") != null) {
- if (log) log('http error', u, 'X-Login');
+ if (debug) debug('http error', u, 'X-Login');
// Redirect to login page if not signed in
document.location = '/login/';
return null;
} else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
- if (log) log('http error', u, 'No-Content');
+ if (debug) debug('http error', u, 'No-Content');
return null;
}
- localStorage.setItem(u, http.responseText);
+ try { ls.setItem(u, http.responseText); } catch(e) {}
return http.responseText;
}
- if (log) log('http error', u, http.status, http.statusText);
+ if (debug) debug('http error', u, http.status, http.statusText);
// Redirect to login page if not signed in
if (http.status == 403)
document.location = '/login/';
@@ -72,6 +75,10 @@ appcache.get = function(uri) {
bootjs.text = appcache.get('/all-min.js');
document.head.appendChild(bootjs);
document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css')));
+
+ // Disable cache for testing
+ lstorage.enabled = false;
+
})();
</script>
@@ -84,7 +91,7 @@ if (document.location.protocol == 'https:' && !hasauthcookie())
</script>
</head>
<body class="delayed" onload="onload();">
-<div id="mainbodydiv" class="mainbodydiv" style="overflow: visible;">
+<div id="mainbodydiv" class="mainbody">
<div id="headdiv" class="hsection">
<script type="text/javascript">
@@ -94,87 +101,95 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/config-min.js')));
</script>
</div>
-<div id="menubackground" style="position: absolute; top: 0px; left: 0px; z-index: -1; width: 100%; visibility: hidden;">
-<table cellpadding="0" cellspacing="0" width="100%" class="tbar"><tr><td class="dtbar">
-<table border="0" cellspacing="0" cellpadding="0"><tr><td class="ltbar"><span class="tbarsmenu">>&nbsp</span></td></tr></table>
-</td></tr></table>
-</div>
+<div id="menubackground" class="tbarbackground fixed"></div>
+<div id="menu" class="tbarmenu fixed"></div>
-<div id="menu"></div>
+<div id="viewheadbackground" class="viewheadbackground fixed"></div>
+<div id="viewhead" class="viewhead fixed"></div>
-<div id="content" class="bodydiv" style="overflow: visible;">
<div id="viewcontainer"></div>
-</div>
<script type="text/javascript">
-// Set page titles
+// Init service references
+var editWidget = sca.component("EditWidget");
+var user= sca.defun(sca.reference(editWidget, "user"));
+var accounts = sca.reference(editWidget, "accounts");
+
+// Set page title
document.title = ui.windowtitle(location.hostname);
// Init div variables
var bdiv = $('mainbodydiv');
var mdiv = $('menu');
-var cdiv = $('content');
-var mbgdiv = $('menubackground');
-mbgdiv.style.top = ui.pixpos(mdiv.offsetTop);
+var hdiv = $('viewhead');
var vcontainer = $('viewcontainer');
vcontainer.className = ui.isMobile()? 'viewcontainer3dm' : 'viewcontainer3d';
/**
+ * The current user name and account entry.
+ */
+var username;
+var accountentry;
+
+/**
* Pre-fetch app resources.
*/
var appresources = [
['/all-min.js'],
['/ui-min.css'],
- ['/account/', 'flip'],
- ['/clone/', 'flip'],
- ['/create/', 'flip'],
- ['/graph/', 'flip'],
+ ['/account/', '9'],
+ ['/clone/', '3'],
+ ['/create/', '2'],
+ ['/delete/', '3'],
+ ['/graph/', '5'],
['/config-min.js'],
- ['/home/', 'right'],
+ ['/home/', '0'],
['/home/home.b64'],
- ['/page/', 'flip'],
+ ['/page/', '4'],
['/public/app.b64'],
['/public/config-min.js'],
['/public/grid72.b64'],
['/public/iframe-min.html'],
['/public/img.b64'],
['/public/user.b64'],
- ['/stats/', 'flip'],
- ['/store/', 'left']
+ ['/stats/', '2'],
+ ['/store/', '1']
];
/**
* Handle application cache events.
*/
applicationCache.addEventListener('checking', function(e) {
- //log('appcache checking', e);
+ //debug('appcache checking', e);
showStatus('Checking');
}, false);
applicationCache.addEventListener('error', function(e) {
- //log('appcache error', e);
+ //debug('appcache error', e);
showStatus(defaultStatus());
}, false);
applicationCache.addEventListener('noupdate', function(e) {
- //log('appcache noupdate', e);
+ //debug('appcache noupdate', e);
showStatus(defaultStatus());
}, false);
applicationCache.addEventListener('downloading', function(e) {
- //log('appcache downloading', e);
+ //debug('appcache downloading', e);
showStatus('Updating');
}, false);
applicationCache.addEventListener('progress', function(e) {
- //log('appcache progress', e);
+ //debug('appcache progress', e);
showStatus('Updating');
}, false);
applicationCache.addEventListener('updateready', function(e) {
- //log('appcache updateready', e);
- applicationCache.swapCache();
+ //debug('appcache updateready', e);
+ try {
+ applicationCache.swapCache();
+ } catch(e) {}
showStatus(defaultStatus());
- //log('appcache swapped', e);
+ //debug('appcache swapped', e);
}, false);
applicationCache.addEventListener('cached', function(e) {
- //log('appcache cached', e);
+ //debug('appcache cached', e);
map(function(res) {
showStatus('Updating');
appcache.get(res[0]);
@@ -186,15 +201,15 @@ applicationCache.addEventListener('cached', function(e) {
* Handle network offline/online events.
*/
window.addEventListener('offline', function(e) {
- //log('going offline');
+ //debug('going offline');
showStatus('Offline');
}, false);
window.addEventListener('online', function(e) {
- //log('going online');
+ //debug('going online');
showStatus('Online');
}, false);
-//log(navigator.onLine? 'online' : 'offline');
+//debug(navigator.onLine? 'online' : 'offline');
/**
* Handle view transitions.
@@ -218,9 +233,14 @@ map(function(res) {
/**
* Return the transition that should be applied to a resource.
*/
-function viewtransition(uri) {
+function viewtransition(ouri, uri) {
+ var ot = apptransitions[ouri];
+ if (isNil(ot))
+ return 'left';
var t = apptransitions[uri];
- return isNil(t)? 'left' : t;
+ if (isNil(t))
+ return 'left';
+ return t < ot? 'right' : 'left';
}
/**
@@ -234,7 +254,7 @@ function mkviewdiv(cname) {
// Handle view transition end
function viewdivtransitionend(e) {
- if (e.target.className == 'leftviewunloaded3dm' || e.target.className == 'rightviewunloaded3dm' || e.target.className == 'flipviewunloaded3dm')
+ if (e.target.className == 'leftviewunloaded3dm' || e.target.className == 'rightviewunloaded3dm')
e.target.parentNode.removeChild(e.target);
}
vdiv.addEventListener('webkitTransitionEnd', viewdivtransitionend, false);
@@ -246,7 +266,7 @@ function mkviewdiv(cname) {
* Return the last visited location.
*/
function lastvisited() {
- return localStorage.getItem('ui.lastvisited')
+ return lstorage.getItem('ui.lastvisited');
}
/**
@@ -260,10 +280,12 @@ function showmenu(mdiv, view, appname) {
mklist(
ui.menu('Stats', '/#view=stats&app=' + appname, '_view', view == 'stats'),
ui.menu('Page', '/#view=page&app=' + appname, '_view', view == 'page'),
- ui.menu(isNil(config.compose)? 'Composition' : config.compose, '/#view=graph&app=' + appname, '_view', view == 'graph'))),
- mklist(
- ui.menu('Account', '/#view=account', '_view', view == 'account'),
- hasauthcookie()? ui.menufunc('Sign out', 'logout();', false) : ui.menu('Sign in', '/login/', '_self', false)));
+ ui.menu(config.logic, '/#view=graph&app=' + appname, '_view', view == 'graph'),
+ ui.menu('<span class="greentext" style="font-weight: bold">Run!</span>', '/' + appname + '/', '_blank', false))),
+ (isNil(appname) || appname == 'undefined')? mklist(
+ hasauthcookie()? ui.menufunc('Sign out', 'logout();', false) : ui.menu('Sign in', '/login/', '_self', false),
+ ui.menu('Account', '/#view=account', '_view', view == 'account')) :
+ mklist());
}
/**
@@ -273,6 +295,19 @@ function showStatus(s) {
var sdiv = $('status');
if (isNil(sdiv))
return s;
+ sdiv.style.color = '#808080'
+ sdiv.innerHTML = s;
+ return s;
+}
+
+/**
+ * Show an error message.
+ */
+function showError(s) {
+ var sdiv = $('status');
+ if (isNil(sdiv))
+ return s;
+ sdiv.style.color = '#dd4b39'
sdiv.innerHTML = s;
return s;
}
@@ -285,13 +320,30 @@ function defaultStatus() {
}
/**
+ * Get the current user's account.
+ */
+function getaccount() {
+ var doc = accounts.get();
+
+ // Stop now if we didn't get an account
+ if (doc == null) {
+ username = 'anonymous';
+ return false;
+ }
+
+ accountentry = car(elementsToValues(atom.readATOMEntry(mklist(doc))));
+ username = cadr(assoc("'id", cdr(accountentry)));
+ return true;
+}
+
+/**
* Show a view.
*/
function showview(url) {
- //log('showview', url);
+ //debug('showview', url);
// Save last visited location
- localStorage.setItem('ui.lastvisited', url);
+ lstorage.setItem('ui.lastvisited', url);
// Determine the view to show
var params = ui.fragmentParams(url);
@@ -300,15 +352,7 @@ function showview(url) {
var idx = isNil(params['idx'])? '1' : params['idx'];
// Determine the transition to use
- var vt = viewtransition(uri);
- var ovt = viewtransition(viewuri);
- var vtransition;
- if (ovt == 'flip')
- vtransition = 'flip';
- else if (uri == viewuri && (vt == 'left' || vt == 'right'))
- vtransition = idx >= viewidx? 'left' : 'right';
- else
- vtransition = vt;
+ var vtransition = uri == viewuri? (idx >= viewidx? 'left' : 'right') : viewtransition(viewuri, uri);
// Track current view url and uri
viewurl = url;
@@ -318,17 +362,10 @@ function showview(url) {
// Show the menu bar
var appname = params['app'];
showmenu(mdiv, view, appname);
- cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
// Scroll to the top and hide the address bar
window.scrollTo(0, 0);
- // Compute the viewport size
- var iswide = view == 'graph' || view == 'page';
- var vwidth = iswide? '2500px' : '100%';
- mbgdiv.style.visibility = iswide? 'visible' : 'hidden';
- mbgdiv.style.width = vwidth;
-
// Start to unload the front view and create a new view
if (ui.isMobile()) {
// Prepare current view for transition out
@@ -340,12 +377,12 @@ function showview(url) {
// Load the requested doc into a new view
var vdiv = mkviewdiv(vtransition + 'viewloading3dm');
- vcontainer.appendChild(vdiv);
var vdoc = appcache.get(uri);
vdiv.innerHTML = vdoc;
+ vcontainer.appendChild(vdiv);
map(ui.evalScript, ui.innerScripts(vdiv));
- // Show the document
+ // Make sure the top document is visible
if (document.body.style.visibility != 'visible')
document.body.style.visibility = 'visible';
@@ -356,7 +393,8 @@ function showview(url) {
// Transition the new view in
vdiv.className = 'viewloaded3dm';
- }, 0);
+ }, 100);
+
} else {
// Prepare current view for transition out
var ovdiv = viewdiv;
@@ -365,12 +403,12 @@ function showview(url) {
// Load the requested doc into the view
var vdiv = mkviewdiv('viewloading3d');
- vcontainer.appendChild(vdiv);
var vdoc = appcache.get(uri);
vdiv.innerHTML = vdoc;
+ vcontainer.appendChild(vdiv);
map(ui.evalScript, ui.innerScripts(vdiv));
- // Show the document
+ // Make sure the top document is visible
if (document.body.style.visibility != 'visible')
document.body.style.visibility = 'visible';
@@ -381,7 +419,7 @@ function showview(url) {
// Transition the old view out
if (!isNil(ovdiv))
ovdiv.parentNode.removeChild(ovdiv);
- }, 0);
+ }, 100);
}
// Track the current visible view
@@ -394,18 +432,18 @@ function showview(url) {
* Update the browser window location.
*/
function updatelocation(url) {
- //log('updatelocation', url);
+ //debug('updatelocation', url);
// Add url to the history if necessary
if (url != ui.pathandparams(location)) {
history.pushState(null, null, url);
- //log('pushstate', history.length);
+ //debug('pushstate', history.length);
// Update the location hash if necessary
var f = ui.fragment(url);
if (f != '' && f != location.hash) {
location.hash = f;
- //log('hash', f);
+ //debug('hash', f);
}
}
return url;
@@ -415,7 +453,7 @@ function updatelocation(url) {
* Handle navigations.
*/
window.onnavigate = function(url) {
- //log('onnavigate', url);
+ //debug('onnavigate', url);
updatelocation(url);
@@ -438,9 +476,8 @@ window.onloginredirect = function(e) {
function logout() {
// Clear session cookie and user-specific local storage entries
clearauthcookie();
- localStorage.removeItem('/r/EditWidget/accounts');
- localStorage.removeItem('/r/EditWidget/dashboards');
- //localStorage.clear();
+ lstorage.removeItem('/r/EditWidget/accounts');
+ lstorage.removeItem('/r/EditWidget/dashboards');
document.location = '/login/';
return true;
}
@@ -449,7 +486,7 @@ function logout() {
* Handle history.
*/
window.addEventListener('popstate', function(e) {
- //log('onpopstate', history.length);
+ //debug('onpopstate', history.length);
var furl = ui.fragment(location);
var url = location.pathname + (furl == ''? '' : '#' + furl);
@@ -461,7 +498,7 @@ window.addEventListener('popstate', function(e) {
}, false);
window.addEventListener('hashchange', function(e) {
- //log('onhashchange');
+ //debug('onhashchange');
var furl = ui.fragment(location);
var url = location.pathname + (furl == ''? '' : '#' + furl);
@@ -476,11 +513,12 @@ window.addEventListener('hashchange', function(e) {
* Handle orientation change.
*/
document.body.onorientationchange = function(e) {
- //log('onorientationchange');
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
+ //debug('onorientationchange');
+ ui.onorientationchange(e);
+ // Resize menu and view header
+ mdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
+ hdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
return true;
};
@@ -488,10 +526,14 @@ document.body.onorientationchange = function(e) {
* Document load post processing.
*/
function onload() {
- //log('onload', history.length);
- var furl = ui.fragment(location);
+ //debug('onload', history.length);
+ ui.onload();
+
+ // Get the current user account
+ getaccount();
// Show the view specified in the given url fragment
+ var furl = ui.fragment(location);
if (furl != '') {
var url = location.pathname + '#' + furl;
if (url == viewurl)
@@ -501,7 +543,7 @@ function onload() {
// Show the last visited view
if (ui.isMobile() && (document.referrer == null || document.referrer == '')) {
- //log('show lastvisited');
+ //debug('show lastvisited');
var lv = lastvisited();
var url = isNil(lv)? location.pathname : lv;
updatelocation(url);
diff --git a/sca-cpp/trunk/hosting/server/htdocs/login/index.html b/sca-cpp/trunk/hosting/server/htdocs/login/index.html
index 359afc1807..f9fa9f6f2f 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/login/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/login/index.html
@@ -24,49 +24,96 @@
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<base href="/login/"/>
-<link rel="stylesheet" type="text/css" href="/ui-min.css"/>
-<script type="text/javascript" src="/all-min.js"></script>
+<script type="text/javascript">
+
+window.appcache = {};
+
+/**
+ * Get and cache a resource.
+ */
+appcache.get = function(uri) {
+ var h = uri.indexOf('#');
+ var u = h == -1? uri : uri.substring(0, h);
+
+ // Get resource from local storage first
+ var ls = window.lstorage || localStorage;
+ var item = null;
+ try { item = ls.getItem(u); } catch(e) {}
+ if (item != null && item != '')
+ return item;
+
+ // Get resource from network
+ var http = new XMLHttpRequest();
+ http.open("GET", u, false);
+ http.setRequestHeader("Accept", "*/*");
+ http.send(null);
+ if (http.status == 200) {
+ if (http.getResponseHeader("X-Login") != null) {
+ if (debug) debug('http error', u, 'X-Login');
+ return null;
+ } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
+ if (debug) debug('http error', u, 'No-Content');
+ return null;
+ }
+ try { ls.setItem(u, http.responseText); } catch(e) {}
+ return http.responseText;
+ }
+ if (debug) debug('http error', u, http.status, http.statusText);
+ return null;
+};
+
+// Load Javascript and CSS
+(function() {
+ var bootjs = document.createElement('script');
+ bootjs.type = 'text/javascript';
+ bootjs.text = appcache.get('/all-min.js');
+ document.head.appendChild(bootjs);
+ document.head.appendChild(ui.declareCSS(appcache.get('/ui-min.css')));
+})();
+
+</script>
</head>
<body class="delayed" onload="onload();">
-<div id="bodydiv" class="bodydiv">
+<div id="mainbodydiv" class="bodydiv">
+
+<div id="headdiv" class="hsection">
+<script type="text/javascript">
+(function() {
+$('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js')));
+})();
+</script>
+</div>
-<h1>Sign in</h1>
+<div id="menubackground" class="tbarbackground fixed"></div>
+<div id="menu" class="tbarmenu fixed"></div>
-<form name="googleOpenIDForm">
-<table border="0">
-<tr><td><b>Sign in with your Google account (using OpenID)</b></td></tr>
-<tr><td><input type="button" value="Sign in" class="graybutton" style="font-weight: bold;" onclick="submitOpenIDSignin(withGoogleOpenID)"/></td></tr>
-</table>
-</form>
+<div id="viewheadbackground" class="viewheadbackground fixed"></div>
+<div id="viewhead" class="viewhead fixed">
+<span class="cmenu">Sign in</span>
+</div>
-<form name="facebookOAuth2Form">
-<table border="0">
-<tr><td><b>Sign in with your Facebook account (using OAuth)</b></td></tr>
-<tr><td><input type="button" value="Sign in" class="graybutton" style="font-weight: bold;" onclick="submitOAuth2Signin(withFacebook)"/></td></tr>
-</table>
-</form>
+<div id="viewcontainer">
+<div id="view">
+<div id="viewcontent" class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;">
-<form name="googleOAuth2Form">
-<table border="0">
-<tr><td><b>Sign in with your Google account (using OAuth)</b></td></tr>
-<tr><td><input type="button" value="Sign in" class="graybutton" style="font-weight: bold;" onclick="submitOAuth2Signin(withGoogleOAuth)"/></td></tr>
-</table>
+<form name="facebookOAuth2Form" style="width: 100%;">
+<p style="font-size: 16px;">Sign in with your <span style="font-weight: bold;">Facebook</span> account</p>
+<p><input type="button" value="Sign in" class="graybutton bluebutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px" onclick="submitOAuth2Signin(withFacebook)"/></p>
</form>
-<form name="formSignin" method="POST" action="/login/dologin">
-<table border="0">
-<tr><td colspan="2"><b>Sign in with your user id and password</b></td></tr>
-<tr><td>User id:</td><td><input type="text" name="httpd_username" value=""/></td></tr>
-<tr><td>Password:</td><td><input type="password" name="httpd_password" value=""/></td></tr>
-<tr><td><input type="button" class="graybutton" style="font-weight: bold;" onclick="submitFormSignin()" value="Sign in"/></td><td></td></tr>
-</table>
-</p>
-<input type="hidden" name="httpd_location" value="/"/>
+<form name="googleOAuth2Form" style="width: 100%;">
+<p style="font-size: 16px;">Sign in with your <span style="font-weight: bold;" >Google</span> account</p>
+<p><input type="button" value="Sign in" class="graybutton bluebutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px" onclick="submitOAuth2Signin(withGoogleOAuth)"/></p>
</form>
-<form name="openIDSignin" action="/" method="GET">
-<input type="hidden" name="openid_identifier" value=""/>
+<form name="formSignin" method="POST" action="/login/dologin" onsubmit="submitFormSignin()" style="width: 100%;">
+<p id="loginprompt" style="font-size: 16px;"></p>
+<p><input type="text" class="flatentry" name="httpd_username" value="" placeholder="User id"/></p>
+<p><input type="password" class="flatentry" name="httpd_password" value="" placeholder="Password"/></p>
+<p><input type="submit" class="graybutton bluebutton" style="font-size: 16px; line-height: 16px; padding: 6px; height: 32px" value="Sign in"/></p>
+<input type="hidden" name="httpd_location" value="/"/>
</form>
+<br/>
<form name="oauth2Signin" action="/oauth2/authorize/" method="GET">
<input type="hidden" name="oauth2_authorize" value=""/>
@@ -78,7 +125,37 @@
<input type="hidden" name="openauth_referrer" value=""/>
</form>
+</div>
+</div>
+</div>
+
<script type="text/javascript">
+
+// Init div variables
+var mbdiv = $('menubackground');
+var mdiv = $('menu');
+var hdiv = $('viewhead');
+var hbdiv = $('viewheadbackground');
+$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm';
+$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm';
+$('loginprompt').innerHTML = config.loginprompt;
+
+// Set page titles
+document.title = ui.windowtitle(location.hostname) + ' - Sign in';
+$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle + '</span>';
+
+/**
+ * Build and show the menu bar.
+ */
+function showmenu(mdiv) {
+ mdiv.innerHTML = ui.menubar(mklist(ui.menu('Home', '/', '_self', false)), mklist(ui.menu('Sign in', '/login/', '_self', true)));
+}
+
+showmenu(mdiv);
+
+/**
+ * Parse the query parameeters.
+ */
function queryParams() {
var qp = new Array();
var qs = window.location.search.substring(1).split('&');
@@ -90,6 +167,9 @@ function queryParams() {
return qp;
}
+/**
+ * Return the referrer URL.
+ */
function openauthReferrer() {
var r = queryParams()['openauth_referrer'];
if (typeof(r) == 'undefined' || domainname(r) != domainname(window.location.hostname))
@@ -103,11 +183,11 @@ function openauthReferrer() {
/**
* Signin with OpenID.
*/
+/*
function submitOpenIDSignin(w) {
clearauthcookie();
- localStorage.removeItem('/r/EditWidget/accounts');
- localStorage.removeItem('/r/EditWidget/dashboards');
- //localStorage.clear();
+ lstorage.removeItem('/r/EditWidget/accounts');
+ lstorage.removeItem('/r/EditWidget/dashboards');
document.openIDSignin.openid_identifier.value = w();
document.openIDSignin.action = openauthReferrer();
document.openIDSignin.submit();
@@ -116,6 +196,7 @@ function submitOpenIDSignin(w) {
function withGoogleOpenID() {
return 'https://www.google.com/accounts/o8/id';
}
+*/
/**
* Signin with OAuth 2.0.
@@ -123,9 +204,8 @@ function withGoogleOpenID() {
function submitOAuth2Signin(w) {
parms = w();
clearauthcookie();
- localStorage.removeItem('/r/EditWidget/accounts');
- localStorage.removeItem('/r/EditWidget/dashboards');
- //localStorage.clear();
+ lstorage.removeItem('/r/EditWidget/accounts');
+ lstorage.removeItem('/r/EditWidget/dashboards');
document.oauth2Signin.oauth2_authorize.value = parms[0];
document.oauth2Signin.oauth2_access_token.value = parms[1];
document.oauth2Signin.oauth2_client_id.value = parms[2];
@@ -160,25 +240,24 @@ function submitFormSignin() {
* Handle orientation change.
*/
document.body.onorientationchange = function(e) {
- //log('onorientationchange');
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
+ //debug('onorientationchange');
+ ui.onorientationchange(e);
+ // Resize menu and view header
+ mdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
+ hdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
return true;
};
/**
- * Document load post processing.
+ * Load post processing.
*/
function onload() {
- //log('onload');
+ //debug('onload');
+ ui.onload();
// Show the page
document.body.style.visibility = 'visible';
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
return true;
}
diff --git a/sca-cpp/trunk/hosting/server/htdocs/page/index.html b/sca-cpp/trunk/hosting/server/htdocs/page/index.html
index c379dd3698..215383c76c 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/page/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/page/index.html
@@ -17,66 +17,50 @@
* specific language governing permissions and limitations
* under the License.
-->
-<div id="bodydiv" class="bodydiv" style="overflow: visible;">
+<div id="bodydiv" class="body">
-<table style="width: 100%;">
-<tr>
-<td><h2><span id="appNameHeader"></span></h2></td>
-<td style="vertical-align: middle; text-align: right; padding-right: 8px;"><span id="status" style="font-weight: bold; color: #808080;"></span></td>
-</tr>
-</table>
-
-<table id="widgetValueBackground" style="width: 2500px; position: absolute; top: 59px; left: 0px; z-index: -1;">
-<tr>
-<th class="thr thl"><span style="display: inline-block; padding-top: 0px; padding-bottom: 0px; height: 24px;"></span></th>
-</tr>
-</table>
-
-<table id="widgetValueTable" style="width: 100%;">
-<tr>
-<td class="thl thr" style="text-align: right; padding-right: 2px; vertical-align: top;">
-<span id="deleteWidgetButton" title="Delete a Widget" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">-</span>
-
-<span id="copyWidgetButton" title="Copy a Widget" class="graybutton" style="font-weight: bold; font-size: 16px; color: #808080; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; text-align: center; margin-left: 0px; margin-right: 0px;">c</span>
-
-<span id="addWidgetButton" title="Add a Widget" class="graybutton" style="font-weight: bold; font-size: 16px; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; middle; text-align: center; margin-left: 0px; margin-right: 0px;">+</span>
-
-<span id="playPageButton" title="View page" class="graybutton" style="font-weight: bold; font-size: 16px; display: inline-block; width: 24px; height: 20px; padding-top: 0px; padding-bottom: 0px; middle; text-align: center; margin-left: 0px; margin-right: 0px;">&gt;</span>
-</td>
-
-<td class="thl thr" style="padding-left: 2px; padding-right: 2px; vertical-align: top; width: 100%;">
-<input id="widgetValue" type="text" value="" title="Widget value" autocapitalize="off" placeholder="Value" style="position: relative; visibility: hidden; width: 100%;"/>
-</td>
-</tr>
-</table>
+<div id="contentdiv" class="viewcontent" style="width: 2500px;">
+<div id="pagediv" class="pagediv" style="top: 0px; left: -2500px; width: 5000px; height: 5000px;">
-<div id="contentdiv" style="margin-top: 4px; width: 2500px;">
-<div id="editdiv" style="visibility: visible; position: relative; top: 0px; left: -2500px; width: 2500px; height: 5000px;">
-
-<div style="position: relative; left: 2500px; top: 0px; right: 0px; height: 5000px; border:1px; border-style: solid; border-color: #a2bae7;"><span id="editgrid"></span></div>
+<!--
<div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 320px; height: 460px;"></div>
<div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 480px; height: 300px;"></div>
<div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 768px; height: 911px;"></div>
<div class="guide" style="position: absolute; left: 2500px; top: 0px; width: 1024px; height: 655px;"></div>
+-->
-<span class="h1" id="palette:h1" style="position: absolute; left: 0px; top: 0px;"><h1>Header1</h1></span>
-<span class="h2" id="palette:h2" style="position: absolute; left: 0px; top: 30px;"><h2>Header2</h2></span>
-<span class="section" id="palette:section" style="position: absolute; left: 0px; top: 60px; width: 200px;"><span class="section">section</span></span>
-<span class="button" id="palette:button" style="position: absolute; left: 0px; top: 90px;"><input type="button" value="button" class="graybutton"/></span>
-<span class="entry" id="palette:entry" style="position: absolute; left: 0px; top: 120px;"><input type="text" value="field" size="20" autocapitalize="off"/></span>
-<span class="password" id="palette:password" style="position: absolute; left: 0px; top: 150px;"><input type="password" value="password" size="20"/></span>
-<span class="checkbox" id="palette:checkbox" style="position: absolute; left: 0px; top: 180px;"><input type="checkbox" value="checkbox"/><span>checkbox</span></span>
-<span class="select" id="palette:select" style="position: absolute; left: 0px; top: 210px;"><select><option value="select">select</option></select></span>
-<span class="list" id="palette:list" style="position: absolute; left: 0px; top: 240px; width: 200px;">
-<table class="datatable" style="width: 200px;"><tr><td class="datatd">list</td></tr><tr><td class="datatd">...</td></tr></table>
+<span class="h1" id="palette:h1" style="position: absolute; left: 0px; top: 0px;"><h1>Header Level 1</h1></span>
+<span class="h2" id="palette:h2" style="position: absolute; left: 0px; top: 30px;"><h2>Header Level 2</h2></span>
+<span class="section" id="palette:section" style="position: absolute; left: 0px; top: 60px; width: 220px;"><span class="Section">Section</span></span>
+<span class="text" id="palette:text" style="position: absolute; left: 0px; top: 90px;"><span>Some text</span></span>
+<span class="link" id="palette:link" style="position: absolute; left: 80px; top: 90px;"><a href="/"><span>Link</span></a></span>
+<span class="button" id="palette:graybutton" style="position: absolute; left: 0px; top: 120px;"><input type="button" value="Button" class="graybutton"/></span>
+<span class="button" id="palette:bluebutton" style="position: absolute; left: 80px; top: 120px;"><input type="button" value="Button" class="graybutton bluebutton"/></span>
+<span class="button" id="palette:redbutton" style="position: absolute; left: 160px; top: 120px;"><input type="button" value="Button" class="graybutton redbutton"/></span>
+<span class="entry" id="palette:entry" style="position: absolute; left: 0px; top: 160px;"><input type="text" value="Entry Field" class="flatentry" size="20" autocapitalize="off"/></span>
+<span class="password" id="palette:password" style="position: absolute; left: 0px; top: 190px;"><input type="password" value="Password" class="flatentry" size="20"/></span>
+<span class="checkbox" id="palette:checkbox" style="position: absolute; left: 0px; top: 220px;"><input type="checkbox" value="Checkbox" class="flatcheckbox"/><span>Checkbox</span></span>
+<!--
+<span class="select" id="palette:select" style="position: absolute; left: 80px; top: 220px;"><select><option value="select">Selection</option></select></span>
+-->
+<span class="list" id="palette:list" style="position: absolute; left: 0px; top: 250px; width: 220px;">
+<table class="datatable" style="width: 220px;">
+<tr><td class="datatd">List</td></tr>
+<tr><td class="datatd">List</td></tr>
+<tr><td class="datatd">List</td></tr>
+</table>
</span>
-<span class="table" id="palette:table" style="position: absolute; left: 0px; top: 290px; width: 200px;">
-<table class="datatable" style="width: 200px;"><tr><td class="datatdl">table</td><td class="datatdr">...</td></tr><tr><td class="datatdl">...</td><td class="datatdr">...</td></tr></table>
+<span class="table" id="palette:table" style="position: absolute; left: 0px; top: 320px; width: 220px;">
+<table class="datatable" style="width: 220px;">
+<tr><td class="datatdl">Table</td><td class="datatdr">Table</td></tr>
+<tr><td class="datatdl">Table</td><td class="datatdr">Table</td></tr>
+<tr><td class="datatdl">Table</td><td class="datatdr">Table</td></tr>
+</table>
</span>
-<span class="link" id="palette:link" style="position: absolute; left: 0px; top: 340px;"><a href="/"><span>link</span></a></span>
-<span class="text" id="palette:text" style="position: absolute; left: 0px; top: 370px;"><span>text</span></span>
-<span class="iframe fakeframe" id="palette:iframe" style="position: absolute; left: 0px; top: 400px; width: 200px;"><a href="/public/iframe-min.html"><span class="fakeframe"><span>frame ...</span></span></a></span>
-<span class="img" id="palette:img" style="position: absolute; left: 0px; top: 430px;"><img id="imgimg"/></span>
+<!--
+<span class="iframe fakeframe" id="palette:iframe" style="position: absolute; left: 0px; top: 380px; width: 200px;"><a href="/public/iframe-min.html"><span class="fakeframe"><span>Frame ...</span></span></a></span>
+-->
+<span class="img" id="palette:img" style="position: absolute; left: 0px; top: 410px;"><img id="imgimg"/></span>
</div>
<div id="playdiv" style="visibility: hidden; position: absolute; top: 0px; left: 0px; width: 2500px; height: 5000px;">
@@ -106,48 +90,39 @@ function applink(appname) {
// Set page titles
document.title = ui.windowtitle(location.hostname) + ' - Page - ' + appname;
-$('appNameHeader').innerHTML = '<a href=\"/' + appname + '/\" target=\"' + '_blank' + '\">' + appname + '</a>';
+
+// Set header div
+$('viewhead').innerHTML = '<span id="appTitle" class="cmenu">' + appname + '</span>' +
+'<input type="button" id="deleteWidgetButton" title="Delete a Widget" class="graybutton redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" disabled="true" value="-"/>' +
+'<span style="position: absolute; top: 0px; left: 45px; right: 115px; padding: 0px; background: transparent;"><input id="widgetValue" type="text" value="" class="flatentry" title="Widget value" autocapitalize="off" placeholder="Value" style="position: absolute; left: 0px; top: 4px; width: 100%; visibility: hidden;" readonly="readonly"/></span>' +
+'<input type="button" id="playPageButton" title="View page" class="graybutton plusminus" style="position: absolute; top: 4px; right: 75px;" value="&gt;"/>' +
+'<input type="button" id="copyWidgetButton" title="Copy a Widget" class="graybutton bluebutton" style="position: absolute; top: 4px; right: 40px; font-size: 16px;" disabled="true" value="C"/>' +
+'<input type="button" id="addWidgetButton" title="Add a Widget" class="graybutton bluebutton plusminus" style="position: absolute; top: 4px; right: 5px;" disabled="true" value="+"/>';
+
+/**
+ * Track the current page, author, and saved XHTML content.
+ */
+var author = '';
+var editable = false;
+var savedpagexhtml = '';
/**
* Page editor area, widget value field, add, delete and play page buttons.
*/
var cdiv = $('contentdiv');
-var ediv = $('editdiv');
+var pagediv = $('pagediv');
var evisible = true;
var pdiv = $('playdiv');
-var wvalue = $('widgetValue');
var wadd = $('addWidgetButton');
var wdelete = $('deleteWidgetButton');
var wcopy = $('copyWidgetButton');
+var wvalue = $('widgetValue');
+var atitle = $('appTitle');
var pplay = $('playPageButton');
// Set images
-$('editgrid').parentNode.style.background = 'url(\'' + ui.b64img(appcache.get('/public/grid72.b64')) + '\')';
$('imgimg').src = ui.b64img(appcache.get('/public/img.b64'));
-// Position edit and play divs inside the content div
-ediv.style.position = 'absolute';
-ediv.style.top = cdiv.offsetTop + 'px';
-pdiv.style.position = 'absolute';
-pdiv.style.top = cdiv.offsetTop + 'px';
-
-// Position background divs
-var wvbackground = $('widgetValueBackground');
-var wvtable = $('widgetValueTable');
-wvbackground.style.top = ui.pixpos(wvtable.offsetTop);
-
-/**
- * Adjust widget value field size.
- */
-function resizeFields() {
- wvalue.style.width = '0px';
- wvalue.style.width = ui.pixpos(wvalue.parentNode.clientWidth - 18);
- return true;
-}
-
-resizeFields();
-window.onresize = resizeFields;
-
// Init component references
var editWidget = sca.component('EditWidget');
var pages = sca.reference(editWidget, 'pages');
@@ -163,57 +138,75 @@ var page = {};
page.palcx = 2500;
/**
- * Init a page editor. Works with all browsers except IE.
+ * Init a page editor.
*/
-page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
+page.mkedit = function(pagediv, atitle, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
// Track element dragging and selection
page.dragging = null;
page.selected = null;
- wvalue.disabled = true;
+ wvalue.readOnly = true;
wvalue.style.visibility = 'hidden';
+ atitle.style.visibility = 'visible';
+ page.mousemoved = false;
wcopy.disabled = true;
wdelete.disabled = true;
+ wadd.disabled = !editable;
// Trigger widget select and page change events
page.onpagechange = onchange;
- page.onwidgetselect = onselect;
+ page.onselectwidget = onselect;
/**
* Handle a mouse down event.
*/
- elem.onmousedown = function(e) {
-
- // On mouse controlled devices, engage the click component selection
- // logic right away
- if (typeof e.touches == 'undefined')
- elem.onclick(e);
+ function onmousedown(e) {
+ // On mouse controlled devices, run component selection logic right away
+ var selected = page.selected;
+ if (typeof e.touches == 'undefined') {
+ //debug('onmousedown-click');
+ onclick(e);
+ }
// Find a draggable element
- var dragging = page.draggable(e.target, elem);
+ var dragging = page.draggable(e.target, pagediv);
if (dragging == null || dragging != page.selected)
return true;
page.dragging = dragging;
// Remember mouse position
var pos = typeof e.touches != "undefined"? e.touches[0] : e;
+ page.mousemoved = false;
page.dragX = pos.screenX;
page.dragY = pos.screenY;
+ page.moveX = pos.screenX;
+ page.moveY = pos.screenY;
+ // Prevent default behavior on first click on a widget
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
+
return true;
- };
+ }
- // Support touch devices
- elem.ontouchstart = elem.onmousedown;
+ if (!ui.isMobile()) {
+ pagediv.onmousedown = function(e) {
+ //debug('onmousedown');
+ return onmousedown(e);
+ };
+ } else {
+ pagediv.ontouchstart = function(e) {
+ //debug('ontouchstart');
+ return onmousedown(e);
+ };
+ }
/**
* Handle a mouse up event.
*/
- elem.onmouseup = function(e) {
+ function onmouseup(e) {
if (page.dragging == null)
return true;
@@ -222,87 +215,123 @@ page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
var newY = page.gridsnap(ui.numpos(page.dragging.style.top));
page.dragging.style.left = ui.pixpos(newX);
page.dragging.style.top = ui.pixpos(newY);
- page.dragging.cover.style.left = ui.pixpos(newX);
- page.dragging.cover.style.top = ui.pixpos(newY);
// Fixup widget style
- page.fixupwidget(page.dragging);
+ page.initwidget(page.dragging);
// Forget dragged element
page.dragging = null;
// Trigger page change event
page.onpagechange(false);
+
+ // Simulate onclick event
+ onclick(e);
+
return true;
- };
+ }
- // Support touch devices
- elem.ontouchend = elem.onmouseup;
+ if (!ui.isMobile()) {
+ pagediv.onmouseup = function(e) {
+ //debug('onmouseup');
+ return onmouseup(e);
+ };
+ } else {
+ pagediv.ontouchend = function(e) {
+ //debug('ontouchend');
+ return onmouseup(e);
+ }
+ }
/**
* Handle a mouse move event.
*/
- window.onmousemove = function(e) {
+ function onmousemove(e) {
+
+ // Track mouse moves
+ page.mousemoved = true;
+
if (page.dragging == null)
return true;
- // Get the mouse position
- var pos = typeof e.touches != "undefined"? e.touches[0] : e;
- if (pos.screenX == page.dragX && pos.screenY == page.dragY)
+ // Ignore duplicate mouse move events
+ if (page.moveX == page.dragX && page.moveY == page.dragY)
return true;
// Compute position of dragged element
var curX = ui.numpos(page.dragging.style.left);
var curY = ui.numpos(page.dragging.style.top);
- var newX = curX + (pos.screenX - page.dragX);
- var newY = curY + (pos.screenY - page.dragY);
+ var newX = curX + (page.moveX - page.dragX);
+ var newY = curY + (page.moveY - page.dragY);
if (newX >= page.palcx)
- page.dragX = pos.screenX;
+ page.dragX = page.moveX;
else
newX = page.palcx;
if (newY >= 0)
- page.dragY = pos.screenY;
+ page.dragY = page.moveY;
else
newY = 0;
// Move the dragged element
page.dragging.style.left = ui.pixpos(newX);
page.dragging.style.top = ui.pixpos(newY);
- page.dragging.cover.style.left = ui.pixpos(newX);
- page.dragging.cover.style.top = ui.pixpos(newY);
+ page.constrainwidget(page.dragging);
+
return true;
- };
+ }
- // Support touch devices
- elem.ontouchmove = window.onmousemove;
+ if (!ui.isMobile()) {
+ window.onmousemove = function(e) {
+
+ // Remember mouse position
+ page.moveX = e.screenX;
+ page.moveY = e.screenY;
+
+ return onmousemove(e);
+ };
+ } else {
+ pagediv.ontouchmove = function(e) {
+
+ // Remember touch position
+ var pos = e.touches[0];
+ if (page.moveX == pos.screenX && page.moveY == pos.screenY)
+ return true;
+ page.moveX = pos.screenX;
+ page.moveY = pos.screenY;
+ if (page.moveX == page.dragX && page.moveY == page.dragY)
+ return true;
+
+ onmousemove(e);
+ };
+ }
/**
* Handle a mouse click event.
*/
- elem.onclick = function(e) {
+ function onclick(e) {
// Find selected element
- var selected = page.draggable(e.target, elem);
+ var selected = page.draggable(e.target, pagediv);
if (selected == null) {
if (page.selected != null) {
// Reset current selection
- page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
+ page.selectwidget(page.selected, false, atitle, wvalue, wcopy, wdelete);
page.selected = null;
// Trigger widget select event
- page.onwidgetselect(null);
+ page.onselectwidget(null);
}
// Dismiss the palette
- if (ui.numpos(elem.style.left) != (page.palcx * -1))
- elem.style.left = ui.pixpos(page.palcx * -1);
+ if (ui.numpos(pagediv.style.left) != (page.palcx * -1))
+ pagediv.style.left = ui.pixpos(page.palcx * -1);
return true;
}
// Deselect the previously selected element
- page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
+ page.selectwidget(page.selected, false, atitle, wvalue, wcopy, wdelete);
// Clone element dragged from palette
if (selected.id.substring(0, 8) == 'palette:') {
@@ -310,8 +339,9 @@ page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
// Move into the editing area and hide the palette
page.selected.style.left = ui.pixpos(ui.numpos(page.selected.style.left) + page.palcx);
- page.selected.cover.style.left = ui.pixpos(ui.numpos(page.selected.cover.style.left) + page.palcx);
- elem.style.left = ui.pixpos(page.palcx * -1);
+ page.initwidget(page.selected);
+ pagediv.style.left = ui.pixpos(page.palcx * -1);
+ page.constrainwidget(page.selected);
// Bring it to the top
page.bringtotop(page.selected);
@@ -319,21 +349,40 @@ page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
// Trigger page change event
page.onpagechange(true);
- } else {
+ // Select the element
+ page.selectwidget(page.selected, true, atitle, wvalue, wcopy, wdelete);
+
+ // Trigger widget select event
+ page.onselectwidget(page.selected);
+
+ return true;
- // Bring selected element to the top
- page.selected = selected;
- page.bringtotop(page.selected);
}
+ // Bring selected element to the top
+ page.selected = selected;
+ page.bringtotop(page.selected);
+
// Select the element
- page.widgetselect(page.selected, true, wvalue, wcopy, wdelete);
+ page.selectwidget(page.selected, true, atitle, wvalue, wcopy, wdelete);
// Trigger widget select event
- page.onwidgetselect(page.selected);
+ page.onselectwidget(page.selected);
return true;
- };
+ }
+
+ if (!ui.isMobile()) {
+ pagediv.onclick = function(e) {
+ //debug('onclick');
+ return onclick(e);
+ };
+ } else {
+ pagediv.onclick = function(e) {
+ //debug('onclick');
+ return onclick(e);
+ };
+ }
/**
* Handle field on change events.
@@ -342,8 +391,6 @@ page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
if (page.selected == null)
return false;
page.settext(page.selected, wvalue.value);
- page.selected.cover.style.width = ui.pixpos(page.selected.clientWidth + 4);
- page.selected.cover.style.height = ui.pixpos(page.selected.clientHeight + 4);
// Trigger page change event
page.onpagechange(true);
@@ -354,7 +401,7 @@ page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
wadd.onclick = function() {
// Show the palette
- elem.style.left = ui.pixpos(0);
+ pagediv.style.left = ui.pixpos(0);
return false;
};
@@ -364,15 +411,14 @@ page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
return false;
// Reset current selection
- page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
+ page.selectwidget(page.selected, false, atitle, wvalue, wcopy, wdelete);
// Remove selected widget
page.selected.parentNode.removeChild(page.selected);
- page.selected.cover.parentNode.removeChild(page.selected.cover);
page.selected = null;
// Trigger widget select event
- page.onwidgetselect(null);
+ page.onselectwidget(null);
// Trigger page change event
page.onpagechange(true);
@@ -387,7 +433,7 @@ page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
return false;
// Reset current selection
- page.widgetselect(page.selected, false, wvalue, wcopy, wdelete);
+ page.selectwidget(page.selected, false, atitle, wvalue, wcopy, wdelete);
// Clone selected widget
page.selected = page.clone(page.selected);
@@ -395,28 +441,23 @@ page.edit = function(elem, wvalue, wadd, wcopy, wdelete, onchange, onselect) {
// Move 10 pixels down right
page.selected.style.left = ui.pixpos(ui.numpos(page.selected.style.left) + 10);
page.selected.style.top = ui.pixpos(ui.numpos(page.selected.style.top) + 10);
- page.selected.cover.style.left = ui.pixpos(ui.numpos(page.selected.cover.style.left) + 10);
- page.selected.cover.style.top = ui.pixpos(ui.numpos(page.selected.cover.style.top) + 10);
+ page.constrainwidget(page.selected);
// Bring it to the top
page.bringtotop(page.selected);
// Select the element
- page.widgetselect(page.selected, true, wvalue, wcopy, wdelete);
+ page.selectwidget(page.selected, true, atitle, wvalue, wcopy, wdelete);
// Trigger widget select event
- page.onwidgetselect(page.selected);
+ page.onselectwidget(page.selected);
// Trigger page change event
page.onpagechange(true);
return false;
};
- // Cover child elements with span elements to prevent
- // any input events to reach them
- map(page.cover, nodeList(elem.childNodes));
-
- return elem;
+ return pagediv;
};
/**
@@ -443,10 +484,12 @@ page.text = function(e) {
var t = car(childElements(e)).defaultValue;
return t == f? '' : t;
}
+ /*
if (e.className == 'select') {
var t = car(childElements(car(childElements(e)))).value;
return t == f? '' : t;
}
+ */
if (e.className == 'link') {
var lhr = car(childElements(e)).href;
var hr = lhr.substring(0, 5) == 'link:'? lhr.substring(5) : '';
@@ -457,10 +500,12 @@ page.text = function(e) {
var src = car(childElements(e)).src;
return src == location.href? '' : src;
}
+ /*
if (e.className == 'iframe') {
var hr = car(childElements(e)).href;
return hr == location.href? '' : hr;
}
+ */
if (e.className == 'list')
return '';
if (e.className == 'table')
@@ -483,14 +528,18 @@ page.hastext = function(e) {
return true;
if (e.className == 'entry' || e.className == 'password')
return true;
+ /*
if (e.className == 'select')
return false;
+ */
if (e.className == 'link')
return true;
if (e.className == 'img')
return true;
+ /*
if (e.className == 'iframe')
return true;
+ */
if (e.className == 'list')
return false;
if (e.className == 'table')
@@ -521,7 +570,11 @@ page.settext = function(e, t) {
car(childElements(e)).innerHTML = isNil(c)? f : car(c);
return t;
}
- if (e.className == 'button' || e.className == 'entry' || e.className == 'password') {
+ if (e.className == 'button') {
+ car(childElements(e)).value = isNil(c)? f : car(c);
+ return t;
+ }
+ if (e.className == 'entry' || e.className == 'password') {
car(childElements(e)).defaultValue = isNil(c)? f : car(c);
return t;
}
@@ -530,12 +583,14 @@ page.settext = function(e, t) {
map(function(n) { if (n.nodeName == "SPAN") n.innerHTML = isNil(c)? f : car(c); return n; }, nodeList(e.childNodes));
return t;
}
+ /*
if (e.className == 'select') {
var ce = car(childElements(car(childElements(e))));
ce.value = isNil(c)? f : car(c);
ce.innerHTML = isNil(c)? f : car(c);
return t;
}
+ */
if (e.className == 'list') {
e.innerHTML = '<table class="datatable" style="width: 100%;;"><tr><td class="datatd">' + (isNil(c)? f : car(c)) + '</td></tr><tr><td class="datatd">...</td></tr></table>';
return t;
@@ -554,34 +609,48 @@ page.settext = function(e, t) {
car(childElements(e)).src = isNil(c)? '/public/img.png' : car(c);
return t;
}
+ /*
if (e.className == 'iframe') {
car(childElements(e)).href = isNil(c)? '/public/iframe-min.html' : car(c);
return t;
}
+ */
return '';
};
/**
- * Initial fixup of a widget.
+ * Initialize a widget.
*/
-page.fixupwidget = function(e) {
+page.initwidget = function(e) {
+
+ // Add a Webkit transform to leverage hardware acceleration
+ e.style.setProperty('-webkit-transform', 'translate(0px, 0px)', null);
+
+ /*
if (e.className == 'iframe') {
var f = car(childElements(e));
//e.innerHTML = '<iframe src="' + f.href + '" frameborder="no" scrolling="no"></iframe>';
return e;
}
+ */
+
if (e.className == 'section') {
e.style.width = '100%';
return e;
}
- if (e.className == 'list') {
- e.style.width = '100%';
- car(childElements(e)).style.width = '100%';
+ if (e.className == 'text' || e.className == 'h1' || e.className == 'h2') {
return e;
}
- if (e.className == 'table') {
+ if (e.className == 'button') {
+ return e;
+ }
+ if (e.className == 'checkbox') {
+ return e;
+ }
+ if (e.className == 'list' || e.className == 'table') {
e.style.width = '100%';
- car(childElements(e)).style.width = '100%';
+ var t = car(childElements(e));
+ t.style.width = '100%';
return e;
}
if (e.className == 'img') {
@@ -590,6 +659,54 @@ page.fixupwidget = function(e) {
i.src = '/public/img.png';
return e;
}
+ if (e.className == 'entry' || e.className == 'password') {
+ var i = car(childElements(e));
+ i.readOnly = true;
+ i.style.cursor = 'default';
+ return e;
+ }
+ if (e.className == 'link') {
+ var l = car(childElements(e));
+ l.onclick = function(e) { return false; };
+ return e;
+ }
+ return e;
+}
+
+/**
+ * Enforce widget position and style constraints.
+ */
+page.constrainwidget = function(e) {
+ if (e.className == 'section' || e.className == 'list' || e.className == 'table') {
+ e.style.left = ui.pixpos(page.palcx);
+ return e;
+ }
+ return e;
+};
+
+/**
+ * Cleanup of a widget before saving it.
+ */
+page.cleanupwidget = function(e) {
+ //debug('cleanupwidget', e);
+
+ // Clear outline
+ e.style.outline = null;
+
+ // Clear the Webkit transform
+ e.style.removeProperty('-webkit-transform');
+
+ if (e.className == 'entry' || e.className == 'password') {
+ var i = car(childElements(e));
+ i.readOnly = false;
+ i.style.cursor = null;
+ return e;
+ }
+ if (e.className == 'link') {
+ var l = car(childElements(e));
+ l.onclick = null;
+ return e;
+ }
return e;
}
@@ -599,10 +716,8 @@ page.fixupwidget = function(e) {
page.draggable = function(n, e) {
if (n == e)
return null;
- if (n.id != '')
+ if (!isNil(n.id) && n.id != '')
return n;
- if (n.covered)
- return n.covered;
return page.draggable(n.parentNode, e);
}
@@ -618,65 +733,44 @@ page.gridsnap = function(x) {
*/
page.bringtotop = function(n) {
n.parentNode.appendChild(n);
- n.cover.parentNode.appendChild(n.cover);
}
/**
- * Draw widget selection.
+ * Select a widget.
*/
-page.widgetselect = function(n, s, wvalue, wcopy, wdelete) {
+page.selectwidget = function(n, s, atitle, wvalue, wcopy, wdelete) {
+ //debug('selectwidget', n, s);
if (isNil(n) || !s) {
// Clear the widget value field
wvalue.value = '';
- wvalue.disabled = true;
+ wvalue.readOnly = true;
wvalue.style.visibility = 'hidden';
+ atitle.style.visibility = 'visible';
wcopy.disabled = true;
wdelete.disabled = true;
// Clear the widget outline
if (!isNil(n))
- n.cover.style.borderWidth = '0px';
+ n.style.outline = null;
+
return true;
}
+ // Outline the widget
+ n.style.outline = '2px solid #598edd';
+
// Update the widget value field
wvalue.value = page.text(n);
- wvalue.disabled = false;
+ wvalue.readOnly = false || !editable;
wvalue.style.visibility = 'visible';
- wcopy.disabled = false;
- wdelete.disabled = false;
+ atitle.style.visibility = 'hidden';
+ wcopy.disabled = false || !editable;
+ wdelete.disabled = false || !editable;
- // Outline the widget
- n.cover.style.borderWidth = '2px';
return true;
};
/**
- * Cover a page element with a <span> element to prevent
- * any input events to reach it.
- */
-page.cover = function(e) {
- if (e.id == '' || isNil(e.style))
- return e;
- var cover = document.createElement('div');
- cover.style.position = 'absolute';
- cover.style.left = ui.pixpos(ui.numpos(e.style.left) - 2);
- cover.style.top = ui.pixpos(ui.numpos(e.style.top) - 2);
- cover.style.width = ui.pixpos(e.clientWidth + 4);
- cover.style.height = ui.pixpos(e.clientHeight + 4);
- cover.style.visibility = 'inherit';
- cover.style.borderStyle = 'solid';
- cover.style.borderWidth = '0px';
- cover.style.borderColor = '#598edd';
- cover.style.padding = '0px';
- cover.style.margin = '0px';
- cover.covered = e;
- e.cover = cover;
- e.parentNode.appendChild(cover);
- return e;
-}
-
-/**
* Clone a palette element.
*/
page.clone = function(e) {
@@ -695,7 +789,7 @@ page.clone = function(e) {
ne.innerHTML = e.innerHTML;
// Fixup the widget style
- page.fixupwidget(ne);
+ page.initwidget(ne);
return ne;
}
@@ -708,7 +802,6 @@ page.clone = function(e) {
ne.style.left = ui.pixpos(ui.numpos(e.style.left));
ne.style.top = ui.pixpos(ui.numpos(e.style.top));
e.parentNode.appendChild(ne);
- page.cover(ne);
return ne;
}
@@ -716,24 +809,6 @@ page.clone = function(e) {
};
/**
- * Return the page in an ATOM entry.
- */
-function atompage(doc) {
- var entry = atom.readATOMEntry(mklist(doc));
- if (isNil(entry))
- return mklist();
- var content = namedElementChild("'content", car(entry));
- if (content == null)
- return mklist();
- return elementChildren(content);
-}
-
-/**
- * Track the current page saved XHTML content.
- */
-var savedpagexhtml = '';
-
-/**
* Track the current widget.
*/
var widget = null;
@@ -741,7 +816,7 @@ var widget = null;
/**
* Get and display an app page.
*/
-function getpage(name, ediv) {
+function getpage(name, pagediv) {
if (isNil(name))
return false;
showStatus('Loading');
@@ -750,16 +825,15 @@ function getpage(name, ediv) {
// Stop now if we didn't get a page
if (doc == null) {
- showStatus('No data');
+ showError('App not available');
return false;
}
- showStatus(defaultStatus());
- // Convert the page to XHTML and place it in a hidden buffer
+ // Get the page from the ATOM entry, convert it to XHTML and place it in a hidden buffer
+ var pageentry = car(atom.readATOMEntry(mklist(doc)));
+ var content = namedElementChild("'content", pageentry);
+ var el = isNil(content)? mklist() : elementChildren(content);
var buffer = $('buffer');
- var el = atompage(doc);
-
- // Create a default empty page if necessary
if (isNil(el))
buffer.innerHTML = '<div id="page"></div>';
else
@@ -773,21 +847,29 @@ function getpage(name, ediv) {
if (x < 0 || ui.numpos(e.style.top) < 0)
return false;
return true;
- }, nodeList(ediv.childNodes));
+ }, nodeList(pagediv.childNodes));
map(function(e) {
- ediv.removeChild(e);
+ pagediv.removeChild(e);
}, fnodes);
// Append new page nodes to editor
map(function(e) {
- ediv.appendChild(e);
+ pagediv.appendChild(e);
if (!isNil(e.style))
e.style.left = ui.pixpos(ui.numpos(e.style.left) + 2500);
- return page.cover(e);
+ page.initwidget(e);
+ return e;
}, nodeList(buffer.childNodes[0].childNodes));
- savedpagexhtml = pagexhtml(ediv);
+ savedpagexhtml = pagexhtml(pagediv);
+
+ // Enable author to edit the page
+ author = elementValue(namedElementChild("'author", pageentry));
+ editable = author == username;
+ wadd.disabled = !editable;
+ showStatus(editable? defaultStatus() : 'Read only');
+
return true;
});
}
@@ -797,13 +879,13 @@ function getpage(name, ediv) {
*/
wadd.onclick = function(e) {
// Show the widget palette
- ediv.style.left = ui.pixpos(0);
+ pagediv.style.left = ui.pixpos(0);
};
/**
* Return the current page XHTML content.
*/
-function pagexhtml(ediv) {
+function pagexhtml(pagediv) {
// Copy page DOM to hidden buffer
var buffer = $('buffer');
@@ -811,7 +893,7 @@ function pagexhtml(ediv) {
var div = buffer.childNodes[0];
// Capture the nodes inside the page div
- div.innerHTML = ediv.innerHTML;
+ div.innerHTML = pagediv.innerHTML;
var nodes = nodeList(div.childNodes);
map(function(e) {
div.removeChild(e);
@@ -830,10 +912,11 @@ function pagexhtml(ediv) {
return true;
}, nodes);
- // Reposition nodes
+ // Reposition and cleanup nodes
map(function(e) {
var x = ui.numpos(e.style.left) - 2500;
e.style.left = ui.pixpos(x);
+ page.cleanupwidget(e);
return e;
}, fnodes);
@@ -873,8 +956,8 @@ function save(newxml) {
// Update the page ATOM entry
var entry = '<?xml version="1.0" encoding="UTF-8"?>\n' + '<entry xmlns="http://www.w3.org/2005/Atom">' +
- '<title type="text">' + appname + '</title><id>' + appname + '</id><content type="application/xml">' +
- newxml + '</content></entry>';
+ '<title type="text">' + appname + '</title><id>' + appname + '</id><author><email>' + author + '</email></author>' +
+ '<content type="application/xml">' + newxml + '</content></entry>';
pages.put(appname, entry, function(e) {
if (e) {
@@ -891,7 +974,10 @@ function save(newxml) {
* Handle a page change event
*/
function onpagechange(prop) {
- var newxml = pagexhtml(ediv);
+ if (!editable)
+ return false;
+
+ var newxml = pagexhtml(pagediv);
if (savedpagexhtml == newxml)
return false;
showStatus('Modified');
@@ -914,17 +1000,10 @@ function onpagechange(prop) {
/**
* Handle a widget select event.
*/
-function onwidgetselect(w) {
+function onselectwidget(w) {
if (w == widget)
return true;
widget = w;
-
- function updateButton(b, v) {
- b.style.color = v? '#000000' : '#808080';
- }
-
- updateButton(wdelete, !isNil(w));
- updateButton(wcopy, !isNil(w));
return true;
}
@@ -934,31 +1013,30 @@ function onwidgetselect(w) {
function playpage() {
if (!evisible)
return true;
- page.widgetselect(widget, false, wvalue, wcopy, wdelete);
+ page.selectwidget(widget, false, atitle, wvalue, wcopy, wdelete);
page.selected = null;
- wvalue.value = applink(appname);
- pplay.innerHTML = '&lt;';
+ pplay.value = '<';
evisible = false;
pdiv.style.visibility = 'visible';
pdiv.innerHTML = '';
pdiv.innerHTML = '<iframe id="playappframe" style="position: relative; height: 5000px; width: 2500px; border: 0px;" scrolling="no" frameborder="0" src="/' +
appname + '"></iframe>';
setTimeout(function() {
- ediv.style.visibility = 'hidden'
+ pagediv.style.visibility = 'hidden'
}, 0);
return true;
}
/**
- * Show the page editor.
+ * Show the page editor.
*/
function showedit() {
if (evisible)
return true;
- pplay.innerHTML = '&gt;';
- ediv.style.visibility = 'visible'
+ pplay.value = '>';
+ pagediv.style.visibility = 'visible'
evisible = true;
- page.widgetselect(widget, true, wvalue, wcopy, wdelete);
+ page.selectwidget(widget, true, atitle, wvalue, wcopy, wdelete);
page.selected = widget;
setTimeout(function() {
pdiv.style.visibility = 'hidden';
@@ -977,10 +1055,10 @@ pplay.onclick = function() {
}
// Initialize the page editor
-page.edit(ediv, wvalue, wadd, wcopy, wdelete, onpagechange, onwidgetselect);
+page.mkedit(pagediv, atitle, wvalue, wadd, wcopy, wdelete, onpagechange, onselectwidget);
// Get and display the current app page
-getpage(appname, ediv);
+getpage(appname, pagediv);
</script>
diff --git a/sca-cpp/trunk/hosting/server/htdocs/proxy/public/oops/index.html b/sca-cpp/trunk/hosting/server/htdocs/proxy/public/oops/index.html
new file mode 100644
index 0000000000..9c795bf843
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/proxy/public/oops/index.html
@@ -0,0 +1,171 @@
+<!DOCTYPE html>
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<html>
+<head>
+<title>Oops</title>
+<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"/>
+<meta name="apple-mobile-web-app-capable" content="yes"/>
+<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
+<base href="/proxy/public/oops/"/>
+<script type="text/javascript">
+
+window.appcache = {};
+
+/**
+ * Get and cache a resource.
+ */
+appcache.get = function(uri) {
+ var h = uri.indexOf('#');
+ var u = h == -1? uri : uri.substring(0, h);
+
+ // Get resource from local storage first
+ var ls = window.lstorage || localStorage;
+ var item = null;
+ try { item = ls.getItem(u); } catch(e) {}
+ if (item != null && item != '')
+ return item;
+
+ // Get resource from network
+ var http = new XMLHttpRequest();
+ http.open("GET", u, false);
+ http.setRequestHeader("Accept", "*/*");
+ http.send(null);
+ if (http.status == 200) {
+ if (http.getResponseHeader("X-Login") != null) {
+ if (debug) debug('http error', u, 'X-Login');
+ return null;
+ } else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
+ if (debug) debug('http error', u, 'No-Content');
+ return null;
+ }
+ try { ls.setItem(u, http.responseText); } catch(e) {}
+ return http.responseText;
+ }
+ if (debug) debug('http error', u, http.status, http.statusText);
+ return null;
+};
+
+// Load Javascript and CSS
+(function() {
+ var bootjs = document.createElement('script');
+ bootjs.type = 'text/javascript';
+ bootjs.text = appcache.get('/proxy/all-min.js');
+ document.head.appendChild(bootjs);
+ document.head.appendChild(ui.declareCSS(appcache.get('/proxy/ui-min.css')));
+})();
+
+</script>
+</head>
+<body class="delayed" onload="onload();">
+<div id="mainbodydiv" class="mainbodydiv">
+
+<div id="headdiv" class="hsection">
+<script type="text/javascript">
+(function() {
+$('headdiv').appendChild(ui.declareScript(appcache.get('/proxy/public/config-min.js')));
+})();
+</script>
+</div>
+
+<div id="menubackground" class="tbarbackground fixed"></div>
+<div id="menu" class="tbarmenu fixed"></div>
+
+<div id="viewheadbackground" class="viewheadbackground fixed"></div>
+<div id="viewhead" class="viewhead fixed"></div>
+
+<div id="viewcontainer">
+<div id="view">
+<div id="viewcontent" class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;">
+
+<br/>
+<div class="hd2">Oops, something went wrong...</div>
+
+</div>
+</div>
+</div>
+
+<script type="text/javascript">
+
+// Init div variables
+var mdiv = $('menu');
+var hdiv = $('viewhead');
+$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm';
+$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm';
+
+// Set page title
+document.title = ui.windowtitle(location.hostname) + ' - Oops';
+$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle + '</span>';
+
+/**
+ * Build and show the menu bar.
+ */
+function showmenu(mdiv) {
+ mdiv.innerHTML = ui.menubar(
+ mklist(ui.menu('Home', '/', '_view', false)),
+ mklist(hasauthcookie()? ui.menufunc('Sign out', 'logout();', false) : ui.menu('Sign in', '/login/', '_self', false)));
+}
+
+showmenu(mdiv);
+
+/**
+ * Log the current user out.
+ */
+function logout() {
+ // Clear session cookie and user-specific local storage entries
+ clearauthcookie();
+ lstorage.removeItem('/r/EditWidget/accounts');
+ lstorage.removeItem('/r/EditWidget/dashboards');
+ document.location = '/login/';
+ return true;
+}
+
+/**
+ * Handle orientation change.
+ */
+document.body.onorientationchange = function(e) {
+ //debug('onorientationchange');
+ ui.onorientationchange(e);
+
+ // Resize menu and view header
+ mdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
+ hdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
+ return true;
+};
+
+/**
+ * Load post processing.
+ */
+function onload() {
+ //debug('onload');
+ ui.onload();
+
+ // Show the page
+ document.body.style.visibility = 'visible';
+ return true;
+}
+
+</script>
+
+<div id="footdiv" class="fsection">
+</div>
+
+</div>
+</body>
+</html>
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/config.js b/sca-cpp/trunk/hosting/server/htdocs/public/config.js
new file mode 100644
index 0000000000..41a3bf6771
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/config.js
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+if (isNil(config))
+ config = {};
+
+/**
+ * UI configuration.
+ */
+config.windowtitle = 'App Builder'
+config.pagetitle = '<span style="font-weight: bold;">App Builder</span>';
+config.loginprompt = '<span>Sign in with your userid and password</span>';
+
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html b/sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html
index 0c0435d8a7..959c6be327 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/notauth/index.html
@@ -36,26 +36,29 @@ appcache.get = function(uri) {
var u = h == -1? uri : uri.substring(0, h);
// Get resource from local storage first
- var item = localStorage.getItem(u);
+ var ls = window.lstorage || localStorage;
+ var item = null;
+ try { item = ls.getItem(u); } catch(e) {}
if (item != null && item != '')
return item;
// Get resource from network
var http = new XMLHttpRequest();
http.open("GET", u, false);
+ http.setRequestHeader("Accept", "*/*");
http.send(null);
if (http.status == 200) {
if (http.getResponseHeader("X-Login") != null) {
- if (log) log('http error', u, 'X-Login');
+ if (debug) debug('http error', u, 'X-Login');
return null;
} else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
- if (log) log('http error', u, 'No-Content');
+ if (debug) debug('http error', u, 'No-Content');
return null;
}
- localStorage.setItem(u, http.responseText);
+ try { ls.setItem(u, http.responseText); } catch(e) {}
return http.responseText;
}
- if (log) log('http error', u, http.status, http.statusText);
+ if (debug) debug('http error', u, http.status, http.statusText);
return null;
};
@@ -71,7 +74,7 @@ appcache.get = function(uri) {
</script>
</head>
<body class="delayed" onload="onload();">
-<div id="bodydiv" class="mainbodydiv">
+<div id="mainbodydiv" class="mainbodydiv">
<div id="headdiv" class="hsection">
<script type="text/javascript">
@@ -81,28 +84,35 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js'))
</script>
</div>
-<div id="menu"></div>
+<div id="menubackground" class="tbarbackground fixed"></div>
+<div id="menu" class="tbarmenu fixed"></div>
-<div id="content" class="viewloaded3d">
+<div id="viewheadbackground" class="viewheadbackground fixed"></div>
+<div id="viewhead" class="viewhead fixed"></div>
-<table style="width: 100%;">
-<tr><td><h2><span id="h1"></span></h2></td></tr>
-</table>
+<div id="viewcontainer">
+<div id="view">
+<div id="viewcontent" class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;">
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
+<br/>
<div class="hd2">Sorry, you're not authorized to view this page.</div>
-</div>
</div>
+</div>
+</div>
<script type="text/javascript">
-// Set page title
-$('h1').innerHTML = ui.hometitle(location.hostname);
-
// Init div variables
var mdiv = $('menu');
-var cdiv = $('content');
+var hdiv = $('viewhead');
+$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle + '</span>';
+$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm';
+$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm';
+
+// Set page title
+document.title = ui.windowtitle(location.hostname) + ' - Sorry';
+$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle + '</span>';
/**
* Build and show the menu bar.
@@ -114,7 +124,6 @@ function showmenu(mdiv) {
}
showmenu(mdiv);
-cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
/**
* Log the current user out.
@@ -122,9 +131,8 @@ cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
function logout() {
// Clear session cookie and user-specific local storage entries
clearauthcookie();
- localStorage.removeItem('/r/EditWidget/accounts');
- localStorage.removeItem('/r/EditWidget/dashboards');
- //localStorage.clear();
+ lstorage.removeItem('/r/EditWidget/accounts');
+ lstorage.removeItem('/r/EditWidget/dashboards');
document.location = '/login/';
return true;
}
@@ -133,10 +141,12 @@ function logout() {
* Handle orientation change.
*/
document.body.onorientationchange = function(e) {
- //log('onorientationchange');
+ //debug('onorientationchange');
+ ui.onorientationchange(e);
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
+ // Resize menu and view header
+ mdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
+ hdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
return true;
};
@@ -145,13 +155,11 @@ document.body.onorientationchange = function(e) {
* Load post processing.
*/
function onload() {
- //log('onload');
+ //debug('onload');
+ ui.onload();
// Show the page
document.body.style.visibility = 'visible';
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
return true;
}
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html b/sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html
index 0b364b1753..f2e4f6695a 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/notfound/index.html
@@ -36,26 +36,29 @@ appcache.get = function(uri) {
var u = h == -1? uri : uri.substring(0, h);
// Get resource from local storage first
- var item = localStorage.getItem(u);
+ var ls = window.lstorage || localStorage;
+ var item = null;
+ try { item = ls.getItem(u); } catch(e) {}
if (item != null && item != '')
return item;
// Get resource from network
var http = new XMLHttpRequest();
http.open("GET", u, false);
+ http.setRequestHeader("Accept", "*/*");
http.send(null);
if (http.status == 200) {
if (http.getResponseHeader("X-Login") != null) {
- if (log) log('http error', u, 'X-Login');
+ if (debug) debug('http error', u, 'X-Login');
return null;
} else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
- if (log) log('http error', u, 'No-Content');
+ if (debug) debug('http error', u, 'No-Content');
return null;
}
- localStorage.setItem(u, http.responseText);
+ try { ls.setItem(u, http.responseText); } catch(e) {}
return http.responseText;
}
- if (log) log('http error', u, http.status, http.statusText);
+ if (debug) debug('http error', u, http.status, http.statusText);
return null;
};
@@ -71,7 +74,7 @@ appcache.get = function(uri) {
</script>
</head>
<body class="delayed" onload="onload();">
-<div id="bodydiv" class="mainbodydiv">
+<div id="mainbodydiv" class="mainbodydiv">
<div id="headdiv" class="hsection">
<script type="text/javascript">
@@ -81,29 +84,35 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js'))
</script>
</div>
-<div id="menu"></div>
+<div id="menubackground" class="tbarbackground fixed"></div>
+<div id="menu" class="tbarmenu fixed"></div>
-<div id="content" class="viewloaded3d">
+<div id="viewheadbackground" class="viewheadbackground fixed"></div>
+<div id="viewhead" class="viewhead fixed"></div>
-<table style="width: 100%;">
-<tr><td><h2><span id="h1"></span></h2></td></tr>
-</table>
+<div id="viewcontainer">
+<div id="view">
+<div id="viewcontent" class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;">
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
+<br/>
<div class="hd2">Sorry, that page was not found.</div>
<div>You may have clicked an expired link or mistyped the address.</div>
-</div>
</div>
+</div>
+</div>
<script type="text/javascript">
-// Set page title
-$('h1').innerHTML = ui.hometitle(location.hostname);
-
// Init div variables
var mdiv = $('menu');
-var cdiv = $('content');
+var hdiv = $('viewhead');
+$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm';
+$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm';
+
+// Set page title
+document.title = ui.windowtitle(location.hostname) + ' - Page not found';
+$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle + '</span>';
/**
* Build and show the menu bar.
@@ -115,7 +124,6 @@ function showmenu(mdiv) {
}
showmenu(mdiv);
-cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
/**
* Log the current user out.
@@ -123,9 +131,8 @@ cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
function logout() {
// Clear session cookie and user-specific local storage entries
clearauthcookie();
- localStorage.removeItem('/r/EditWidget/accounts');
- localStorage.removeItem('/r/EditWidget/dashboards');
- //localStorage.clear();
+ lstorage.removeItem('/r/EditWidget/accounts');
+ lstorage.removeItem('/r/EditWidget/dashboards');
document.location = '/login/';
return true;
}
@@ -134,11 +141,12 @@ function logout() {
* Handle orientation change.
*/
document.body.onorientationchange = function(e) {
- //log('onorientationchange');
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
+ //debug('onorientationchange');
+ ui.onorientationchange(e);
+ // Resize menu and view header
+ mdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
+ hdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
return true;
};
@@ -146,13 +154,11 @@ document.body.onorientationchange = function(e) {
* Load post processing.
*/
function onload() {
- //log('onload');
+ //debug('onload');
+ ui.onload();
// Show the page
document.body.style.visibility = 'visible';
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
return true;
}
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html b/sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html
index 11d25eedef..24f738ec09 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/notyet/index.html
@@ -36,26 +36,29 @@ appcache.get = function(uri) {
var u = h == -1? uri : uri.substring(0, h);
// Get resource from local storage first
- var item = localStorage.getItem(u);
+ var ls = window.lstorage || localStorage;
+ var item = null;
+ try { item = ls.getItem(u); } catch(e) {}
if (item != null && item != '')
return item;
// Get resource from network
var http = new XMLHttpRequest();
http.open("GET", u, false);
+ http.setRequestHeader("Accept", "*/*");
http.send(null);
if (http.status == 200) {
if (http.getResponseHeader("X-Login") != null) {
- if (log) log('http error', u, 'X-Login');
+ if (debug) debug('http error', u, 'X-Login');
return null;
} else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
- if (log) log('http error', u, 'No-Content');
+ if (debug) debug('http error', u, 'No-Content');
return null;
}
- localStorage.setItem(u, http.responseText);
+ try { ls.setItem(u, http.responseText); } catch(e) {}
return http.responseText;
}
- if (log) log('http error', u, http.status, http.statusText);
+ if (debug) debug('http error', u, http.status, http.statusText);
return null;
};
@@ -71,7 +74,7 @@ appcache.get = function(uri) {
</script>
</head>
<body class="delayed" onload="onload();">
-<div id="bodydiv" class="mainbodydiv">
+<div id="mainbodydiv" class="mainbodydiv">
<div id="headdiv" class="hsection">
<script type="text/javascript">
@@ -81,29 +84,35 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js'))
</script>
</div>
-<div id="menu"></div>
+<div id="menubackground" class="tbarbackground fixed"></div>
+<div id="menu" class="tbarmenu fixed"></div>
-<div id="content" class="viewloaded3d">
+<div id="viewheadbackground" class="viewheadbackground fixed"></div>
+<div id="viewhead" class="viewhead fixed"></div>
-<table style="width: 100%;">
-<tr><td><h2><span id="h1"></span></h2></td></tr>
-</table>
+<div id="viewcontainer">
+<div id="view">
+<div id="viewcontent" class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;">
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
+<br/>
<div class="hd2">Sorry, that page is still under construction.</div>
<div>Please check back later.</div>
-</div>
</div>
+</div>
+</div>
<script type="text/javascript">
-// Set page title
-$('h1').innerHTML = ui.hometitle(location.hostname);
-
// Init div variables
var mdiv = $('menu');
-var cdiv = $('content');
+var hdiv = $('viewhead');
+$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm';
+$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm';
+
+// Set page title
+document.title = ui.windowtitle(location.hostname) + ' - Page not found';
+$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle + '</span>';
/**
* Build and show the menu bar.
@@ -115,7 +124,6 @@ function showmenu(mdiv) {
}
showmenu(mdiv);
-cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
/**
* Log the current user out.
@@ -123,9 +131,8 @@ cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
function logout() {
// Clear session cookie and user-specific local storage entries
clearauthcookie();
- localStorage.removeItem('/r/EditWidget/accounts');
- localStorage.removeItem('/r/EditWidget/dashboards');
- //localStorage.clear();
+ lstorage.removeItem('/r/EditWidget/accounts');
+ lstorage.removeItem('/r/EditWidget/dashboards');
document.location = '/login/';
return true;
}
@@ -134,11 +141,12 @@ function logout() {
* Handle orientation change.
*/
document.body.onorientationchange = function(e) {
- //log('onorientationchange');
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
+ //debug('onorientationchange');
+ ui.onorientationchange(e);
+ // Resize menu and view header
+ mdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
+ hdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
return true;
};
@@ -146,13 +154,11 @@ document.body.onorientationchange = function(e) {
* Load post processing.
*/
function onload() {
- //log('onload');
+ //debug('onload');
+ ui.onload();
// Show the page
document.body.style.visibility = 'visible';
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
return true;
}
diff --git a/sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html b/sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html
index 8d27c498d2..ea190f4ec9 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/public/oops/index.html
@@ -36,26 +36,29 @@ appcache.get = function(uri) {
var u = h == -1? uri : uri.substring(0, h);
// Get resource from local storage first
- var item = localStorage.getItem(u);
+ var ls = window.lstorage || localStorage;
+ var item = null;
+ try { item = ls.getItem(u); } catch(e) {}
if (item != null && item != '')
return item;
// Get resource from network
var http = new XMLHttpRequest();
http.open("GET", u, false);
+ http.setRequestHeader("Accept", "*/*");
http.send(null);
if (http.status == 200) {
if (http.getResponseHeader("X-Login") != null) {
- if (log) log('http error', u, 'X-Login');
+ if (debug) debug('http error', u, 'X-Login');
return null;
} else if (http.responseText == '' || http.getResponseHeader("Content-Type") == null) {
- if (log) log('http error', u, 'No-Content');
+ if (debug) debug('http error', u, 'No-Content');
return null;
}
- localStorage.setItem(u, http.responseText);
+ try { ls.setItem(u, http.responseText); } catch(e) {}
return http.responseText;
}
- if (log) log('http error', u, http.status, http.statusText);
+ if (debug) debug('http error', u, http.status, http.statusText);
return null;
};
@@ -71,7 +74,7 @@ appcache.get = function(uri) {
</script>
</head>
<body class="delayed" onload="onload();">
-<div id="bodydiv" class="mainbodydiv">
+<div id="mainbodydiv" class="mainbodydiv">
<div id="headdiv" class="hsection">
<script type="text/javascript">
@@ -81,28 +84,34 @@ $('headdiv').appendChild(ui.declareScript(appcache.get('/public/config-min.js'))
</script>
</div>
-<div id="menu"></div>
+<div id="menubackground" class="tbarbackground fixed"></div>
+<div id="menu" class="tbarmenu fixed"></div>
-<div id="content" class="viewloaded3d">
+<div id="viewheadbackground" class="viewheadbackground fixed"></div>
+<div id="viewhead" class="viewhead fixed"></div>
-<table style="width: 100%;">
-<tr><td><h2><span id="h1"></span></h2></td></tr>
-</table>
+<div id="viewcontainer">
+<div id="view">
+<div id="viewcontent" class="viewcontent" style="margin-left: auto; margin-right: auto; text-align: center;">
-<div style="margin-left: auto; margin-right: auto; text-align: center;">
+<br/>
<div class="hd2">Oops, something went wrong...</div>
-</div>
</div>
+</div>
+</div>
<script type="text/javascript">
-// Set page title
-$('h1').innerHTML = ui.hometitle(location.hostname);
-
// Init div variables
var mdiv = $('menu');
-var cdiv = $('content');
+var hdiv = $('viewhead');
+$('viewcontainer').className = ui.isMobile()? 'viewcontainer3d' : 'viewcontainer3dm';
+$('view').className = ui.isMobile()? 'viewloaded3d' : 'viewloaded3dm';
+
+// Set page title
+document.title = ui.windowtitle(location.hostname) + ' - Oops';
+$('viewhead').innerHTML = '<span class="bcmenu">' + config.pagetitle + '</span>';
/**
* Build and show the menu bar.
@@ -114,7 +123,6 @@ function showmenu(mdiv) {
}
showmenu(mdiv);
-cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
/**
* Log the current user out.
@@ -122,9 +130,8 @@ cdiv.style.top = ui.pixpos(mdiv.offsetTop + mdiv.offsetHeight);
function logout() {
// Clear session cookie and user-specific local storage entries
clearauthcookie();
- localStorage.removeItem('/r/EditWidget/accounts');
- localStorage.removeItem('/r/EditWidget/dashboards');
- //localStorage.clear();
+ lstorage.removeItem('/r/EditWidget/accounts');
+ lstorage.removeItem('/r/EditWidget/dashboards');
document.location = '/login/';
return true;
}
@@ -133,11 +140,12 @@ function logout() {
* Handle orientation change.
*/
document.body.onorientationchange = function(e) {
- //log('onorientationchange');
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
+ //debug('onorientationchange');
+ ui.onorientationchange(e);
+ // Resize menu and view header
+ mdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
+ hdiv.style.width = ui.pixpos(document.documentElement.clientWidth);
return true;
};
@@ -145,13 +153,11 @@ document.body.onorientationchange = function(e) {
* Load post processing.
*/
function onload() {
- //log('onload');
+ //debug('onload');
+ ui.onload();
// Show the page
document.body.style.visibility = 'visible';
-
- // Scroll to the top and hide the address bar
- window.scrollTo(0, 0);
return true;
}
diff --git a/sca-cpp/trunk/hosting/server/htdocs/stats/index.html b/sca-cpp/trunk/hosting/server/htdocs/stats/index.html
index 81f06c95e4..1dd12de1f3 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/stats/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/stats/index.html
@@ -17,40 +17,27 @@
* specific language governing permissions and limitations
* under the License.
-->
-<div id="bodydiv" class="bodydiv">
+<div id="bodydiv" class="body">
-<table style="width: 100%;">
-<tr>
-<td><h2><span id="h1"></span><span id="appNameHeader"></span></h2></td>
-<td style="vertical-align: middle; text-align: right;"><span id="status" style="font-weight: bold; color: #808080;"></span></td>
-</tr>
-</table>
-
-<table style="width: 100%;">
-<tr>
-<th class="thl thr" style="padding-top: 4px; padding-bottom: 4px; padding-left: 2px; padding-right: 2px; ">Stats</th>
-
-<th class="thl thr" style="width: 100%; text-align: right; padding-right: 2px; padding-top: 0px; padding-bottom: 0px;">
-<input type="button" class="graybutton" style="font-weight: bold; margin-top: 0px; margin-bottom: 0px; height: 24px;" id="cloneApp" value="Clone" title="Clone this app"/>
-</th>
-</tr>
-</table>
+<div class="viewform">
<form id="appForm">
<table style="width: 100%;">
-<tr><tr><td><b>App Icon:</b></td></tr>
+<tr><tr><td><b>Icon:</b></td></tr>
<tr><td><img id="appimg" style="width: 50px; height: 50px; vertical-align: top;"></td></tr>
-<tr><tr><td style="padding-top: 6px;"><b>Sharing:</b></td></tr>
-<tr><td><input type="checkbox" value="shared"/><span>Shared</span></td></tr>
-<tr><tr><td style="padding-top: 6px;"><b>App Title:</b></td></tr>
-<tr><td><input type="text" id="appTitle" size="30" placeholder="Enter the title of your app" style="width: 300px;"/></td></tr>
+<tr><tr><td style="padding-top: 6px;"><b>Title:</b></td></tr>
+<tr><td><input type="text" class="flatentry" id="appTitle" size="30" readonly="readonly" placeholder="Enter the title of your app" style="width: 300px;"/></td></tr>
+<tr><tr><td style="padding-top: 6px;"><b>Author:</b></td></tr>
+<tr><td><span id="appAuthor"></span></td></tr>
<tr><tr><td style="padding-top: 6px;"><b>Updated:</b></td></tr>
<tr><td><span id="appUpdated"></span></td></tr>
<tr><tr><td style="padding-top: 6px;"><b>Description:</b></td></tr>
-<tr><td><textarea id="appDescription" cols="40" rows="3" placeholder="Enter a short description of your app" style="width: 300px;"></textarea></td></tr>
+<tr><td><textarea id="appDescription" class="flatentry" cols="40" rows="3" readonly="readonly" placeholder="Enter a short description of your app" style="width: 300px;"></textarea></td></tr>
</table>
</form>
+</div>
+
<script type="text/javascript">
// Get the app name
@@ -58,24 +45,23 @@ var appname = ui.fragmentParams(location)['app'];
// Set page titles
document.title = ui.windowtitle(location.hostname) + ' - Stats - ' + appname;
-$('appNameHeader').innerHTML = '<a href=\"/' + appname + '/\" target=\"' + '_blank' + '\">' + appname + '</a>';
-var tclone = isNil(config.clone)? 'Clone' : config.clone;
-$('cloneApp').value = tclone;
-$('cloneApp').title = tclone + ' this app';
+$('viewhead').innerHTML = '<span id="appname" class="cmenu">' + appname + '</span>' +
+'<input type="button" class="graybutton redbutton plusminus" style="position: absolute; top: 4px; left: 5px;" id="deleteApp" value="-" title="Delete the app" disabled="true"/>' +
+'<input type="button" class="graybutton bluebutton" style="position: absolute; top: 4px; right: 5px;" id="cloneApp" value="'+ config.clone +'" title="' + config.clone + ' this app"/>';
// Set images
$('appimg').src = ui.b64img(appcache.get('/public/app.b64'));
// Init service references
var editWidget = sca.component("EditWidget");
-var dashboards = sca.reference(editWidget, "dashboards");
var apps = sca.reference(editWidget, "apps");
/**
- * The current app entry and corresponding saved XML content.
+ * The current app entry, author and saved XML content.
*/
-var appentry;
var savedappentryxml = '';
+var author;
+var appentry;
/**
* Get and display an app.
@@ -89,17 +75,34 @@ function getapp(name) {
// Stop now if we didn't get the app
if (doc == null) {
- showStatus('No data');
+ showError('App not available');
return false;
}
- showStatus(defaultStatus());
- appentry = doc != null? car(elementsToValues(atom.readATOMEntry(mklist(doc)))) : mklist("'entry", mklist("'title", ''), mklist("'id", name));
- var title = cadr(assoc("'title", cdr(appentry)));
- $('appTitle').value = title;
- $('appUpdated').innerHTML = 'Apr 24, 2011'
- $('appDescription').innerHTML = '';
+ appentry = car(elementsToValues(atom.readATOMEntry(mklist(doc))));
+ $('appTitle').value = cadr(assoc("'title", cdr(appentry)));
+ author = cadr(assoc("'author", cdr(appentry)));
+ $('appAuthor').innerHTML = author;
+ $('appUpdated').innerHTML = cadr(assoc("'updated", cdr(appentry)));
+ var content = cadr(assoc("'content", cdr(appentry)));
+ var description = assoc("'description", content);
+ $('appDescription').value = isNil(description) || isNil(cadr(description))? '' : cadr(description);
savedappentryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry))));
+
+ // Enable author to edit and delete the app
+ if (username == author) {
+ $('appTitle').readOnly = false;
+ $('appDescription').readOnly = false;
+ $('deleteApp').disabled = false;
+ $('deleteApp').onclick = function() {
+ return ui.navigate('/#view=delete&app=' + appname, '_view');
+ }
+ showStatus(defaultStatus());
+ } else {
+ $('appTitle').placeholder = '';
+ $('appDescription').placeholder = '';
+ showStatus('Read only');
+ }
return true;
});
}
@@ -110,7 +113,7 @@ function getapp(name) {
function save(entryxml) {
showStatus('Saving');
savedappentryxml = entryxml;
- dashboards.put(appname, savedappentryxml, function(e) {
+ apps.put(appname, savedappentryxml, function(e) {
if (e) {
showStatus('Local copy');
return false;
@@ -126,8 +129,11 @@ function save(entryxml) {
* Handle a change event
*/
function onappchange() {
+ if (username != author)
+ return false;
var title = $('appTitle').value;
- var appentry = mklist("'entry", mklist("'title", title != ''? title : appname), mklist("'id", appname));
+ var description = $('appDescription').value;
+ appentry = mklist("'entry", mklist("'title", title != ''? title : appname), mklist("'id", appname), mklist("'content", mklist("'stats", mklist("'description", description))));
var entryxml = car(atom.writeATOMEntry(valuesToElements(mklist(appentry))));
if (savedappentryxml == entryxml)
return false;
diff --git a/sca-cpp/trunk/hosting/server/htdocs/store/index.html b/sca-cpp/trunk/hosting/server/htdocs/store/index.html
index bcb3ba7c85..471229d9d5 100644
--- a/sca-cpp/trunk/hosting/server/htdocs/store/index.html
+++ b/sca-cpp/trunk/hosting/server/htdocs/store/index.html
@@ -17,29 +17,19 @@
* specific language governing permissions and limitations
* under the License.
-->
-<div id="bodydiv" class="bodydiv">
+<div id="bodydiv" class="body">
-<table style="width: 100%;">
-<tr>
-<td><h2><span id="h1"></span></h2></td>
-<td style="vertical-align: middle; text-align: right;"><span id="status" style="font-weight: bold; color: #808080;"></span></td>
-</tr>
-</table>
-
-<div id="catmenu"></div>
-
-<div id="apps"></div>
+<div id="apps" class="viewcontent"></div>
<script type="text/javascript">
// Set page titles
document.title = ui.windowtitle(location.hostname) + ' - Store';
-$('h1').innerHTML = ui.hometitle(location.hostname);
// Get the store category
var category = ui.fragmentParams(location)['category'];
if (isNil(category))
- category = 'myapps';
+ category = 'top';
/**
* Build store menu bar
@@ -47,28 +37,21 @@ if (isNil(category))
function catmenu() {
function catmenuitem(name, cat, idx) {
var c = cat == category? 'smenu' : 'amenu';
- return '<th class="thl thr" style="width: 10px; padding-top: 4px; padding-bottom: 4px; padding-right: 6px;">'
- + ui.ahref('/#view=store&category=' + cat + '&idx=' + idx, '_view', '<span class="' + c + '">' + name + '</span>') + '</th>';
+ return '<span>' + ui.ahref('/#view=store&category=' + cat + '&idx=' + idx, '_view', '<span class="' + c + '">' + name + '</span>') + '</span>';
}
- var m = '<table style="width: 100%; margin-bottom: 2px;"><tr>';
- m += catmenuitem('My Apps', 'myapps', '1');
- m += catmenuitem('New', 'new', '2');
- m += catmenuitem('Top', 'top', '3');
- m += catmenuitem('Featured', 'featured', '4');
- m += catmenuitem('All', 'all', '5');
- if (category == 'myapps') {
- m += '<th class="thl thr" style="width: 100%; padding-top: 0px; padding-bottom: 0px; padding-right: 0px; text-align: right;">';
- m += '<input type="button" class="graybutton" id="createApp" title="Create a new app" style="font-weight: bold; margin-top: 0px; margin-bottom: 0px; height: 24px;" Value="New App"/>';
- m += '</th></tr></table>';
- return m;
- }
- m += '<th class="thl thr" style="width: 100%;"></th></tr></table>';
+ var m = '';
+ //m += catmenuitem('Featured', 'featured', '1');
+ m += catmenuitem('Top', 'top', '2');
+ m += catmenuitem('New', 'new', '3');
+ m += catmenuitem('Search', 'all', '4');
+ m += catmenuitem('My Apps', 'myapps', '5');
+ m += '<span class="rmenu"><input type="button" class="graybutton bluebutton" id="createApp" title="Create a new app" Value="Create"/></span>';
return m;
}
// Build store menu bar
-$('catmenu').innerHTML = catmenu();
+$('viewhead').innerHTML = catmenu();
/**
* Service references.
@@ -94,24 +77,22 @@ function viewApp(appname) {
/**
* Create an app.
*/
-if (category == 'myapps') {
- $('createApp').onclick = function() {
- return ui.navigate('/#view=create', '_view');
- }
+$('createApp').onclick = function() {
+ return ui.navigate('/#view=create', '_view');
}
/**
* Get and display list of apps.
*/
function getapps(category) {
- //log('category', category);
+ //debug('category', category);
showStatus('Loading');
function display(doc) {
// Stop now if we didn't get the apps
if (doc == null) {
- showStatus('No data');
+ showError('App not available');
return false;
}
showStatus(defaultStatus());
@@ -128,23 +109,17 @@ function getapps(category) {
return apps;
var entry = car(entries);
var title = cadr(assoc("'title", entry))
- var name = cadr(assoc("'id", entry))
- var author = 'joe';
- var clone = isNil(config.clone)? 'Clone' : config.clone;
-
- apps += '<div class="box" style="width: 150px; display: inline-block; border: 1px; border-style: solid; border-color: #dcdcdc; border-collapse: collapse; margin: 2px; padding: 2px; vertical-align: top;">'
- apps += '<table><tr>';
- apps += '<td>';
- apps += '<div>' + ui.ahref('/#view=stats&app=' + name, '_view', '<img src="' + appimg + '" width="50" height="50" style="height: 50px; width: 50px; vertical-align: top; margin: 0px; padding: 0px;"></img>') + '</div>';
- apps += '</td>';
- apps += '<td class="tdw">';
- apps += '<div style="font-weight: bold">' + ui.ahref('/' + name + '/', '_blank', name) + '</div>';
- if (category == 'myapps')
- apps += '<div style="color: #808080;">Shared</div>';
- else
- apps += '<div>' + 'by&nbsp;' + '<span style="font-weight: bold;">' + author + '</span></div>';
- apps += '</td>';
- apps += '</tr></table>';
+ var name = cadr(assoc("'id", entry));
+ var author = cadr(assoc("'author", entry));
+ var updated = cadr(assoc("'updated", entry));
+
+ apps += '<div class="box">'
+ apps += '<span class="appicon">' + ui.ahref('/#view=stats&app=' + name, '_view', '<img src="' + appimg + '" width="50" height="50"></img>') + '</span>';
+ apps += '<span>'
+ apps += '<span class="apptitle">' + ui.ahref('/#view=stats&app=' + name, '_view', name) + '</span>';
+ if (category != 'myapps')
+ apps += '<br/><span>' + 'by&nbsp;' + author.split('@')[0] + '</span>';
+ apps += '</span>';
apps += '</div>';
return displayentries(cdr(entries));
}
diff --git a/sca-cpp/trunk/hosting/server/pages.py b/sca-cpp/trunk/hosting/server/pages.py
index ae641817ec..a4f6d056f9 100644
--- a/sca-cpp/trunk/hosting/server/pages.py
+++ b/sca-cpp/trunk/hosting/server/pages.py
@@ -16,29 +16,81 @@
# under the License.
# App pages collection implementation
+from time import strftime
from util import *
+from sys import debug
-# Convert an id to an app id
-def appid(id):
+# Convert an id to a page id
+def pageid(id):
return ("apps", car(id), "htdocs", "app.html")
-# Put an app page into the apps db
-def put(id, app, cache):
- xhtml = cdr(cadddr(car(app)))
- cache.put(appid(id), xhtml)
- return True
+# Put a page into the page db
+def put(id, page, user, cache, apps):
+ debug('pages.py::put::id', id)
+ debug('pages.py::put::page', page)
-# Get an app page from the apps db
-def get(id, cache):
+ # Get the requested app
+ app = apps.get(id);
+ if isNil(app) or app is None:
+ debug('pages.py::put', 'app not found', id)
+ return False
+
+ # Check app author
+ author = cadr(assoc("'author", car(app)))
+ if author != user.get(()):
+ debug('pages.py::put', 'different author', author)
+ return False
+
+ # Update the page in the page db
+ pageentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y')), assoc("'content", car(page))),)
+ debug('pages.py::put::pageentry', pageentry)
+ return cache.put(pageid(id), pageentry)
+
+# Get a page from the page db
+def get(id, user, cache, apps):
+ debug('pages.py::get::id', id)
if isNil(id):
return (("'feed", ("'title", "Pages"), ("'id", "pages")),)
- xhtml = cache.get(appid(id))
- if isNil(xhtml) or xhtml is None:
- return (("'entry", ("'title", car(id)), ("'id", car(id))),)
- return (("'entry", ("'title", car(id)), ("'id", car(id)), ("'content", car(xhtml))),)
-
-# Delete an app page from the apps db
-def delete(id, cache):
- cache.delete(appid(id))
- return True
+
+ # Get the requested app
+ app = apps.get(id)
+ if isNil(app) or app is None:
+ debug('pages.py::get', 'app not found', id)
+
+ # Return a default new page
+ return (("'entry", ("'title", car(id)), ("'id", car(id)), ("'author", user.get(())), ("'updated", strftime('%b %d, %Y'))),)
+
+ # Get the requested page
+ page = cache.get(pageid(id))
+ if isNil(page) or page is None:
+ debug('pages.py::get', 'page not found', id)
+
+ # Return a default new page
+ return (("'entry", ("'title", car(id)), ("'id", car(id)), assoc("'author", car(app)), assoc("'updated", car(app))),)
+
+ # Return the page
+ def updated(u):
+ return assoc("'updated", car(app)) if isNil(u) or u is None else u
+ pageentry = (("'entry", assoc("'title", car(app)), ("'id", car(id)), assoc("'author", car(app)), updated(assoc("'updated", car(page))), assoc("'content", car(page))),)
+ debug('pages.py::get::pageentry', pageentry)
+ return pageentry
+
+# Delete a page from the page db
+def delete(id, user, cache, apps):
+ debug('pages.py::delete::id', id)
+
+ # Get the requested app
+ app = apps.get(id);
+ if isNil(app) or app is None:
+ debug('pages.py::delete', 'app not found', id)
+ return False
+
+ # Check app author
+ author = cadr(assoc("'author", car(app)))
+ if author != user.get(()):
+ debug('pages.py::delete', 'different author', author)
+ return False
+
+ # Delete the page
+ return cache.delete(pageid(id))
diff --git a/sca-cpp/trunk/hosting/server/server-test b/sca-cpp/trunk/hosting/server/server-test
new file mode 100755
index 0000000000..d4767e858f
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/server-test
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Run Python test cases
+here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here`
+python_prefix=`cat $here/../../modules/python/python.prefix`
+export LD_LIBRARY_PATH=$python_prefix/lib:$LD_LIBRARY_PATH
+
+$python_prefix/bin/python test.py 2>/dev/null
+rc=$?
+
+exit $rc
diff --git a/sca-cpp/trunk/hosting/server/server.composite b/sca-cpp/trunk/hosting/server/server.composite
index 911ec3eefe..0806a2fa4b 100644
--- a/sca-cpp/trunk/hosting/server/server.composite
+++ b/sca-cpp/trunk/hosting/server/server.composite
@@ -30,6 +30,7 @@
<property name="firstname">?</property>
<property name="lastname">?</property>
<property name="realm">?</property>
+ <property name="host">?</property>
<service name="User">
<binding.http uri="user"/>
</service>
@@ -52,6 +53,7 @@
<implementation.widget location="/app/index.html"/>
<reference name="user" target="User"/>
<reference name="pages" target="Pages"/>
+ <reference name="composites" target="Composites"/>
<reference name="log" target="Log"/>
</component>
@@ -79,7 +81,9 @@
<service name="AppStore">
<binding.http uri="appstore"/>
</service>
+ <reference name="user" target="User"/>
<reference name="cache" target="Cache"/>
+ <reference name="apps" target="Apps"/>
</component>
<component name="Apps">
@@ -87,7 +91,9 @@
<service name="Apps">
<binding.http uri="apps"/>
</service>
+ <reference name="user" target="User"/>
<reference name="cache" target="Cache"/>
+ <reference name="dashboard" target="Dashboards"/>
<reference name="store" target="AppStore"/>
<reference name="composites" target="Composites"/>
<reference name="pages" target="Pages"/>
@@ -98,7 +104,9 @@
<service name="Composites">
<binding.http uri="composites"/>
</service>
+ <reference name="user" target="User"/>
<reference name="cache" target="Doccache"/>
+ <reference name="apps" target="Apps"/>
</component>
<component name="Pages">
@@ -106,7 +114,9 @@
<service name="Pages">
<binding.http uri="pages"/>
</service>
+ <reference name="user" target="User"/>
<reference name="cache" target="Doccache"/>
+ <reference name="apps" target="Apps"/>
</component>
<component name="Palettes">
diff --git a/sca-cpp/trunk/hosting/server/ssl-start b/sca-cpp/trunk/hosting/server/ssl-start
index bbe6a46ea1..b80b2adf40 100755
--- a/sca-cpp/trunk/hosting/server/ssl-start
+++ b/sca-cpp/trunk/hosting/server/ssl-start
@@ -17,7 +17,8 @@
# specific language governing permissions and limitations
# under the License.
-# For this module to work, add the www.example.com domain to your /etc/hosts as follows:
+# For this module to work, add the www.example.com domain to your /etc/hosts as
+# follows:
# 127.0.0.1 www.example.com
here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here`
@@ -45,10 +46,8 @@ fi
../../modules/http/httpd-event-conf tmp
../../modules/http/httpd-ssl-conf tmp 8453
-# Configure OpenID step2 authentication
-../../modules/openid/openid-conf tmp
-../../modules/openid/openid-step2-conf tmp
-../../modules/openid/openid-memcached-conf tmp localhost 11212
+# Configure HTTP basic auth
+../../modules/http/basic-auth-conf tmp
# Configure OAuth authentication
# Configure your OAuth app keys here
@@ -63,6 +62,11 @@ fi
../../modules/http/passwd-auth-conf tmp jane jane
../../modules/http/passwd-auth-conf tmp admin admin
+# Configure OpenID step2 authentication
+../../modules/openid/openid-conf tmp
+../../modules/openid/openid-step2-conf tmp
+../../modules/openid/openid-memcached-conf tmp localhost 11212
+
# Configure authorized users
../../modules/http/group-auth-conf tmp john
../../modules/http/group-auth-conf tmp jane
@@ -72,7 +76,7 @@ fi
../../modules/http/group-auth-conf tmp jane@example.com
# Configure mod-security
-../../modules/http/mod-security-conf tmp
+#../../modules/http/mod-security-conf tmp
# Configure Python component support
../../modules/server/server-conf tmp
@@ -93,11 +97,11 @@ CustomLog "|$here/../../components/log/scribe-cat $host server" sslcombined
EOF
-# cat >tmp/conf/mod-security-log.conf <<EOF
+ cat >tmp/conf/mod-security-log.conf <<EOF
# Generated by: ssl-start $*
-#SecAuditLog "|$here/../../components/log/scribe-cat $host secaudit"
-#
-#EOF
+SecAuditLog "|$here/../../components/log/scribe-cat $host secaudit"
+
+EOF
else
cat >tmp/conf/log.conf <<EOF
@@ -113,13 +117,14 @@ CustomLog $here/tmp/logs/ssl_access_log sslcombined
EOF
-# cat >tmp/conf/mod-security-log.conf <<EOF
+ cat >tmp/conf/mod-security-log.conf <<EOF
# Generated by: ssl-start $*
-#SecAuditLog $here/tmp/logs/secaudit_log
-#
-#EOF
+SecAuditLog $here/tmp/logs/secaudit_log
+
+EOF
fi
+#../../modules/http/httpd-loglevel-conf tmp debug
# Configure certificate mime type
cat >>tmp/conf/svhost-ssl.conf <<EOF
@@ -139,6 +144,8 @@ ErrorDocument 404 /public/notfound/
ErrorDocument 401 /public/notauth/
ErrorDocument 403 /public/notauth/
ErrorDocument 500 /public/oops/
+ErrorDocument 502 /public/oops/
+ErrorDocument 503 /public/oops/
ErrorDocument 405 /public/oops/
EOF
@@ -151,8 +158,6 @@ SCAContribution $here/
SCAComposite server.composite
# Configure SCA Composite for mass dynamic virtual Hosting
-#SCAVirtualContribution $here/data/apps/
-#SCAVirtualComposite app.composite
SCAVirtualContributor Composites
EOF
@@ -162,6 +167,8 @@ cat >>tmp/conf/httpd.conf <<EOF
# Generated by: ssl-start $*
Alias /home/home.png $here/htdocs/home/home.png
Alias /home/home.b64 $here/htdocs/home/home.b64
+Alias /proxy/public/config.js $here/htdocs/public/config.js
+Alias /proxy/public/config-min.js $here/public/config-min.js
EOF
@@ -182,6 +189,5 @@ AliasMatch /v/([^/]+)(.*)$ $here/htdocs/app\$2
EOF
# Start server
-#../../modules/http/httpd-loglevel-conf tmp debug
../../modules/http/httpd-start tmp
diff --git a/sca-cpp/trunk/hosting/server/start b/sca-cpp/trunk/hosting/server/start
index a608ccb2e5..5675e851f8 100755
--- a/sca-cpp/trunk/hosting/server/start
+++ b/sca-cpp/trunk/hosting/server/start
@@ -17,8 +17,9 @@
# specific language governing permissions and limitations
# under the License.
-# For this module to work, add the example.com domain to your /etc/hosts as follows:
-# 127.0.0.1 example.com
+# For this module to work, add the www.example.com domain to your /etc/hosts as
+# follows:
+# 127.0.0.1 www.example.com
here=`echo "import os; print os.path.realpath('$0')" | python`; here=`dirname $here`
jsprefix=`echo "import os; print os.path.realpath('$here/../../modules/js')" | python`
@@ -37,7 +38,7 @@ fi
../../components/cache/memcached-start tmp 11212
# Configure server
-../../modules/http/httpd-conf tmp example.com 8090 htdocs
+../../modules/http/httpd-conf tmp www.example.com 8090 htdocs
../../modules/http/httpd-event-conf tmp
# Configure Python component support
@@ -95,6 +96,8 @@ cat >>tmp/conf/httpd.conf <<EOF
# Generated by: start $*
Alias /home/home.png $here/htdocs/home/home.png
Alias /home/home.b64 $here/htdocs/home/home.b64
+Alias /proxy/public/config.js $here/htdocs/public/config.js
+Alias /proxy/public/config-min.js $here/public/config-min.js
EOF
diff --git a/sca-cpp/trunk/hosting/server/store.py b/sca-cpp/trunk/hosting/server/store.py
index 930e8b3639..054f546c2d 100644
--- a/sca-cpp/trunk/hosting/server/store.py
+++ b/sca-cpp/trunk/hosting/server/store.py
@@ -15,8 +15,9 @@
# specific language governing permissions and limitations
# under the License.
-# stores collection implementation
+# Stores collection implementation
from util import *
+from sys import debug
# Convert a particular store tag to a store id
def storeid(tag):
@@ -24,61 +25,88 @@ def storeid(tag):
# Get a store from the cache
def getstore(id, cache):
+ debug('store.py::getstore::id', id)
val = cache.get(id)
if isNil(val) or val is None:
return ()
- return cdddr(car(val))
+ store = cdddr(car(val))
+ if not isNil(store) and isList(car(cadr(car(store)))):
+ # Expand list of entries
+ estore = tuple(map(lambda e: cons("'entry", e), cadr(car(store))))
+ debug('store.py::getstore::estore', estore)
+ return estore
+
+ debug('store.py::getstore::store', store)
+ return store
# Put a store into the cache
def putstore(id, store, cache):
+ debug('store.py::putstore::id', id)
+ debug('store.py::putstore::store', store)
val = ((("'feed", ("'title", "App Store"), ("'id", cadr(id))) + store),)
+ return cache.put(id, val)
# Put an app into a store
-def put(key, app, cache):
- def putapp(app, store):
+def put(id, app, user, cache, apps):
+ debug('store.py::put::id', id)
+ debug('store.py::put::app', app)
+ tag = car(id)
+ appid = cdr(id)
+
+ def putapp(appid, app, store):
if isNil(store):
return app
- if cadr(caddr(car(app))) == cadr(caddr(car(store))):
+ if car(appid) == cadr(assoc("'id", car(store))):
return cons(car(app), cdr(store))
- return cons(car(store), putapp(app, cdr(store)))
+ return cons(car(store), putapp(appid, app, cdr(store)))
- tag = car(key)
- store = putapp(app, getstore(storeid(tag), cache))
- putstore(storeid(tag), store, cache)
- return True
+ appentry = (("'entry", assoc("'title", car(app)), ("'id", car(appid)), ("'author", user.get(())), assoc("'updated", car(app)), assoc("'content", car(app))),)
+ debug('store.py::put::appentry', appentry)
+
+ store = putapp(appid, appentry, getstore(storeid(tag), cache))
+ return putstore(storeid(tag), store, cache)
# Get apps from a store
-def get(key, cache):
- tag = car(key)
- id = cdr(key)
+def get(id, user, cache, apps):
+ debug('store.py::get::id', id)
+ tag = car(id)
+ appid = cdr(id)
- def findapp(id, store):
+ def findapp(appid, store):
if isNil(store):
return None
- if car(id) == cadr(caddr(car(store))):
+ if car(appid) == cadr(assoc("'id", car(store))):
return (car(store),)
- return findapp(id, cdr(store))
+ return findapp(appid, cdr(store))
+
+ if isNil(appid):
+ store = ((("'feed", ("'title", "App Store"), ("'id", tag)) + getstore(storeid(tag), cache)),)
+ debug('store.py::get::store', store)
+ return store
- if isNil(id):
- return ((("'feed", ("'title", "App Store"), ("'id", tag)) + getstore(storeid(tag), cache)),)
- return findapp(id, getstore(storeid(tag), cache))
+ app = findapp(appid, getstore(storeid(tag), cache))
+ debug('store.py::get::app', app)
+ return app
# Delete apps from a store
-def delete(key, cache):
- tag = car(key)
- id = cdr(key)
+def delete(id, user, cache, apps):
+ debug('store.py::delete::id', id)
+ tag = car(id)
+ appid = cdr(id)
- if isNil(id):
+ if isNil(appid):
return cache.delete(storeid(tag))
- def deleteapp(id, store):
+ def deleteapp(appid, store):
if isNil(store):
return ()
- if car(id) == cadr(caddr(car(store))):
+ if car(appid) == cadr(assoc("'id", car(store))):
return cdr(store)
- return cons(car(store), deleteapp(id, cdr(store)))
+ return cons(car(store), deleteapp(appid, cdr(store)))
- store = deleteapp(id, getstore(storeid(tag), cache))
- putstore(storeid(tag), store, cache)
- return True
+ store = getstore(storeid(tag), cache)
+ deleted = deleteapp(appid, store)
+ if deleted == store:
+ return False
+ return putstore(storeid(tag), deleted, cache)
diff --git a/sca-cpp/trunk/hosting/server/test.py b/sca-cpp/trunk/hosting/server/test.py
new file mode 100755
index 0000000000..2575fb7b92
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/test.py
@@ -0,0 +1,296 @@
+#!/usr/bin/python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Test the hosting server components
+
+import sys
+sys.debug = lambda *l: sys.stderr.write('python::debug ' + repr(l) + '\n')
+import time
+time.strftime = lambda f: 'Jan 01, 2012'
+
+import unittest
+from test.property import *
+from test.reference import *
+from test.cache import *
+
+import user
+import accounts
+import pages
+import composites
+import apps
+import store
+import dashboards
+
+def testUser():
+ # Return current user
+ assert user.get((), mkprop('user', lambda: 'johndoe'), mkprop('email', lambda: 'jdoe@example.com'), mkprop('nick', lambda: 'jdoe'), mkprop('full', lambda: 'john doe'), mkprop('first', lambda: 'john'), mkprop('last', lambda: 'doe'), mkprop('realm', lambda: 'example.com'), mkprop('host', lambda: 'localhost')) == 'jdoe@example.com'
+ return True
+
+def testAccounts():
+ # Get default account
+ defaccount = (("'entry", ("'title", 'jdoe@example.com'), ("'id", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012')),)
+ assert accounts.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {})) == defaccount
+
+ # Get user's account
+ jdoe = (("'entry", ("'title", 'John Doe'), ("'id", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'key", 'value'))),)
+ assert accounts.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('accounts', 'jdoe@example.com', 'user.account') : jdoe})) == jdoe
+
+ # Put and get account
+ cache1 = mkcache('cache', {})
+ assert accounts.put((), jdoe, mkref('user', lambda id: 'jdoe@example.com'), cache1) == True
+ assert accounts.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1) == jdoe
+ return True
+
+def testPages():
+ # Get default page
+ defpage = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012')),)
+ app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),)
+ assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: None)) == defpage
+ defpagefromapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 03, 2012')),)
+ assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: app1)) == defpagefromapp
+
+ # Get a page
+ page1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ())),)
+ assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('apps', 'app1', 'htdocs', 'app.html') : page1}), mkref('apps', lambda id: app1)) == page1
+
+ # Put and get a page
+ cache1 = mkcache('cache', {})
+ page1updated = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012'), ("'content", ())),)
+ assert pages.put(('app1',), page1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True
+ assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == page1updated
+
+ # Reject put from user other than the author
+ app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),)
+ page1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 02, 2012')),)
+ assert pages.put(('app1',), page1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False
+ assert pages.put(('app1',), page1otherauthor, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False
+ assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == page1updated
+
+ # Reject delete from user other than the author
+ assert pages.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False
+ assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == page1updated
+
+ # Delete a page
+ assert pages.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True
+ assert pages.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == defpagefromapp
+ return True
+
+def testComposites():
+ # Get default composite
+ defcomposite = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012')),)
+ app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),)
+ assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: None)) == defcomposite
+ defcompositefromapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 03, 2012')),)
+ assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: app1)) == defcompositefromapp
+
+ # Get a composite
+ composite1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ())),)
+ assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('apps', 'app1', 'app.composite') : composite1}), mkref('apps', lambda id: app1)) == composite1
+
+ # Put and get a composite
+ cache1 = mkcache('cache', {})
+ composite1updated = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012'), ("'content", ())),)
+ assert composites.put(('app1',), composite1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True
+ assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == composite1updated
+
+ # Reject put from user other than the author
+ app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),)
+ composite1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 02, 2012')),)
+ assert composites.put(('app1',), composite1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False
+ assert composites.put(('app1',), composite1otherauthor, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False
+ assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == composite1updated
+
+ # Reject delete from user other than the author
+ assert composites.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1otherauthor)) == False
+ assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == composite1updated
+
+ # Delete a composite
+ assert composites.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == True
+ assert composites.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: app1)) == defcompositefromapp
+ return True
+
+def testApps():
+ # Get default app
+ defapp = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012'), ("'content", ("'stats", ("'description", '')))),)
+ assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == defapp
+
+ # Get an app
+ app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 01, 2012'), ("'content", ("'stats", ("'description", '')))),)
+ assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('apps', 'app1', 'app.stats') : app1}), mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == app1
+
+ # Put and get an app
+ cache1 = mkcache('cache', {})
+ assert apps.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('dashboard', lambda id, app: True), mkref('store', lambda id, app: True), mkref('composites', lambda id, app: True), mkref('pages', lambda id, app: True)) == True
+ assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == app1
+ return True
+
+ # Reject put from user other than the author
+ app1otherauthor = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jane@example.com'), ("'updated", 'Jan 03, 2012'), ("'content", ())),)
+ assert apps.put(('app1',), app1, mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id, app: True), mkref('store', lambda id, app: True), mkref('composites', lambda id, app: True), mkref('pages', lambda id, app: True)) == false
+ assert apps.get(('app1',), mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == app1
+
+ # Reject delete from user other than the author
+ assert apps.delete(('app1',), mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == False
+ assert apps.get(('app1',), mkref('user', lambda id: 'jane@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == app1
+
+ # Delete an app
+ assert apps.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == True
+ assert apps.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('dashboard', lambda id: None), mkref('store', lambda id: None), mkref('composites', lambda id: None), mkref('pages', lambda id: None)) == defapp
+ return True
+
+def testStore():
+ # Get default store
+ defstore = (("'feed", ("'title", 'App Store'), ("'id", 'top')),)
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: None)) == defstore
+
+ # Get a store
+ store1= (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))),)
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('store', 'top', 'store.apps') : store1}), mkref('apps', lambda id: None)) == store1
+
+ store1compact = (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ((("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), (("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))))),)
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('store', 'top', 'store.apps') : store1compact}), mkref('apps', lambda id: None)) == store1
+
+ # Put an app in an empty store
+ cache1 = mkcache('cache', {})
+ app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))),)
+ store1withapp1 = (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1'))))),)
+ assert store.put(('top', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp1
+ assert store.put(('top', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp1
+
+ # Put a second app in the store
+ app2 = (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2')))),)
+ store1withapp2 = (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))),)
+ assert store.put(('top', 'app2'), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp2
+ assert store.put(('top', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp2
+ assert store.put(('top', 'app2'), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp2
+
+ # Put a third app in the store
+ app3 = (("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app3')))),)
+ store1withapp3 = (("'feed", ("'title", 'App Store'), ("'id", 'top'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2')))), ("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app3'))))),)
+ assert store.put(('top', 'app3'), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp3
+ assert store.put(('top', 'app1'), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp3
+ assert store.put(('top', 'app2'), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp3
+ assert store.put(('top', 'app3'), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == store1withapp3
+
+ # Get an app from the store
+ assert store.get(('top','app1'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app1
+ assert store.get(('top','app2'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app2
+ assert store.get(('top','app3'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app3
+
+ # Put a third app in the store, starting from a compacted list
+ cache2 = mkcache('cache', {('store', 'top', 'store.apps') : store1compact})
+ assert store.put(('top', 'app3'), app3, mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == store1withapp3
+
+ # Delete the apps
+ assert store.delete(('top', 'app2'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.delete(('top', 'app4'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == False
+ assert store.delete(('top', 'app1'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.delete(('top', 'app3'), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == defstore
+
+ # Delete a store
+ assert store.delete(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == True
+ assert store.get(('top',), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == defstore
+ return True
+
+def testDashboards():
+ # Get default dashboard
+ defdashboard = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com')),)
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {}), mkref('apps', lambda id: None)) == defdashboard
+
+ # Get the user's dashboard
+ dash1= (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))),)
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('dashboards', 'jdoe@example.com', 'user.apps') : dash1}), mkref('apps', lambda id: None)) == dash1
+
+ dash1compact = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ((("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), (("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))))),)
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), mkcache('cache', {('dashboards', 'jdoe@example.com', 'user.apps') : dash1compact}), mkref('apps', lambda id: None)) == dash1
+
+ # Put an app in an empty dashboard
+ cache1 = mkcache('cache', {})
+ app1 = (("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))),)
+ dash1withapp1 = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1'))))),)
+ assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp1
+ assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp1
+
+ # Put a second app in the dashboard
+ app2 = (("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2')))),)
+ dash1withapp2 = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2'))))),)
+ assert dashboards.put(('app2',), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp2
+ assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp2
+ assert dashboards.put(('app2',), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp2
+
+ # Put a third app in the dashboard
+ app3 = (("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app3')))),)
+ dash1withapp3 = (("'feed", ("'title", 'Your Apps'), ("'id", 'jdoe@example.com'), ("'entry", ("'title", 'app1'), ("'id", 'app1'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app1')))), ("'entry", ("'title", 'app2'), ("'id", 'app2'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app2')))), ("'entry", ("'title", 'app3'), ("'id", 'app3'), ("'author", 'jdoe@example.com'), ("'updated", 'Jan 02, 2012'), ("'content", ("'stats", ("'description", 'app3'))))),)
+ assert dashboards.put(('app3',), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp3
+ assert dashboards.put(('app1',), app1, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp3
+ assert dashboards.put(('app2',), app2, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp3
+ assert dashboards.put(('app3',), app3, mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == dash1withapp3
+
+ # Get an app from the user's dashboard
+ assert dashboards.get(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app1
+ assert dashboards.get(('app2',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app2
+ assert dashboards.get(('app3',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == app3
+
+ # Put a third app in the dashboard, starting from a compacted list
+ cache2 = mkcache('cache', {('dashboards', 'jdoe@example.com', 'user.apps') : dash1compact})
+ assert dashboards.put(('app3',), app3, mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == dash1withapp3
+
+ # Delete the apps
+ assert dashboards.delete(('app2',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.delete(('app4',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == False
+ assert dashboards.delete(('app1',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.delete(('app3',), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache1, mkref('apps', lambda id: None)) == defdashboard
+
+ # Delete the dashboard
+ assert dashboards.delete((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == True
+ assert dashboards.get((), mkref('user', lambda id: 'jdoe@example.com'), cache2, mkref('apps', lambda id: None)) == defdashboard
+ return True
+
+if __name__ == '__main__':
+ print 'Testing...'
+ testUser()
+ testAccounts()
+ testPages()
+ testComposites()
+ testApps()
+ testStore()
+ testDashboards()
+ print 'OK'
+
diff --git a/sca-cpp/trunk/hosting/server/test/__init__.py b/sca-cpp/trunk/hosting/server/test/__init__.py
new file mode 100644
index 0000000000..de5c2d1b1e
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/test/__init__.py
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
diff --git a/sca-cpp/trunk/hosting/server/test/cache.py b/sca-cpp/trunk/hosting/server/test/cache.py
new file mode 100644
index 0000000000..98fa174c00
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/test/cache.py
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Mockup cache for testing
+
+class cache:
+ def __init__(self, name, values):
+ self.name = name
+ self.values = values
+
+ def get(self, id):
+ if id in self.values:
+ return self.values[id]
+ return None
+
+ def put(self, id, value):
+ self.values[id] = value
+ return True
+
+ def post(self, id):
+ return self.put(id)
+
+ def delete(self, id):
+ if id in self.values:
+ del self.values[id]
+ return True
+ return False
+
+ def __repr__(self):
+ return repr((self.name, self.values))
+
+def mkcache(name, values = {}):
+ return cache(name, values)
+
diff --git a/sca-cpp/trunk/hosting/server/test/property.py b/sca-cpp/trunk/hosting/server/test/property.py
new file mode 100644
index 0000000000..1cbb4b2cab
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/test/property.py
@@ -0,0 +1,38 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Mockup component properties for testing
+
+class property:
+ def __init__(self, name, l):
+ self.name = name
+ self.l = l
+
+ def __call__(self, *args):
+ return self.l(*args)
+
+ def __getattr__(self, name):
+ if name == "eval":
+ return self
+ raise AttributeError()
+
+ def __repr__(self):
+ return repr((self.name, self.l()))
+
+def mkprop(name, l):
+ return property(name, l)
+
diff --git a/sca-cpp/trunk/hosting/server/test/reference.py b/sca-cpp/trunk/hosting/server/test/reference.py
new file mode 100644
index 0000000000..fe4a66a087
--- /dev/null
+++ b/sca-cpp/trunk/hosting/server/test/reference.py
@@ -0,0 +1,38 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Mockup component references for testing
+
+class reference:
+ def __init__(self, name, l):
+ self.name = name
+ self.l = l
+
+ def __call__(self, *args):
+ return self.l(*args)
+
+ def __getattr__(self, name):
+ if name == "get" or name == "put":
+ return self
+ raise AttributeError()
+
+ def __repr__(self):
+ return repr((self.name, self.l))
+
+def mkref(name, l):
+ return reference(name, l)
+
diff --git a/sca-cpp/trunk/hosting/server/user.py b/sca-cpp/trunk/hosting/server/user.py
index 227722ac48..9be48b33da 100644
--- a/sca-cpp/trunk/hosting/server/user.py
+++ b/sca-cpp/trunk/hosting/server/user.py
@@ -17,12 +17,13 @@
# User info service component
-# Return the user id
-def id(user, email, nick, full, first, last, realm):
+# Return the current user id
+def get(i, user, email, nick, full, first, last, realm, host):
if email.eval() != '?':
return email.eval()
if nick.eval() != '?':
return nick.eval() + '@' + realm.eval()
if user.eval() != '?':
return user.eval() + '@' + realm.eval()
- return 'joe@localhost'
+ return 'anonymous@' + host.eval();
+