Skip to content

Commit 4718daa

Browse files
authored
feat: add Hilla demo project (#213)
1 parent 8478efe commit 4718daa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2133
-0
lines changed

.editorconfig

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# http://editorconfig.org
2+
3+
root = true
4+
5+
[*]
6+
charset = utf-8
7+
indent_style = space
8+
indent_size = 2
9+
end_of_line = lf
10+
insert_final_newline = true
11+
trim_trailing_whitespace = true
12+
13+
[*.{java,xml}]
14+
indent_size = 4
15+
16+
[*.md]
17+
insert_final_newline = false
18+
trim_trailing_whitespace = false

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@ package-lock.json
2727
tsconfig.json
2828
types.d.ts
2929
vite.*
30+
31+
!observability-kit-hilla-demo/**/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"extends": [
3+
"vaadin/typescript-requiring-type-checking",
4+
"vaadin/imports-typescript",
5+
"vaadin/lit",
6+
"vaadin/prettier"
7+
],
8+
"rules": {
9+
"class-methods-use-this": "off",
10+
"import/no-unassigned-import": "off",
11+
"import/no-cycle": "off",
12+
"@typescript-eslint/unbound-method": "off"
13+
}
14+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/target/
2+
.idea/
3+
.vscode/
4+
.settings
5+
.project
6+
.classpath
7+
8+
*.iml
9+
.DS_Store
10+
11+
# The following files are generated/updated by vaadin-maven-plugin
12+
node_modules/
13+
frontend/generated/
14+
pnpmfile.js
15+
vite.generated.ts
16+
17+
# Browser drivers for local integration tests
18+
drivers/
19+
# Error screenshots generated by TestBench for failed integration tests
20+
error-screenshots/
21+
webpack.generated.js
22+
config/secrets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import micromatch from 'micromatch';
2+
3+
const excludePatterns = ['**/node_modules/**/*', 'vite.generated.ts'];
4+
5+
function createExcludeCallback(command) {
6+
return (files) => {
7+
const matched = micromatch.not(files, excludePatterns);
8+
9+
return matched.length > 0 ? [`${command} ${matched.join(' ')}`] : [];
10+
};
11+
}
12+
13+
export const commands = [createExcludeCallback('eslint --fix'), createExcludeCallback('prettier --write')];
14+
15+
export default {
16+
'*{.js,.ts}': commands,
17+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"bracketSpacing": true,
3+
"printWidth": 120,
4+
"trailingComma": "all",
5+
"tabWidth": 2,
6+
"singleQuote": true
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"pre-commit": "npx lint-staged"
3+
}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
This is free and unencumbered software released into the public domain.
2+
3+
Anyone is free to copy, modify, publish, use, compile, sell, or
4+
distribute this software, either in source code form or as a compiled
5+
binary, for any purpose, commercial or non-commercial, and by any
6+
means.
7+
8+
In jurisdictions that recognize copyright laws, the author or authors
9+
of this software dedicate any and all copyright interest in the
10+
software to the public domain. We make this dedication for the benefit
11+
of the public at large and to the detriment of our heirs and
12+
successors. We intend this dedication to be an overt act of
13+
relinquishment in perpetuity of all present and future rights to this
14+
software under copyright law.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
OTHER DEALINGS IN THE SOFTWARE.
23+
24+
For more information, please refer to <http://unlicense.org>
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Custom project from Hilla
2+
3+
This project can be used as a starting point to create your own Hilla application with Spring Boot.
4+
It contains all the necessary configuration and some placeholder files to get you started.
5+
6+
## Running the application
7+
8+
The project is a standard Maven project. To run it from the command line,
9+
type `mvnw` (Windows), or `./mvnw` (Mac & Linux), then open
10+
http://localhost:8080 in your browser.
11+
12+
You can also import the project to your IDE of choice as you would with any
13+
Maven project.
14+
15+
## Deploying to Production
16+
17+
To create a production build, call `mvnw clean package -Pproduction` (Windows),
18+
or `./mvnw clean package -Pproduction` (Mac & Linux).
19+
This will build a JAR file with all the dependencies and front-end resources,
20+
ready to be deployed. The file can be found in the `target` folder after the build completes.
21+
22+
Once the JAR file is built, you can run it using
23+
`java -jar target/myapp-1.0-SNAPSHOT.jar` (NOTE, replace
24+
`myapp-1.0-SNAPSHOT.jar` with the name of your jar).
25+
26+
## Project structure
27+
28+
<table style="width:100%; text-align: left;">
29+
<tr><th>Directory</th><th>Description</th></tr>
30+
<tr><td><code>frontend/</code></td><td>Client-side source directory</td></tr>
31+
<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>index.html</code></td><td>HTML template</td></tr>
32+
<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>index.ts</code></td><td>Frontend entrypoint, contains the client-side routing setup using <a href="https://hilla.dev/docs/routing/router">Hilla Router</a></td></tr>
33+
<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>main-layout.ts</code></td><td>Main layout Web Component, contains the navigation menu, uses <a href="https://vaadin.com/docs/latest/ds/components/app-layout">App Layout</a></td></tr>
34+
<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>views/</code></td><td>UI views Web Components (TypeScript)</td></tr>
35+
<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>themes/</code></td><td>Custom
36+
CSS styles</td></tr>
37+
<tr><td><code>src/main/java/&lt;groupId&gt;/</code></td><td>Server-side
38+
source directory, contains the server-side Java views</td></tr>
39+
<tr><td>&nbsp;&nbsp;&nbsp;&nbsp;<code>Application.java</code></td><td>Server entry-point</td></tr>
40+
</table>
41+
42+
## Useful links
43+
44+
- Read the documentation at [hilla.dev/docs](https://hilla.dev/docs/).
45+
- Ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/hilla) or join our [Discord channel](https://discord.gg/MYFq5RTbBn).
46+
- Report issues, create pull requests in [GitHub](https://github.com/vaadin/hilla).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.button:hover {
2+
text-decoration: none;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { html, type TemplateResult } from 'lit';
2+
import { customElement, property } from 'lit/decorators.js';
3+
import styles from './auth-button.css';
4+
import { Layout } from 'Frontend/views/view.js';
5+
6+
@customElement('auth-button')
7+
export default class AuthButton extends Layout {
8+
static override readonly styles = styles;
9+
10+
@property()
11+
to: string = '#';
12+
13+
override render(): TemplateResult {
14+
return html`<a href=${this.to} class="button block rounded-l ms-auto text-l bg-primary text-primary-contrast p-s">
15+
<slot></slot>
16+
</a>`;
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { type MiddlewareContext, type MiddlewareNext, ConnectClient } from '@hilla/frontend';
2+
import { logout } from 'Frontend/stores/login-store.js';
3+
4+
const client = new ConnectClient({
5+
middlewares: [
6+
async (context: MiddlewareContext, next: MiddlewareNext) => {
7+
const response = await next(context);
8+
9+
// Log out if the authentication has expired
10+
if (response.status === 401) {
11+
await logout();
12+
}
13+
14+
return response;
15+
},
16+
],
17+
prefix: 'connect',
18+
});
19+
20+
export default client;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>observability-kit-hilla-demo</title>
7+
<style>
8+
body {
9+
margin: 0;
10+
width: 100vw;
11+
height: 100vh;
12+
}
13+
14+
#outlet {
15+
height: 100%;
16+
}
17+
</style>
18+
<!-- index.ts is included here automatically (either by the dev server or during the build) -->
19+
</head>
20+
21+
<body>
22+
<div id="outlet"></div>
23+
</body>
24+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* eslint-disable import/prefer-default-export */
2+
import { Router } from '@vaadin/router';
3+
import { routes } from './routes.js';
4+
import { appName, location, setLocation } from './stores/location-store.js';
5+
6+
addEventListener('vaadin-router-location-changed', ({ detail: { location: loc } }) => {
7+
setLocation(loc);
8+
const { title } = location.value;
9+
document.title = title ? `${title} | ${appName}` : appName;
10+
});
11+
12+
export const router = new Router(document.querySelector('#outlet'));
13+
await router.setRoutes(routes);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import type { Route, Router } from '@vaadin/router';
2+
import './views/helloworld/hello-world-view.js';
3+
import './views/main-layout.js';
4+
import './views/login-view.ts';
5+
import './views/image-list/image-list-view.js';
6+
import { user, login, logout, doesUserHaveRole } from 'Frontend/stores/login-store.js';
7+
8+
export type ViewRoute = Route & {
9+
children?: ViewRoute[];
10+
icon?: string;
11+
title?: string;
12+
requiresRole?: string;
13+
showInMenu?(): boolean;
14+
};
15+
16+
export function hasAccess(route: ViewRoute): boolean {
17+
return !route.requiresRole || doesUserHaveRole(route.requiresRole);
18+
}
19+
20+
function checkAuthentication(this: ViewRoute, context: Router.Context, commands: Router.Commands) {
21+
if (!hasAccess(this)) {
22+
return commands.redirect('/login');
23+
}
24+
25+
return undefined;
26+
}
27+
28+
export const views = [
29+
// Place routes below (more info https://hilla.dev/docs/routing)
30+
{
31+
action: checkAuthentication,
32+
component: 'hello-world-view',
33+
path: 'hello',
34+
title: 'Hello World',
35+
},
36+
{
37+
action: checkAuthentication,
38+
component: 'image-list-view',
39+
path: 'image-list',
40+
title: 'Image List',
41+
},
42+
{
43+
action: checkAuthentication,
44+
component: 'address-form-view',
45+
path: 'address-form',
46+
requiresRole: 'ROLE_USER',
47+
title: 'Address Form',
48+
},
49+
] satisfies readonly ViewRoute[];
50+
51+
export const routes = [
52+
{
53+
children: [
54+
{
55+
action(context, commands) {
56+
if (user.value && context.pathname === '/login') {
57+
return commands.redirect('/');
58+
}
59+
60+
if (!hasAccess(context.route as ViewRoute)) {
61+
return commands.redirect('/login');
62+
}
63+
64+
return undefined;
65+
},
66+
component: 'login-view',
67+
path: 'login',
68+
},
69+
...views,
70+
],
71+
component: 'main-layout',
72+
path: '',
73+
},
74+
{
75+
async action(_, commands) {
76+
await logout();
77+
return commands.redirect('/login');
78+
},
79+
path: 'logout',
80+
},
81+
] satisfies readonly ViewRoute[];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { signal } from '@preact/signals-core';
2+
import type { RouterLocation } from '@vaadin/router';
3+
import type { ViewRoute } from 'Frontend/routes.js';
4+
5+
export const appName = 'observability-kit-hilla-demo';
6+
7+
export const location = signal({
8+
path: '',
9+
title: '',
10+
});
11+
12+
export function setLocation(loc: RouterLocation): void {
13+
const serverSideRoute = loc.route?.path === '(.*)';
14+
15+
let path: string;
16+
if (loc.route && !serverSideRoute) {
17+
({ path } = loc.route);
18+
} else if (loc.pathname.startsWith(loc.baseUrl)) {
19+
path = loc.pathname.substring(loc.baseUrl.length);
20+
} else {
21+
path = loc.pathname;
22+
}
23+
24+
let title: string;
25+
if (serverSideRoute) {
26+
({ title } = document); // Title set by server
27+
} else {
28+
({ title = '' } = loc.route as ViewRoute);
29+
}
30+
31+
location.value = { path, title };
32+
}

0 commit comments

Comments
 (0)