Browse Source

demo: progressive web app

mattias 3 years ago
parent
commit
b0dbc97234

+ 82 - 0
demo/pwa/ServiceWorker.lpi

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <Runnable Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="ServiceWorker"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <CustomData Count="3">
+      <Item0 Name="Pas2JSProject" Value="1"/>
+      <Item1 Name="PasJSPort" Value="3001"/>
+      <Item2 Name="PasJSWebBrowserProject" Value="1"/>
+    </CustomData>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="ServiceWorker.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target FileExt=".js">
+      <Filename Value="www/ServiceWorker.js"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="js"/>
+    </SearchPaths>
+    <Parsing>
+      <SyntaxOptions>
+        <AllowLabel Value="False"/>
+        <CPPInline Value="False"/>
+        <UseAnsiStrings Value="False"/>
+      </SyntaxOptions>
+    </Parsing>
+    <CodeGeneration>
+      <TargetOS Value="browser"/>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <GenerateDebugInfo Value="False"/>
+        <UseLineInfoUnit Value="False"/>
+      </Debugging>
+    </Linking>
+    <Other>
+      <CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/>
+      <CompilerPath Value="$(pas2js)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 113 - 0
demo/pwa/ServiceWorker.lpr

@@ -0,0 +1,113 @@
+program ServiceWorker;
+
+{$mode objfpc}
+
+uses
+  JS, Web;
+
+const
+  CacheName = 'v4';
+
+  FallbackURL = '/images/error.png';
+
+  Resources: array[0..12] of string = (
+    '/index.html',
+    '/css/style.css',
+    '/SimplePWA1.js',
+    '/images/Alpha.png',
+    '/images/Beta.png',
+    '/images/Gamma.png',
+    '/images/Delta.png',
+    '/images/Epsilon.png',
+    '/images/Zeta.png',
+    '/images/Eta.png',
+    '/images/Theta.png',
+    '/images/Iota.png',
+    '/images/error.png'
+    );
+
+procedure PutInCache(Request: TJSRequest; Response: TJSResponse); async;
+var
+  Cache: TJSCache;
+begin
+  Cache := await(TJSCache,Caches.open(CacheName));
+  await(TJSCache,Cache.put(Request, Response));
+end;
+
+function CacheFirst(Request: TJSRequest; PreloadResponsePromise: TJSPromise;
+  FallbackUrl: string): jsvalue; async;
+var
+  ResponseFromCache, PreloadResponse, ResponseFromNetwork, FallbackResponse: TJSResponse;
+begin
+  Result:=nil;
+
+  // First try to get the resource from the cache
+  ResponseFromCache := await(TJSResponse,caches.match(Request));
+  if Assigned(ResponseFromCache) then
+    exit(ResponseFromCache);
+
+  // Next try to use (and cache) the preloaded response, if it's there
+  PreloadResponse := await(TJSResponse,PreloadResponsePromise);
+  if Assigned(PreloadResponse) then
+  begin
+    console.info('using preload response: '+String(JSValue(PreloadResponse)));
+    putInCache(Request, PreloadResponse.clone());
+    exit(PreloadResponse);
+  end;
+
+  // Next try to get the resource from the network
+  try
+    ResponseFromNetwork := await(TJSResponse,window.fetch(Request));
+    // response may be used only once
+    // we need to save clone to put one copy in cache
+    // and serve second one
+    PutInCache(Request, ResponseFromNetwork.clone());
+    exit(ResponseFromNetwork);
+  except
+    FallbackResponse := await(TJSResponse,caches.match(FallbackUrl));
+    if Assigned(FallbackResponse) then
+      exit(FallbackResponse);
+
+    // when even the fallback response is not available,
+    // there is nothing we can do, but we must always
+    // return a Response object
+    Result:=TJSResponse.new('Network error happened', js.new([
+      'status', 408,
+      'headers',
+        js.new(['Content-Type', 'text/plain' ])
+      ]) );
+  end;
+end;
+
+// Enable navigation preload
+function EnableNavigationPreload: jsvalue; async;
+begin
+  Result:=nil;
+  if jsvalue(serviceWorker.registration.navigationPreload) then
+    // Enable navigation preloads!
+    await(serviceWorker.registration.navigationPreload.enable());
+end;
+
+begin
+  serviceWorker.addEventListener('activate', procedure(Event: TJSExtendableEvent)
+    begin
+      Event.waitUntil(EnableNavigationPreload());
+    end);
+
+  ServiceWorker.addEventListener('install', procedure(Event: TJSExtendableEvent)
+    begin
+      Event.waitUntil(
+        Caches.Open(CacheName)._then(
+          TJSPromiseResolver(procedure(Cache: TJSCache)
+          begin
+            Cache.addAll(Resources);
+          end))
+      );
+    end);
+
+  ServiceWorker.addEventListener('fetch', procedure(FetchEvent: TJSFetchEvent)
+    begin
+      FetchEvent.RespondWith(CacheFirst(FetchEvent.request,
+                             FetchEvent.PreloadResponse,FallbackURL) );
+    end);
+end.

+ 90 - 0
demo/pwa/SimplePWA1.lpi

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <Runnable Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="SimplePWA1"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <CustomData Count="4">
+      <Item0 Name="MaintainHTML" Value="1"/>
+      <Item1 Name="Pas2JSProject" Value="1"/>
+      <Item2 Name="PasJSPort" Value="3001"/>
+      <Item3 Name="PasJSWebBrowserProject" Value="1"/>
+    </CustomData>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="SimplePWA1.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="www/index.html"/>
+        <IsPartOfProject Value="True"/>
+        <CustomData Count="1">
+          <Item0 Name="PasJSIsProjectHTMLFile" Value="1"/>
+        </CustomData>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="www/SimplePWA1.js"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="js"/>
+    </SearchPaths>
+    <Parsing>
+      <SyntaxOptions>
+        <AllowLabel Value="False"/>
+        <CPPInline Value="False"/>
+        <UseAnsiStrings Value="False"/>
+      </SyntaxOptions>
+    </Parsing>
+    <CodeGeneration>
+      <TargetOS Value="browser"/>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <GenerateDebugInfo Value="False"/>
+        <UseLineInfoUnit Value="False"/>
+      </Debugging>
+    </Linking>
+    <Other>
+      <CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/>
+      <CompilerPath Value="$(pas2js)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 52 - 0
demo/pwa/SimplePWA1.lpr

@@ -0,0 +1,52 @@
+program SimplePWA1;
+
+{$mode objfpc}
+
+uses
+  JS, Classes, SysUtils, Web;
+
+const
+  GreekLetters: array[1..9] of string = (
+   'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta', 'Eta', 'Theta', 'Iota'
+    );
+
+procedure ShowLetters;
+var
+  h, Letter: String;
+  container: TJSElement;
+begin
+  h:='';
+  for Letter in GreekLetters do
+  begin
+    h:=h+'<div class="card">'#10
+        +'  <img class="card--image" src="/images/'+Letter+'.png"/>'#10
+        +'  <h1 class="card--title">'+Letter+'</h1>'#10
+        +'  <a class="card--link" href="#">Click</a>'#10
+        +'</div>'#10;
+  end;
+  container:=document.querySelector('.container');
+  container.innerHTML := h;
+end;
+
+begin
+  // Your code here
+  document.addEventListener('DOMContentLoaded', @ShowLetters);
+
+  // register service worker
+  if IsServiceWorker then
+    Window.addEventListener('load',
+      procedure()
+      begin
+        Window.navigator.serviceWorker
+          .register('/ServiceWorker.js')
+          ._then(TJSPromiseResolver(procedure(Registration: TJSServiceWorkerRegistration)
+            begin
+              console.log('service worker registered');
+              if IsDefined(Registration.installing) then ;
+            end))
+          .catch(TJSPromiseResolver(procedure(err: JSValue)
+            begin
+              console.log('service worker not registered: '+String(err));
+            end));
+      end);
+end.

+ 84 - 0
demo/pwa/www/css/style.css

@@ -0,0 +1,84 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+body {
+  background: #f0f0f0;
+  font-family: "Arial";
+  font-size: 1rem;
+}
+
+main {
+  max-width: 800px;
+  margin: auto;
+  padding: 0.5rem;
+  text-align: center;
+}
+
+nav {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+ul {
+  list-style: none;
+  display: flex;
+}
+
+li {
+  margin-right: 1rem;
+}
+
+h1 {
+  color: #e04030;
+  margin-bottom: 0.5rem;
+}
+
+.container {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
+  grid-gap: 1rem;
+  justify-content: center;
+  align-items: center;
+  margin: auto;
+  padding: 1rem 0;
+}
+
+.card {
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  width: 16rem auto;
+  height: 18rem;
+  background: #fff;
+  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
+  border-radius: 10px;
+  margin: auto;
+  overflow: hidden;
+}
+
+.card--image {
+  width: 100%;
+  height: 10rem;
+  object-fit: cover;
+}
+
+.card--title {
+  color: #222;
+  font-weight: 700;
+  text-transform: capitalize;
+  font-size: 1.1rem;
+  margin-top: 0.5rem;
+}
+
+.card--link {
+  text-decoration: none;
+  background: #d04030;
+  color: #fff;
+  padding: 0.3rem 1rem;
+  border-radius: 20px;
+}
+

BIN
demo/pwa/www/favicon.ico


BIN
demo/pwa/www/images/Alpha.png


BIN
demo/pwa/www/images/Beta.png


BIN
demo/pwa/www/images/Delta.png


BIN
demo/pwa/www/images/Epsilon.png


BIN
demo/pwa/www/images/Eta.png


BIN
demo/pwa/www/images/Gamma.png


BIN
demo/pwa/www/images/Iota.png


BIN
demo/pwa/www/images/Theta.png


BIN
demo/pwa/www/images/Zeta.png


BIN
demo/pwa/www/images/error.png


BIN
demo/pwa/www/images/icons/icon-128x128.png


BIN
demo/pwa/www/images/icons/icon-144x144.png


BIN
demo/pwa/www/images/icons/icon-152x152.png


BIN
demo/pwa/www/images/icons/icon-16x16.png


BIN
demo/pwa/www/images/icons/icon-192x192.png


BIN
demo/pwa/www/images/icons/icon-24x24.png


BIN
demo/pwa/www/images/icons/icon-256x256.png


BIN
demo/pwa/www/images/icons/icon-32x32.png


BIN
demo/pwa/www/images/icons/icon-384x384.png


BIN
demo/pwa/www/images/icons/icon-48x48.png


BIN
demo/pwa/www/images/icons/icon-512x512.png


BIN
demo/pwa/www/images/icons/icon-72x72.png


BIN
demo/pwa/www/images/icons/icon-96x96.png


+ 39 - 0
demo/pwa/www/index.html

@@ -0,0 +1,39 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="stylesheet" href="css/style.css" />
+  <link rel="manifest" href="manifest.json">
+  <title>Simple Progressive Web App</title>
+  <meta name="apple-mobile-web-app-status-bar" content="#d04010" />
+  <meta name="theme-color" content="#d04010" />
+
+    <link rel="apple-touch-icon" href="images/icons/icon-32x32.png" />
+    <link rel="apple-touch-icon" href="images/icons/icon-48x48.png" />
+    <link rel="apple-touch-icon" href="images/icons/icon-72x72.png" />
+    <link rel="apple-touch-icon" href="images/icons/icon-96x96.png" />
+    <link rel="apple-touch-icon" href="images/icons/icon-128x128.png" />
+    <link rel="apple-touch-icon" href="images/icons/icon-144x144.png" />
+    <link rel="apple-touch-icon" href="images/icons/icon-152x152.png" />
+    <link rel="apple-touch-icon" href="images/icons/icon-192x192.png" />
+    <link rel="apple-touch-icon" href="images/icons/icon-256x256.png" />
+
+  <script src="SimplePWA1.js"></script>
+</head>
+<body>
+  <main>
+    <nav>
+      <h1>Greek Letters</h1>
+      <ul>
+        <li>Home</li>
+        <li>About</li>
+      </ul>
+    </nav>
+    <div class="container"></div>
+  </main>
+  <script>
+    rtl.run();
+  </script>
+</body>
+</html>

+ 43 - 0
demo/pwa/www/manifest.json

@@ -0,0 +1,43 @@
+{
+  "name": "Greek Letters",
+  "short_name": "GreekLetters",
+  "start_url": "index.html",
+  "display": "standalone",
+  "background_color": "#f0f0f0",
+  "theme_color": "#d04030",
+  "orientation": "portrait-primary",
+  "icons": [
+    {
+      "src": "/images/icons/icon-72x72.png",
+      "type": "image/png", "sizes": "72x72"
+    },
+    {
+      "src": "/images/icons/icon-96x96.png",
+      "type": "image/png", "sizes": "96x96"
+    },
+    {
+      "src": "/images/icons/icon-128x128.png",
+      "type": "image/png","sizes": "128x128"
+    },
+    {
+      "src": "/images/icons/icon-144x144.png",
+      "type": "image/png", "sizes": "144x144"
+    },
+    {
+      "src": "/images/icons/icon-152x152.png",
+      "type": "image/png", "sizes": "152x152"
+    },
+    {
+      "src": "/images/icons/icon-192x192.png",
+      "type": "image/png", "sizes": "192x192"
+    },
+    {
+      "src": "/images/icons/icon-384x384.png",
+      "type": "image/png", "sizes": "384x384"
+    },
+    {
+      "src": "/images/icons/icon-512x512.png",
+      "type": "image/png", "sizes": "512x512"
+    }
+  ]
+}