Guide · Panelith 0.1.0 · plain HTML edition; the interactive edition has search and live examples
Saved layouts
On this page 3 sections
A visitor arranges the canvas, names the arrangement, and gets it back later in one press: the same panes at the same sizes, each showing what it showed. The Layouts button in the top bar opens the list; so does the app.layouts built-in from the command bar.
topbar = TopBar(buttons=["list:contents", "layouts", "commands", "hotkeys"])
A layout is a recipe, not a photograph#
A saved layout holds the split geometry (columns, rows, their proportions, which tab is in front) and, for every tab, its kind and the keys the kind chose to persist. Nothing else. When the layout is restored every tab is opened afresh by its kind and the server rebuilds its state from those keys, so a dashboard saved last week shows today's figures under last week's selection, and a documentation page reopens at the same section with the current text.
The kind decides what is worth carrying with persist:
class Overview(Pane):
kind = "overview"
persist = ["site", "shift"] # the selection; the figures are fetched again
def initial_state(self, ctx):
return {"site": "SEA1", "shift": "day", **fetch_figures("SEA1", "day")}
def restore_state(self, ctx, saved):
site = saved.get("site", "SEA1")
shift = saved.get("shift", "day")
return {"site": site, "shift": shift, **fetch_figures(site, shift)}
restore_state(ctx, saved) receives the persisted keys as they were when the layout was saved and returns the state to show now. The default is initial_state with the saved keys laid over it, which is right whenever the saved keys are plain settings; override it when they need work, as the docs' own page kind does: it persists the page id and rebuilds the text from the current source.
class Page(Pane):
kind = "page"
widget = "markdown"
persist = ["page", "anchor"] # which page; the text is rebuilt on restore
def restore_state(self, ctx, saved):
page = PAGES.get(str(saved.get("page") or ""))
return page_state(page, saved.get("anchor")) if page else self.initial_state(ctx)
A reload goes the same way: the browser re-registers each pane with the copy it kept, and a kind with persist is rebuilt from those keys by restore_state, so a page shows the current text after a deploy rather than the copy from before it. A kind without persist comes back exactly as the browser kept it.
A kind with nothing live can carry its content: the Counter in these docs persists its items, so a restored counter shows the number it had. Children a kind opens for itself (children) are part of the saved layout already, so a restore does not open them a second time; on_open still runs.
What the visitor sees#
- Save: type a name in the drawer and press Save. Saving under an existing name replaces it. The row shows the name, when it was saved and what was open.
- Restore: choose a row. The canvas is replaced, not added to; the arrangement that was on screen is kept automatically as Before restore, so a wrong click is one restore away from undone.
- Rename: the pencil on a row, or a double-click on its name; Enter keeps the new name, Escape drops it.
- Remove: the × on a row, pressed twice.
- Scroll positions inside markdown panes are part of the layout; a page comes back where it was.
Where layouts live#
By default the browser keeps them in local storage, per app, next to the colour-scheme choice: nothing is sent to the server and nothing follows the visitor to another browser. An app that knows who its visitors are can keep them server-side instead by setting layouts to a LayoutStore and naming the visitor in profile:
from panelith import App, LayoutStore
class SqliteLayouts(LayoutStore):
def list(self, profile): # newest first
...
def save(self, profile, record): # record is a schema.LayoutRecord dict
...
def delete(self, profile, layout_id):
...
class Ops(App):
layouts = SqliteLayouts()
def profile(self, ctx):
return self.session().data.get("user") # whatever your login put there
The shell then reads and writes /api/layouts instead of local storage; the drawer and the records are the same. profile defaults to ctx.user, which is None until an app sets it, so without an override every visitor shares one set of layouts; each profile holds at most max_layouts (50) records. MemoryLayoutStore is the in-process reference implementation (and what the tests use). The record's data is the shell's: an app stores it opaquely and never has to understand it.
Note
A layout references kinds by id. If an app drops a kind, tabs of that kind are left out when an old layout is restored; the rest comes back.