Repository navigation
Expand file tree
/
Copy pathpwa.js
More file actions
156 lines (143 loc) · 6.93 KB
/
Copy pathpwa.js
File metadata and controls
156 lines (143 loc) · 6.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
(function () {
// An installed app runs in its own window; a tab reports 'browser'.
// 'fullscreen' is left out on purpose: F11 in an ordinary tab matches it.
var appWindow = ['standalone', 'minimal-ui', 'window-controls-overlay'].some(function (mode) {
return window.matchMedia('(display-mode: ' + mode + ')').matches;
}) || navigator.standalone === true;
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js', {scope: './'}).catch(console.error);
// Hand the worker what this page already loaded (see sw.js 'message').
// Twice: once when it is ready, and again after the app has had time to
// pull its packages.
var sendLoaded = function () {
navigator.serviceWorker.ready.then(function (registration) {
if (!registration.active) return;
var urls = performance.getEntriesByType('resource').map(function (entry) {
return entry.name;
}).filter(function (url) {
return url.indexOf('http') === 0;
});
urls.push(location.href);
registration.active.postMessage({type: 'cache-urls', urls: urls});
});
};
sendLoaded();
window.addEventListener('load', function () { setTimeout(sendLoaded, 15000); });
}
// main.py may say what it is doing; if it does not (an example dropped in
// as main.py), do not leave "Loading Python…" up once the script has run.
window.addEventListener('py:all-done', function () {
document.documentElement.classList.add('py-done');
var status = document.getElementById('status');
if (status && status.textContent === 'Loading Python…') status.textContent = 'Running.';
});
// ---- The app window matches the display -------------------------------
//
// In its own window (installed, launched from the dock, Start menu or
// launcher) the page is just the canvas, so size the window to the display
// that board_config asked for: the canvas size plus the window's own frame
// and title bar. Chrome and Edge allow resizeTo() in an installed app's
// window and refuse it in a tab, so a tab is left alone.
//
// It runs when the display first reports its size and again only if that
// size changes (a rotation, say). Resizing the window yourself afterwards
// is never undone, and a maximized window is left maximized.
var fittedTo = null;
function fitWindow() {
var canvas = document.getElementById('display_canvas');
if (!appWindow || !canvas || !window.resizeTo) return;
var width = canvas.width, height = canvas.height;
var key = width + 'x' + height;
if (key === fittedTo) return;
fittedTo = key;
if (window.outerWidth >= screen.availWidth && window.outerHeight >= screen.availHeight) return;
var frameW = window.outerWidth - window.innerWidth;
var frameH = window.outerHeight - window.innerHeight;
var outerW = Math.min(width + frameW, screen.availWidth);
var outerH = Math.min(height + frameH, screen.availHeight);
if (Math.abs(outerW - window.outerWidth) < 2 && Math.abs(outerH - window.outerHeight) < 2) return;
try { window.resizeTo(outerW, outerH); } catch (err) { /* not allowed here */ }
}
if (appWindow) {
var canvas = document.getElementById('display_canvas');
var pending = false;
var schedule = function () {
if (pending) return;
pending = true;
requestAnimationFrame(function () { pending = false; fitWindow(); });
};
// PSDisplay writes the canvas's width and height when board_config
// creates the display; that is the size to fit, not the size the
// page starts with.
if (canvas && window.MutationObserver) {
new MutationObserver(schedule).observe(canvas, {attributes: true, attributeFilter: ['width', 'height']});
}
// A main.py that never builds a display still gets one fit.
window.addEventListener('py:all-done', schedule);
}
// ---- A hint that points at the browser's own install control ----------
//
// Modelled on Chrome's address-bar install icon and on the per-platform
// instructions of @khmyznikov/pwa-install (iOS/iPadOS Share > Add to Home
// Screen, macOS Safari Add to Dock). Chromium only gets the hint after
// 'beforeinstallprompt' fires, which is when its install icon appears.
// Firefox on the desktop has nothing to point at, so it gets nothing.
var DISMISSED = 'pydevices-install-hint-dismissed';
var hint = null;
function dismissed() {
try { return localStorage.getItem(DISMISSED) === '1'; } catch (err) { return false; }
}
function showHint(mode, text) {
if (appWindow || hint || dismissed()) return;
hint = document.createElement('aside');
hint.className = 'install-hint install-hint--' + mode;
hint.setAttribute('aria-label', 'Install this app');
var message = document.createElement('p');
message.innerHTML = text;
var close = document.createElement('button');
close.type = 'button';
close.className = 'install-hint__close';
close.setAttribute('aria-label', 'Dismiss');
close.textContent = '×';
close.addEventListener('click', function () {
try { localStorage.setItem(DISMISSED, '1'); } catch (err) { /* private mode */ }
hideHint();
});
hint.appendChild(message);
hint.appendChild(close);
document.body.appendChild(hint);
}
function hideHint() {
if (hint) hint.remove();
hint = null;
}
var ua = navigator.userAgent;
var android = /Android/i.test(ua);
// iPadOS reports itself as a Mac; touch points give it away.
var appleMobile = /iPhone|iPad|iPod/.test(ua) || (/Macintosh/.test(ua) && navigator.maxTouchPoints > 1);
var safariVersion = /Version\/(\d+)[.\d]* (Mobile\/\S+ )?Safari\//.exec(ua);
var macSafari = /Macintosh/.test(ua) && !appleMobile && safariVersion && Number(safariVersion[1]) >= 17;
window.addEventListener('beforeinstallprompt', function () {
// Not preventDefault(): the browser's own install UI stays as it is.
if (android) {
showHint('menu', 'Install this app: open the browser menu <b>⋮</b> and choose <b>Install app</b>.');
} else {
showHint('omnibox', 'Install this app with the install icon at the right of the address bar.');
}
});
window.addEventListener('appinstalled', hideHint);
if (!appWindow && (appleMobile || macSafari || (android && !('BeforeInstallPromptEvent' in window)))) {
window.addEventListener('load', function () {
setTimeout(function () {
if (appleMobile) {
// An iPhone's Share button lives in the bottom toolbar; an iPad's at the top.
showHint(/iPhone|iPod/.test(ua) ? 'note install-hint--bottom' : 'note', 'Install this app: tap <b>Share</b> (under <b>⋯</b> if you don’t see it), then <b>Add to Home Screen</b>.');
} else if (macSafari) {
showHint('note', 'Install this app: choose <b>File › Add to Dock</b>.');
} else {
showHint('note', 'Install this app: open the browser menu and choose <b>Install</b> or <b>Add to Home screen</b>.');
}
}, 3000);
});
}
})();