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.

Digital Dreams

Please choose your seats

Fri 02/May
16:00
JMAX

Screen

/* * 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)    );  }}