dropdown-demo
A project's LEAD, chosen from a dropdown — the sample that proves M121's `Slot(item)` renders, and that a
4 source files1 test file

Get it
// not one of the apps the toolchain ships: this one lives in the // repository. Clone it, then: $ cd demo/dropdown-demo $ osy launch
The app
app.osy8 lines
// A project's LEAD, chosen from a dropdown — the sample that proves M121's `Slot(item)` renders, and that a // dropdown bound to an ENTITY-typed property round-trips the selection back into the data. app DropdownDemo { use Osyrin.Ui; model "model/**/*.osy"; tests "tests/**/*.test.osy"; }
model/auth.osy62 lines
// Auth, on the `demo/auth-demo` shape — the smallest real login, because this demo needs WRITES and the deny-all // endpoint gate correctly refuses an anonymous one. That is not incidental to what is being demonstrated: choosing // a lead WRITES `Project.Lead`, so a version of this demo that opened with no login could not have proven the // binding at all. [Role] enum AppRole { Authenticator, Member } entity RoleGrant { User Grantee; [Required] AppRole Level; security { } } policy IsAuthenticator => RoleGrant.Any(g => g.Grantee == user && g.Level == AppRole.Authenticator); [Principal] entity User { [MaxLength(200), Unique] string Email; [MaxLength(200)] string PasswordHash; [MaxLength(80)] string Name; [MaxLength(120)] string Title; [MaxLength(4)] string Initials; security { deny read PasswordHash when !IsAuthenticator; allow read when IsAuthenticator; allow create when IsAuthenticator; allow update when IsAuthenticator; allow read when IsAuthenticated; } } [AuthMethod] string Signup(string email, string password) { var u = new User { Email = email, PasswordHash = Security.HashPassword(password), Name = email, Title = "Founder", Initials = "ME", }; if (!User.Any(x => x.Email == "ada@apollo.test")) { new User { Email = "ada@apollo.test", Name = "Ada Lovelace", Title = "Principal engineer", Initials = "AL", PasswordHash = Security.HashPassword("x") }; new User { Email = "grace@apollo.test", Name = "Grace Hopper", Title = "Engineering manager", Initials = "GH", PasswordHash = Security.HashPassword("x") }; new User { Email = "alan@apollo.test", Name = "Alan Turing", Title = "Research lead", Initials = "AT", PasswordHash = Security.HashPassword("x") }; new User { Email = "katherine@apollo.test", Name = "Katherine Johnson", Title = "Staff engineer", Initials = "KJ", PasswordHash = Security.HashPassword("x") }; } return Security.IssueJwt(u.Id, u.Email); } [AuthMethod] string Login(string email, string password) { var u = User.Where(x => x.Email == email).FirstOrDefault(); if (u == null) { Security.VerifyPassword(password); return ""; } if (Security.VerifyPassword(password, u.PasswordHash)) { return Security.IssueJwt(u.Id, u.Email); } return ""; } app.AuthBootstrap = new AuthBootstrap { Role = AppRole.Authenticator, Login = Login, Signup = Signup, LoginPage = LoginPage, };
model/home.osy103 lines
// THE POINT OF THIS DEMO, in one line: `Dropdown(value: p.Lead, options: people)` — a generic dropdown over // ENTITIES, whose caller decides what a row looks like, bound two-way to an entity-typed property. using Osyrin.Ui; theme Studio { Colors { Bg = Modes.Of(light: "#F6F7F9", dark: "#0E1116"); OnBg = Modes.Of(light: "#16181D", dark: "#E6EAF0"); Border = Modes.Of(light: "#DDE1E6", dark: "#2A2F37"); BorderStrong = Modes.Of(light: "#C3C9D0", dark: "#3A414D"); Surface = Modes.Of(light: "#FFFFFF", dark: "#171B21"); OnSurface = Modes.Of(light: "#1A1F26", dark: "#E6EAF0"); Muted = Modes.Of(light: "#EFF1F5", dark: "#232833"); Subtle = Modes.Of(light: "#6B7280", dark: "#8A94A6"); Accent = "#4F46E5"; OnAccent = "#FFFFFF"; } Radius { Control = "8px"; Card = "14px"; Pill = "999px"; } ZIndex { Overlay = "50"; } FontSize { Caption = "13px"; Body = "15px"; Section = "17px"; Display = "28px"; } FontWeight { Medium = "600"; } Length { Avatar = "28px"; Field = "300px"; } } entity Project { [MaxLength(80)] string Name; User? Lead; security { allow read, create, update when IsAuthenticated; } } [Composable] component Avatar(string initials) { render { Box(w: Length.Avatar, h: Length.Avatar, rounded: Radius.Pill, bg: Colors.Accent, color: Colors.OnAccent) { Row(align: Align.Center, justify: Justify.Center, h: "100%") { Text(initials, fontSize: FontSize.Caption, fontWeight: FontWeight.Medium); } } } } [Page("/")] component Home() { live var people = User.OrderBy(u => u.Name); live var project = Project.FirstOrDefault(); on mount { SeedDemo(); } action Save() { UnitOfWork.Commit(); } render { Stack(gap: 6, p: 8, minH: "100vh", bg: Colors.Bg, color: Colors.OnBg) { if (project == null) { Text("Seeding…", color: Colors.Subtle); } else { Stack(gap: 1) { Text(project.Name, fontSize: FontSize.Display, fontWeight: FontWeight.Medium); Row(gap: 2, align: Align.Center) { Text("Lead:", color: Colors.Subtle, fontSize: FontSize.Body); if (project.Lead == null) { Text("nobody yet", color: Colors.Subtle, fontSize: FontSize.Body); } else { Text(project.Lead.Name, fontSize: FontSize.Body, fontWeight: FontWeight.Medium); } } } Stack(gap: 2) { Text("Project lead", color: Colors.Subtle, fontSize: FontSize.Caption, fontWeight: FontWeight.Medium); Dropdown(value: project.Lead, options: people, placeholder: "Choose a lead…", label: "Project lead") { d => Avatar(d.Initials); Stack(gap: 0) { Text(d.Name, fontSize: FontSize.Body); Text(d.Title, fontSize: FontSize.Caption, color: Colors.Subtle); } } Row(w: Length.Field) { Button("Save", onPress: Save); } } } } } } [Page("/login")] [AllowAnonymous] [Render(CSR)] component LoginPage() { string email = "you@apollo.test"; string password = "demo-password"; action DoLogin() { Session.SignIn(Login(email, password)); } action DoSignup() { Session.SignIn(Signup(email, password)); } render { Stack(gap: 4, p: 8, minH: "100vh", bg: Colors.Bg, color: Colors.OnBg) { Text("Dropdown demo", fontSize: FontSize.Display, fontWeight: FontWeight.Medium); Text("Sign up with anything — it is a local throwaway app.", color: Colors.Subtle, fontSize: FontSize.Body); Stack(gap: 2, w: Length.Field) { Input(value: email, label: "Email", placeholder: "you@apollo.test"); Input(value: password, label: "Password", type: "password", placeholder: "password"); Row(gap: 2) { Button("Create account", onPress: DoSignup); Button("Sign in", onPress: DoLogin); } } } } }
model/seed.osy8 lines
// The project to lead. Written by a SIGNED-IN member on first visit — `Project` grants create to any authenticated // caller, which is exactly who this is. void SeedDemo() { if (Project.Any()) { return; } new Project { Name = "Apollo" }; UnitOfWork.Commit(); }