Open a file from the desktop app
You'll add an Import CSV button to the shop from 700. In the desktop window it opens the system file dialog limited to CSV files and the shop imports the products from the file you choose. A second button opens a help page in the system browser. In an ordinary browser tab the same page shows a note instead of the buttons.
Desktop mode is experimental. It has been tried on macOS only. The Windows and Linux code is written but has not been built or run. Packaging and signing do not exist yet.
Before you start
Install from crates.io. You need no Node and no package manager.
cargo install snapfire_compiler
cargo install snapfire_fsr_cli
fsr --version
Every command and screenshot on this page was captured with fsr 0.x.
You need the CLI built with the desktop feature, as in 700: cargo install snapfire_fsr_cli --features desktop.
The desktop bridge#
A window opened by fsr serve --desktop or fsr dev --desktop installs a bridge before the page's own scripts run. Page code reaches it through desktop from @snapfire/fsr/client:
| Call | What it does |
|---|---|
desktop.isDesktop() | true in a desktop window, false in a browser |
desktop.openFile(options) | Opens the system's open dialog and answers the chosen absolute paths. It answers null when the user cancels |
desktop.openExternal(url) | Hands an http, https or mailto link to the system's browser or mail app |
openFile takes multiple to allow several files and filters to limit what the dialog shows. A filter is a label and its extensions without the dot.
The calls run in the browser, so a component that makes them is an island.
Write the import island#
;
;
inWindow starts false and the effect sets it once the island is mounted. The server has no window, so it renders the note. The browser then asks the bridge and swaps the note for the buttons when there is one.
Outside a window, both calls reject. The try keeps the message on the page instead of an uncaught error in the console:
desktop.openFile runs only in an fsr --desktop window
Place the island at the top of the product list:
;
;
;
Build it#
The island is lowered, so the server writes its first render. The two browser rows name the handlers that run only in the browser. That is what you want here: the dialog and the system browser are the window's, never the server's.
Try it in a browser first#
The island's markup is the note:
Importing a CSV works in the desktop app.Open http://127.0.0.1:3402/ in a browser. The note stays, since a browser has no bridge.
Pick a file in the window#
Make a CSV to import, anywhere on your machine:
sku,name,price_cents
CUP-04,Stoneware cup,1200
JAR-05,Glass jar,600
Open the window:
The window shows the two buttons in place of the note. Click Import CSV and the system's open dialog appears, showing CSV files only. Choose products.csv and its absolute path appears under the buttons. Cancel and openFile answers null, so nothing changes. Click CSV format and the link opens in your default browser. openExternal refuses any link that is not http, https or mailto.
Read the file#
openFile answers paths, not contents. The page runs in the webview, which can't read a path on the disk, so the island hands the path to an action. The action runs in the host the window started, on your machine, so it reads the file with file from @snapfire/fsr/host.
The imported products are kept in the session. Add them to the session schema:
;
;Declare the action's input:
The action reads the file, skips the header row and turns each line into a product:
;
;
;
;
;The root loader lists the imported products after the built-in ones:
;
;
;Call the action from the island#
pick now hands the chosen path to the action. A successful call revalidates the route, so the loader runs again and the list shows the new rows:
;
;
;
The rest of the island is the same as before.
Import it#
Click Import CSV and choose products.csv. The line under the buttons reads 2 products from followed by the file's path. The list grows to five:
Products
Enamel mug $14.00
Loose leaf tea $9.00
Cast iron pan $48.00
Stoneware cup $12.00
Glass jar $6.00
The import is in the session, so it is still there after a reload of the window. Import another file and its rows replace the ones before.
Next up: 500. Move a loader into Rust.