Konubinix' opinionated web of thoughts

Simple Pwa Offline Camera

Fleeting

For the fun of it, I created a TWA using PWABuilder, available here (for the record, it also worked with bubblewrap).

open

{
    "name": "camera",
    "short_name": "camera",
    "description": "camera",
    "start_url": "./index.html",
    "display": "fullscreen",
    "background_color": "#ffffff",
    "theme_color": "#4285f4",
    "icons": [
        {
            "src": "./icons/icon-192x192.png",
            "type": "image/png",
            "sizes": "192x192"
        },
        {
            "src": "./icons/icon-512x512.png",
            "type": "image/png",
            "sizes": "512x512"
        }
    ],
    "orientation": "any",
    "prefer_related_applications": false,
    "scope": "./",
    "permissions": [],
    "splash_pages": null,
    "categories": []
}

const PRECACHE = 'precache-v1';
const RUNTIME = 'runtime';

const PRECACHE_URLS = [
    "index.html",
];


self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(PRECACHE)
            .then(cache => cache.addAll(PRECACHE_URLS))
            .then(self.skipWaiting())
    );
});

self.addEventListener('activate', event => {
    const currentCaches = [PRECACHE, RUNTIME];
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));
        }).then(cachesToDelete => {
            return Promise.all(cachesToDelete.map(cacheToDelete => {
                return caches.delete(cacheToDelete);
            }));
        }).then(() => self.clients.claim())
    );
});

[[progressive_web_app.org:network_first-ex()]]

    <html>
      <head>
        <meta charset="UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
        <link rel="manifest" href="./manifest.json">
        <script defer src="https://konubinix.eu/ipfs/bafkreic33rowgvvugzgzajagkuwidfnit2dyqyn465iygfs67agsukk24i?orig=https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
        <script src="https://konubinix.eu/ipfs/bafybeihp5kzlgqt56dmy5l4z7kpymfc4kn3fnehrrtr7cid7cn7ra36yha?orig=https://cdn.tailwindcss.com/3.4.3"></script>
        <script>
window.onbeforeunload = function() {
        return "Dude, are you sure you want to leave? Think of the kittens!";
    }
          document.addEventListener('alpine:init', () => {
              Alpine.data('app', () => ({
                  video: null,
                  image_capture: null,
                  wakelock: null,
                  taking_pic: false,
                  algo: "canva",
                  async captureImage() {
                      this.taking_pic = true
                      if(this.algo === "take"){
                          blob = await this.image_capture.takePhoto()
                          // Create a link element to download the image
                          const link = document.createElement('a');
                          link.download = `photo_${this.getCurrentDateTime()}.png`;

                          const url = URL.createObjectURL(blob);
                          link.href = url;
                          link.click();
                          URL.revokeObjectURL(url);
                      }
                      else if(this.algo === "canva")
                      {
                          const canvas = document.createElement('canvas');
                          const ctx = canvas.getContext('2d');

                          canvas.width = video.videoWidth;
                          canvas.height = video.videoHeight;

                          ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

                          // Convert canvas to blob
                          canvas.toBlob((blob) => {
                              // Create a link element to download the image
                              const link = document.createElement('a');
                              link.download = `photo_${this.getCurrentDateTime()}.png`;

                              const url = URL.createObjectURL(blob);
                              link.href = url;
                              link.click();
                              URL.revokeObjectURL(url);
                          }, 'image/png');
                      }
                      else if (this.algo === "grab")
                      {
                          imageBitmap = await this.image_capture.grabFrame()
                          const canvas = document.createElement('canvas');
                          canvas.width = imageBitmap.width;
                          canvas.height = imageBitmap.height;
                          const ctx = canvas.getContext('2d');
                          ctx.drawImage(imageBitmap, 0, 0);

                          canvas.toBlob(blob => {
                              // Create a link element to download the image
                              const link = document.createElement('a');
                              link.download = `photo_${this.getCurrentDateTime()}.png`;

                              const url = URL.createObjectURL(blob);
                              link.href = url;
                              link.click();
                              URL.revokeObjectURL(url);
                          }, 'image/png');

                      }
                      else{
                          alert(`unrecognize algo: ${this.algo}`)
                      }
                      setTimeout(() => {this.taking_pic = false}, 125)
                  },
                  getCurrentDateTime() {
                      const now = new Date();
                      const year = now.getFullYear();
                      const month = String(now.getMonth() + 1).padStart(2, '0');
                      const day = String(now.getDate()).padStart(2, '0');
                      const hours = String(now.getHours()).padStart(2, '0');
                      const minutes = String(now.getMinutes()).padStart(2, '0');
                      const seconds = String(now.getSeconds()).padStart(2, '0');
                      const milliseconds = String(now.getMilliseconds()).padStart(3, '0');

                      return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}.${milliseconds}`;
                  },
                  async init () {
                      if ('serviceWorker' in navigator) {
                          navigator.serviceWorker
                              .register('./sw.js')
                              .then(function() {
                                  console.log('Service Worker Registered');
                              });
                      }
                      this.video = document.getElementById("video")
                      if(! 'mediaDevices' in navigator) {
                          alert( 'Your browser does not support media devices.' );
                      }
                      await this.setupMediaStream()
                      try{
                          navigator.wakeLock.request("screen").then(
                              (w) => {
                                  this.wakelock = w;
                                  this.message += "wakelock acquired"
                              }
                          ).catch((err) => {
                              this.message += `${err.name}, ${err.message}`;
                          });
                      } catch (err) {
                          this.message += err.toString()
                      };
                  },
                  async setupMediaStream() {
                      this.mediaStream = await navigator.mediaDevices.getUserMedia(
                          {
                              video: {
                                  facingMode: {
                                      exact: "environment"
                                  },
                              },
                              // audio: true,
                          }
                      );
                      const track = this.mediaStream.getVideoTracks()[0];
                      this.image_capture = new ImageCapture(track);
                      this.video.srcObject = this.mediaStream
                  },
              }))
          })
        </script>
      </head>
      <body x-data="app" class="text-slate-700" :class="{'bg-white': taking_pic, 'bg-black': !taking_pic}">
        <div>
          <div class="justify-center h-screen">
            <div class="relative h-screen">
              <div class="flex justify-center">
                <video id="video" @dblclick="$event.target.requestFullscreen()" class="max-w-screen h-screen" poster="https://d1tobl1u8ox4qn.cloudfront.net/2018/05/3814a83b-a2d4-4dfd-8945-f1cd003eb16f-1920x1080.jpg" autoplay="true" playsinline="true"></video>
              </div>
              <button class="absolute right-4 top-1/2" @click="captureImage"><img class="bg-white rounded-full w-10 h-10" src="data:image/png;base64,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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcGD+f0/5Jg5kLhNvAAAAAElFTkSuQmCC"/></button>
            </div>
          </div>
        </div>
      </body>
    </html>

config

Notes linking here