Summary#
A store that wants a new design should not need a new app. Write the design as a look: the frame around every page, the front page and the other pages a customer walks through, the theme and fonts, and a design of every block staff arrange. Install several looks — five shop designs, say — and staff pick one per version of the site, at the page desk: drafted, previewed as a whole site, switched on a date. Orders, stock, customers and everything staff arranged stay put; only the design changes.
// a contract — the shape, no design (a contract kit's, or the app's)
[Block("Hero")] public abstract component Hero([Label("Headline")] string title, Picture photo = "");
// a look — one design (a look kit's namespace)
namespace Osysharp.Shop.Looks.Atelier;
look Atelier { Label = "Atelier"; Theme = Gallery; }
component AtelierHero(string title, Picture photo = "") : Hero { render { … } }
// the app — installs looks, names the one it wears until staff choose
app.Look = Atelier;Signature#
[Block("Label")] // optional: a BLOCK TYPE staff place in a region
public abstract component Name(params); // a contract: parameters, no body
component Design(params) : Name { render { … } } // a look's design of it — exactly the contract's parameters
look Name { Label = "…"; Hint = "…"; Theme = SomeTheme; } // in a namespace of its own
app.Look = Name; // the look the site wears where a version names noneDescription#
What is a contract? — abstract component#
An abstract component has parameters and no body: it is the SHAPE of something a look draws. With [Block("…")] it
is a block type — a hero, a quote, a row of products — which staff place in a region and fill in at the desk, exactly
like a block (Blocks and regions — pages the staff arrange): its parameters are the fields, their types choose the editors, [Label]/[Hint]/[Sample]
name them. Without [Block] it is a part a page places — the frame around every page, the front page, a listing.
Anywhere a contract is called — a page, a layout, a region's arrangement — the compiler draws the design of the look the site is wearing, handing on every parameter and every slot.
What is a design? — component X(…) : Contract#
A component that writes : Hero is a DESIGN of the contract. It takes exactly the contract's parameters — the same names,
the same types, in the same order — and is refused otherwise, like a class against an interface, naming the difference:
- "'NightHero' implements Hero, whose 'photo' is a Picture, not a string."
- "'NightHero' implements Hero, and leaves out its parameter 'ground'."
- "'NightHero' implements Hero, which has no parameter 'moon' — … Something only this look shows is a block of the look's own."
A design is not a block of its own (the [Block] is the contract's, so staff see one "Hero" whichever look draws it),
and a look has one design per contract. A design outside every look is the default design: what a look with none of
its own draws, and what an app with no looks draws.
An app may design a PACKAGE's contract in its own source, as a class implements a library's interface — no kit of its own needed:
using Osysharp.Site.Looks;
component StoreHero(string title, Markdown words = "", Picture photo = "", Picture second = "", string button = "Shop now",
PageLink link = "/", Color ground = Colors.Surface) : Osysharp.Site.Looks.Hero {
render { Stack(p: 12, bg: ground) { Text(title, fontSize: FontSize.Title); Markdown(words); } }
}The app's design is drawn wherever the contract is placed — by the app's pages, and by the package's own pages and regions too. It takes the place of the package's design for the same look: an app's design outside every look replaces the kit's default design, as an app's component shadows a kit's of the same name.
What is a look? — look Name { … }#
A look is everything its NAMESPACE declares: its designs, its regions, its own blocks and the theme it names. So a look
lives in a namespace of its own — a look kit's namespace Osysharp.Shop.Looks.Atelier;.
| setting | what it is |
|---|---|
Label = "…" | what staff call it on the desk (its name if left out) |
Hint = "…" | the line under it (the first sentence of its doc comment if left out) |
Theme = X | its theme — served only while the site wears it, under :root[data-look="atelier"], so five looks' palettes live in one app without one taking the others' names (theme tokens); its dark column and its variants (a Christmas palette of this look) come with it |
A region the look declares may share its name with another look's: it is ONE arrangement, drawn by each look in its
own design, and what it allows and its default arrangement are the look's. A [Block] declared in the look's namespace is
a look-only block — drawn only while the site wears that look.
An app with more than one look names the one the site wears until staff choose another — app.Look = Atelier; — and is
refused without it. A version's look is SiteVersion.Look; the one showing to this browser is Visitor.Look, which the
server stamps on the first paint (data-look on the page) and Theme.Look(name) moves (the PageBuilder kit's SiteLook()
does, when a version switches or a member of staff previews another).
app Florist {
use Osysharp.Ui;
use Osysharp.Blocks;
model "model/**/*.osy";
}
using Osysharp.Blocks;
app.Look = Meadow;
[Principal] public entity Member { [Required, Unique, MaxLength(200)] string Email; }
/// This week's flowers, large, with a line under them.
[Block("Bouquet")]
public abstract component Bouquet([Label("Headline"), MaxLength(80)] string title, [Label("Words under it")] Markdown words = "");
/// The front page, in whichever look the site wears.
public abstract component FrontPage();
[Route("/")]
[AllowAnonymous]
component Front() { render { FrontPage(); } }namespace Florist.Looks.Meadow;
using Osysharp.Blocks;
/// Pale paper and a green that grows.
look Meadow { Label = "Meadow"; Theme = MeadowTheme; }
theme MeadowTheme { Colors { Bg = "#FFFDF5"; Leaf = "#3A7D44"; } }
component MeadowFront() : FrontPage {
render { Region("front", allow: [Bouquet, Pressed]) { Bouquet(title: "Flowers cut this morning"); Pressed(); } }
}
component MeadowBouquet(string title, Markdown words = "") : Bouquet {
render { Stack(gap: 2, p: 8, label: "Bouquet") { Text(title, fontSize: FontSize.Title, color: Colors.Leaf); Markdown(words); } }
}
/// A flower pressed between two pages — Meadow's own block.
[Block("Pressed flower")]
component Pressed([Label("Words"), MaxLength(80)] string words = "A daisy from June") {
render { Text("✿ " + words, p: 4, color: Colors.Leaf); }
}namespace Florist.Looks.Night;
using Osysharp.Blocks;
look Night { Label = "Night"; Theme = NightTheme; }
theme NightTheme { Colors { Bg = "#101014"; Leaf = "#9FD3A8"; } }
component NightFront() : FrontPage {
render { Box(bg: Colors.Bg, minH: "100vh") { Region("front", allow: [Bouquet]) { Bouquet(title: "Evening bouquets"); } } }
}
component NightBouquet(string title, Markdown words = "") : Bouquet {
render { Text(title, p: 6, fontSize: FontSize.Title, color: Colors.Leaf); }
}Staff who arranged Meadow's front — their own headline on the bouquet — and switch the version to Night see the same headline in Night's design. The pressed flower has no design in Night: it stays in the arrangement and is not drawn while the version wears Night, and comes back with Meadow.
Switching a version's look — and what will not come along#
At the desk ([[ui-pages-kit#looks]]) each version names its look. Before a switch, the desk lists everything the new look has no design for — a look-only block, a type the look does not draw, a region it has no place for — and switches only when staff say so. Nothing is dropped: the arrangement keeps it, and a later switch back draws it again.
The mechanism is Osysharp.Blocks': SetLook(version, look) (a look the app has, or "" for the app's), LookLeavesBehind (version, look) (one line per thing that will not come along), SiteOffers().Looks and .DefaultLook, and
BlockSpec.EveryLook/.Looks (which looks draw a block).
How do looks ship as kits? — the three layers#
A look is normally a KIT, so a store installs designs like any package and a marketplace can offer them. Two contracts:
Osysharp.Site.Looks(kits/site-looks) — the GENERAL site contract, for any consumer-facing site:Hero,PictureWithWords,Quote,SignUp,Banner,PhotoGallery, and around every pageAnnouncement,FooterWords,FooterLink,OpeningHours,Address,FooterSignUp.Osysharp.Shop.Looks(inside the shop kit, its types being the shop's data) — the SHOP's: the block typesProductRowandCategoryTiles, and the parts of a store —ShopFrame(with the page inside it),FrontPage,ShopSection,ListingPage,ProductGrid,ProductPage,NotFound. The shop kit's plain design of each block type — the shop's two and every general one — is the default design. The parts have none: a store that names them draws them in a look, so a shop look draws all seven (one it skips is drawn by the store's default look's design).
A look kit draws both (kits/look-atelier … kits/look-studio, the five designs of the Vall fashion store, and
kits/look-rail … kits/look-loud, the five of the Second Round second-hand shop), and carries its
fonts in its own osyrin.lock — a store that installs it ships them. A look kit carries no data and no logic, and needs
no egress and no secret: it reads what a store's page reads, as the visitor.
So there are three layers, and each answers one question: the site contract says what any site has, a category contract (the shop's, today) says what that kind of site has on top, and a look says how all of it looks — it draws both contracts below it. Building one from an empty kit — the manifest, the theme, a design of each part, the fonts, a block of its own, seeing it in a store, and where a café's contract would sit beside the shop's — is Building a look — from an empty kit to a design a store can wear.
What the compiler refuses#
- a design whose parameters are not the contract's — a name, a type, one too many or too few, named;
: Xwhere X is not a contract; two designs of one contract in one look, or two outside every look;[Block]or[Route]on a design; anabstract componentwith a body, or implementing something itself;- a look outside a namespace of its own, two looks of one name, a look naming a theme nobody declares, two looks
sharing a theme, a setting other than
Label/Hint/Theme; - an app with several looks and no
app.Look, and anapp.Looknaming a look the app does not have; - an app's own design of a package's contract (see the warning above);
- two regions of one name within one look.
See also#
- Building a look — from an empty kit to a design a store can wear — building a look kit, step by step.
- Blocks and regions — pages the staff arrange — blocks, regions, and what a value may be.
- The PageBuilder kit — the desk staff arrange the site at — the desk: versions, the look each wears, the preview.
- The site's frame — a menu, a strip and a footer staff change — the frame's menu, strip and footer, which a look's frame declares.
- [[ui-theming#variants]] — a theme's seasonal variants, which a look's theme has too.