diff --git a/README.md b/README.md index 6b35c08..9511b0d 100644 --- a/README.md +++ b/README.md @@ -1,86 +1 @@ -# App README - -- [ ] TODO Replace or update this README with instructions relevant to your application - -## Project Structure - -The sources of your App have the following structure: - -``` -src -├── main/frontend -│ └── themes -│ └── default -│ ├── styles.css -│ └── theme.json -├── main/java -│ └── [application package] -│ ├── base -│ │ └── ui -│ │ ├── component -│ │ │ └── ViewToolbar.java -│ │ ├── MainErrorHandler.java -│ │ └── MainLayout.java -│ ├── examplefeature -│ │ ├── ui -│ │ │ └── TaskListView.java -│ │ ├── Task.java -│ │ ├── TaskRepository.java -│ │ └── TaskService.java -│ └── Application.java -└── test/java - └── [application package] - └── examplefeature - └── TaskServiceTest.java -``` - -The main entry point into the application is `Application.java`. This class contains the `main()` method that start up -the Spring Boot application. - -The skeleton follows a *feature-based package structure*, organizing code by *functional units* rather than traditional -architectural layers. It includes two feature packages: `base` and `examplefeature`. - -* The `base` package contains classes meant for reuse across different features, either through composition or - inheritance. You can use them as-is, tweak them to your needs, or remove them. -* The `examplefeature` package is an example feature package that demonstrates the structure. It represents a - *self-contained unit of functionality*, including UI components, business logic, data access, and an integration test. - Once you create your own features, *you'll remove this package*. - -The `src/main/frontend` directory contains an empty theme called `default`, based on the Lumo theme. It is activated in -the `Application` class, using the `@Theme` annotation. - -## Starting in Development Mode - -To start the application in development mode, import it into your IDE and run the `Application` class. -You can also start the application from the command line by running: - -```bash -./mvnw -``` - -## Building for Production - -To build the application in production mode, run: - -```bash -./mvnw -Pproduction package -``` - -To build a Docker image, run: - -```bash -docker build -t my-application:latest . -``` - -If you use commercial components, pass the license key as a build secret: - -```bash -docker build --secret id=proKey,src=$HOME/.vaadin/proKey . -``` - -## Getting Started - -The [Getting Started](https://vaadin.com/docs/latest/getting-started) guide will quickly familiarize you with your new -App implementation. You'll learn how to set up your development environment, understand the project -structure, and find resources to help you add muscles to your skeleton — transforming it into a fully-featured -application. +Demo application to show the contrast problem with warning messages using the Lumo theme of Vaadin. diff --git a/mvnw b/mvnw old mode 100755 new mode 100644 diff --git a/src/main/bundles/README.md b/src/main/bundles/README.md new file mode 100644 index 0000000..c9737d1 --- /dev/null +++ b/src/main/bundles/README.md @@ -0,0 +1,32 @@ +This directory is automatically generated by Vaadin and contains the pre-compiled +frontend files/resources for your project (frontend development bundle). + +It should be added to Version Control System and committed, so that other developers +do not have to compile it again. + +Frontend development bundle is automatically updated when needed: +- an npm/pnpm package is added with @NpmPackage or directly into package.json +- CSS, JavaScript or TypeScript files are added with @CssImport, @JsModule or @JavaScript +- Vaadin add-on with front-end customizations is added +- Custom theme imports/assets added into 'theme.json' file +- Exported web component is added. + +If your project development needs a hot deployment of the frontend changes, +you can switch Flow to use Vite development server (default in Vaadin 23.3 and earlier versions): +- set `vaadin.frontend.hotdeploy=true` in `application.properties` +- configure `vaadin-maven-plugin`: +``` + + true + +``` +- configure `jetty-maven-plugin`: +``` + + + true + + +``` + +Read more [about Vaadin development mode](https://vaadin.com/docs/next/flow/configuration/development-mode#precompiled-bundle). \ No newline at end of file diff --git a/src/main/bundles/dev.bundle b/src/main/bundles/dev.bundle new file mode 100644 index 0000000..db786bd Binary files /dev/null and b/src/main/bundles/dev.bundle differ diff --git a/src/main/frontend/index.html b/src/main/frontend/index.html new file mode 100644 index 0000000..eb0c53b --- /dev/null +++ b/src/main/frontend/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + +
+ + diff --git a/src/main/frontend/themes/default/styles.css b/src/main/frontend/themes/default/styles.css index 4eecf84..776ff8a 100644 --- a/src/main/frontend/themes/default/styles.css +++ b/src/main/frontend/themes/default/styles.css @@ -1 +1,16 @@ /* Add your styles here */ + +.warning { + display: flex; + justify-content: center; + justify-items: center; + background-color: var(--lumo-warning-color); + color: var(--lumo-warning-text-color); + box-shadow: var(--lumo-box-shadow-s); + font-size: var(--lumo-font-size-xl); +} + +h1 { + font-size: var(--lumo-font-size-l); + padding-top: var(--lumo-space-l); +} diff --git a/src/main/frontend/themes/default/theme.json b/src/main/frontend/themes/default/theme.json index e36d300..2a27fca 100644 --- a/src/main/frontend/themes/default/theme.json +++ b/src/main/frontend/themes/default/theme.json @@ -1,9 +1,4 @@ { - "lumoImports": [ - "typography", - "color", - "spacing", - "badge", - "utility" - ] -} \ No newline at end of file + "lumoImports" : [ "typography", "color", "spacing", "badge", "utility" ], + "autoInjectComponents" : "false" +} diff --git a/src/main/java/com/example/base/ui/MainErrorHandler.java b/src/main/java/com/example/base/ui/MainErrorHandler.java deleted file mode 100644 index 418324f..0000000 --- a/src/main/java/com/example/base/ui/MainErrorHandler.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.example.base.ui; - -import com.vaadin.flow.component.Component; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; -import com.vaadin.flow.server.VaadinServiceInitListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -class MainErrorHandler { - - private static final Logger log = LoggerFactory.getLogger(MainErrorHandler.class); - - @Bean - public VaadinServiceInitListener errorHandlerInitializer() { - return (event) -> event.getSource().addSessionInitListener( - sessionInitEvent -> sessionInitEvent.getSession().setErrorHandler(errorEvent -> { - log.error("An unexpected error occurred", errorEvent.getThrowable()); - errorEvent.getComponent().flatMap(Component::getUI).ifPresent(ui -> { - var notification = new Notification( - "An unexpected error has occurred. Please try again later."); - notification.addThemeVariants(NotificationVariant.LUMO_ERROR); - notification.setPosition(Notification.Position.TOP_CENTER); - notification.setDuration(3000); - ui.access(notification::open); - }); - })); - } -} diff --git a/src/main/java/com/example/base/ui/MainLayout.java b/src/main/java/com/example/base/ui/MainLayout.java deleted file mode 100644 index 558849b..0000000 --- a/src/main/java/com/example/base/ui/MainLayout.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.example.base.ui; - -import com.vaadin.flow.component.applayout.AppLayout; -import com.vaadin.flow.component.html.Div; -import com.vaadin.flow.component.html.Span; -import com.vaadin.flow.component.icon.Icon; -import com.vaadin.flow.component.icon.VaadinIcon; -import com.vaadin.flow.component.orderedlayout.Scroller; -import com.vaadin.flow.component.sidenav.SideNav; -import com.vaadin.flow.component.sidenav.SideNavItem; -import com.vaadin.flow.router.Layout; -import com.vaadin.flow.server.menu.MenuConfiguration; -import com.vaadin.flow.server.menu.MenuEntry; - -import static com.vaadin.flow.theme.lumo.LumoUtility.*; - -@Layout -public final class MainLayout extends AppLayout { - - MainLayout() { - setPrimarySection(Section.DRAWER); - addToDrawer(createHeader(), new Scroller(createSideNav())); - } - - private Div createHeader() { - // TODO Replace with real application logo and name - var appLogo = VaadinIcon.CUBES.create(); - appLogo.addClassNames(TextColor.PRIMARY, IconSize.LARGE); - - var appName = new Span("App"); - appName.addClassNames(FontWeight.SEMIBOLD, FontSize.LARGE); - - var header = new Div(appLogo, appName); - header.addClassNames(Display.FLEX, Padding.MEDIUM, Gap.MEDIUM, AlignItems.CENTER); - return header; - } - - private SideNav createSideNav() { - var nav = new SideNav(); - nav.addClassNames(Margin.Horizontal.MEDIUM); - MenuConfiguration.getMenuEntries().forEach(entry -> nav.addItem(createSideNavItem(entry))); - return nav; - } - - private SideNavItem createSideNavItem(MenuEntry menuEntry) { - if (menuEntry.icon() != null) { - return new SideNavItem(menuEntry.title(), menuEntry.path(), new Icon(menuEntry.icon())); - } else { - return new SideNavItem(menuEntry.title(), menuEntry.path()); - } - } -} diff --git a/src/main/java/com/example/base/ui/component/ViewToolbar.java b/src/main/java/com/example/base/ui/component/ViewToolbar.java deleted file mode 100644 index dace3ab..0000000 --- a/src/main/java/com/example/base/ui/component/ViewToolbar.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.example.base.ui.component; - -import com.vaadin.flow.component.Component; -import com.vaadin.flow.component.Composite; -import com.vaadin.flow.component.applayout.DrawerToggle; -import com.vaadin.flow.component.html.Div; -import com.vaadin.flow.component.html.H1; -import com.vaadin.flow.component.html.Header; -import com.vaadin.flow.theme.lumo.LumoUtility.*; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; - -@NullMarked -public final class ViewToolbar extends Composite
{ - - public ViewToolbar(@Nullable String viewTitle, Component... components) { - addClassNames(Display.FLEX, FlexDirection.COLUMN, JustifyContent.BETWEEN, AlignItems.STRETCH, Gap.MEDIUM, - FlexDirection.Breakpoint.Medium.ROW, AlignItems.Breakpoint.Medium.CENTER); - - var drawerToggle = new DrawerToggle(); - drawerToggle.addClassNames(Margin.NONE); - - var title = new H1(viewTitle); - title.addClassNames(FontSize.XLARGE, Margin.NONE, FontWeight.LIGHT); - - var toggleAndTitle = new Div(drawerToggle, title); - toggleAndTitle.addClassNames(Display.FLEX, AlignItems.CENTER); - getContent().add(toggleAndTitle); - - if (components.length > 0) { - var actions = new Div(components); - actions.addClassNames(Display.FLEX, FlexDirection.COLUMN, JustifyContent.BETWEEN, Flex.GROW, Gap.SMALL, - FlexDirection.Breakpoint.Medium.ROW); - getContent().add(actions); - } - } - - public static Component group(Component... components) { - var group = new Div(components); - group.addClassNames(Display.FLEX, FlexDirection.COLUMN, AlignItems.STRETCH, Gap.SMALL, - FlexDirection.Breakpoint.Medium.ROW, AlignItems.Breakpoint.Medium.CENTER); - return group; - } -} diff --git a/src/main/java/com/example/examplefeature/Task.java b/src/main/java/com/example/examplefeature/Task.java deleted file mode 100644 index f412e08..0000000 --- a/src/main/java/com/example/examplefeature/Task.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.example.examplefeature; - -import jakarta.persistence.*; -import org.jspecify.annotations.Nullable; - -import java.time.Instant; -import java.time.LocalDate; - -@Entity -@Table(name = "task") -public class Task { - - public static final int DESCRIPTION_MAX_LENGTH = 300; - - @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE) - @Column(name = "task_id") - private Long id; - - @Column(name = "description", nullable = false, length = DESCRIPTION_MAX_LENGTH) - private String description = ""; - - @Column(name = "creation_date", nullable = false) - private Instant creationDate; - - @Column(name = "due_date") - @Nullable - private LocalDate dueDate; - - protected Task() { // To keep Hibernate happy - } - - public Task(String description, Instant creationDate) { - setDescription(description); - this.creationDate = creationDate; - } - - public @Nullable Long getId() { - return id; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - if (description.length() > DESCRIPTION_MAX_LENGTH) { - throw new IllegalArgumentException("Description length exceeds " + DESCRIPTION_MAX_LENGTH); - } - this.description = description; - } - - public Instant getCreationDate() { - return creationDate; - } - - public @Nullable LocalDate getDueDate() { - return dueDate; - } - - public void setDueDate(@Nullable LocalDate dueDate) { - this.dueDate = dueDate; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || !getClass().isAssignableFrom(obj.getClass())) { - return false; - } - if (obj == this) { - return true; - } - - Task other = (Task) obj; - return getId() != null && getId().equals(other.getId()); - } - - @Override - public int hashCode() { - // Hashcode should never change during the lifetime of an object. Because of - // this we can't use getId() to calculate the hashcode. Unless you have sets - // with lots of entities in them, returning the same hashcode should not be a - // problem. - return getClass().hashCode(); - } -} diff --git a/src/main/java/com/example/examplefeature/TaskRepository.java b/src/main/java/com/example/examplefeature/TaskRepository.java deleted file mode 100644 index a1b35d9..0000000 --- a/src/main/java/com/example/examplefeature/TaskRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.examplefeature; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -interface TaskRepository extends JpaRepository, JpaSpecificationExecutor { - - // If you don't need a total row count, Slice is better than Page as it only performs a select query. - // Page performs both a select and a count query. - Slice findAllBy(Pageable pageable); -} diff --git a/src/main/java/com/example/examplefeature/TaskService.java b/src/main/java/com/example/examplefeature/TaskService.java deleted file mode 100644 index 504ba3d..0000000 --- a/src/main/java/com/example/examplefeature/TaskService.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.example.examplefeature; - -import org.jspecify.annotations.Nullable; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.Instant; -import java.time.LocalDate; -import java.util.List; - -@Service -public class TaskService { - - private final TaskRepository taskRepository; - - TaskService(TaskRepository taskRepository) { - this.taskRepository = taskRepository; - } - - @Transactional - public void createTask(String description, @Nullable LocalDate dueDate) { - if ("fail".equals(description)) { - throw new RuntimeException("This is for testing the error handler"); - } - var task = new Task(description, Instant.now()); - task.setDueDate(dueDate); - taskRepository.saveAndFlush(task); - } - - @Transactional(readOnly = true) - public List list(Pageable pageable) { - return taskRepository.findAllBy(pageable).toList(); - } - -} diff --git a/src/main/java/com/example/examplefeature/package-info.java b/src/main/java/com/example/examplefeature/package-info.java deleted file mode 100644 index 5764d7e..0000000 --- a/src/main/java/com/example/examplefeature/package-info.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * This is a feature package for the Task Management sample feature. Its purpose is to demonstrate how you typically - * structure Vaadin business applications, and how the different building blocks interact. - *

- * A feature package represents a self-contained unit of functionality, including UI components, business logic, and - * data access. It could be a subdomain or bounded context (e.g., "Billing"), a specific use case (e.g., "User - * Registration"), or even a complex UI view (e.g., "Dashboard"). - *

- *

- * If your application is very small, you may not need dedicated feature packages. In that case, move the subpackages - * directly to the application package. - *

- */ -@NullMarked -package com.example.examplefeature; -// TODO Remove this package once you have added real features - -import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/com/example/examplefeature/ui/TaskListView.java b/src/main/java/com/example/examplefeature/ui/TaskListView.java deleted file mode 100644 index 6d3d31e..0000000 --- a/src/main/java/com/example/examplefeature/ui/TaskListView.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.example.examplefeature.ui; - -import com.example.base.ui.component.ViewToolbar; -import com.example.examplefeature.Task; -import com.example.examplefeature.TaskService; -import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.button.ButtonVariant; -import com.vaadin.flow.component.datepicker.DatePicker; -import com.vaadin.flow.component.grid.Grid; -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.component.notification.Notification; -import com.vaadin.flow.component.notification.NotificationVariant; -import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.router.Menu; -import com.vaadin.flow.router.PageTitle; -import com.vaadin.flow.router.Route; -import com.vaadin.flow.theme.lumo.LumoUtility; - -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.Optional; - -import static com.vaadin.flow.spring.data.VaadinSpringDataHelpers.toSpringPageRequest; - -@Route("") -@PageTitle("Task List") -@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Task List") -class TaskListView extends Main { - - private final TaskService taskService; - - final TextField description; - final DatePicker dueDate; - final Button createBtn; - final Grid taskGrid; - - TaskListView(TaskService taskService) { - this.taskService = taskService; - - description = new TextField(); - description.setPlaceholder("What do you want to do?"); - description.setAriaLabel("Task description"); - description.setMaxLength(Task.DESCRIPTION_MAX_LENGTH); - description.setMinWidth("20em"); - - dueDate = new DatePicker(); - dueDate.setPlaceholder("Due date"); - dueDate.setAriaLabel("Due date"); - - createBtn = new Button("Create", event -> createTask()); - createBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY); - - var dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(getLocale()) - .withZone(ZoneId.systemDefault()); - var dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(getLocale()); - - taskGrid = new Grid<>(); - taskGrid.setItems(query -> taskService.list(toSpringPageRequest(query)).stream()); - taskGrid.addColumn(Task::getDescription).setHeader("Description"); - taskGrid.addColumn(task -> Optional.ofNullable(task.getDueDate()).map(dateFormatter::format).orElse("Never")) - .setHeader("Due Date"); - taskGrid.addColumn(task -> dateTimeFormatter.format(task.getCreationDate())).setHeader("Creation Date"); - taskGrid.setSizeFull(); - - setSizeFull(); - addClassNames(LumoUtility.BoxSizing.BORDER, LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN, - LumoUtility.Padding.MEDIUM, LumoUtility.Gap.SMALL); - - add(new ViewToolbar("Task List", ViewToolbar.group(description, dueDate, createBtn))); - add(taskGrid); - } - - private void createTask() { - taskService.createTask(description.getValue(), dueDate.getValue()); - taskGrid.getDataProvider().refreshAll(); - description.clear(); - dueDate.clear(); - Notification.show("Task added", 3000, Notification.Position.BOTTOM_END) - .addThemeVariants(NotificationVariant.LUMO_SUCCESS); - } - -} diff --git a/src/main/java/com/example/views/TestView.java b/src/main/java/com/example/views/TestView.java new file mode 100644 index 0000000..543f500 --- /dev/null +++ b/src/main/java/com/example/views/TestView.java @@ -0,0 +1,31 @@ +package com.example.views; + +import com.vaadin.flow.component.checkbox.Checkbox; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.RouterLayout; + +@Route("") +public class TestView extends Div implements RouterLayout { + + public TestView() { + + final var warning = new Div("This is a warning message."); + warning.setClassName("warning"); + add(warning); + + add(new H1("Lumo Theme Test")); + + final var darkModeSwitcher = new Checkbox("Dark mode"); + add(darkModeSwitcher); + darkModeSwitcher.addValueChangeListener(event -> { + if (event.getValue()) { + getElement().getThemeList().add("dark"); + } else { + getElement().getThemeList().remove("dark"); + } + }); + + } +}