1. Welcome
In this lab, you'll take an existing web application and add advanced capabilities to it. This is the sixth in a series of companion codelabs for the Progressive Web App workshop. The previous codelab was Prompting & Measuring Install. There are two more codelabs in this series.
What you'll learn
- Open and save files from the user's file system using the File System Access API
- Register your installed PWA as a file handler with the File Handling API
- Choose the right screen to open a window with using the Multi-Screen Window Placement API
- Prevent a screen from falling asleep using the Screen Wake Lock API
What you should know
- JavaScript
What you will need
- A browser that supports the above APIs. For some APIs, you may need to use a browser with an active Developer Trial or Origin Trial to complete.
2. Get Set Up
Start by either cloning or downloading the starter code needed to complete this codelab:
If you clone the repo, make sure you're on the pwa05--empowering-your-pwa
branch. The zip file contains the code for that branch, too.
This codebase requires Node.js 14 or higher. Once you have the code available, run npm ci
from the command line in the code's folder in order to install all of the dependencies you'll need. Then, run npm start
to start the development server for the codelab.
The source code's README.md
file provides an explanation for all distributed files. In addition, the following are the key existing files you'll be working with throughout this codelab:
Key Files
js/lib/actions.js
- Provides a base class for the menu
Important Architectural Note
Throughout this codelab, you'll be editing js/lib/action.js
which manages actions for the different buttons in the app's menu. You can access any property in the initialized menu's constructor, which will include this.editor
for an instance of the main text editor. Two important editor methods you'll be using throughout this codelab are:
this.editor.setContent(content)
- Sets the content of the editor to the provided content argumentthis.editor.content()
- Gets the current content of the editor
3. Manage Files
Opening, saving, and creating new files on a user's computer is now possible thanks to the File System Access API. Combined with the File Handling API, allowing users to open files directly in your PWA, your PWA can feel seamlessly integrated into your user's every day lives.
Open from within the app
The first action to hook up is being able to open a file from the user's filesystem from within the app. In js/lib/actions.js
, in the open
method of the Actions
class, write code that does the following:
- Open a file picker that will take
text/markdown
file with extensions.md
or.markdown
- Set the page's title to the open files name, plus
PWA Edit
- Store the file handler under
this.handler
- Set the content of the editor to the text content of the file
- Save the handler to the
settings
object store in thesettings-store
IndexedDB database.
Positive : Remember: class constructors can't be async
functions but you can call Promises inside them.
Now that you can open a file and save what file is open between loads, there are two more things you need to do: set the handler back up when the app loads, and unset it when the user resets the app.
To accomplish the first, in the constructor of the Actions
class in js/lib/actions.js
, do the following:
- Open the
settings-store
database - Get the saved handler from the
settings
object store - Set
this.handler
to the retrieved value and the title of page to the handler's file name (plusPWA Edit
) if there is a saved handler
In order to reset the state of the app (which can be accomplished with CTRL
/CMD
+Shift
+R
), update the reset
method of the Actions
class in js/lib/actions.js
to do the following:
- Set the document title to
PWA Edit
- Set the editor's content to an empty string
- Set
this.handler
tonull
- Delete the saved handler from the
settings
object store
Open from the user's file system
Now that you can open a file from your app, you should let users open your app with their file! Registering as a file handler for a device will let a user open files in your app from anywhere in their file system.
Negative : You may need to enable a Developer or Origin Trial for this to work. If you need to enable a Developer Trial, it's recommended you do so in a copy of Chrome Canary instead of your normal browser. If you need to enable an Origin Trial, you should register for it as normal and add the tag to
index.html
To start, in manifest.json
, add a file_handlers
entry that does the following:
- Opens
/
- Accepts
text/markdown
with.md
or.markdown
file extensions.
That will allow users to open files with your app, but won't actually open the files in your app. To do so, in the Actions
class in js/lib/actions.js
, do the following:
- Add a
window.launchQueue
consumer in the constructor, callingthis.open
with the handler, if there are any. - Update
this.open
to accept an optional launch handler- If it exists and is an instance of
FileSystemFileHandle
, use that as the file handler for the function - If it doesn't, then open the file picker
- If it exists and is an instance of
After doing both of the above, install your PWA and try opening a file with it from the file system!
Saving a file
There are two different save paths a user may want to take: saving changes to a file already open, or saving to a new file. With the File System Access API, saving to a new file is really creating a new file and getting a file handler back, so to start, let's save from an existing handler.
In the save
method in the Actions
class in js/lib/actions.js
, do the following:
- Get the handler either from
this.handler
or, if that doesn't exist, get the saved handler from the database - Create the file handler's
FileSystemWritableFileStream
- Write the content of the editor to the stream
- Close the stream
Once you can save a file, it's time to implement save as. To do so, in the saveAs
method in the Actions
class in js/lib/actions.js
, do the following:
- Show the save file picker, describing it as a
Markdown File
and have it accepttext/markdown
files with a.md
extension - Set
this.handler
to the returned handler - Save the handler to the
settings
object store - Wait for
this.save
to finish to save the content to the newly created file
Once you've done that, go back to the save
method, check to see if the handler
exists before trying to write to it and, if it doesn't, instead wait for this.saveAs
to finish.
4. Show a Preview
With a Markdown editor, users want to see a preview of the rendered output. Using the Window Management API, you'll open a preview of the rendered content on the user's primary screen.
Before starting, make a file js/preview.js
, and add the following code to it to it to have it display a preview when loaded:
import { openDB } from 'idb';
import { marked } from 'marked';
window.addEventListener('DOMContentLoaded', async () => {
const preview = document.querySelector('.preview');
const db = await openDB('settings-store');
const content = (await db.get('settings', 'content')) || '';
preview.innerHTML = marked(content);
});
The preview should behave in the following ways:
- When a user clicks the preview button and a preview isn't open, it should open the preview
- When a user clicks the preview button and a preview is open, it should close the preview
- When the user closes or refreshes the PWA, the preview should close
Taking these in order, start by editing the preview
method in the Actions
class in js/lib/actions.js
to do the following:
- Get the available screens using the Window Management API
- Filter the screens to find the primary screen
- Open a window for
/preview
with a title ofMarkdown preview
that takes up half of the available width, and the whole available height, of the primary screen, positioned so it takes up the full available right half of that screen. The available dimensions exclude reserved areas of the screen, such as a system menubar, toolbar, status, or location. - Save this open window to
this.previewWindow
- At the top of the method, check to see if
this.previewWindow
exists and, if it does, close the window and unsetthis.previewWindow
instead of opening a window preview
Finally, do the following at the end of the constructor of the Actions
class in js/lib/actions.js
:
- Close
this.previewWindow
during thebeforeunload
event
5. Focus
Finally, we want to offer users a distraction-free writing mode. Distraction free not only means no clutter from other apps, but preventing the user's screen from falling asleep. To do this, you'll use the Screen Wake Lock API.
The wake lock button will work just like the preview button, toggling between the on and off state. To do so, in the focus
method of the Actions
class in js/lib/actions.js
, do the following:
- Check to see if the document has a full-screen element
- If it does:
- Exit full screen
- If
this.wakeLock
exists, release the wake lock and resetthis.wakeLock
- If it doesn't:
- Request a wake lock sentinel and set it to
this.wakeLock
- Request that the document's body go full screen.
- Request a wake lock sentinel and set it to
6. Congratulations!
You've learned how to manage system files and integrate your PWA with a system using the File System Access API and File Handling API, open windows across different screens with the Window Management API, and prevent a screen from falling asleep with the Screen Wake Lock API.
The next codelab in the series is Service Worker Includes