All files / src main.ts

0% Statements 0/28
100% Branches 1/1
100% Functions 1/1
0% Lines 0/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                                                                                                                                                                   
/**
 * The entry point, and nothing else.
 *
 * Four things have to happen before anything is drawn, in this order, and none
 * of them belongs to the app object: the theme is applied so a user who chose
 * dark gets no flash of light, a page a stale service worker served from the
 * wrong build is put right, the service worker is registered so the second
 * visit works offline, and a hash that is not a view state is cleared so the
 * picker does not open under a stale fragment.
 *
 * The app itself is `app/App.ts` — the window — and `app/Session.ts` — one open
 * file and the views of it. This file used to be both, which worked exactly as
 * long as there was one of the second.
 */
 
import './ui/styles.css'
 
import { App } from './app/App'
import { embedded } from './embed'
import { parseViewState } from './state/ViewState'
import { readShareHash } from './state/shareLink'
import { initTheme } from './ui/ThemeToggle'
import { installColourPicker } from './ui/colourPicker'
import { mountPinnedNotice } from './ui/VersionPicker'
import { recoverPinnedNavigation, registerServiceWorker } from './pwa'
 
// Before the app mounts, so a user who chose dark gets no flash of light.
initTheme()
 
// Every colour input opens the app's own picker rather than the platform's (§26.6).
installColourPicker()
 
// Before *anything else* mounts. The case this catches is a document built for
// the site root whose address names an archived build, which only a worker
// published before those existed can produce: the page is already being
// reloaded, and drawing an interface over it would be a flash of the wrong app.
if (!recoverPinnedNavigation()) {
  // Neither of the next two in an embed, and for one reason with two halves.
  //
  // A worker is for an app somebody keeps: it makes the second visit work with
  // the network out, and it is registered per origin — so an embed would install
  // the app's cache from a page that was only ever showing a figure, on behalf
  // of a reader who never chose to keep it. Nothing an embed does needs it: the
  // frame is a fresh load of a URL that says what to show.
  //
  // And the notice is a bar across the top of the window offering to move to
  // another build. In a frame that top is somebody's figure, and the offer is
  // one only the page's author could sensibly accept — they chose the pinned
  // build in the `src=`, on purpose, so that the embed still renders the same
  // way in five years (docs/deploy.md).
  if (!embedded()) {
    // Offline, after the first visit. Nothing downstream depends on it: a browser
    // that refuses, or a page served over plain HTTP, simply has no cache.
    registerServiceWorker()
 
    // Only ever visible in an archived build, and the one thing standing between
    // a stale link and an afternoon's work in a build from July.
    mountPinnedNotice()
  }
 
  const root = document.getElementById('app')
  if (root !== null) {
    // A hash that carries no `uri` is not a view state; clear it so the picker
    // does not leave a stale fragment in the address bar.
    //
    // A **shared session** is the exception, and it has to be made here rather
    // than downstream: this line runs before `App` exists, so without it a
    // pasted share link is wiped between the page loading and anything being
    // able to read it. The symptom is a link that opens the picker, every time,
    // with no error — which is the hardest possible thing to diagnose from a
    // bug report.
    if (
      window.location.hash !== '' &&
      readShareHash(window.location.hash) === null &&
      parseViewState(window.location.hash) === null
    ) {
      window.history.replaceState(null, '', window.location.pathname + window.location.search)
    }
    new App(root)
  }
}