This website is built entirely using Java
Objectos Way allows you to create complete web applications using nothing but the Java programming language.
Live Objectos Way Demo
This demo is written entirely in Java
using Objectos Way, JDK 23 and the H2 database engine.
The main panel displays the application itself,
while the secondary panel shows the source code used to generate the view.
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import demo.landing.LandingDemo;import demo.landing.app.Kino.Query;import java.util.Optional;import objectos.way.Html;import objectos.way.Http;import objectos.way.Note;import objectos.way.Sql;final class Seats implements Kino.GET, Kino.POST { static final Note.Ref1<SeatsData> DATA_READ = Note.Ref1.create(Seats.class, "Read", Note.DEBUG); private final Kino.Ctx ctx; Seats(Kino.Ctx ctx) { this.ctx = ctx; } @Override public final Html.Component get(Http.Exchange http) { final Sql.Transaction trx; trx = http.get(Sql.Transaction.class); final Kino.Query query; query = http.get(Kino.Query.class); final int state; state = query.aux(); if (state == SeatsView.BACK) { return getBackButton(trx, query); } final int showId; showId = query.idAsInt(); final Optional<SeatsShow> maybeShow; maybeShow = SeatsShow.queryOptional(trx, showId); if (maybeShow.isEmpty()) { return NotFound.create(); } final long reservationId; reservationId = ctx.nextReservation(); trx.sql(""" insert into RESERVATION (RESERVATION_ID, SHOW_ID) values (?, ?) """); trx.add(reservationId); trx.add(showId); trx.update(); final SeatsShow show; show = maybeShow.get(); final SeatsGrid grid; grid = SeatsGrid.query(trx, reservationId); return view(state, reservationId, show, grid); } private Html.Component getBackButton(Sql.Transaction trx, Query query) { final long reservationId; reservationId = query.id(); final Optional<SeatsShow> maybeShow; maybeShow = SeatsShow.queryBackButton(trx, reservationId); if (maybeShow.isEmpty()) { return NotFound.create(); } final SeatsShow show; show = maybeShow.get(); final SeatsGrid grid; grid = SeatsGrid.query(trx, reservationId); return view(SeatsView.DEFAULT, reservationId, show, grid); } @Override public final Kino.PostResult post(Http.Exchange http) { final Sql.Transaction trx; trx = http.get(Sql.Transaction.class); final SeatsData data; data = SeatsData.parse(http); ctx.send(DATA_READ, data); if (data.seats() == 0) { // no seats were selected... return postState(trx, data, SeatsView.EMPTY); } if (data.seats() > 6) { // too many seats were selected... return postState(trx, data, SeatsView.LIMIT); } final Sql.BatchUpdate tmpSelectionResult; tmpSelectionResult = data.persistTmpSelection(trx); return switch (tmpSelectionResult) { case Sql.BatchUpdateFailed _ -> tmpSelectionError(trx, data); case Sql.BatchUpdateSuccess _ -> tmpSelectionOk(trx, data); }; } private Kino.PostResult postState(Sql.Transaction trx, SeatsData data, int state) { // just in case, clear this user's selection data.clearUserSelection(trx); final long reservationId; reservationId = data.reservationId(); return embedView(trx, state, reservationId); } private Kino.PostResult embedView(Sql.Transaction trx, int state, long reservationId) { final SeatsShow show; show = SeatsShow.queryReservation(trx, reservationId); final SeatsGrid grid; grid = SeatsGrid.query(trx, reservationId); final Html.Component view; view = view(state, reservationId, show, grid); return LandingDemo.embedOk(view); } private Kino.PostResult tmpSelectionError(Sql.Transaction trx, SeatsData data) { // clear TMP_SELECTION just in case some of the records were inserted data.clearTmpSelection(trx); return LandingDemo.embedBadRequest(NotFound.create()); } private Kino.PostResult tmpSelectionOk(Sql.Transaction trx, SeatsData data) { final Sql.Update userSelectionResult; userSelectionResult = data.persisUserSelection(trx); return switch (userSelectionResult) { case Sql.UpdateFailed error -> userSelectionError(trx, data, error); case Sql.UpdateSuccess ok -> userSelectionOk(trx, data, ok); }; } private Kino.PostResult userSelectionError(Sql.Transaction trx, SeatsData data, Sql.UpdateFailed error) { // one or more seats were committed by another trx... // inform user final int state; state = SeatsView.BOOKED; final long reservationId; reservationId = data.reservationId(); if (data.wayRequest()) { return embedView(trx, state, reservationId); } else { final Kino.Query query; query = Kino.Page.SEATS.query(reservationId, state); final String href; href = ctx.href(query); return LandingDemo.redirect(href); } } private Kino.PostResult userSelectionOk(Sql.Transaction trx, SeatsData data, Sql.UpdateSuccess ok) { int count; count = ok.count(); if (data.seats() != count) { // some or possibly all of the seats were not selected. // 1) maybe an already sold ticket was submitted // 2) seats refer to a different show // anyways... bad data // clear SELECTION just in case some of the records were inserted data.clearUserSelection(trx); return LandingDemo.embedBadRequest(NotFound.create()); } // all seats were persisted. // go to next screen. final long reservationId; reservationId = data.reservationId(); return data.wayRequest() ? LandingDemo.embedOk( Confirm.create(ctx, trx, reservationId) ) : LandingDemo.redirect( ctx.href(Kino.Page.CONFIRM, reservationId) ); } private Html.Component view(int state, long reservationId, SeatsShow show, SeatsGrid grid) { return Shell.create(shell -> { shell.appFrame = shell.sourceFrame = "show-" + show.showId(); shell.app = new SeatsView(ctx, state, reservationId, show, grid); shell.sources( Source.Seats, Source.SeatsData, Source.SeatsGrid, Source.SeatsShow, Source.SeatsView ); }); }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import java.util.Arrays;import objectos.way.Http;import objectos.way.Sql;import objectos.way.Web;/** * Represents the user submitted data. */record SeatsData(boolean wayRequest, long reservationId, int screenId, int[] selection) { public static SeatsData parse(Http.Exchange http) { final Kino.Query query; query = http.get(Kino.Query.class); final Web.FormData form; form = Web.FormData.parse(http); return new SeatsData( wayRequest(http), query.id(), query.aux(), form.getAllAsInt("seat", Integer.MIN_VALUE).distinct().toArray() ); } private static boolean wayRequest(Http.Exchange http) { final String maybe; maybe = http.header(Http.HeaderName.WAY_REQUEST); return "true".equals(maybe); } @Override public final String toString() { return String.format( "SeatsData[wayRequest=%s, reservationId=%d, screenId=%d, selection=%s]", wayRequest, reservationId, screenId, Arrays.toString(selection) ); } final void clearTmpSelection(Sql.Transaction trx) { // clear this user's current tmp selection trx.sql(""" delete from TMP_SELECTION where RESERVATION_ID = ? """); trx.add(reservationId); trx.update(); } final Sql.BatchUpdate persistTmpSelection(Sql.Transaction trx) { clearTmpSelection(trx); // insert the data. // it will fail if either one is true: // 1) invalid RESERVATION_ID // 2) invalid SEAT_ID // 3) invalid SCREEN_ID // 4) SEAT_ID and SCREEN_ID are valid but SEAT_ID does not belong to SCREEN_ID trx.sql(""" insert into TMP_SELECTION (RESERVATION_ID, SEAT_ID, SCREEN_ID) values (?, ?, ?) """); for (int seatId : selection) { trx.add(reservationId); trx.add(seatId); trx.add(screenId); trx.addBatch(); } return trx.batchUpdateWithResult(); } final void clearUserSelection(Sql.Transaction trx) { // clear this user's current selection. // does nothing (hopefully) if RESERVATION_ID refers to an already sold ticket trx.sql(""" delete from SELECTION where RESERVATION_ID in ( select RESERVATION_ID from RESERVATION where RESERVATION_ID = ? and RESERVATION.TICKET_TIME is null ) """); trx.add(reservationId); trx.update(); } final Sql.Update persisUserSelection(Sql.Transaction trx) { clearUserSelection(trx); // persist this user's selection. // it will fail if: // 1) we try to insert an already selected seat (unique constraint) // // it will select only a subset (possibly empty) if: // 1) RESERVATION_ID refers to an already sold ticket // 2) SEAT is from a different screen that the current SHOW trx.sql(""" insert into SELECTION (RESERVATION_ID, SEAT_ID, SHOW_ID) select TMP_SELECTION.RESERVATION_ID, TMP_SELECTION.SEAT_ID, SHOW.SHOW_ID from TMP_SELECTION join RESERVATION on TMP_SELECTION.RESERVATION_ID = RESERVATION.RESERVATION_ID join SHOW on RESERVATION.SHOW_ID = SHOW.SHOW_ID join SCREENING on SHOW.SCREENING_ID = SCREENING.SCREENING_ID join SEAT on TMP_SELECTION.SEAT_ID = SEAT.SEAT_ID where RESERVATION.RESERVATION_ID = ? and RESERVATION.TICKET_TIME is null and SCREENING.SCREEN_ID = SEAT.SCREEN_ID """); trx.add(reservationId); return trx.updateWithResult(); } final int seats() { return selection.length; }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import java.sql.ResultSet;import java.sql.SQLException;import java.util.Iterator;import java.util.List;import objectos.way.Sql;final class SeatsGrid implements Iterable<SeatsGrid.Cell> { record Cell( int gridY, int gridX, int seatId, String name, int state ) { private Cell(ResultSet rs, int idx) throws SQLException { this( rs.getInt(idx++), rs.getInt(idx++), rs.getInt(idx++), rs.getString(idx++), rs.getInt(idx++) ); } public final boolean checked() { return state == 1; } public final boolean reserved() { return state == 2; } } private final List<Cell> cells; private SeatsGrid(List<Cell> cells) { this.cells = cells; } public static SeatsGrid query(Sql.Transaction trx, long reservationId) { trx.sql(""" with MAIN as ( select RESERVATION.RESERVATION_ID, SHOW.SHOW_ID, SCREENING.SCREEN_ID from RESERVATION join SHOW on RESERVATION.SHOW_ID = SHOW.SHOW_ID join SCREENING on SHOW.SCREENING_ID = SCREENING.SCREENING_ID where RESERVATION.RESERVATION_ID = ? ), GRID as ( select X as GRID_Y, gx.GRID_X from system_range (0, 9) cross join ( select X as GRID_X from system_range (0, 9) ) gx ), SELF as ( select SEAT_ID from SELECTION where RESERVATION_ID = ( select RESERVATION_ID from MAIN ) ), OTHERS as ( select SELECTION.SEAT_ID from MAIN join RESERVATION on MAIN.RESERVATION_ID <> RESERVATION.RESERVATION_ID and MAIN.SHOW_ID = RESERVATION.SHOW_ID join SELECTION on RESERVATION.RESERVATION_ID = SELECTION.RESERVATION_ID ) select GRID.GRID_Y, GRID.GRID_X, coalesce(SEAT.SEAT_ID, -1) as SEAT_ID, concat (SEAT_ROW, SEAT_COL) as SEAT_NAME, case SELF.SEAT_ID when is not null then 1 else case OTHERS.SEAT_ID when is not null then 2 else 0 end end from MAIN cross join GRID left join SEAT on MAIN.SCREEN_ID = SEAT.SCREEN_ID and GRID.GRID_Y = SEAT.GRID_Y and GRID.GRID_X = SEAT.GRID_X left join SELF on SEAT.SEAT_ID = SELF.SEAT_ID left join OTHERS on SEAT.SEAT_ID = OTHERS.SEAT_ID order by GRID.GRID_Y, GRID.GRID_X """); trx.add(reservationId); final List<Cell> cells; cells = trx.query(Cell::new); return new SeatsGrid(cells); } @Override public final Iterator<Cell> iterator() { return cells.iterator(); } @Override public final String toString() { final StringBuilder sb; sb = new StringBuilder(); sb.append(". = empty space\n"); sb.append("# = reserved\n"); sb.append("o = selectable\n"); sb.append("x = checked\n"); int lastY = -1; for (SeatsGrid.Cell cell : cells) { int gridY; gridY = cell.gridY; if (gridY != lastY) { sb.append('\n'); lastY = gridY; } else { sb.append(' '); } int seatId; seatId = cell.seatId; if (seatId < 0) { sb.append('.'); } else if (cell.checked()) { sb.append('x'); } else if (cell.reserved()) { sb.append('#'); } else { sb.append('o'); } } sb.append('\n'); return sb.toString(); }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import java.sql.ResultSet;import java.sql.SQLException;import java.util.Optional;import objectos.way.Sql;record SeatsShow( int showId, String date, String time, int screenId, String screen, int capacity, int movieId, String title) { private SeatsShow(ResultSet rs, int idx) throws SQLException { this( rs.getInt(idx++), rs.getString(idx++), rs.getString(idx++), rs.getInt(idx++), rs.getString(idx++), rs.getInt(idx++), rs.getInt(idx++), rs.getString(idx++) ); } public static Optional<SeatsShow> queryOptional(Sql.Transaction trx, int id) { trx.sql(""" select SHOW.SHOW_ID, formatdatetime(SHOW.SHOWDATE, 'EEE dd/LLL'), formatdatetime(SHOW.SHOWTIME, 'kk:mm'), SCREEN.SCREEN_ID, SCREEN.NAME, SCREEN.SEATING_CAPACITY, MOVIE.MOVIE_ID, MOVIE.TITLE from SHOW natural join SCREENING natural join MOVIE join SCREEN on SCREENING.SCREEN_ID = SCREEN.SCREEN_ID where SHOW.SHOW_ID = ? """); trx.add(id); return trx.queryOptional(SeatsShow::new); } public static Optional<SeatsShow> queryBackButton(Sql.Transaction trx, long reservationId) { reservation(trx, reservationId); return trx.queryOptional(SeatsShow::new); } public static SeatsShow queryReservation(Sql.Transaction trx, long reservationId) { reservation(trx, reservationId); return trx.querySingle(SeatsShow::new); } private static void reservation(Sql.Transaction trx, long reservationId) { trx.sql(""" select SHOW.SHOW_ID, formatdatetime(SHOW.SHOWDATE, 'EEE dd/LLL'), formatdatetime(SHOW.SHOWTIME, 'kk:mm'), SCREEN.SCREEN_ID, SCREEN.NAME, SCREEN.SEATING_CAPACITY, MOVIE.MOVIE_ID, MOVIE.TITLE from RESERVATION natural join SHOW natural join SCREENING natural join MOVIE join SCREEN on SCREENING.SCREEN_ID = SCREEN.SCREEN_ID where RESERVATION.RESERVATION_ID = ? """); trx.add(reservationId); }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import objectos.way.Css;import objectos.way.Html;@Css.Sourcefinal class SeatsView extends Kino.View { static final int BACK = 999; static final int DEFAULT = 0; static final int BOOKED = 1; static final int EMPTY = 2; static final int LIMIT = 3; private static final String BOOKED_MSG = """ We regret to inform you that another customer has already reserved one or more of the selected seats. Kindly choose alternative seats."""; private static final String EMPTY_MSG = """ Kindly choose at least 1 seat."""; private static final String LIMIT_MSG = """ We regret to inform you that we limit purchases to 6 tickets per person. Kindly choose at most 6 seats."""; private static final String FORM_ID = "seats-form"; private final Kino.Ctx ctx; private final int state; private final long reservationId; private final SeatsShow show; private final SeatsGrid grid; SeatsView(Kino.Ctx ctx, int state, long reservationId, SeatsShow show, SeatsGrid grid) { this.ctx = ctx; this.state = state; this.reservationId = reservationId; this.show = show; this.grid = grid; } @Override protected final void render() { backLink(ctx, Kino.Page.MOVIE, show.movieId()); // this node is for testing only, it is not rendered in the final HTML testableH1("Show details"); h2( testableField("title", show.title()) ); p("Please choose your seats"); div( dataFrame("seats-alert", Integer.toString(state)), renderFragment(this::renderAlert) ); div( css(""" border:1px_solid_border display:flex gap:24rx margin:32rx_0 overflow-x:auto padding:32rx_24rx """), renderFragment(this::renderDetails), renderFragment(this::renderSeats) ); } private void renderAlert() { switch (state) { case BOOKED -> { testableField("alert", "BOOKED"); renderAlert(BOOKED_MSG); } case EMPTY -> { testableField("alert", "EMPTY"); renderAlert(EMPTY_MSG); } case LIMIT -> { testableField("alert", "LIMIT"); renderAlert(LIMIT_MSG); } } } private void renderAlert(String msg) { div( css(""" align-items:center background-color:blue-100 border-left:3px_solid_blue-800 display:flex font-size:14rx line-height:16rx margin:16rx_0 padding:16rx """), icon( Kino.Icon.INFO, css(""" height:20rx width:auto padding-right:16rx """) ), div( css(""" flex:1 """), text(msg) ) ); } private void renderDetails() { div( css(""" display:flex flex-direction:column font-size:14rx gap:16rx """), renderDetailsItem(Kino.Icon.CALENDAR_CHECK, "date", show.date()), renderDetailsItem(Kino.Icon.CLOCK, "time", show.time()), renderDetailsItem(Kino.Icon.PROJECTOR, "screen", show.screen()) ); } private Html.Instruction.OfElement renderDetailsItem(Kino.Icon icon, String name, String value) { return div( css(""" align-items:center display:flex flex-direction:column gap:4rx """), icon( icon, css(""" height:auto stroke:icon width:20rx """) ), span( css(""" text-align:center width:6rch """), text(testableField(name, value)) ) ); } private void renderSeats() { // this node is for testing only, it is not rendered in the final HTML testableH1("Seats"); div( css(""" display:flex flex-direction:column flex-grow:1 justify-content:start """), renderFragment(this::renderSeatsScreen), renderFragment(this::renderSeatsForm), renderFragment(this::renderSeatsAction) ); } private void renderSeatsScreen() { svg( css(""" display:block margin:0_auto max-height:60rx max-width:400rx min-height:0 min-width:0 stroke:icon width:100% """), width("400"), height("60"), viewBox("0 0 400 60"), xmlns("http://www.w3.org/2000/svg"), path(d("M 0 50 Q 200 0 400 50")), fill("none"), strokeWidth("1.25") ); p( css(""" font-size:14rx inset:-32rx_0_auto position:relative text-align:center """), text("Screen") ); } private void renderSeatsForm() { form( id(FORM_ID), formAction(ctx, Kino.Page.SEATS, reservationId, show.screenId()), css(""" aspect-ratio:1.15 display:grid flex-grow:1 gap:8rx grid-template-columns:repeat(10,1fr) grid-template-rows:repeat(10,minmax(20rx,1fr)) margin:0_auto max-width:400rx width:100% """), dataOnSuccess(script -> { final String successUrl; successUrl = ctx.href(Kino.Page.CONFIRM, reservationId); script.replaceState(successUrl); }), method("post"), renderFragment(this::renderSeatsFormGrid) ); } private void renderSeatsFormGrid() { for (SeatsGrid.Cell cell : grid) { final int seatId; seatId = cell.seatId(); if (seatId < 0) { div(); } else { final String seatIdValue; seatIdValue = Integer.toString(seatId); input( css(""" cursor:pointer disabled:cursor:default """), name("seat"), type("checkbox"), cell.checked() ? checked() : noop(), cell.reserved() ? disabled() : noop(), value(seatIdValue) ); if (cell.checked()) { testableField("checked", seatIdValue); } } } } private void renderSeatsAction() { div( css(""" display:flex justify-content:end padding-top:64rx margin:0_auto max-width:400rx width:100% """), button( PRIMARY, form(FORM_ID), type("submit"), text("Book seats") ) ); }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import demo.landing.LandingDemo;import demo.landing.LandingDemoConfig;import java.time.Clock;import java.time.LocalDateTime;import java.util.Objects;import objectos.way.Css;import objectos.way.Html;import objectos.way.Http;import objectos.way.Note;import objectos.way.Script;import objectos.way.Sql;/** * Demo entry point. */public final class Kino implements LandingDemo { private final Ctx ctx; private Kino(Ctx ctx) { this.ctx = ctx; } /** * Creates a new {@code Demo} instance with the specified configuration. */ public static Kino create(LandingDemoConfig config) { Objects.requireNonNull(config, "config == null"); final Ctx ctx; ctx = Ctx.of(config); return new Kino(ctx); } /** * Handles a GET request. * * <p> * Typically this would be a {@code Http.Handler} instance. However, as this * will be embedded in another application, we return a HTML component * instead. */ @Override public final Html.Component get(Http.Exchange http) { final Query query; query = ctx.decode(http); // based on the 'demo' value we create our controller final GET controller; controller = switch (query.page) { case CONFIRM -> new Confirm(ctx); case MOVIE -> new Movie(ctx); case NOW_SHOWING -> new NowShowing(ctx); case SEATS -> new Seats(ctx); case TICKET -> new Ticket(); case BAD_REQUEST -> new NotFound(); }; // we intercept all controllers even though // not all require DB access strictly speaking return ctx.transactional(http, controller); } /** * Handles a POST request. */ @Override public final Kino.PostResult post(Http.Exchange http) { final Query query; query = ctx.decode(http); final POST controller; controller = switch (query.page) { case CONFIRM -> new Confirm(ctx); case SEATS -> new Seats(ctx); default -> new NotFound(); }; return ctx.transactional(http, controller); } // // UI related classes // /** * SVG icons from the Lucide project. */ enum Icon { ARROW_LEFT(""" <path d="m12 19-7-7 7-7"/><path d="M19 12H5"/>"""), CALENDAR_CHECK(""" <path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="m9 16 2 2 4-4"/>"""), CLOCK(""" <circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>"""), CREDIT_CARD(""" <rect width="20" height="14" x="2" y="5" rx="2"/><line x1="2" x2="22" y1="10" y2="10"/>"""), FILM( """ <rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 3v18"/><path d="M3 7.5h4"/><path d="M3 12h18"/><path d="M3 16.5h4"/><path d="M17 3v18"/><path d="M17 7.5h4"/><path d="M17 16.5h4"/>"""), FROWN(""" <circle cx="12" cy="12" r="10"/><path d="M16 16s-1.5-2-4-2-4 2-4 2"/><line x1="9" x2="9.01" y1="9" y2="9"/><line x1="15" x2="15.01" y1="9" y2="9"/>"""), INFO(""" <circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>"""), PROJECTOR( """ <path d="M5 7 3 5"/><path d="M9 6V3"/><path d="m13 7 2-2"/><circle cx="9" cy="13" r="3"/><path d="M11.83 12H20a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h2.17"/><path d="M16 16h2"/>"""), RECEIPT(""" <path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z"/><path d="M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8"/><path d="M12 17.5v-11"/>"""), TICKET(""" <path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/>"""); final String contents; Icon(String contents) { this.contents = contents; } } /** * Base HTML template of the application. Provides a utility methods for * rendering UI fragments common to the application. */ @Css.Source // Indicates to the CSS Generator that it should scan this class for CSS utilities. static abstract class View extends Html.Template { static final Html.ClassName PRIMARY = Html.ClassName.ofText(""" appearance:none background-color:btn-primary color:btn-primary-text cursor:pointer display:flex font-size:14rx min-height:48rx padding:14rx_63rx_14rx_15rx active:background-color:btn-primary-active hover:background-color:btn-primary-hover """); // // component methods // /** * Renders the "Go Back" link. */ final Html.Instruction.OfElement backLink(Ctx ctx, Page page) { testableField("back-link", page.name()); return backLink(ctx.href(page)); } /** * Renders the "Go Back" link. */ final Html.Instruction.OfElement backLink(Ctx ctx, Page page, long id) { testableField("back-link", page.name() + ":" + id); return backLink(ctx.href(page, id)); } /** * Renders the "Go Back" link. */ final Html.Instruction.OfElement backLink(Ctx ctx, Page page, long id, int aux) { testableField("back-link", page.name() + ":" + id + ":" + aux); Query query = page.query(id, aux); return backLink(ctx.href(query)); } /** * Renders the "Go Back" link. */ final Html.Instruction.OfElement backLink(String href) { return a( css(""" border-radius:9999px padding:6rx margin:6rx_0_0_-6rx position:absolute active:background-color:btn-ghost-active hover:background-color:btn-ghost-hover """), dataOnClick(this::navigate), href(href), rel("nofollow"), icon( Kino.Icon.ARROW_LEFT, css(""" height:20rx width:20rx """) ) ); } final Html.Instruction.OfAttribute formAction(Ctx ctx, Page page, long id) { testableField("action", page.name() + ":" + id); return action(ctx.action(page, id)); } final Html.Instruction.OfAttribute formAction(Ctx ctx, Page page, long id, int aux) { testableField("action", page.name() + ":" + id + ":" + aux); return action(ctx.action(page, id, aux)); } /** * Renders a Lucide SVG icon. */ final Html.Instruction icon(Kino.Icon icon, Html.Instruction... more) { return svg( xmlns("http://www.w3.org/2000/svg"), width("24"), height("24"), viewBox("0 0 24 24"), fill("none"), stroke("currentColor"), strokeWidth("2"), strokeLinecap("round"), strokeLinejoin("round"), flatten(more), raw(icon.contents) ); } /* * Typically we would use Script::navigate for "boosted" links. * But a regular Script::navigate performs a scrollTo(0,0) after * the request is completed. We don't want that as this demo is * embedded in another page. In other words, we want the scroll * position to remain the same after, e.g., we click on a movie. */ final void navigate(Script script) { var el = script.element(); script.request(req -> { req.method(Script.GET); req.url(el.attr(Html.AttributeName.HREF)); req.onSuccess(() -> { var shell = script.elementById(Shell.APP); shell.scroll(0, 0); }); }); } } // // Configuration related classes // /** * Application-level context. */ record Ctx( Clock clock, KinoCodec codec, Note.Sink noteSink, Reservation reservation, Transactional transactional ) { static Ctx of(LandingDemoConfig config) { final Clock clock; clock = config.clock; final byte[] codecKey; codecKey = config.codecKey(); final KinoCodec codec; codec = new KinoCodec(clock, codecKey); final Note.Sink noteSink; noteSink = config.noteSink; final Reservation reservation; reservation = new Reservation(clock, config.reservationEpoch, config.reservationRandom); final Transactional transactional; transactional = new Transactional(config.stage, config.database); return new Ctx(clock, codec, noteSink, reservation, transactional); } final String action(Page page, long id) { return action(page, id, 0); } final String action(Page page, long id, int aux) { Query query; query = page.query(id, aux); String demo; demo = codec.encode(query); return "/demo/landing?demo=" + demo; } final String href(Page page) { return href(page, 0L); } final String href(Page page, long id) { Query query; query = page.query(id); return href(query); } final String href(Query query) { String demo; demo = codec.encode(query); return "/index.html?demo=" + demo; } final long nextReservation() { return reservation.next(); } final <T1> void send(Note.Ref1<T1> note, T1 v1) { noteSink.send(note, v1); } final LocalDateTime today() { return LocalDateTime.now(clock); } final Html.Component transactional(Http.Exchange http, GET action) { return transactional.get(http, action); } final PostResult transactional(Http.Exchange http, POST action) { return transactional.post(http, action); } private Query decode(Http.Exchange http) { // We cannot rely on the path to render the different pages // of the application because this demo will be embedded in another page. // So, we use an URL query parameter. final String demo; demo = http.queryParam("demo"); // the query parameter value is encoded/obfuscated. // we use the codec to decode it. final Query query; query = codec.decode(demo); http.set(Query.class, query); return query; } } // // SQL related classes // private static final class Transactional { private final Kino.Stage stage; private final Sql.Database db; Transactional(Kino.Stage stage, Sql.Database db) { this.stage = stage; this.db = db; } public final Html.Component get(Http.Exchange http, Kino.GET action) { return execute(http, action::get); } public final Kino.PostResult post(Http.Exchange http, Kino.POST action) { return execute(http, action::post); } private <T> T execute(Http.Exchange http, Action<T> action) { return switch (stage) { case DEFAULT -> { final Sql.Transaction trx; trx = db.beginTransaction(Sql.READ_COMMITED); try { trx.sql("set schema CINEMA"); trx.update(); http.set(Sql.Transaction.class, trx); final T result; result = action.execute(http); trx.commit(); yield result; } catch (Throwable e) { throw trx.rollbackAndWrap(e); } finally { trx.close(); } } // this is a no-op during testing. case TESTING -> action.execute(http); }; } } // // Embedded related classes // // Most Objectos Way applications will not require the classes in this section. // They are required because this demo will be embedded in another application. // /** * Represents an HTTP action in the demo application. */ @FunctionalInterface private interface Action<T> { T execute(Http.Exchange http); } /** * Handles a GET request. Similar to a {@code Http.Handler} instance, but for * an embedded application. */ interface GET { Html.Component get(Http.Exchange http); } /** * Handles a POST request. Similar to a {@code Http.Handler} instance, but for * an embedded application. */ interface POST { PostResult post(Http.Exchange http); } /** * The pages of this application. * * <p> * As a reminder, as this application will be embedded in another one, it does * not have actual pages. */ enum Page { NOW_SHOWING, MOVIE, SEATS, CONFIRM, TICKET, BAD_REQUEST; final Query query() { return new Query(this, 0L, 0); } final Query query(long id) { return new Query(this, id, 0); } final Query query(long id, int aux) { return new Query(this, id, aux); } } /** * Represents the demo query parameter. */ record Query(Page page, long id, int aux) { final int idAsInt() { return (int) id; } }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import java.util.ArrayList;import java.util.List;import java.util.Objects;import java.util.function.Consumer;import objectos.way.Css;import objectos.way.Html;import objectos.way.Syntax;/** * The demo UI shell responsible for displaying the application on the * top/right and the source code on the bottom/left. */@Css.Source // Indicates to the CSS Generator that it should scan this class for CSS utilities.final class Shell extends Kino.View { static final class Builder { String appFrame; Html.Component app; String sourceFrame; private final List<SourceModel> sources = new ArrayList<>(); private Builder() {} final void sources(SourceModel... values) { for (SourceModel value : values) { sources.add(value); } } private Shell build() { Objects.requireNonNull(appFrame, "appFrame == null"); Objects.requireNonNull(app, "app == null"); Objects.requireNonNull(sourceFrame, "sourceFrame == null"); sources.add(Source.Kino); sources.add(Source.Shell); sources.add(Source.SourceModel_); return new Shell(this); } } static final Html.Id APP = Html.Id.of("demo-app"); private final Builder builder; private Shell(Builder builder) { this.builder = builder; } public static Shell create(Consumer<Builder> config) { final Builder builder; builder = new Builder(); config.accept(builder); return builder.build(); } private final Html.Id sourceFrame = Html.Id.of("source-frame"); private final Html.AttributeName dataButton = Html.AttributeName.of("data-button"); private final Html.AttributeName dataPanel = Html.AttributeName.of("data-panel"); private final Html.AttributeName dataSelected = Html.AttributeName.of("data-selected"); @Override protected final void render() { // the demo container div( css(""" display:grid grid-template:'a'_448rx_'b'_auto_'c'_448rx_/_1fr lg:grid-template:'c_a'_512rx_'b_b'_auto_/_1fr_1fr xl:grid-template:'b_c_a'_512rx_/_200rx_1fr_1fr """), renderFragment(this::renderApp), renderFragment(this::renderSourceMenu), renderFragment(this::renderSourceCode) ); } private void renderApp() { // the app container div( APP, css(""" border:1px_solid_border overflow:auto grid-area:a position:relative lg:border-bottom-width:1px lg:border-left-width:0px """), div( css(""" align-items:center background-color:layer color:gray-500 display:flex height:64rx justify-content:space-between padding:0_16rx position:sticky top:0px z-index:8000 """), a( css(""" align-items:center display:flex gap:6rx height:100% """), href("/index.html"), dataOnClick(this::navigate), objectosLogo(), span( css(""" font-size:24rx font-weight:300 line-height:1 transform:translateY(-1px) """), text("kino") ) ), a( css(""" align-items:center border-radius:6rx display:flex padding:8rx active:background-color:btn-ghost-active hover:background-color:btn-ghost-hover """), href("https://github.com/objectos/demo.landing"), gitHubLogo() ) ), div( css(""" padding:0_16rx_16rx h2:font-size:36rx h2:font-weight:200 h2:line-height:1 h2:padding:48rx_0_8rx """), dataFrame("demo-app", builder.appFrame), renderComponent(builder.app) ) ); } private Html.Instruction objectosLogo() { return svg( css(""" width:auto height:24rx fill:logo transition:fill_300ms_ease """), xmlns("http://www.w3.org/2000/svg"), width("200"), height("49.28"), viewBox("0 0 200 49.28"), path( d("m189.6 38.21q-2.53 0-5.3-0.932-2.76-0.903-4.6-3.087-0.38-0.495-0.35-1.02 0.12-0.582 0.64-0.99 0.5-0.32 1.02-0.233 0.58 0.09 0.93 0.524 1.4 1.66 3.38 2.33 2.04 0.641 4.43 0.641 4.08 0 5.77-1.456 1.71-1.456 1.71-3.408 0-1.893-1.86-3.087-1.78-1.281-5.56-1.806-4.87-0.669-7.2-2.621-2.33-1.951-2.33-4.514 0-2.417 1.23-4.077 1.19-1.689 3.29-2.534 2.12-0.874 4.86-0.874 3.29 0 5.56 1.223 2.31 1.165 3.7 3.146 0.38 0.495 0.24 1.077-0.1 0.525-0.73 0.874-0.47 0.233-1.02 0.146-0.53-0.09-0.9-0.583-1.23-1.543-2.97-2.33-1.69-0.815-3.99-0.815-3.06 0-4.75 1.31-1.69 1.311-1.69 3.146 0 1.252 0.67 2.242 0.73 0.903 2.33 1.602 1.6 0.612 4.28 1.02 3.64 0.466 5.71 1.63 2.15 1.165 3.03 2.738 0.9 1.485 0.9 3.233 0 2.301-1.46 3.99-1.45 1.689-3.81 2.621-2.39 0.874-5.16 0.874zm-27.97 0q-3.9 0-6.99-1.748-3.05-1.805-4.85-4.863-1.75-3.088-1.75-6.932 0-3.873 1.75-6.931 1.8-3.117 4.85-4.864 3.09-1.806 6.99-1.806 3.89 0 6.95 1.806 3.05 1.747 4.8 4.864 1.81 3.058 1.81 6.931 0 3.844-1.81 6.932-1.75 3.058-4.8 4.863-3.06 1.748-6.95 1.748zm0-2.709q3.07 0 5.43-1.427 2.45-1.456 3.79-3.873 1.42-2.476 1.42-5.592 0-3.058-1.42-5.475-1.34-2.476-3.79-3.874-2.36-1.456-5.43-1.456-3.03 0-5.45 1.456-2.41 1.398-3.83 3.874-1.4 2.417-1.4 5.533 0 3.058 1.4 5.534 1.42 2.417 3.83 3.873 2.42 1.427 5.45 1.427zm-19.01 2.418q-2.65-0.06-4.75-1.224-2.09-1.194-3.26-3.291-1.16-2.126-1.16-4.805v-24.17q0-0.67 0.4-1.078 0.44-0.436 1.05-0.436 0.7 0 1.08 0.436 0.44 0.408 0.44 1.078v24.17q0 2.825 1.74 4.601 1.75 1.748 4.52 1.748h1.08q0.67 0 1.05 0.437 0.43 0.407 0.43 1.077 0 0.641-0.43 1.078-0.38 0.379-1.05 0.379zm-12.96-22.95q-0.58 0-0.96-0.35-0.35-0.378-0.35-0.961 0-0.582 0.35-0.932 0.38-0.378 0.96-0.378h13.16q0.59 0 0.94 0.378 0.38 0.35 0.38 0.932 0 0.583-0.38 0.961-0.35 0.35-0.94 0.35zm-13.09 23.24q-3.78 0-6.79-1.806-2.96-1.776-4.71-4.834-1.7-3.059-1.7-6.903 0-3.873 1.6-6.931t4.42-4.806q2.81-1.806 6.45-1.806 3.1 0 5.7 1.224 2.56 1.165 4.45 3.582 0.38 0.495 0.29 1.019-0.1 0.525-0.64 0.874-0.44 0.349-0.96 0.291-0.52-0.09-0.93-0.582-3.09-3.699-7.91-3.699-2.86 0-5.04 1.427-2.14 1.398-3.35 3.815-1.17 2.447-1.17 5.592 0 3.058 1.31 5.534 1.31 2.417 3.59 3.873 2.33 1.427 5.39 1.427 1.99 0 3.74-0.582 1.81-0.583 3.12-1.806 0.44-0.379 0.96-0.437 0.52-0.06 0.93 0.35 0.47 0.437 0.47 1.019 0.1 0.524-0.38 0.903-3.55 3.262-8.84 3.262zm-27.69-0.06q-3.84 0-6.85-1.69-2.96-1.747-4.66-4.805t-1.7-6.99q0-3.99 1.61-6.99 1.6-3.058 4.41-4.805 2.82-1.748 6.46-1.748 3.59 0 6.36 1.69 2.76 1.66 4.31 4.63 1.56 2.913 1.56 6.728 0 0.641-0.39 1.019-0.39 0.35-1.02 0.35h-21.35v-2.534h22.13l-2.14 1.602q0.1-3.146-1.07-5.563-1.16-2.446-3.35-3.786-2.13-1.427-5.04-1.427-2.77 0-4.95 1.427-2.14 1.34-3.4 3.786-1.21 2.417-1.21 5.621 0 3.146 1.31 5.592 1.31 2.417 3.64 3.815 2.33 1.369 5.34 1.369 1.89 0 3.78-0.641 1.94-0.67 3.06-1.747 0.39-0.379 0.92-0.379 0.58-0.06 0.97 0.291 0.54 0.437 0.54 0.962 0 0.553-0.44 0.99-1.55 1.398-4.08 2.33-2.47 0.903-4.75 0.903zm-30.93 11.13q-0.64 0-1.07-0.44-0.44-0.38-0.44-1.02 0-0.67 0.44-1.1 0.43-0.41 1.07-0.41 2.47 0 4.31-1.05 1.9-1.08 2.97-2.97 1.06-1.89 1.06-4.313v-25.16q0-0.67 0.39-1.049 0.44-0.408 1.07-0.408 0.68 0 1.07 0.408 0.43 0.379 0.43 1.049v25.16q0 3.291-1.45 5.821-1.46 2.57-4.03 4.02-2.52 1.46-5.82 1.46zm9.75-43.48q-0.92 0-1.6-0.641-0.63-0.67-0.63-1.66 0-1.107 0.68-1.631 0.73-0.582 1.6-0.582 0.82 0 1.5 0.582 0.73 0.524 0.73 1.631 0 0.99-0.68 1.66-0.63 0.641-1.6 0.641zm-21.55 32.42q-3.79 0-6.84-1.748-3.06-1.747-4.86-4.747-1.75-3.029-1.84-6.815v-23.44q0-0.67 0.39-1.048 0.43-0.408 1.06-0.408 0.68 0 1.07 0.408 0.39 0.378 0.39 1.048v15.14q1.55-2.505 4.32-4.019 2.82-1.515 6.31-1.515 3.88 0 6.94 1.806 3.11 1.747 4.85 4.805 1.8 3.058 1.8 6.932 0 3.902-1.8 6.99-1.74 3.058-4.85 4.863-3.06 1.748-6.94 1.748zm0-2.709q3.06 0 5.44-1.427 2.42-1.456 3.83-3.873 1.41-2.476 1.41-5.592 0-3.087-1.41-5.534-1.41-2.417-3.83-3.815-2.38-1.456-5.44-1.456-3.01 0-5.44 1.456-2.42 1.398-3.83 3.815-1.36 2.447-1.36 5.534 0 3.116 1.36 5.592 1.41 2.417 3.83 3.873 2.43 1.427 5.44 1.427zm-32.53 2.709q-3.88 0-6.99-1.748-3.06-1.805-4.85-4.863-1.75-3.088-1.75-6.932 0-3.873 1.75-6.931 1.79-3.117 4.85-4.864 3.11-1.806 6.99-1.806t6.94 1.806q3.06 1.747 4.8 4.864 1.8 3.058 1.8 6.931 0 3.844-1.8 6.932-1.74 3.058-4.8 4.863-3.06 1.748-6.94 1.748zm0-2.709q3.06 0 5.44-1.427 2.42-1.456 3.78-3.873 1.41-2.476 1.41-5.592 0-3.058-1.41-5.475-1.36-2.476-3.78-3.874-2.38-1.456-5.44-1.456-3.01 0-5.44 1.456-2.42 1.398-3.83 3.874-1.41 2.417-1.41 5.533 0 3.058 1.41 5.534 1.41 2.417 3.83 3.873 2.43 1.427 5.44 1.427z"), strokeWidth(".9101") ) ); } private Html.Instruction gitHubLogo() { return svg( css(""" width:auto height:24rx fill:logo """), xmlns("http://www.w3.org/2000/svg"), width("98"), height("96"), viewBox("0 0 98 96"), path( fillRule("evenodd"), clipRule("evenodd"), d( "M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z") ) ); } private void renderSourceMenu() { final List<SourceModel> items; items = builder.sources; final SourceModel first; first = items.getFirst(); // the source file selector div( sourceFrame, css(""" border-left:1px_solid_border border-right:1px_solid_border display:flex font-size:14rx gap:4rx_8rx grid-area:b padding:16rx overflow-x:auto lg:border-bottom:1px_solid_border xl:border-top:1px_solid_border xl:border-right-width:0px xl:flex-direction:column """), dataFrame("demo-source-menu", builder.sourceFrame), // stores the current selected button in the data-button attribute attr(dataButton, first.button().value()), // stores the current selected panel in the data-panel attribute attr(dataPanel, first.panel().value()), renderFragment(this::renderSourceMenuItems) ); } private void renderSourceMenuItems() { final List<SourceModel> items; items = builder.sources; for (int idx = 0, size = items.size(); idx < size; idx++) { final SourceModel item; item = items.get(idx); button( item.button(), css(""" border-radius:6rx cursor:pointer padding:4rx_8rx [data-selected=true]:background-color:btn-ghost-active active:background-color:btn-ghost-active hover:background-color:btn-ghost-hover """), attr(dataSelected, Boolean.toString(idx == 0)), dataOnClick(script -> { // 'deselects' current var frame = script.elementById(sourceFrame); var selectedButton = script.elementById(frame.attr(dataButton)); selectedButton.attr(dataSelected, "false"); var selectedPanel = script.elementById(frame.attr(dataPanel)); selectedPanel.attr(dataSelected, "false"); // 'selects' self var selfButton = script.elementById(item.button()); selfButton.attr(dataSelected, "true"); var selfPanel = script.elementById(item.panel()); selfPanel.attr(dataSelected, "true"); // stores selected frame.attr(dataButton, item.button().value()); frame.attr(dataPanel, item.panel().value()); }), text(item.name()) ); } } private void renderSourceCode() { // the Java source code display div( css(""" border:1px_solid_border display:flex flex-direction:column grid-area:c """), css(""" flex:1 min-height:0 overflow:auto """), dataFrame("demo-source-code", builder.sourceFrame), renderFragment(this::renderSourceCodeItems) ); } private void renderSourceCodeItems() { final List<SourceModel> items; items = builder.sources; for (int idx = 0, size = items.size(); idx < size; idx++) { final SourceModel item; item = items.get(idx); final String source; source = item.value(); pre( item.panel(), css(""" display:none font-family:mono font-size:13rx line-height:18.6rx padding:16rx [data-selected=true]:display:flex span:[data-line]:display:block span:[data-line]:min-height:1lh span:[data-line]:nth-child(-n+15):display:none span:[data-high=annotation]:color:high-meta span:[data-high=comment]:color:high-comment span:[data-high=comment]:font-style:italic span:[data-high=keyword]:color:high-keyword span:[data-high=string]:color:high-string """), attr(dataSelected, Boolean.toString(idx == 0)), code( css(""" flex-grow:1 """), renderComponent( Syntax.highlight(Syntax.JAVA, source) ) ) ); } }}
/* * Copyright (C) 2024-2025 Objectos Software LTDA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package demo.landing.app;import objectos.way.Html;record SourceModel(String name, String value, Html.Id button, Html.Id panel) { private static int INDEX = 0; public static SourceModel create(String name, String value) { final int index; index = INDEX++; return new SourceModel( name, value, Html.Id.of("src-btn-" + index), Html.Id.of("src-panel-" + index) ); }}