Skip to content

Commit

Permalink
Implement PWA Support
Browse files Browse the repository at this point in the history
  • Loading branch information
Daltz333 committed Feb 13, 2022
1 parent 989e680 commit eb1234d
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 1 deletion.
125 changes: 125 additions & 0 deletions source/_extensions/pwa_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import sphinx as Sphinx
from typing import Any, Dict, List
import os
from docutils import nodes
import json
import shutil
from urllib.parse import urljoin, urlparse, urlunparse
from sphinx.util import logging
from sphinx.util.console import green, red, yellow # pylint: disable=no-name-in-module

manifest = {
"name": "",
"short_name": "",
"theme_color": "",
"background_color": "",
"display": "standalone",
"scope": "/",
"start_url": "/index.html",
"icons": [],
}

logger = logging.getLogger(__name__)

def get_files_to_cache(outDir: str, config: Dict[str, Any]):
files_to_cache = []
for (dirpath, dirname, filenames) in os.walk(outDir):
dirpath = dirpath.split(outDir)[1]

# skip adding sources to cache
if os.sep + "_sources" + os.sep in dirpath:
continue

# add files to cache
for name in filenames:
if "sw.js" in name:
continue

dirpath = dirpath.replace("\\", "/")
dirpath = dirpath.lstrip("/")

# we have to use absolute urls in our cache resource, because fetch will return an absolute url
# this means that we cannot accurately cache resources that are in PRs because RTD does not give us
# the url
if config["html_baseurl"] is not None:
# readthedocs uses html_baseurl for sphinx > 1.8
parse_result = urlparse(config["html_baseurl"])

# Grab root url from canonical url
url = parse_result.netloc

# enables RTD multilanguage support
if os.getenv("READTHEDOCS"):
url = "https://" + url + "/" + os.getenv("READTHEDOCS_LANGUAGE") + "/" + os.getenv("READTHEDOCS_VERSION") + "/"

if config["html_baseurl"] is None and not os.getenv("CI"):
logger.warning(
red(f"html_baseurl is not configured. This can be ignored if deployed in RTD environments.")
)
url = ""

if dirpath == "":
resource_url = urljoin(
url, name
)
files_to_cache.append(resource_url)
else:
resource_url = url + dirpath + "/" + name
files_to_cache.append(resource_url)

return files_to_cache


def build_finished(app: Sphinx, exception: Exception):
outDir = app.outdir
outDirStatic = outDir + os.sep + "_static" + os.sep
files_to_cache = get_files_to_cache(outDir, app.config)

# dumps a json file with our cache
with open(outDirStatic + "cache.json", "w") as f:
json.dump(files_to_cache, f)

# copies over our service worker
shutil.copyfile(
os.path.dirname(__file__) + os.sep + "pwa_service_files" + os.sep + "sw.js",
outDir + os.sep + "sw.js",
)


def html_page_context(
app: Sphinx,
pagename: str,
templatename: str,
context: Dict[str, Any],
doctree: nodes.document,
) -> None:
if pagename == "index":
context[
"metatags"
] += '<script>"serviceWorker"in navigator&&navigator.serviceWorker.register("sw.js").catch((e) => window.alert(e));</script>'
context[
"metatags"
] += f'<link rel="manifest" href="_static/frcdocs.webmanifest"/>'

if app.config["pwa_apple_icon"] is not None:
context[
"metatags"
] += f'<link rel="apple-touch-icon" href="{app.config["pwa_apple_icon"]}">'


def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value("pwa_name", "", "html")
app.add_config_value("pwa_short_name", "", "html")
app.add_config_value("pwa_theme_color", "", "html")
app.add_config_value("pwa_background_color", "", "html")
app.add_config_value("pwa_display", "standalone", "html")
app.add_config_value("pwa_icons", [], "html")
app.add_config_value("pwa_apple_icon", "", "html")

app.connect("html-page-context", html_page_context)
app.connect("build-finished", build_finished)

return {
"parallel_read_safe": True,
"parallel_write_safe": True,
}
121 changes: 121 additions & 0 deletions source/_extensions/pwa_service_files/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use strict";
// extend this to update the service worker every push
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Offline_Service_workers
let cacheName = 'js13kPWA-v1';

// todo test
self.addEventListener('install', function (e) {
e.waitUntil(async function() {
await fetch('_static/cache.json')
.then(response => response.json())
.then(async (data) => {
for (let i = 0; i < data.length / 10; i++) {
const tofetch = data.slice(i * 10, i * 10 + 10);
await addKeys(tofetch)
}
})
}());
});

// opt for a cache first response, for quickest load times
// we'll still update the page assets in the background
self.addEventListener('fetch', function (event) {
event.respondWith(async function () {
let request_url = event.request.url;

try {
await addKeys([request_url]) //put in format our addKeys function expects
} catch (error) {
console.error("Error downloading from remote:", error)
}

let res = await getKey(request_url)

console.log("Fetching:", event.request.url)
return res;
}());
});

let dbPromise;

async function getDB() {
if (dbPromise) {
return dbPromise;
} else {
let request = indexedDB.open("frc-docs", "1")

dbPromise = new Promise((resolve, reject) => {
request.onsuccess = function (event) {
console.log("Successfully opened database!")
resolve(event.target.result)
}

request.onerror = function (event) {
console.error("Error opening database for getKey():", request.error)
reject()
}

request.onupgradeneeded = function (event) {
let db = event.target.result;
db.createObjectStore("urls", { keyPath: 'key' })
}
});

return dbPromise;
}
}

async function getKey(key) {
let db = await getDB()
console.log("Grabbing key", key)
return new Promise((resolve, reject) => {
try {
let transaction = db.transaction("urls").objectStore("urls");
let request = transaction.get(key)

request.onsuccess = function (event) {
let res = request.result;
console.log("Successfully retrieved result:", res)
resolve(new Response(res.value));
}

request.onerror = function (event) {
console.error("Error on retrieving blob:", key, request.error)
reject()
}

} catch (ex) {
console.error(ex.message);
reject()
}
})
}

async function addKeys(datas) {
let db = await getDB()
return Promise.all(
datas.map(async (data) => {
let fetchedData = await fetch(data)
.then(x => x.blob())
.catch((error) => {
console.error("Error fetching", data)
return new Promise((resolve, reject) => {
reject();
})
})
let transaction = db.transaction("urls", "readwrite").objectStore("urls")
let request = transaction.put({key: data, value: fetchedData})

return new Promise((resolve, reject) => {
request.onsuccess = function() {
resolve()
}
request.onerror = function () {
console.log(request.error)
reject(request.error)
}
});
})
);
// data is already a key/value object with url/data
}
Binary file added source/_static/first-logo-256px.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added source/_static/first-logo-512px.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions source/_static/frcdocs.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "FRC Docs",
"short_name": "FRC Docs",
"theme_color": "#003974",
"background_color": "#003974",
"display": "standalone",
"scope": "../",
"start_url": "../index.html",
"icons": [
{
"src": "/_static/first-logo-256px.png",
"type": "image/png",
"sizes": "256x256"
},
{
"src": "/_static/first-logo-512px.png",
"type": "image/png",
"sizes": "512x512"
}
]
}
Binary file added source/_static/touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"_extensions.post_process",
"_extensions.rtd_patch",
"_extensions.localization",
"_extensions.pwa_service",
]

extensions += local_extensions
Expand Down Expand Up @@ -177,6 +178,9 @@
# Use MathJax3 for better page loading times
mathjax_path = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"

# PWA Specific Settings
pwa_apple_icon = "_static/touch-icon.png"


# -- Options for HTML output -------------------------------------------------

Expand All @@ -198,7 +202,10 @@

# Specify canonical root
# This tells search engines that this domain is preferred
html_baseurl = "https://docs.wpilib.org/en/stable/"
if os.getenv("TESTING"):
html_baseurl = "http://localhost:8000/"
else:
html_baseurl = "https://frc-docs--1704.org.readthedocs.build/en/1704/"

html_theme_options = {
"collapse_navigation": True,
Expand Down

0 comments on commit eb1234d

Please sign in to comment.