diff options
52 files changed, 3282 insertions, 1791 deletions
diff --git a/sca-cpp/trunk/.gitignore b/sca-cpp/trunk/.gitignore index b43b8a7d7a..af24acaaa7 100644 --- a/sca-cpp/trunk/.gitignore +++ b/sca-cpp/trunk/.gitignore @@ -56,7 +56,6 @@ m4/ config.guess config.sub config.status -config.js all.js *-min.html *-min.js 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;">></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=">"/>' + +'<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 = '<'; + 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 = '>'; - 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">> </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;">></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=">"/>' + +'<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 = '<'; + 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 = '>'; - 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 ' + '<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 ' + 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(); + diff --git a/sca-cpp/trunk/modules/atom/atom-test.cpp b/sca-cpp/trunk/modules/atom/atom-test.cpp index 3030777b87..e00c75a62f 100644 --- a/sca-cpp/trunk/modules/atom/atom-test.cpp +++ b/sca-cpp/trunk/modules/atom/atom-test.cpp @@ -41,6 +41,10 @@ const string itemEntry( "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n" " <title type=\"text\">item</title>\n" " <id>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b</id>\n" + " <author>\n" + " <email>jane@example.com</email>\n" + " </author>\n" + " <updated>Fri Jan 01 08:11:36 PDT 2012</updated>\n" " <content type=\"application/xml\">\n" " <item>\n" " <name>Apple</name>\n" @@ -54,6 +58,10 @@ const string itemTextEntry("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n" " <title type=\"text\">item</title>\n" " <id>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b</id>\n" + " <author>\n" + " <email>jane@example.com</email>\n" + " </author>\n" + " <updated>Fri Jan 01 08:11:36 PDT 2012</updated>\n" " <content type=\"text\">Apple</content>\n" " <link href=\"cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b\"/>\n" "</entry>\n"); @@ -62,6 +70,10 @@ const string itemNoContentEntry("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n" " <title type=\"text\">item</title>\n" " <id>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b</id>\n" + " <author>\n" + " <name>jane</name>\n" + " </author>\n" + " <updated>Fri Jan 01 08:11:36 PDT 2012</updated>\n" " <link href=\"cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b\"/>\n" "</entry>\n"); @@ -96,6 +108,8 @@ bool testEntry() { const list<value> a = list<value>() + (list<value>() + element + value("entry") + value(list<value>() + element + value("title") + value(string("item"))) + value(list<value>() + element + value("id") + value(string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))) + + value(list<value>() + element + value("author") + value(string("jane@example.com"))) + + value(list<value>() + element + value("updated") + value(string("Fri Jan 01 08:11:36 PDT 2012"))) + value(list<value>() + element + value("content") + value(i))); ostringstream os; writeATOMEntry<ostream*>(writer, &os, a); @@ -105,6 +119,8 @@ bool testEntry() { const list<value> a = list<value>() + (list<value>() + element + value("entry") + value(list<value>() + element + value("title") + value(string("item"))) + value(list<value>() + element + value("id") + value(string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))) + + value(list<value>() + element + value("author") + value(string("jane@example.com"))) + + value(list<value>() + element + value("updated") + value(string("Fri Jan 01 08:11:36 PDT 2012"))) + value(list<value>() + element + value("content") + value(string("Apple")))); ostringstream os; writeATOMEntry<ostream*>(writer, &os, a); @@ -113,7 +129,9 @@ bool testEntry() { { const list<value> a = list<value>() + (list<value>() + element + value("entry") + value(list<value>() + element + value("title") + value(string("item"))) - + value(list<value>() + element + value("id") + value(string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b")))); + + value(list<value>() + element + value("id") + value(string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))) + + value(list<value>() + element + value("author") + value(string("jane"))) + + value(list<value>() + element + value("updated") + value(string("Fri Jan 01 08:11:36 PDT 2012")))); ostringstream os; writeATOMEntry<ostream*>(writer, &os, a); assert(str(os) == itemNoContentEntry); @@ -158,6 +176,10 @@ const string itemFeed("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" " <entry xmlns=\"http://www.w3.org/2005/Atom\">\n" " <title type=\"text\">item</title>\n" " <id>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b</id>\n" + " <author>\n" + " <email>jane@example.com</email>\n" + " </author>\n" + " <updated>Fri Jan 01 08:11:36 PDT 2012</updated>\n" " <content type=\"application/xml\">\n" " <item>\n" " <name>Apple</name>\n" @@ -169,6 +191,10 @@ const string itemFeed("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" " <entry xmlns=\"http://www.w3.org/2005/Atom\">\n" " <title type=\"text\">item</title>\n" " <id>cart-53d67a61-aa5e-4e5e-8401-39edeba8b83c</id>\n" + " <author>\n" + " <email>jane@example.com</email>\n" + " </author>\n" + " <updated>Fri Jan 01 08:11:36 PDT 2012</updated>\n" " <content type=\"application/xml\">\n" " <item>\n" " <name>Orange</name>\n" @@ -183,7 +209,9 @@ bool testFeed() { { const list<value> a = list<value>() + (list<value>() + element + value("feed") + value(list<value>() + element + value("title") + value(string("Feed"))) - + value(list<value>() + element + value("id") + value(string("1234")))); + + value(list<value>() + element + value("id") + value(string("1234"))) + + value(list<value>() + element + value("author") + value(string("jane@example.com"))) + + value(list<value>() + element + value("updated") + value(string("Fri Jan 01 08:11:36 PDT 2012")))); ostringstream os; writeATOMFeed<ostream*>(writer, &os, a); assert(str(os) == emptyFeed); @@ -207,10 +235,14 @@ bool testFeed() { + value(list<value>() + element + value("entry") + value(list<value>() + element + value("title") + value(string("item"))) + value(list<value>() + element + value("id") + value(string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83b"))) + + value(list<value>() + element + value("author") + value(string("jane@example.com"))) + + value(list<value>() + element + value("updated") + value(string("Fri Jan 01 08:11:36 PDT 2012"))) + value(list<value>() + element + value("content") + value(i1))) + value(list<value>() + element + value("entry") + value(list<value>() + element + value("title") + value(string("item"))) + value(list<value>() + element + value("id") + value(string("cart-53d67a61-aa5e-4e5e-8401-39edeba8b83c"))) + + value(list<value>() + element + value("author") + value(string("jane@example.com"))) + + value(list<value>() + element + value("updated") + value(string("Fri Jan 01 08:11:36 PDT 2012"))) + value(list<value>() + element + value("content") + value(i2))); const list<value> a = list<value>() + (append<value>(list<value>() + element + value("feed") diff --git a/sca-cpp/trunk/modules/atom/atom.hpp b/sca-cpp/trunk/modules/atom/atom.hpp index 1d4a12a867..5de3894214 100644 --- a/sca-cpp/trunk/modules/atom/atom.hpp +++ b/sca-cpp/trunk/modules/atom/atom.hpp @@ -46,14 +46,24 @@ const value entry("entry"); */ const list<value> entryElementValues(const list<value>& e) { const list<value> lt = filter<value>(selector(mklist<value>(element, "title")), e); - const value t = isNil(lt)? value(emptyString) : elementValue(car(lt)); + const list<value> t = list<value>() + element + value("title") + (isNil(lt)? value(emptyString) : elementValue(car(lt))); + const list<value> li = filter<value>(selector(mklist<value>(element, "id")), e); - const value i = isNil(li)? value(emptyString) : elementValue(car(li)); + const list<value> i = list<value>() + element + value("id") + (isNil(li)? value(emptyString) : elementValue(car(li))); + + const list<value> la = filter<value>(selector(mklist<value>(element, "author")), e); + const list<value> lan = isNil(la)? list<value>() : filter<value>(selector(mklist<value>(element, "name")), car(la)); + const list<value> lae = isNil(la)? list<value>() : filter<value>(selector(mklist<value>(element, "email")), car(la)); + const list<value> laa = isNil(lan)? lae : lan; + const list<value> a = isNil(laa)? list<value>() : mklist<value>(list<value>() + element + value("author") + elementValue(car(laa))); + + const list<value> lu = filter<value>(selector(mklist<value>(element, "updated")), e); + const list<value> u = isNil(lu)? list<value>() : mklist<value>(list<value>() + element + value("updated") + elementValue(car(lu))); + const list<value> lc = filter<value>(selector(mklist<value>(element, "content")), e); - return append<value>(list<value>() + element + entry - + value(list<value>() + element + value("title") + t) - + value(list<value>() + element + value("id") + i), - isNil(lc)? list<value>() : mklist<value>(value(list<value>() + element + value("content") + elementValue(car(lc))))); + const list<value> c = isNil(lc)? list<value>() : mklist<value>(list<value>() + element + value("content") + elementValue(car(lc))); + + return append<value>(append<value>(append<value>(list<value>() + element + entry + value(t) + value(i), a), u), c); } /** @@ -110,22 +120,44 @@ const failable<list<value> > readATOMFeed(const list<string>& ilist) { } /** + * Returns children of an ATOM content element. + */ +struct filterContentElementChildren { + const value type; + filterContentElementChildren() : type("type") { + } + const bool operator()(const value& v) const { + return !(isAttribute(v) && attributeName((list<value>)v) == type); + } +}; + +const list<value> contentElementChildren(const value& content) { + return filter<value>(filterContentElementChildren(), elementChildren(content)); +} + +/** * Convert a list of element values representing an ATOM entry to a list of elements. */ const list<value> entryElement(const list<value>& l) { - const value title = elementValue(elementChild("title", l)); - const value id = elementValue(elementChild("id", l)); + const value title = elementChild("title", l); + const value id = elementChild("id", l); + const value author = elementChild("author", l); + const bool email = isNil(author)? false : contains(elementValue(author), "@"); + const value updated = elementChild("updated", l); const value content = elementChild("content", l); const bool text = isNil(content)? false : elementHasValue(content); return list<value>() + element + entry + (list<value>() + attribute + "xmlns" + "http://www.w3.org/2005/Atom") - + (list<value>() + element + "title" + (list<value>() + attribute + "type" + "text") + title) - + (list<value>() + element + "id" + id) + + (list<value>() + element + "title" + (list<value>() + attribute + "type" + "text") + elementValue(title)) + + (list<value>() + element + "id" + elementValue(id)) + + (isNil(author)? list<value>() : (list<value>() + element + "author" + + (email? (list<value>() + element + "email" + elementValue(author)) : (list<value>() + element + "name" + elementValue(author))))) + + (isNil(updated)? list<value>() : (list<value>() + element + "updated" + elementValue(updated))) + (isNil(content)? list<value>() : append<value>(list<value>() + element + "content" + (list<value>() + attribute + "type" + (text? "text" : "application/xml")), - text? mklist<value>(elementValue(content)) : elementChildren(content))) - + (list<value>() + element + "link" + (list<value>() + attribute + "href" + id)); + text? mklist<value>(elementValue(content)) : contentElementChildren(content))) + + (list<value>() + element + "link" + (list<value>() + attribute + "href" + elementValue(id))); } /** diff --git a/sca-cpp/trunk/modules/http/conf/mime.types b/sca-cpp/trunk/modules/http/conf/mime.types index 430aa95f0f..3f083f9a32 100644 --- a/sca-cpp/trunk/modules/http/conf/mime.types +++ b/sca-cpp/trunk/modules/http/conf/mime.types @@ -471,7 +471,7 @@ image/gif gif image/ief ief image/jpeg jpeg jpg jpe image/naplps -image/png png +image/png png b64 image/prs.btif image/prs.pti image/svg+xml svg @@ -546,7 +546,7 @@ text/directory text/enriched text/html html htm text/parityfec -text/plain asc txt b64 +text/plain asc txt text/prs.lines.tag text/rfc822-headers text/richtext rtx diff --git a/sca-cpp/trunk/modules/http/form-auth-conf b/sca-cpp/trunk/modules/http/form-auth-conf index 2898d9b7ed..08b97b9df8 100755 --- a/sca-cpp/trunk/modules/http/form-auth-conf +++ b/sca-cpp/trunk/modules/http/form-auth-conf @@ -57,7 +57,7 @@ AuthFormProvider file AuthFormLoginRequiredLocation /login AuthFormLogoutLocation / Session On -SessionCookieName TuscanyFormAuth path=/;secure=TRUE +SessionCookieName TuscanyFormAuth domain=.$host; path=/ SessionCryptoPassphrase $pw Require valid-user </Location> diff --git a/sca-cpp/trunk/modules/http/httpd-conf b/sca-cpp/trunk/modules/http/httpd-conf index 74b3944cc1..5b034c7928 100755 --- a/sca-cpp/trunk/modules/http/httpd-conf +++ b/sca-cpp/trunk/modules/http/httpd-conf @@ -35,8 +35,6 @@ else pportsuffix=":$pport" fi -dothost=`echo $host | grep "\."` - mkdir -p $4 htdocs=`echo "import os; print os.path.realpath('$4')" | python` @@ -83,12 +81,9 @@ HostNameLookups Off # [timestamp] [access] remote-host remote-ident remote-user "request-line" # status response-size "referrer" "user-agent" "user-track" local-IP # virtual-host response-time bytes-received bytes-sent -LogFormat "[%{%a %b %d %H:%M:%S %Y}t] [access] %h %l %u \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" \"%{cookie}n\" %A %V %D %I %O" combined +LogFormat "[%{%a %b %d %H:%M:%S %Y}t] [access] %h %l %u \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" \"%{cookie}n\" %A %V %D %I %O %{mod_security-message}i" combined Include conf/log.conf -# Configure tracking -Include conf/tracking.conf - # Configure Mime types and default charsets TypesConfig $here/conf/mime.types AddDefaultCharset utf-8 @@ -116,7 +111,8 @@ Require all denied # Configure output filters to enable compression and rate limiting <Location /> -SetOutputFilter RATE_LIMIT;DEFLATE +#SetOutputFilter RATE_LIMIT;DEFLATE +SetOutputFilter DEFLATE BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip @@ -125,7 +121,7 @@ BrowserMatch ^check_http/ check_http SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary Header append Vary User-Agent env=!dont-vary -SetEnv rate-limit 400 +#SetEnv rate-limit 400 </Location> # Listen on HTTP port @@ -165,26 +161,6 @@ Include conf/adminauth.conf EOF -# Generate tracking configuration -cat >$root/conf/tracking.conf <<EOF -# Generated by: httpd-conf $* -# Configure tracking -CookieTracking on -CookieName TuscanyVisitorId -CookieStyle Cookie -CookieExpires 31556926 - -EOF - -if [ "$dothost" != "" ]; then - cat >>$root/conf/tracking.conf <<EOF -# Generated by: httpd-conf $* -CookieDomain .$dothost - -EOF - -fi - # Configure logging cat >$root/conf/log.conf <<EOF # Generated by: httpd-conf $* @@ -303,6 +279,10 @@ Require all granted AuthType None Require all granted </Location> +<Location /proxy/public> +AuthType None +Require all granted +</Location> <Location /favicon.ico> AuthType None Require all granted diff --git a/sca-cpp/trunk/modules/http/httpd-ssl-conf b/sca-cpp/trunk/modules/http/httpd-ssl-conf index 420d08ff87..b5f82d9690 100755 --- a/sca-cpp/trunk/modules/http/httpd-ssl-conf +++ b/sca-cpp/trunk/modules/http/httpd-ssl-conf @@ -37,6 +37,8 @@ else sslpportsuffix=":$sslpport" fi +dothost=`echo $host | grep "\."` + htdocs=`echo $conf | awk '{ print $8 }'` mkdir -p $htdocs htdocs=`echo "import os; print os.path.realpath('$htdocs')" | python` @@ -80,6 +82,9 @@ Include conf/locauth-ssl.conf Include conf/pubauth-ssl.conf Include conf/adminauth-ssl.conf +# Configure tracking +Include conf/tracking-ssl.conf + </VirtualHost> EOF @@ -163,7 +168,7 @@ SSLRequire %{SSL_CIPHER_USEKEYSIZE} >= 128 # SSL-cipher "request-line" status response-size "referrer" "user-agent" # "SSL-client-I-DN" "SSL-client-S-DN" "user-track" local-IP virtual-host # response-time bytes-received bytes-sent -LogFormat "[%{%a %b %d %H:%M:%S %Y}t] [sslaccess] %h %l %u %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" \"%{SSL_CLIENT_I_DN}x\" \"%{SSL_CLIENT_S_DN}x\" \"%{cookie}n\" %A %V %D %I %O" sslcombined +LogFormat "[%{%a %b %d %H:%M:%S %Y}t] [sslaccess] %h %l %u %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" \"%{SSL_CLIENT_I_DN}x\" \"%{SSL_CLIENT_S_DN}x\" \"%{cookie}n\" %A %V %D %I %O %{mod_security-message}i" sslcombined Include conf/log-ssl.conf # Enable HTTPS reverse proxy @@ -180,6 +185,26 @@ SSLProxyCheckPeerCN Off EOF +# Generate tracking configuration +cat >$root/conf/tracking-ssl.conf <<EOF +# Generated by: httpd-ssl-conf $* +# Configure tracking +CookieTracking on +CookieName TuscanyVisitorId +CookieStyle Cookie +CookieExpires 31556926 + +EOF + +if [ "$dothost" != "" ]; then + cat >>$root/conf/tracking-ssl.conf <<EOF +# Generated by: httpd-ssl-conf $* +CookieDomain .$dothost + +EOF + +fi + # Configure logging cat >$root/conf/log-ssl.conf <<EOF # Generated by: httpd-ssl-conf $* diff --git a/sca-cpp/trunk/modules/http/proxy-base-conf b/sca-cpp/trunk/modules/http/proxy-base-conf index c61c0e20d8..cbd62bcc14 100755 --- a/sca-cpp/trunk/modules/http/proxy-base-conf +++ b/sca-cpp/trunk/modules/http/proxy-base-conf @@ -24,10 +24,11 @@ root=`echo "import os; print os.path.realpath('$1')" | python` cat >>$root/conf/vhost.conf <<EOF # Generated by: proxy-base-conf $* -# Enable load balancing +# Do not proxy admin pages ProxyPass /balancer-manager ! ProxyPass /server-status ! ProxyPass /server-info ! +ProxyPass /proxy ! # Enable balancer manager <Location /balancer-manager> @@ -38,7 +39,7 @@ HostnameLookups on EOF cat >>$root/conf/adminauth.conf <<EOF -# Generated by: proxy-conf $* +# Generated by: proxy-base-conf $* # Allow the server admin to manage the load balancer <Location /balancer-manager> Require user admin diff --git a/sca-cpp/trunk/modules/http/proxy-conf b/sca-cpp/trunk/modules/http/proxy-conf index b2156e6f74..dd51a34b5b 100755 --- a/sca-cpp/trunk/modules/http/proxy-conf +++ b/sca-cpp/trunk/modules/http/proxy-conf @@ -24,12 +24,14 @@ root=`echo "import os; print os.path.realpath('$1')" | python` cat >>$root/conf/vhost.conf <<EOF # Generated by: proxy-conf $* -# Enable load balancing +# Do not proxy admin pages ProxyPass /balancer-manager ! ProxyPass /server-status ! ProxyPass /server-info ! -ProxyPass / balancer://cluster/ +ProxyPass /proxy ! +# Enable load balancing +ProxyPass / balancer://cluster/ <Proxy balancer://cluster> Require all granted ProxySet lbmethod=byrequests diff --git a/sca-cpp/trunk/modules/http/proxy-ssl-conf b/sca-cpp/trunk/modules/http/proxy-ssl-conf index 94318d7db5..150cf88b60 100755 --- a/sca-cpp/trunk/modules/http/proxy-ssl-conf +++ b/sca-cpp/trunk/modules/http/proxy-ssl-conf @@ -24,12 +24,14 @@ root=`echo "import os; print os.path.realpath('$1')" | python` cat >>$root/conf/vhost-ssl.conf <<EOF # Generated by: proxy-ssl-conf $* -# Enable load balancing +# Do not proxy admin pages ProxyPass /balancer-manager ! ProxyPass /server-status ! ProxyPass /server-info ! -ProxyPass / balancer://sslcluster/ +ProxyPass /proxy ! +# Enable load balancing +ProxyPass / balancer://sslcluster/ <Proxy balancer://sslcluster> Require all granted ProxySet lbmethod=byrequests diff --git a/sca-cpp/trunk/modules/js/htdocs/atomutil.js b/sca-cpp/trunk/modules/js/htdocs/atomutil.js index 62f390d37e..068b5de2fd 100644 --- a/sca-cpp/trunk/modules/js/htdocs/atomutil.js +++ b/sca-cpp/trunk/modules/js/htdocs/atomutil.js @@ -27,12 +27,24 @@ var atom = {}; */ atom.entryElementValues = function(e) { var lt = filter(selector(mklist(element, "'title")), e); - var t = isNil(lt)? '' : elementValue(car(lt)); + var t = mklist(element, "'title", isNil(lt)? '' : elementValue(car(lt))); + var li = filter(selector(mklist(element, "'id")), e); - var i = isNil(li)? '' : elementValue(car(li)); + var i = mklist(element, "'id", isNil(li)? '' : elementValue(car(li))); + + var la = filter(selector(mklist(element, "'author")), e); + var lan = isNil(la)? mklist() : filter(selector(mklist(element, "'name")), car(la)); + var lae = isNil(la)? mklist() : filter(selector(mklist(element, "'email")), car(la)); + var laa = isNil(lan)? lae : lan; + var a = isNil(laa)? mklist() : mklist(mklist(element, "'author", elementValue(car(laa)))); + + var lu = filter(selector(mklist(element, "'updated")), e); + var u = isNil(lu)? mklist() : mklist(mklist(element, "'updated", elementValue(car(lu)))); + var lc = filter(selector(mklist(element, "'content")), e); - return append(mklist(element, "'entry", mklist(element, "'title", t), mklist(element, "'id", i)), - isNil(lc)? mklist() : mklist(mklist(element, "'content", elementValue(car(lc))))) + var c = isNil(lc)? mklist() : mklist(mklist(element, "'content", elementValue(car(lc)))); + + return append(append(append(mklist(element, "'entry", t, i), a), u), c); }; /** @@ -107,14 +119,20 @@ atom.readATOMFeed = function(l) { atom.entryElement = function(l) { var title = elementValue(namedElementChild("'title", l)); var id = elementValue(namedElementChild("'id", l)); + var author = namedElementChild("'author", l); + var email = isNil(author)? false : (elementValue(author).indexOf('@') != -1); + var updated = namedElementChild("'updated", l); var content = namedElementChild("'content", l); var text = isNil(content)? false : elementHasValue(content); - return append(append( + return append(append(append(append( mklist(element, "'entry", mklist(attribute, "'xmlns", "http://www.w3.org/2005/Atom"), mklist(element, "'title", mklist(attribute, "'type", "text"), title), mklist(element, "'id", id)), + isNil(author)? mklist() : mklist(element, "'author", + (email? mklist(element, "'email", elementValue(author)) : mklist(element, "'name", elementValue(author))))), + isNil(updated)? mklist() : mklist(element, "'updated", elementValue(updated))), isNil(content)? mklist() : - mklist(append(mklist(element, "'content", mklist(attribute, "'type", text? "text" : "application/xml")), - text? mklist(elementValue(content)) : elementChildren(content)))), + mklist(append(mklist(element, "'content", mklist(attribute, "'type", text? "text" : "application/xml")), + text? mklist(elementValue(content)) : elementChildren(content)))), mklist(mklist(element, "'link", mklist(attribute, "'href", id)))); }; diff --git a/sca-cpp/trunk/modules/js/htdocs/component.js b/sca-cpp/trunk/modules/js/htdocs/component.js index 10b4535470..c3799ef708 100644 --- a/sca-cpp/trunk/modules/js/htdocs/component.js +++ b/sca-cpp/trunk/modules/js/htdocs/component.js @@ -191,6 +191,7 @@ HTTPBindingClient.prototype.jsonApply = function(req) { var http = HTTPBindingClient.getHTTPRequest(); var hascb = req.cb? true : false; http.open("POST", this.uri, hascb); + http.setRequestHeader("Accept", "*/*"); http.setRequestHeader("Content-Type", "application/json-rpc"); // Construct call back if we have one @@ -238,7 +239,9 @@ HTTPBindingClient.prototype.get = function(id, cb) { var hascb = cb? true : false; // Get from local storage first - var item = localStorage.getItem(u); + var ls = window.lstorage || localStorage; + var item = null; + try { item = ls.getItem(u); } catch(e) {} //log('localStorage.getItem', u, item); if (item != null && item != '') { if (!hascb) @@ -253,6 +256,7 @@ HTTPBindingClient.prototype.get = function(id, cb) { // Connect to the service var http = HTTPBindingClient.getHTTPRequest(); http.open("GET", u, hascb); + http.setRequestHeader("Accept", "*/*"); // Construct call back if we have one if (hascb) { @@ -282,7 +286,7 @@ HTTPBindingClient.prototype.get = function(id, cb) { // Store retrieved entry in local storage if (http.responseText != null) { //log('localStorage.setItem', u, http.responseText); - localStorage.setItem(u, http.responseText); + try { ls.setItem(u, http.responseText); } catch(e) {} } try { return cb(http.responseText); @@ -337,6 +341,7 @@ HTTPBindingClient.prototype.getnocache = function(id, cb) { // Connect to the service var http = HTTPBindingClient.getHTTPRequest(); http.open("GET", u, hascb); + http.setRequestHeader("Accept", "*/*"); // Construct call back if we have one if (hascb) { @@ -407,6 +412,7 @@ HTTPBindingClient.prototype.post = function (entry, cb) { var http = HTTPBindingClient.getHTTPRequest(); var hascb = cb? true : false; http.open("POST", this.uri, hascb); + http.setRequestHeader("Accept", "*/*"); http.setRequestHeader("Content-Type", "application/atom+xml"); // Construct call back if we have one @@ -445,13 +451,15 @@ HTTPBindingClient.prototype.put = function (id, entry, cb) { var u = this.uri + '/' + id; // Update local storage - localStorage.setItem(u, entry); + var ls = window.lstorage || localStorage; + try { ls.setItem(u, entry); } catch(e) {} //log('localStorage.setItem', u, entry); // Connect to the service var http = HTTPBindingClient.getHTTPRequest(); var hascb = cb? true : false; http.open("PUT", u, hascb); + http.setRequestHeader("Accept", "*/*"); http.setRequestHeader("Content-Type", "application/atom+xml"); // Construct call back if we have one @@ -489,13 +497,15 @@ HTTPBindingClient.prototype.del = function (id, cb) { var u = this.uri + '/' + id; // Update local storage - localStorage.removeItem(u); + var ls = window.lstorage || localStorage; + try { ls.removeItem(u); } catch(e) {} //log('localStorage.removeItem', u); // Connect to the service var http = HTTPBindingClient.getHTTPRequest(); var hascb = cb? true : false; http.open("DELETE", u, hascb); + http.setRequestHeader("Accept", "*/*"); // Construct call back if we have one if (cb) { diff --git a/sca-cpp/trunk/modules/js/htdocs/ui.css b/sca-cpp/trunk/modules/js/htdocs/ui.css index 364e47774a..3dde10a2c0 100644 --- a/sca-cpp/trunk/modules/js/htdocs/ui.css +++ b/sca-cpp/trunk/modules/js/htdocs/ui.css @@ -19,146 +19,181 @@ body { margin-top: 0px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px; -font-family: "Helvetica Neue", Helvetica; font-style: normal; font-variant: normal; font-size: 13px; +font-family: Arial; font-style: normal; font-variant: normal; font-size: 14px; +background-color: #ffffff; opacity: 1; -webkit-text-size-adjust: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: rgba(0,0,0,0); --webkit-user-select: none; +-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; +cursor: default; +-webkit-backface-visibility: hidden; } .delayed { visibility: hidden; } -.devicewidth { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden; +.fixed { +position: absolute; } -.mainbodydiv { +.devicewidth { position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden; } -.bodydiv { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden; +.mainbody { +position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; +-webkit-backface-visibility: hidden; } .viewcontainer3dm { --webkit-perspective: 1000; +position: absolute; left: 0px; top: 35px; width: 100%; +-webkit-backface-visibility: hidden; } .viewcontainer3d { +position: absolute; left: 0px; top: 35px; width: 100%; +-webkit-backface-visibility: hidden; } .leftviewloading3dm { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; --webkit-transform: translate3d(100%, 0px, 0) rotateY(0deg); --webkit-backface-visibility: hidden; background-color: #ffffff; +position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden; +z-index: -10; background-color: #ffffff; +-webkit-transform: translate(100%, 0px); -moz-transform: translate(100%, 0px); -ms-transform: translate(100%, 0px); +-o-transform: translate(100%, 0px); transform: translate(100%, 0px); +-webkit-backface-visibility: hidden; } .rightviewloading3dm { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; --webkit-transform: translate3d(-100%, 0px, 0) rotateY(0deg); --webkit-backface-visibility: hidden; background-color: #ffffff; +position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden; +z-index: -10; background-color: #ffffff; +-webkit-transform: translate(-100%, 0px); -moz-transform: translate(-100%, 0px); -ms-transform: translate(-100%, 0px); +-o-transform: translate(-100%, 0px); transform: translate(-100%, 0px); -} - -.flipviewloading3dm { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; --webkit-transform: translate3d(0px, 0px, 0) rotateY(180deg); --webkit-backface-visibility: hidden; background-color: #ffffff; --moz-transform: translate(100%, 0px); --ms-transform: translate(100%, 0px); -transform: translate(100%, 0px); +-webkit-backface-visibility: hidden; } .viewloading3d { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; -visibility: hidden; --webkit-transform: translate3d(100%, 0px, 0) rotateY(0deg); --webkit-backface-visibility: hidden; background-color: #ffffff; +position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden; +visibility: hidden; z-index: -10; background-color: #ffffff; +-webkit-transform: translate(100%, 0px); -moz-transform: translate(100%, 0px); -ms-transform: translate(100%, 0px); +-o-transform: translate(100%, 0px); transform: translate(100%, 0px); +-webkit-backface-visibility: hidden; } .viewloaded3dm { position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; --webkit-transition: -webkit-transform 0.5s ease-in-out; --moz-transition: -moz-transform 0.5s ease-in-out; --ms-transition: -ms-transform 0.5s ease-in-out; -transition: transform 0.5s ease-in-out; --webkit-transform: translate3d(0px, 0px, 0) rotateY(0deg); --webkit-backface-visibility: hidden; background-color: #ffffff; +z-index: 0; background-color: #ffffff; +-webkit-transition: -webkit-transform 0.4s ease-in-out; +-moz-transition: -moz-transform 0.4s ease-in-out; +-ms-transition: -ms-transform 0.4s ease-in-out; +-o-transition: -o-transform 0.4s ease-in-out; +transition: transform 0.4s ease-in-out; +-webkit-transform: translate(0px, 0px); -moz-transform: translate(0px, 0px); -ms-transform: translate(0px, 0px); +-o-transform: translate(0px, 0px); transform: translate(0px, 0px); +-webkit-backface-visibility: hidden; } .viewloaded3d { position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; --webkit-transform: translate3d(0px, 0px, 0) rotateY(0deg); --webkit-backface-visibility: hidden; background-color: #ffffff; +z-index: 0; background-color: #ffffff; +-webkit-transform: translate(0px, 0px); -moz-transform: translate(0px, 0px); -ms-transform: translate(0px, 0px); +-o-transform: translate(0px, 0px); transform: translate(0px, 0px); +-webkit-backface-visibility: hidden; } .viewunloading3dm { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; --webkit-transform: translate3d(0px, 0px, 0) rotateY(0deg); --webkit-backface-visibility: hidden; background-color: #ffffff; +position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden; +z-index: 0; background-color: #ffffff; +-webkit-transform: translate(0px, 0px); -moz-transform: translate(0px, 0px); -ms-transform: translate(0px, 0px); +-o-transform: translate(0px, 0px); transform: translate(0px, 0px); +-webkit-backface-visibility: hidden; } .leftviewunloaded3dm { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; --webkit-transition: -webkit-transform 0.5s ease-in-out; --moz-transition: -moz-transform 0.5s ease-in-out; --ms-transition: -ms-transform 0.5s ease-in-out; -transition: transform 0.5s ease-in-out; --webkit-transform: translate3d(-100%, 0px, 0px) rotateY(0deg); --webkit-backface-visibility: hidden; background-color: #ffffff; +position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden; +z-index: -10; background-color: #ffffff; +-webkit-transition: -webkit-transform 0.4s ease-in-out; +-moz-transition: -moz-transform 0.4s ease-in-out; +-ms-transition: -ms-transform 0.4s ease-in-out; +-o-transition: -o-transform 0.4s ease-in-out; +transition: transform 0.4s ease-in-out; +-webkit-transform: translate(-100%, 0px); -moz-transform: translate(-100%, 0px); -ms-transform: translate(-100%, 0px); +-o-transform: translate(-100%, 0px); transform: translate(-100%, 0px); +-webkit-backface-visibility: hidden; } .rightviewunloaded3dm { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; --webkit-transition: -webkit-transform 0.5s ease-in-out; --moz-transition: -moz-transform 0.5s ease-in-out; --ms-transition: -ms-transform 0.5s ease-in-out; -transition: transform 0.5s ease-in-out; --webkit-transform: translate3d(100%, 0px, 0) rotateY(0deg); --webkit-backface-visibility: hidden; background-color: #ffffff; +position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: hidden; +z-index: -10; background-color: #ffffff; +-webkit-transition: -webkit-transform 0.4s ease-in-out; +-moz-transition: -moz-transform 0.4s ease-in-out; +-ms-transition: -ms-transform 0.4s ease-in-out; +-o-transition: -o-transform 0.4s ease-in-out; +transition: transform 0.4s ease-in-out; +-webkit-transform: translate(100%, 0px); -moz-transform: translate(100%, 0px); -ms-transform: translate(100%, 0px); +-o-transform: translate(100%, 0px); transform: translate(100%, 0px); +-webkit-backface-visibility: hidden; } -.flipviewunloaded3dm { -position: absolute; top: 0px; left: 0px; width: 100%; height: 5000px; overflow: visible; --webkit-transition: -webkit-transform 0.5s ease-in-out; --moz-transition: -moz-transform 0.5s ease-in-out; --ms-transition: -ms-transform 0.5s ease-in-out; -transition: transform 0.5s ease-in-out; --webkit-transform: translate3d(0px, 0px, 0) rotateY(-180deg); --webkit-backface-visibility: hidden; background-color: #ffffff; --moz-transform: translate(0px, 0px); --ms-transform: translate(0px, 0px); -transform: translate(0px, 0px); +.body { +width: 100%; height: 5000px; overflow: visible; +-webkit-backface-visibility: hidden; +} + +.viewhead { +position: absolute; left: 0px; top: 35px; height: 35px; line-height: 35px; width: 100%; z-index: 8; +font-size: 110%; font-weight: bold; background-color: #f1f1f1; color: #000000; +border-top: 1px; border-bottom: 1px; border-left: 0px; border-right: 0px; +border-style: solid; border-top-color: #e5e5e5; border-bottom-color: #e5e5e5; +overflow: hidden; +-webkit-backface-visibility: hidden; +} + +.viewheadbackground { +position: absolute; left: 0px; top: 35px; height: 35px; line-height: 35px; width: 2500px; z-index: 7; +font-size: 110%; font-weight: bold; background-color: #f1f1f1; color: #000000; +border-top: 1px; border-bottom: 1px; border-left: 0px; border-right: 0px; +border-style: solid; border-top-color: #e5e5e5; border-bottom-color: #e5e5e5; +overflow: hidden; +-webkit-backface-visibility: hidden; +} + +.viewcontent { +position: absolute; left: 0px; top: 38px; width: 100%; +-webkit-backface-visibility: hidden; +} + +.viewform { +position: absolute; left: 0px; top: 40px; width: 100%; } table { border: 0px; border-collapse: collapse; border-color: #a2bae7; border-style: solid; -font-family: "Helvetica Neue", Helvetica; font-style: normal; font-variant: normal; font-size: 13px; +font-family: Arial; font-style: normal; font-variant: normal; font-size: 14px; overflow: visible; } @@ -167,22 +202,23 @@ border-bottom: 1px; border-bottom-style: solid; border-color: #dcdcdc; } th { -font-weight: bold; background-color: #d4e6fc; color: #000000; height: 18px; +font-size: 110%; font-weight: bold; background-color: #f1f1f1; color: #000000; text-align: left; padding-left: 2px; padding-right: 8px; padding-top: 0px; padding-bottom: 0px; vertical-align: middle; white-space: nowrap; -border-top: 1px; border-bottom: 1px; border-left: 1px; border-right: 1px; border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4; border-left-color: #a2bae7; border-right-color: #a2bae7; +border-top: 1px; border-bottom: 1px; border-left: 1px; border-right: 1px; border-style: solid; border-top-color: #e5e5e5; border-bottom-color: #e5e5e5; border-left-color: #e5e5e5; border-right-color: #e5e5e5; overflow: hidden; } .section { -font-weight: bold; background-color: #d4e6fc; color: #000000; height: 24px; padding-top: 1px; padding-bottom: 0px; padding-left: 2px; padding-right: 2px; -border-top: 1px; border-bottom: 1px; border-left: 0px; border-right: 0px; border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4; border-left-color: #a2bae7; border-right-color: #a2bae7; +font-size: 110%; font-weight: bold; background-color: #f1f1f1; color: #000000; height: 30px; line-height: 30px; +text-align: left; padding-top: 0px; padding-bottom: 0px; padding-left: 2px; padding-right: 2px; white-space: nowrap; +border-top: 1px; border-bottom: 1px; border-left: 0px; border-right: 0px; border-style: solid; border-top-color: #e5e5e5; border-bottom-color: #e5e5e5; border-left-color: #e5e5e5; border-right-color: #e5e5e5; overflow: hidden; } .hsection { width: 100%; height: 0px; visibility: hidden; border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; border-style: solid; border-bottom-color: #000000; background-color: #ffffff; -padding: 0px; margin-bottom: 0px; margin-left: auto; margin-right: auto; text-align: center; +padding-left: 2px; padding-right: 2px; padding-top: 0px; padding-bottom: 0px; margin-bottom: 0px; margin-left: auto; margin-right: auto; text-align: center; } .fsection{ @@ -191,10 +227,39 @@ border-top: 0px; border-bottom: 0px; border-left: 0px; border-right: 0px; border padding: 0px; margin-top: 0px; margin-left: auto; margin-right: auto; text-align: center; } +.bluetext { +color: #4787ed; +} + +.redtext { +color: #d14836; +} + +.mirror { +display: inline-block; +-webkit-transform: scaleX(-1); +-moz-transform: scaleX(-1); +-ms-transform: scaleX(-1); +-o-transform: scaleX(-1); +transform: scaleX(-1); +} + +.greentext { +color: #009900; +} + .text { padding-top: 3px; padding-bottom: 4px; vertical-align: middle; white-space: nowrap; } +.link { +padding-top: 3px; padding-bottom: 4px; vertical-align: middle; white-space: nowrap; +} + +.checkbox { +padding-top: 3px; padding-bottom: 4px; vertical-align: middle; white-space: nowrap; +} + .thl { border-left: 0px; } @@ -264,39 +329,122 @@ padding: 3px; background-color: #dcdcdc; color: #000000; input { vertical-align: middle; -font-family: "Helvetica Neue", Helvetica; font-style: normal; font-variant: normal; font-size: 13px; +font-family: Arial; font-style: normal; font-variant: normal; font-size: 15px; +outline: none; +-webkit-text-size-adjust: 100%; +} + +button { +vertical-align: middle; +font-family: Arial; font-style: normal; font-variant: normal; font-size: 15px; +outline: none; -webkit-text-size-adjust: 100%; } textarea { -font-family: "Helvetica Neue", Helvetica; font-style: normal; font-variant: normal; font-size: 13px; +font-family: Arial; font-style: normal; font-variant: normal; font-size: 15px; +outline: none; overflow: auto; resize: none; } -.editable { +.flatentry { +font-family: Arial; font-style: normal; font-variant: normal; font-size: 15px; +-webkit-appearance: none; appearance: none; +border: 1px solid #d9d9d9!important; border-top: 1px solid silver!important; +box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; +border-radius: 1px; -webkit-border-radius: 1px; -moz-border-radius: 1px; +margin: 1px!important; padding: 3px 1px 3px 3px; +} + +.graphentry { +font-family: Arial; font-style: normal; font-variant: normal; font-size: 13px; +-webkit-appearance: none; appearance: none; +border: 1px solid #d9d9d9!important; border-top: 1px solid silver!important; +box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; +border-radius: 1px; -webkit-border-radius: 1px; -moz-border-radius: 1px; +margin: 0px; padding: 0px; +} + +.flatcheckbox { +} + +.editablewidget { +-webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text; +outline: none; -moz-outline-style: none; +} + +.noneditablewidget { +outline: none; -moz-outline-style: none; +} + +.editablesvg { background-color: transparent; font-family: inherit; font-style: inherit; font-variant: inherit; font-size: inherit; font-weight: inherit; padding: 0px; margin: 0px; overflow: auto; resize: none; -outline: none; -webkit-appearance: none; -moz-outline-style: none; --webkit-text-size-adjust: 100%; +outline: none; -moz-outline-style: none; +-webkit-appearance: none; -webkit-text-size-adjust: 100%; border: 0px; } a:link { -color: #598edd; text-decoration: none; white-space: nowrap; +color: #357ae8; text-decoration: none; white-space: nowrap; cursor: pointer; } a:visited { -color: #598edd; text-decoration: none; white-space: nowrap; +color: #357ae8; text-decoration: none; white-space: nowrap; cursor: pointer; +} + +.tbarmenu { +position: absolute; top: 0px; left: 0px; z-index: 10; width: 100%; margin: 0px; padding: 0px; border-collapse: separate; +height: 35px; line-height: 35px; background-color: #2c2c2c; +border-top: 1px; border-bottom: 1px; border-left: 0px; border-right: 0px; border-style: solid; border-top-color: #2c2c2c; border-bottom-color: #2c2c2c; +-webkit-backface-visibility: hidden; +} + +.tbarbackground { +position: absolute; top: 0px; left: 0px; z-index: 9; width: 2500px; margin: 0px; padding: 0px; border-collapse: separate; +height: 35px; line-height: 35px; background-color: #2c2c2c; +border-top: 1px; border-bottom: 1px; border-left: 0px; border-right: 0px; border-style: solid; border-top-color: #2c2c2c; border-bottom-color: #2c2c2c; +overflow: hidden; +-webkit-backface-visibility: hidden; +} + +.tbarleft { +padding-left: 2px; padding-right: 6px; white-space: nowrap; float: left; +} + +.tbarright { +padding-left: 6px; padding-right: 2px; white-space: nowrap; float: right; +} + +.tbaramenu { +font-size: 110%; color: #cccccc; text-decoration: none; white-space: nowrap; +} + +.tbarsmenu { +font-size: 110%; font-weight: bold; color: #ffffff; text-decoration: none; white-space: nowrap; } .amenu { -color: #598edd; text-decoration: none; white-space: nowrap; +padding-left: 2px; padding-right: 6px; white-space: nowrap; color: #808080; text-decoration: none; float: left; } .smenu { -font-weight: bold; color: #000000; text-decoration: none; white-space: nowrap; +padding-left: 2px; padding-right: 6px; white-space: nowrap; color: #000000; text-decoration: none; float: left; +} + +.cmenu { +font-size: 18px; padding-left: 6px; padding-right: 6px; white-space: nowrap; color: #000000; text-decoration: none; +width: 100%; margin-left: auto; margin-right: auto; text-align: center; float: right; +} + +.bcmenu { +font-size: 22px; padding-left: 2px; padding-right: 6px; white-space: nowrap; color: #000000; text-decoration: none; +} + +.rmenu { +padding-left: 2px; padding-right: 2px; white-space: nowrap; white-space: nowrap; float: right; } h1 { @@ -319,141 +467,161 @@ img { border: 0px; } +.plusminus { +font-size: 18px; font-family: "Courier New"; +} + .imgbutton { width: 142px; height: 64px; margin-left: 20px; margin-right: 20px; padding: 0px; border: 1px; cursor: pointer; } -.toolbutton { -font-weight: bold; font-size: 16px; -display: inline-block; width: 24px; height: 20px; padding: 0px; -vertical-align: middle; text-align: center; margin-left: 0px; margin-right: 0px; -padding-left: 0px; padding-right: 0px; padding-top: 0px; padding-bottom: 0px; +.graybutton { +display: inline-block; text-align: center; color: #444; font-weight: bold; +padding-top: 0px; padding-bottom: 0px; padding-left: 4px; padding-right: 4px; +height: 28px; line-height: 28px; min-width: 30px; +-webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; +border: 1px solid gainsboro; background-color: whiteSmoke; +background-image: -webkit-gradient(linear,left top,left bottom,from(whiteSmoke),to(#f1f1f1)); +background-image: -webkit-linear-gradient(top,whiteSmoke,#f1f1f1); +background-image: -moz-linear-gradient(top,whiteSmoke,#f1f1f1); +background-image: -ms-linear-gradient(top,whiteSmoke,#f1f1f1); +background-image: -o-linear-gradient(top,whiteSmoke,#f1f1f1); +background-image: linear-gradient(top,whiteSmoke,#f1f1f1); +cursor: default; +} + +.graybutton:hover { +border: 1px solid #c6c6c6; color: #333; text-shadow: 0 1px rgba(0, 0, 0, 0.3); background-color: #f8f8f8; +background-image: -webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#f1f1f1)); +background-image: -webkit-linear-gradient(top,#f8f8f8,#f1f1f1); +background-image: -moz-linear-gradient(top,#f8f8f8,#f1f1f1); +background-image: -ms-linear-gradient(top,#f8f8f8,#f1f1f1); +background-image: -o-linear-gradient(top,#f8f8f8,#f1f1f1); +background-image: linear-gradient(top,#f8f8f8,#f1f1f1); } -.greenbutton { --webkit-border-radius: 4px; -border-radius: 4px; -background: #96d333; -background: -moz-linear-gradient(top, #f8f8f8 0%, #96d333 80%); -background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(80%,#96d333)); -filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f8f8f8', endColorstr='#96d333',GradientType=0 ); -background: -o-linear-gradient(top, #f8f8f8 0%,#96d333 80%); -border: 1px outset #dcdcdc; -padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; margin: 2px; -cursor: pointer; +.graybutton:active { +background-color: #f6f6f6; +background-image: -webkit-gradient(linear,left top,left bottom,from(#f6f6f6),to(#f1f1f1)); +background-image: -webkit-linear-gradient(top,#f6f6f6,#f1f1f1); +background-image: -moz-linear-gradient(top,#f6f6f6,#f1f1f1); +background-image: -ms-linear-gradient(top,#f6f6f6,#f1f1f1); +background-image: -o-linear-gradient(top,#f6f6f6,#f1f1f1); +background-image: linear-gradient(top,#f6f6f6,#f1f1f1); +-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); } -.tgreenbutton { --webkit-border-radius: 4px; -border-radius: 4px; -background: #96d333; -background: -moz-linear-gradient(top, #f8f8f8 0%, #96d333 80%); -background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(80%,#96d333)); -filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f8f8f8', endColorstr='#96d333',GradientType=0 ); -background: -o-linear-gradient(top, #f8f8f8 0%,#96d333 80%); -border: 1px outset #dcdcdc; -padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; margin: 2px; -cursor: pointer; +.graybutton:disabled { +color: #c0c0c0; } .bluebutton { --webkit-border-radius: 4px; -border-radius: 4px; -background: #598edd; -background: -moz-linear-gradient(top, #f8f8f8 0%, #598edd 80%); -background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(80%,#598edd)); -filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f8f8f8', endColorstr='#598edd',GradientType=0 ); -background: -o-linear-gradient(top, #f8f8f8 0%,#598edd 80%); -border: 1px outset #dcdcdc; -padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; margin: 2px; -cursor: pointer; +border: 1px solid #3079ed; color: white; text-shadow: 0 1px rgba(0, 0, 0, 0.1); background-color: #4d90fe; +background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#4787ed)); +background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed); +background-image: -moz-linear-gradient(top,#4d90fe,#4787ed); +background-image: -ms-linear-gradient(top,#4d90fe,#4787ed); +background-image: -o-linear-gradient(top,#4d90fe,#4787ed); +background-image: linear-gradient(top,#4d90fe,#4787ed); +} + +.bluebutton:disabled { +color: #c0c0c0; +} + +.bluebutton:hover { +border: 1px solid #2f5bb7; color: white; text-shadow: 0 1px rgba(0, 0, 0, 0.3); background-color: #357ae8; +background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#357ae8)); +background-image: -webkit-linear-gradient(top,#4d90fe,#357ae8); +background-image: -moz-linear-gradient(top,#4d90fe,#357ae8); +background-image: -ms-linear-gradient(top,#4d90fe,#357ae8); +background-image: -o-linear-gradient(top,#4d90fe,#357ae8); +background-image: linear-gradient(top,#4d90fe,#357ae8); +} + +.bluebutton:hover:disabled { +color: #c0c0c0; } .redbutton { --webkit-border-radius: 4px; -border-radius: 4px; -background: #d03f41; -background: -moz-linear-gradient(top, #f8f8f8 0%, #d03f41 80%); -background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(80%,#d03f41)); -filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f8f8f8', endColorstr='#d03f41',GradientType=0 ); -background: -o-linear-gradient(top, #f8f8f8 0%,#d03f41 80%); -border: 1px outset #dcdcdc; -padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; margin: 2px; -cursor: pointer; -} - -.orangebutton { --webkit-border-radius: 4px; -border-radius: 4px; -background: #ffbb00; -background: -moz-linear-gradient(top, #f8f8f8 0%, #ffbb00 80%); -background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(80%,#ffbb00)); -filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f8f8f8', endColorstr='#ffbb00',GradientType=0 ); -background: -o-linear-gradient(top, #f8f8f8 0%,#ffbb00 80%); -border: 1px outset #dcdcdc; -padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; margin: 2px; -cursor: pointer; +border: 1px solid transparent; color: white; text-shadow: 0 1px rgba(0, 0, 0, 0.1); background-color: #d14836; +background-image: -webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#d14836)); +background-image: -webkit-linear-gradient(top,#dd4b39,#d14836); +background-image: -moz-linear-gradient(top,#dd4b39,#d14836); +background-image: -ms-linear-gradient(top,#dd4b39,#d14836); +background-image: -o-linear-gradient(top,#dd4b39,#d14836); +background-image: linear-gradient(top,#dd4b39,#d14836); +-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3); -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3); -ms-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3); +-o-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3); } -.graybutton { --webkit-border-radius: 4px; -border-radius: 4px; -background: #dcdcdc; -background: -moz-linear-gradient(top, #f8f8f8 0%, #dcdcdc 80%); -background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(80%,#dcdcdc)); -filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f8f8f8', endColorstr='#dcdcdc',GradientType=0 ); -background: -o-linear-gradient(top, #f8f8f8 0%,#dcdcdc 80%); -border: 1px outset #dcdcdc; -padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; margin: 2px; -margin: 2px; -cursor: pointer; +.redbutton:disabled { +color: #c0c0c0; } -.tbar { -margin: 0px; width: 100%; padding-top: 0px; padding-left: 0px; padding-right: 0px; padding-bottom: 0px; border-collapse: separate; -background-color: #2c2c2c; +.redbutton:hover { +border: 1px solid #b0281a; border-bottom-color: #af301f; color: white; background-color: #c53727; +background-image: -webkit-linear-gradient(top,#dd4b39,#c53727); +background-image: -moz-linear-gradient(top,#dd4b39,#c53727); +background-image: -ms-linear-gradient(top,#dd4b39,#c53727); +background-image: -o-linear-gradient(top,#dd4b39,#c53727); +background-image: linear-gradient(top,#dd4b39,#c53727); +-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -ms-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); +-o-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); } -.ltbar { -padding-left: 2px; padding-right: 6px; padding-top: 3px; padding-bottom: 4px; white-space: nowrap; vertical-align: middle; +.redbutton:hover:disabled { +color: #c0c0c0; } -.dtbar { -padding-left: 0px; padding-right: 0px; padding-top: 3px; padding-bottom: 4px; white-space: nowrap; vertical-align: middle; text-align: right; +.box { +width: 150px; display: inline-block; +border: 1px; border-style: solid; border-color: #dcdcdc; border-collapse: collapse; +white-space: nowrap; margin: 2px; padding: 2px; vertical-align: top; } -.rtbar { -padding-left: 6px; padding-right: 2px; padding-top: 3px; padding-bottom: 4px; white-space: nowrap; vertical-align: middle; text-align: right; +.appicon { +float: left; +height: 50px; width: 50px; vertical-align: top; margin: 0px; padding: 0px; } -.tbaramenu { -color: #cccccc; text-decoration: none; white-space: nowrap; vertical-align: middle; +.apptitle { +font-weight: bold; } -.tbarsmenu { -font-weight: bold; color: #ffffff; text-decoration: none; white-space: nowrap; vertical-align: middle; +.note { +font-size: 12px; color: #808080; } -.suggest { -background-color: #d4e6fc; color: #598edd; -border-top: 1px; border-bottom: 1px; border-left: 1px; border-right: 1px; border-style: solid; border-top-color: #a2bae7; border-bottom-color: #d1d3d4; border-left-color: #d1d3d4; border-right-color: #d1d3d4; -position: absolute; overflow: auto; overflow-x: hidden; padding: 0px; margin: 0px; -cursor: default; +.pagediv { +position: absolute; display: block; overflow: visible; +-webkit-backface-visibility: hidden; } -.suggestTable { -border: 0px; border-collapse: separate; padding-left: 5px; padding-right: 5px; padding-top: 2px; padding-bottom: 2px; margin: 0px; +.graphdiv { +position: absolute; display: block; overflow: visible; +-webkit-backface-visibility: hidden; } -.suggestItem { -padding-left: 2px; padding-top: 0px; padding-bottom: 0px; padding-right: 2px; vertical-align: middle; background-color: #d4e6fc; color: #598edd; +.g { +position: absolute; display: block; overflow: visible; +-webkit-backface-visibility: hidden; } -.suggestHilighted { -padding-left: 2px; padding-top: 0px; padding-bottom: 0px; padding-right: 2px; vertical-align: middle; background-color: #598edd; color: #d4e6fc; +.path { +position: absolute; background: transparent; display: block; overflow: visible; +visibility: visible; +-webkit-backface-visibility: hidden; } -.svgtitle { -margin: 0px; padding: 0px; font-family: "Helvetica Neue", Helvetica; font-style: normal; font-variant: normal; font-size: 10px; cursor: default; +.gtitle { +position: absolute; +margin: 4px; padding: 0px; line-height: 15px; vertical-align: middle; white-space: nowrap; +font-family: Arial; font-style: normal; font-variant: normal; font-size: 13px; cursor: default; +background: transparent; display: block; overflow: visible; +-webkit-text-size-adjust: none; +-webkit-touch-callout: none; +-webkit-tap-highlight-color: rgba(0,0,0,0); +-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } diff --git a/sca-cpp/trunk/modules/js/htdocs/ui.js b/sca-cpp/trunk/modules/js/htdocs/ui.js index dcc1462084..f200321ccd 100644 --- a/sca-cpp/trunk/modules/js/htdocs/ui.js +++ b/sca-cpp/trunk/modules/js/htdocs/ui.js @@ -50,6 +50,13 @@ function $(id) { }; /** + * Return a list of elements with the given class name. + */ +ui.elementsByClassName = function(node, c) { + return nodeList(node.getElementsByClassName(c)); +}; + +/** * Return the query string of a URL. */ ui.query = function(url) { @@ -151,7 +158,7 @@ ui.evalScript = function(s) { * Include a script. */ ui.includeScript = function(s) { - log('include', s); + //log('include', s); return eval(s); }; @@ -207,18 +214,54 @@ ui.pixpos = function(p) { }; /** - * Reload the current document when orientation changes. + * Default orientation change behavior. */ -ui.onorientationchange = function() { - window.location.reload(); +ui.onorientationchange = function(e) { + + // Scroll to the top and hide the address bar + window.scrollTo(0, 0); + + // Change fixed position elements to absolute then back to fixed + // to make sure they're correctly layed out after the orientation + // change + map(function(e) { + e.style.position = 'absolute'; + return e; + }, ui.elementsByClassName(document, 'fixed')); + + setTimeout(function() { + map(function(e) { + e.style.position = 'fixed'; + return e; + }, ui.elementsByClassName(document, 'fixed')); + }, 0); return true; -} +}; + +/** + * Default page load behavior. + */ +ui.onload = function() { + + // Scroll to the top and hide the address bar + window.scrollTo(0, 0); + + // Initialize fixed position elements only after the page is loaded, + // to workaround layout issues with fixed position on mobile devices + setTimeout(function() { + map(function(e) { + e.style.position = 'fixed'; + return e; + }, ui.elementsByClassName(document, 'fixed')); + }, 0); + return true; +}; /** * Navigate to a new document. */ ui.navigate = function(url, win) { - log('navigate', url, win); + //log('navigate', url, win); // Open a new window if (win == '_blank') @@ -283,17 +326,11 @@ ui.menufunc = function(name, fun, hilight) { }; ui.menubar = function(left, right) { - var bar = '<table cellpadding="0" cellspacing="0" width="100%" class="tbar"><tr>' + - '<td class="dtbar"><table border="0" cellspacing="0" cellpadding="0"><tr>'; + var bar = ''; for (i in left) - bar = bar + '<td class="ltbar">' + left[i].content() + '</td>' - - bar = bar + '</tr></table></td>' + - '<td class="dtbar"><table border="0" cellpadding="0" cellspacing="0" align="right"><tr>'; + bar = bar + '<span class="tbarleft">' + left[i].content() + '</span>'; for (i in right) - bar = bar + '<td class="' + (i == 0? 'dtbar' : 'rtbar') + '">' + right[i].content() + '</td>' - - bar = bar + '</tr></table></td></tr></table>'; + bar = bar + '<span class="tbarright">' + right[i].content() + '</span>'; return bar; }; @@ -379,105 +416,3 @@ ui.datalist = function(l) { return '<table class="datatable ' + (window.name == 'dataFrame'? ' databg' : '') + '" style="width: 100%;">' + rows(l, 0) + '</table>'; } -/** - * Autocomplete / suggest support for input fields - * To use it declare a 'suggest' function as follows: - * function suggestItems() { - * return new Array('abc', 'def', 'ghi'); - * } - * then hook it to an input field as follows: - * suggest(document.yourForm.yourInputField, suggestItems); - */ -ui.selectSuggestion = function(node, value) { - for (;;) { - node = node.parentNode; - if (node.tagName.toLowerCase() == 'div') - break; - } - node.selectSuggestion(value); -}; - -ui.hilightSuggestion = function(node, over) { - if (over) - node.className = 'suggestHilighted'; - node.className = 'suggestItem'; -}; - -ui.suggest = function(input, suggestFunction) { - input.suggest = suggestFunction; - - input.selectSuggestion = function(value) { - this.hideSuggestDiv(); - this.value = value; - } - - input.hideSuggestDiv = function() { - if (this.suggestDiv != null) { - this.suggestDiv.style.visibility = 'hidden'; - } - } - - input.showSuggestDiv = function() { - if (this.suggestDiv == null) { - this.suggestDiv = document.createElement('div'); - this.suggestDiv.input = this; - this.suggestDiv.className = 'suggest'; - input.parentNode.insertBefore(this.suggestDiv, input); - this.suggestDiv.style.visibility = 'hidden'; - this.suggestDiv.style.zIndex = '99'; - - this.suggestDiv.selectSuggestion = function(value) { - this.input.selectSuggestion(value); - } - } - - var values = this.suggest(); - var items = ''; - for (var i = 0; i < values.length; i++) { - if (values[i].indexOf(this.value) == -1) - continue; - if (items.length == 0) - items += '<table class="suggestTable">'; - items += '<tr><td class="suggestItem" ' + - 'onmouseover="ui.hilightSuggestion(this, true)" onmouseout="ui.hilightSuggestion(this, false)" ' + - 'onmousedown="ui.selectSuggestion(this, \'' + values[i] + '\')">' + values[i] + '</td></tr>'; - } - if (items.length != 0) - items += '</table>'; - this.suggestDiv.innerHTML = items; - - if (items.length != 0) { - var node = input; - var left = 0; - var top = 0; - for (;;) { - left += node.offsetLeft; - top += node.offsetTop; - node = node.offsetParent; - if (node.tagName.toLowerCase() == 'body') - break; - } - this.suggestDiv.style.left = left; - this.suggestDiv.style.top = top + input.offsetHeight; - this.suggestDiv.style.visibility = 'visible'; - } else - this.suggestDiv.style.visibility = 'hidden'; - } - - input.onkeydown = function(event) { - this.showSuggestDiv(); - }; - - input.onkeyup = function(event) { - this.showSuggestDiv(); - }; - - input.onmousedown = function(event) { - this.showSuggestDiv(); - }; - - input.onblur = function(event) { - setTimeout(function() { input.hideSuggestDiv(); }, 50); - }; -}; - diff --git a/sca-cpp/trunk/modules/js/htdocs/util.js b/sca-cpp/trunk/modules/js/htdocs/util.js index 677132d2a8..0f7de94289 100644 --- a/sca-cpp/trunk/modules/js/htdocs/util.js +++ b/sca-cpp/trunk/modules/js/htdocs/util.js @@ -170,11 +170,11 @@ function tokens(path) { } /** - * Log a value. + * Debug log a value. */ var rconsole; -function log(v) { +function debug(v) { try { var s = ''; for (i = 0; i < arguments.length; i++) { @@ -196,13 +196,13 @@ function log(v) { } /** - * Dump an object to the debug console. + * Dump an object to the console. */ -function debug(o) { +function dump(o) { try { for (f in o) { try { - log('debug ' + f + '=' + o[f]); + debug('dump ' + f + '=' + o[f]); } catch (e) {} } } catch (e) {} @@ -302,6 +302,51 @@ function unmemo(obj) { } /** + * Local storage. + */ +var lstorage = {}; +lstorage.enabled = true; + +/** + * Get an item. + */ +lstorage.getItem = function(k) { + if (!lstorage.enabled) + return null; + try { + return localStorage.getItem(k); + } catch(e) { + return null; + } +} + +/** + * Set an item. + */ +lstorage.setItem = function(k, v) { + if (!lstorage.enabled) + return null; + try { + return localStorage.setItem(k, v); + } catch(e) { + return null; + } +} + +/** + * Remove an item. + */ +lstorage.removeItem = function(k) { + if (!lstorage.enabled) + return null; + try { + return localStorage.removeItem(k); + } catch(e) { + return null; + } +} + +/** * Returns a list of the properties of an object. */ function properties(o) { |