This is an automated email from the ASF dual-hosted git repository.

rubys pushed a commit to branch master
in repository https://git-dual.apache.org/repos/asf/whimsy.git

commit 62cd170e7674e6cac59ec84cd51aae4529316252
Author: Sam Ruby <ru...@intertwingly.net>
AuthorDate: Wed Jul 27 13:42:59 2016 -0400

    rough in service workers; disabled for now
---
 www/board/agenda/routes.rb                      |  1 -
 www/board/agenda/views/app.js.rb                |  1 +
 www/board/agenda/views/layout/main.js.rb        | 19 ++++++-
 www/board/agenda/views/models/agenda.js.rb      |  7 +--
 www/board/agenda/views/models/events.js.rb      |  4 --
 www/board/agenda/views/models/jsonstorage.js.rb | 15 +++---
 www/board/agenda/views/models/pagecache.js.rb   | 35 ++++++++++---
 www/board/agenda/views/pages/bootstrap.js.rb    |  9 ++++
 www/board/agenda/views/pages/cache.js.rb        | 67 +++++++++++++++++++++++++
 www/board/agenda/views/router.js.rb             |  3 ++
 www/board/agenda/views/sw.js.rb                 | 67 +++++++++++++++++++++++++
 11 files changed, 201 insertions(+), 27 deletions(-)

diff --git a/www/board/agenda/routes.rb b/www/board/agenda/routes.rb
index 15f1fa8..a980a89 100755
--- a/www/board/agenda/routes.rb
+++ b/www/board/agenda/routes.rb
@@ -115,7 +115,6 @@ get %r{/(\d\d\d\d-\d\d-\d\d)/(.*)} do |date, path|
   @cssmtime = File.mtime('public/stylesheets/app.css').to_i
 
   if path == 'bootstrap.html'
-    @page[:path] = ''
     @page[:parsed] = [@page[:parsed].first]
     @page[:digest] = nil
     @page[:etag] = nil
diff --git a/www/board/agenda/views/app.js.rb b/www/board/agenda/views/app.js.rb
index 9242ef3..26095b6 100644
--- a/www/board/agenda/views/app.js.rb
+++ b/www/board/agenda/views/app.js.rb
@@ -10,6 +10,7 @@ require_relative 'layout/footer'
 
 # Individual pages
 require_relative 'pages/adjournment'
+require_relative 'pages/bootstrap'
 require_relative 'pages/index'
 require_relative 'pages/report'
 require_relative 'pages/action-items'
diff --git a/www/board/agenda/views/layout/main.js.rb 
b/www/board/agenda/views/layout/main.js.rb
index 4a993fe..0a593b7 100644
--- a/www/board/agenda/views/layout/main.js.rb
+++ b/www/board/agenda/views/layout/main.js.rb
@@ -46,6 +46,7 @@ class Main < React
 
     Agenda.load(@@page.parsed, @@page.digest)
     Minutes.load(@@page.minutes)
+
     self.route(@@page.path, @@page.query)
 
     # free memory
@@ -92,7 +93,15 @@ class Main < React
     # store initial state in history, taking care not to overwrite
     # history set by the Search component.
     if not history.state or not history.state.query
-      history.replaceState({path: @@page.path}, nil, @@page.path)
+      path = @@page.path
+
+      if path == 'bootstrap.html'
+        path = document.location.href
+        base = document.getElementsByTagName('base')[0].href
+        path = path.slice(base.length) if path.start_with? base
+      end
+
+      history.replaceState({path: path}, nil, path)
     end
 
     # listen for back button, and re-route/re-render when it occcurs
@@ -144,7 +153,13 @@ class Main < React
     Agenda.fetch(@@page.etag, @@page.digest)
 
     # start backchannel
-    Events.monitor()
+    if PageCache.enabled
+      # use Service Workers
+      PageCache.register()
+    else
+      # use localStorage
+      Events.monitor()
+    end
   end
 
   # after each subsequent re-rendering, resize main window
diff --git a/www/board/agenda/views/models/agenda.js.rb 
b/www/board/agenda/views/models/agenda.js.rb
index 49a9350..519362d 100644
--- a/www/board/agenda/views/models/agenda.js.rb
+++ b/www/board/agenda/views/models/agenda.js.rb
@@ -64,7 +64,7 @@ class Agenda
         # if bootstrapping and cache is available, load it
         if not digest
           caches.open('board/agenda').then do |cache|
-            cache.match("#{@@date}.json").then do |response|
+            cache.match("../#{@@date}.json").then do |response|
               if response
                 response.json().then do |json| 
                   Agenda.load(json) unless loaded
@@ -78,9 +78,10 @@ class Agenda
         # set fetch options: credentials and etag
         options = {credentials: 'include'}
         options['headers'] = {'If-None-Match' => @@etag} if @@etag
+        request = Request.new("../#{@@date}.json", options)
 
         # perform fetch
-        fetch("../#{@@date}.json", options).then do |response|
+        fetch(request).then do |response|
           if response
             loaded = true
 
@@ -93,7 +94,7 @@ class Agenda
 
             # save response in the cache
             caches.open('board/agenda').then do |cache|
-              cache.put("#{@@date}.json", response)
+              cache.put(request, response)
             end
           end
         end
diff --git a/www/board/agenda/views/models/events.js.rb 
b/www/board/agenda/views/models/events.js.rb
index 8d20572..351b8ae 100644
--- a/www/board/agenda/views/models/events.js.rb
+++ b/www/board/agenda/views/models/events.js.rb
@@ -40,10 +40,6 @@ class Events
   end
 
   def self.monitor()
-    window.addEventListener :load do |event|
-      PageCache.preload()
-    end
-
     @@prefix = JSONStorage.prefix
 
     # pick something unique to identify this tab/window
diff --git a/www/board/agenda/views/models/jsonstorage.js.rb 
b/www/board/agenda/views/models/jsonstorage.js.rb
index 760356c..94e3f48 100644
--- a/www/board/agenda/views/models/jsonstorage.js.rb
+++ b/www/board/agenda/views/models/jsonstorage.js.rb
@@ -54,16 +54,13 @@ class JSONStorage
         fetched = nil
         clock_counter += 1
 
-        # construct arguments to fetch
-        args = {
-          method: 'get',
-          credentials: 'include',
-          headers: {'Accept' => 'application/json'},
-        }
+        # construct request
+        request = Request.new("../json/#{name}", method: 'get',
+          credentials: 'include', headers: {'Accept' => 'application/json'})
 
         # dispatch request
-        fetch("../json/#{name}", args).then do |response|
-          cache.put(name, response.clone())
+        fetch(request).then do |response|
+          cache.put(request, response.clone())
 
           response.json().then do |json| 
             unless fetched and fetched.inspect == json.inspect
@@ -76,7 +73,7 @@ class JSONStorage
         end
 
         # check cache
-        cache.match(name).then do |response|
+        cache.match("../json/#{name}").then do |response|
           if response and not fetched
             response.json().then do |json| 
               clock_counter -= 1
diff --git a/www/board/agenda/views/models/pagecache.js.rb 
b/www/board/agenda/views/models/pagecache.js.rb
index a41779b..44a9ee5 100644
--- a/www/board/agenda/views/models/pagecache.js.rb
+++ b/www/board/agenda/views/models/pagecache.js.rb
@@ -11,11 +11,32 @@ class PageCache
 
   # is page cache available?
   def self.enabled
+    # disable service workers for now.  See:
+    # https://lists.w3.org/Archives/Public/public-webapps/2016JulSep/0016.html
+    return false 
+
     unless location.protocol == 'https:' or location.hostname == 'localhost'
       return false
     end
 
-    return defined? ServiceWorker
+    defined?(ServiceWorker) and defined?(navigator)
+  end
+
+  # registration and related startup actions
+  def self.register()
+    # preload page cache once page finishes loading
+    window.addEventListener :load do |event|
+      PageCache.preload()
+    end
+
+    # register service worker
+    scope = URL.new('..', document.getElementsByTagName('base')[0].href)
+    navigator.serviceWorker.register(scope + 'sw.js', scope)
+
+    # listen for events
+    navigator.serviceWorker.addEventListener :message do |event|
+      Events.dispatch event
+    end
   end
 
   # fetch and cache page and referenced scripts and stylesheets
@@ -23,9 +44,9 @@ class PageCache
     return unless PageCache.enabled?
 
     base = document.getElementsByTagName('base')[0].href
-    fetch_options = {credentials: 'include'}
+    request = Request.new('bootstrap.html', credentials: 'include')
 
-    fetch('bootstrap.html', fetch_options).then do |response|
+    fetch(request).then do |response|
       response.clone().text().then do |text|
         urls = []
 
@@ -48,13 +69,11 @@ class PageCache
         # add bootstrap.html and each URL to the cache.
         caches.open('board/agenda').then do |cache|
           agenda = Agenda.file[/\d+_\d+_\d+/].gsub('_', '-')
-          cache.put("#{agenda}.html", response)
+          cache.put(request, response)
 
           urls.each do |url|
-            fetch(url.pathname, fetch_options).then do |response|
-              basename = url.pathname.split('/').pop().split('?')[0]
-              cache.put(basename, response)
-            end
+            request2 = Request.new(url, credentials: 'include')
+            fetch(request2).then {|response| cache.put(request2, response)}
           end
         end
       end
diff --git a/www/board/agenda/views/pages/bootstrap.js.rb 
b/www/board/agenda/views/pages/bootstrap.js.rb
new file mode 100644
index 0000000..202c0e9
--- /dev/null
+++ b/www/board/agenda/views/pages/bootstrap.js.rb
@@ -0,0 +1,9 @@
+#
+# Blank canvas shown during bootstrapping
+#
+
+class BootStrapPage < React
+  def render
+    _p ''
+  end
+end
diff --git a/www/board/agenda/views/pages/cache.js.rb 
b/www/board/agenda/views/pages/cache.js.rb
index c94c8e6..8f04b7b 100644
--- a/www/board/agenda/views/pages/cache.js.rb
+++ b/www/board/agenda/views/pages/cache.js.rb
@@ -3,6 +3,10 @@
 #
 
 class CacheStatus < React
+  def self.buttons()
+    return [{button: ClearCache}, {button: UnregisterWorker}]
+  end
+
   def initialize
     @cache = []
     @registrations = []
@@ -86,6 +90,69 @@ class CacheStatus < React
 end
 
 #
+# A button that clear the cache
+#
+class ClearCache < React
+  def initialize
+    @disabled = true
+  end
+
+  def render
+    _button.btn.btn_primary 'Clear Cache', onClick: self.click,
+      disabled: @disabled
+  end 
+
+  # update on first update
+  def componentDidMount()
+    self.componentWillReceiveProps()
+  end
+
+  # enable button if there is anything in the cache
+  def componentWillReceiveProps()
+    if defined? caches
+      caches.open('board/agenda').then do |cache|
+        cache.matchAll().then do |responses|
+          @disabled = responses.empty?
+        end
+      end
+    end
+  end
+
+  def click(event)
+    if defined? caches
+      caches.delete('board/agenda').then do |status|
+        Main.refresh()
+      end
+    end
+  end
+end
+
+#
+# A button that removes the service worker.  Sadly, it doesn't seem to have
+# any affect on the list of registrations that is dynamically returned.
+#
+class UnregisterWorker < React
+  def render
+    _button.btn.btn_primary 'Unregister ServiceWorker', onClick: self.click
+  end 
+
+  def click(event)
+    if defined? caches
+      navigator.serviceWorker.getRegistrations().then do |registrations|
+        base = URL.new('..', 
document.getElementsByTagName('base')[0].href).href
+        registrations.each do |registration|
+          if registration.scope == base
+            registration.unregister().then do |status|
+              Main.refresh()
+            end
+          end
+        end
+      end
+    end
+  end
+end
+
+#
 # Individual Cache page
 #
 
diff --git a/www/board/agenda/views/router.js.rb 
b/www/board/agenda/views/router.js.rb
index 4e1c28f..3aa1a14 100644
--- a/www/board/agenda/views/router.js.rb
+++ b/www/board/agenda/views/router.js.rb
@@ -75,6 +75,9 @@ class Router
     elsif path == 'help'
       item = {view: Help}
 
+    elsif path == 'bootstrap.html'
+      item = {view: BootStrapPage, title: ' '}
+
     elsif path == 'cache/'
       item = {view: CacheStatus}
 
diff --git a/www/board/agenda/views/sw.js.rb b/www/board/agenda/views/sw.js.rb
new file mode 100644
index 0000000..b69fcc1
--- /dev/null
+++ b/www/board/agenda/views/sw.js.rb
@@ -0,0 +1,67 @@
+#
+# A very simple service worker
+#
+#   1) Return back cached bootstrap page instead of fetching agenda pages
+#      from the network.  Bootstrap will construct page from cached
+#      agenda.json, as well as updating the cache.
+#
+#   2) For all other pages, serve cached content when offline
+#
+
+# polyfill if necessary
+window = self
+importScripts 'assets/eventsource.min.js' unless defined? EventSource
+
+events = nil
+
+self.addEventListener :activate do |event|
+  # close any pre-existing event socket
+  if events
+    begin
+      events.close()
+    rescue => e
+      events = nil
+    end
+  end
+ 
+  # create a new event source
+  events = EventSource.new('events')
+ 
+  # dispatch any events received to clients
+  events.addEventListener :message do |event|
+    clients.matchAll().then do |list|
+      list.each do |client|
+        client.postMessage(event.data)
+      end
+    end
+  end
+end
+
+self.addEventListener :fetch do |event|
+  scope = self.registration.scope
+  url = event.request.url
+  url = url.slice(scope.length) if url.start_with? scope
+
+  if url =~ %r{^\d\d\d\d-\d\d-\d\d/} and event.request.method == 'GET'
+    event.respondWith(
+      caches.open('board/agenda').then do |cache|
+        date =  url.split('/')[0]
+        return cache.match("#{date}/bootstrap.html").then do |response|
+          console.log response
+          return response || fetch(event.request.url, credentials: 'include')
+        end
+      end
+    )
+  else
+    event.respondWith(
+      fetch(event.request, credentials: 'include').catch do |error|
+        caches.open('board/agenda').then do |cache|
+          return cache.match(event.request.url) do |response|
+            console.log response
+            return response || error
+          end
+        end
+      end
+    )
+  end
+end

-- 
To stop receiving notification emails like this one, please contact
"commits@whimsical.apache.org" <commits@whimsical.apache.org>.

Reply via email to