Add gradle-managed frontend
It still uses NPM (via gradle tasks) but drops the old Angular-based UI in favor of a Lit-based UI from https://github.com/pwa-builder/pwa-starter. I'll have to rebuild from scratch, but I think it'll be better in the long run Signed-off-by: William Brawner <me@wbrawner.com>
|
@ -1,5 +1,4 @@
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
java
|
java
|
||||||
|
@ -20,7 +19,7 @@ dependencies {
|
||||||
implementation(project(":api"))
|
implementation(project(":api"))
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
implementation(project(":db"))
|
implementation(project(":db"))
|
||||||
implementation(project(":web"))
|
implementation(project(":frontend"))
|
||||||
implementation(libs.kotlin.reflect)
|
implementation(libs.kotlin.reflect)
|
||||||
implementation(libs.bundles.ktor.server)
|
implementation(libs.bundles.ktor.server)
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import com.wbrawner.twigs.*
|
||||||
import com.wbrawner.twigs.db.*
|
import com.wbrawner.twigs.db.*
|
||||||
import com.wbrawner.twigs.model.Session
|
import com.wbrawner.twigs.model.Session
|
||||||
import com.wbrawner.twigs.storage.*
|
import com.wbrawner.twigs.storage.*
|
||||||
import com.wbrawner.twigs.web.webRoutes
|
|
||||||
import com.zaxxer.hikari.HikariConfig
|
import com.zaxxer.hikari.HikariConfig
|
||||||
import com.zaxxer.hikari.HikariDataSource
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package com.wbrawner.twigs.web
|
package com.wbrawner.twigs.server
|
||||||
|
|
||||||
import io.ktor.server.application.*
|
import io.ktor.server.application.*
|
||||||
import io.ktor.server.http.content.*
|
import io.ktor.server.http.content.*
|
||||||
|
@ -9,12 +9,13 @@ import io.ktor.server.routing.*
|
||||||
fun Application.webRoutes() {
|
fun Application.webRoutes() {
|
||||||
routing {
|
routing {
|
||||||
static {
|
static {
|
||||||
resources("twigs")
|
staticBasePackage = "static"
|
||||||
default("index.html")
|
defaultResource("index.html")
|
||||||
|
resources(".")
|
||||||
}
|
}
|
||||||
intercept(ApplicationCallPipeline.Setup) {
|
intercept(ApplicationCallPipeline.Setup) {
|
||||||
if (!call.request.path().startsWith("/api") && !call.request.path().matches(Regex(".*\\.\\w+$"))) {
|
if (!call.request.path().startsWith("/api") && !call.request.path().matches(Regex(".*\\.\\w+$"))) {
|
||||||
call.resolveResource("twigs/index.html")?.let {
|
call.resolveResource("static/index.html")?.let {
|
||||||
call.respond(it)
|
call.respond(it)
|
||||||
return@intercept finish()
|
return@intercept finish()
|
||||||
}
|
}
|
8
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dev-dist
|
||||||
|
build
|
||||||
|
types
|
||||||
|
.idea
|
||||||
|
.github
|
23
frontend/build.gradle.kts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
plugins {
|
||||||
|
`java-library`
|
||||||
|
alias(libs.plugins.node.gradle)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<com.github.gradle.node.npm.task.NpmTask>("package") {
|
||||||
|
group = "build"
|
||||||
|
inputs.files(fileTree("node_modules"))
|
||||||
|
inputs.files(fileTree("src"))
|
||||||
|
inputs.file("package.json")
|
||||||
|
inputs.file("swa-cli.config.json")
|
||||||
|
inputs.file("tsconfig.json")
|
||||||
|
inputs.file("vite.config.ts")
|
||||||
|
|
||||||
|
outputs.dir("build/resources/main/static")
|
||||||
|
|
||||||
|
dependsOn.add(tasks.getByName("npmInstall"))
|
||||||
|
args.set(listOf("run", "build"))
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.getByName("processResources") {
|
||||||
|
dependsOn.add("package")
|
||||||
|
}
|
52
frontend/index.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>PWA Starter</title>
|
||||||
|
|
||||||
|
<base href="/"/>
|
||||||
|
|
||||||
|
<!-- This meta viewport ensures the webpage's dimensions change according to the device it's on. This is called Responsive Web Design.-->
|
||||||
|
<meta name="viewport"
|
||||||
|
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0"/>
|
||||||
|
<meta name="description" content="This is a PWA Starter app"/>
|
||||||
|
|
||||||
|
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#181818"/>
|
||||||
|
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#f3f3f3"/>
|
||||||
|
|
||||||
|
<!-- These meta tags are Apple-specific, and set the web application to run in full-screen mode with a black status bar. Learn more at https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html-->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||||
|
<meta name="apple-mobile-web-app-title" content="PWA Starter"/>
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
|
||||||
|
|
||||||
|
<!-- Imports an icon to represent the document. -->
|
||||||
|
<link rel="icon" href="/assets/icons/icon_24.png" type="image/x-icon"/>
|
||||||
|
|
||||||
|
<!-- Imports the manifest to represent the web application. A web app must have a manifest to be a PWA. -->
|
||||||
|
<link rel="manifest" href="/manifest.json"/>
|
||||||
|
|
||||||
|
<!-- light mode and dark mode CSS -->
|
||||||
|
<link rel="stylesheet" media="(prefers-color-scheme:light)"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.73/dist/themes/light.css">
|
||||||
|
<link rel="stylesheet" media="(prefers-color-scheme:dark)"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0-beta.73/dist/themes/dark.css"
|
||||||
|
onload="document.documentElement.classList.add('sl-theme-dark');">
|
||||||
|
|
||||||
|
<script type="module" src="/src/app-index.ts"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Our app-index web component. This component is defined in src/pages/app-index.ts-->
|
||||||
|
<app-index></app-index>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register(
|
||||||
|
'/sw.js'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
7350
frontend/package-lock.json
generated
Normal file
41
frontend/package.json
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"name": "pwa-starter",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "A starter kit for building PWAs!",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev-server": "vite --open",
|
||||||
|
"dev": "npm run dev-server",
|
||||||
|
"dev-task": "vite",
|
||||||
|
"deploy": " npx @azure/static-web-apps-cli login --no-use-keychain && npx @azure/static-web-apps-cli deploy",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"start": "npm run dev",
|
||||||
|
"start-remote": "vite --host"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@pwabuilder/pwainstall": "^1.6.7",
|
||||||
|
"@shoelace-style/shoelace": "^2.0.0-beta.82",
|
||||||
|
"@vaadin/router": "^1.7.4",
|
||||||
|
"lit": "^2.3.1",
|
||||||
|
"workbox-build": "^6.5.2",
|
||||||
|
"workbox-core": "^6.5.2",
|
||||||
|
"workbox-precaching": "^6.5.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^4.6.3",
|
||||||
|
"vite": "^2.9.0",
|
||||||
|
"vite-plugin-pwa": "^0.11.13"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"quoteProps": "consistent",
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"endOfLine": "crlf",
|
||||||
|
"bracketSpacing": true
|
||||||
|
}
|
||||||
|
}
|
BIN
frontend/public/assets/icons/192x192.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
frontend/public/assets/icons/24x24.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
frontend/public/assets/icons/48x48.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
frontend/public/assets/icons/512x512.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
frontend/public/assets/icons/icon_192.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
frontend/public/assets/icons/icon_24.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
frontend/public/assets/icons/icon_48.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
frontend/public/assets/icons/icon_512.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
frontend/public/assets/readme/build-output.png
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
frontend/public/assets/readme/codespace-button.png
Normal file
After Width: | Height: | Size: 283 KiB |
BIN
frontend/public/assets/readme/copy-starter.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
frontend/public/assets/readme/git-clone.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
frontend/public/assets/readme/intro.png
Normal file
After Width: | Height: | Size: 357 KiB |
BIN
frontend/public/assets/readme/local-button.png
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
frontend/public/assets/readme/new-repo-from-starter.png
Normal file
After Width: | Height: | Size: 155 KiB |
BIN
frontend/public/assets/readme/pwa-running.png
Normal file
After Width: | Height: | Size: 518 KiB |
BIN
frontend/public/assets/readme/pwa-starter-overview.png
Normal file
After Width: | Height: | Size: 319 KiB |
BIN
frontend/public/assets/readme/static-web-app-slash.png
Normal file
After Width: | Height: | Size: 125 KiB |
BIN
frontend/public/assets/readme/use-this-template.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
frontend/public/assets/readme/vscode-in-browser.png
Normal file
After Width: | Height: | Size: 427 KiB |
BIN
frontend/public/assets/screenshots/screen.png
Normal file
After Width: | Height: | Size: 51 KiB |
68
frontend/public/manifest.json
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
"id": "/",
|
||||||
|
"scope": "/",
|
||||||
|
"name": "PWA Starter",
|
||||||
|
"display": "standalone",
|
||||||
|
"start_url": "/",
|
||||||
|
"short_name": "starter",
|
||||||
|
"theme_color": "#E1477E",
|
||||||
|
"description": "This is a PWA Starter app",
|
||||||
|
"orientation": "any",
|
||||||
|
"background_color": "#E1477E",
|
||||||
|
"related_applications": [],
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"display_override": [
|
||||||
|
"window-controls-overlay"
|
||||||
|
],
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "assets/icons/512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/icons/192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/icons/48x48.png",
|
||||||
|
"sizes": "48x48",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/icons/24x24.png",
|
||||||
|
"sizes": "24x24",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"screenshots": [
|
||||||
|
{
|
||||||
|
"src": "assets/screenshots/screen.png",
|
||||||
|
"sizes": "1617x1012",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"features": [
|
||||||
|
"Cross Platform",
|
||||||
|
"fast",
|
||||||
|
"simple"
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
"utility"
|
||||||
|
],
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "Open About",
|
||||||
|
"short_name": "About",
|
||||||
|
"description": "Open the about page",
|
||||||
|
"url": "/about",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "assets/icons/192x192.png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
8
frontend/public/sw.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
importScripts(
|
||||||
|
'https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js'
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is your Service Worker, you can put any of your custom Service Worker
|
||||||
|
// code in this file, above the `precacheAndRoute` line.
|
||||||
|
|
||||||
|
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST || []);
|
91
frontend/src/app-index.ts
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import {css, html, LitElement} from 'lit';
|
||||||
|
import {customElement} from 'lit/decorators.js';
|
||||||
|
import {Router} from '@vaadin/router';
|
||||||
|
|
||||||
|
import './pages/app-home';
|
||||||
|
import './components/header';
|
||||||
|
import './styles/global.css';
|
||||||
|
|
||||||
|
@customElement('app-index')
|
||||||
|
export class AppIndex extends LitElement {
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
main {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#routerOutlet > * {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#routerOutlet > .leaving {
|
||||||
|
animation: 160ms fadeOut ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#routerOutlet > .entering {
|
||||||
|
animation: 160ms fadeIn linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
// this method is a lifecycle even in lit
|
||||||
|
// for more info check out the lit docs https://lit.dev/docs/components/lifecycle/
|
||||||
|
|
||||||
|
// For more info on using the @vaadin/router check here https://vaadin.com/router
|
||||||
|
const router = new Router(this.shadowRoot?.querySelector('#routerOutlet'));
|
||||||
|
router.setRoutes([
|
||||||
|
// temporarily cast to any because of a Type bug with the router
|
||||||
|
{
|
||||||
|
path: (import.meta as any).env.BASE_URL,
|
||||||
|
animate: true,
|
||||||
|
children: [
|
||||||
|
{path: '', component: 'app-home'},
|
||||||
|
{
|
||||||
|
path: 'about',
|
||||||
|
component: 'app-about',
|
||||||
|
action: async () => {
|
||||||
|
await import('./pages/app-about/app-about.js');
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
} as any,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div>
|
||||||
|
<main>
|
||||||
|
<div id="routerOutlet"></div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
80
frontend/src/components/header.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import {css, html, LitElement} from 'lit';
|
||||||
|
import {customElement, property} from 'lit/decorators.js';
|
||||||
|
|
||||||
|
import '@shoelace-style/shoelace/dist/components/button/button.js';
|
||||||
|
|
||||||
|
@customElement('app-header')
|
||||||
|
export class AppHeader extends LitElement {
|
||||||
|
@property({type: String}) title = 'PWA Starter';
|
||||||
|
|
||||||
|
@property({type: Boolean}) enableBack: boolean = false;
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--app-color-primary);
|
||||||
|
color: white;
|
||||||
|
height: 4em;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-top: 12px;
|
||||||
|
|
||||||
|
position: fixed;
|
||||||
|
left: env(titlebar-area-x, 0);
|
||||||
|
top: env(titlebar-area-y, 0);
|
||||||
|
height: env(titlebar-area-height, 50px);
|
||||||
|
width: env(titlebar-area-width, 100%);
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#back-button-block {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(prefers-color-scheme: light) {
|
||||||
|
header {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
color: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<div id="back-button-block">
|
||||||
|
${this.enableBack ? html`<sl-button href="${(import.meta as any).env.BASE_URL}">
|
||||||
|
Back
|
||||||
|
</sl-button>` : null}
|
||||||
|
|
||||||
|
<h1>${this.title}</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
11
frontend/src/pages/app-about/about-styles.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import {css} from 'lit';
|
||||||
|
|
||||||
|
// these styles can be imported from any component
|
||||||
|
// for an example of how to use this, check /pages/about-about.ts
|
||||||
|
export const styles = css`
|
||||||
|
@media(min-width: 1000px) {
|
||||||
|
sl-card {
|
||||||
|
max-width: 70vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
45
frontend/src/pages/app-about/app-about.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import {html, LitElement} from 'lit';
|
||||||
|
import {customElement} from 'lit/decorators.js';
|
||||||
|
|
||||||
|
// You can also import styles from another file
|
||||||
|
// if you prefer to keep your CSS seperate from your component
|
||||||
|
import {styles} from './about-styles';
|
||||||
|
|
||||||
|
import {styles as sharedStyles} from '../../styles/shared-styles'
|
||||||
|
|
||||||
|
import '@shoelace-style/shoelace/dist/components/card/card.js';
|
||||||
|
|
||||||
|
@customElement('app-about')
|
||||||
|
export class AppAbout extends LitElement {
|
||||||
|
static styles = [
|
||||||
|
sharedStyles,
|
||||||
|
styles
|
||||||
|
]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<app-header ?enableBack="${true}"></app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h2>About Page</h2>
|
||||||
|
|
||||||
|
<sl-card>
|
||||||
|
<h2>Did you know?</h2>
|
||||||
|
|
||||||
|
<p>PWAs have access to many useful APIs in modern browsers! These
|
||||||
|
APIs have enabled many new types of apps that can be built as PWAs, such as advanced graphics editing apps, games,
|
||||||
|
apps that use machine learning and more!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Check out <a
|
||||||
|
href="https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files">these
|
||||||
|
docs</a> to learn more about the advanced features that you can use in your PWA</p>
|
||||||
|
</sl-card>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
149
frontend/src/pages/app-home.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import {css, html, LitElement} from 'lit';
|
||||||
|
import {customElement, property} from 'lit/decorators.js';
|
||||||
|
|
||||||
|
// For more info on the @pwabuilder/pwainstall component click here https://github.com/pwa-builder/pwa-install
|
||||||
|
import '@pwabuilder/pwainstall';
|
||||||
|
|
||||||
|
import '@shoelace-style/shoelace/dist/components/card/card.js';
|
||||||
|
import '@shoelace-style/shoelace/dist/components/button/button.js';
|
||||||
|
|
||||||
|
import {styles} from '../styles/shared-styles';
|
||||||
|
|
||||||
|
@customElement('app-home')
|
||||||
|
export class AppHome extends LitElement {
|
||||||
|
|
||||||
|
// For more information on using properties and state in lit
|
||||||
|
// check out this link https://lit.dev/docs/components/properties/
|
||||||
|
@property() message = 'Welcome!';
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [
|
||||||
|
styles,
|
||||||
|
css`
|
||||||
|
#welcomeBar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#welcomeCard,
|
||||||
|
#infoCard {
|
||||||
|
padding: 18px;
|
||||||
|
padding-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pwa-install {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 16px;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
sl-card::part(footer) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(min-width: 750px) {
|
||||||
|
sl-card {
|
||||||
|
width: 70vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (horizontal-viewport-segments: 2) {
|
||||||
|
#welcomeBar {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#welcomeCard {
|
||||||
|
margin-right: 64px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async firstUpdated() {
|
||||||
|
// this method is a lifecycle even in lit
|
||||||
|
// for more info check out the lit docs https://lit.dev/docs/components/lifecycle/
|
||||||
|
console.log('This is your home page');
|
||||||
|
}
|
||||||
|
|
||||||
|
share() {
|
||||||
|
if ((navigator as any).share) {
|
||||||
|
(navigator as any).share({
|
||||||
|
title: 'PWABuilder pwa-starter',
|
||||||
|
text: 'Check out the PWABuilder pwa-starter!',
|
||||||
|
url: 'https://github.com/pwa-builder/pwa-starter',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<app-header></app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div id="welcomeBar">
|
||||||
|
<sl-card id="welcomeCard">
|
||||||
|
<div slot="header">
|
||||||
|
<h2>${this.message}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
For more information on the PWABuilder pwa-starter, check out the
|
||||||
|
<a href="https://github.com/pwa-builder/pwa-starter/wiki/Getting-Started">
|
||||||
|
Documentation on Github</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p id="mainInfo">
|
||||||
|
Welcome to the
|
||||||
|
<a href="https://pwabuilder.com">PWABuilder</a>
|
||||||
|
pwa-starter! Be sure to head back to
|
||||||
|
<a href="https://pwabuilder.com">PWABuilder</a>
|
||||||
|
when you are ready to ship this PWA to the Microsoft Store, Google Play
|
||||||
|
and the Apple App Store!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
${'share' in navigator
|
||||||
|
? html`<sl-button slot="footer" variant="primary" @click="${this.share}">Share this Starter!</sl-button>`
|
||||||
|
: null}
|
||||||
|
</sl-card>
|
||||||
|
|
||||||
|
<sl-card id="infoCard">
|
||||||
|
<h2>Technology Used</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.typescriptlang.org/">TypeScript</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="https://lit.dev">lit</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="https://shoelace.style/">Shoelace</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="https://vaadin.github.io/vaadin-router/vaadin-router/demo/#vaadin-router-getting-started-demos"
|
||||||
|
>Vaadin Router</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</sl-card>
|
||||||
|
|
||||||
|
<sl-button href="${(import.meta as any).env.BASE_URL}about" variant="primary">Navigate to About</sl-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pwa-install>Install PWA Starter</pwa-install>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
31
frontend/src/styles/global.css
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
This file is used for all of your global styles and CSS variables.
|
||||||
|
Check here https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties for more info on using CSS variables.
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
font-family: var(--font-family);
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
background-color: #181818;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
15
frontend/src/styles/shared-styles.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {css} from 'lit';
|
||||||
|
|
||||||
|
// these styles can be imported from any component
|
||||||
|
// for an example of how to use this, check /pages/about-about.ts
|
||||||
|
export const styles = css`
|
||||||
|
@media(min-width: 1000px) {
|
||||||
|
sl-card {
|
||||||
|
max-width: 70vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin-top: 80px;
|
||||||
|
}
|
||||||
|
`;
|
12
frontend/swa-cli.config.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://aka.ms/azure/static-web-apps-cli/schema",
|
||||||
|
"configurations": {
|
||||||
|
"pwa-starter": {
|
||||||
|
"appLocation": ".",
|
||||||
|
"outputLocation": "dist",
|
||||||
|
"appBuildCommand": "npm run build --if-present",
|
||||||
|
"run": "npm start",
|
||||||
|
"appDevserverUrl": "http://localhost:3000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
frontend/tsconfig.json
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "esnext",
|
||||||
|
"lib": [
|
||||||
|
"es2017",
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable"
|
||||||
|
],
|
||||||
|
"declaration": true,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"outDir": "./types",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"types": [
|
||||||
|
"vite-plugin-pwa/client"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
28
frontend/vite.config.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import {defineConfig} from 'vite';
|
||||||
|
import {VitePWA} from 'vite-plugin-pwa';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
base: "/",
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
assetsDir: "code",
|
||||||
|
outDir: "build/resources/main/static"
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
VitePWA({
|
||||||
|
strategies: "injectManifest",
|
||||||
|
injectManifest: {
|
||||||
|
swSrc: 'public/sw.js',
|
||||||
|
swDest: 'build/resources/main/static/sw.js',
|
||||||
|
globDirectory: 'build/resources/main/static',
|
||||||
|
globPatterns: [
|
||||||
|
'**/*.{html,js,css,json, png}',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devOptions: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
|
@ -7,6 +7,7 @@ kotlinx-coroutines = "1.6.2"
|
||||||
ktor = "2.0.2"
|
ktor = "2.0.2"
|
||||||
logback = "1.2.11"
|
logback = "1.2.11"
|
||||||
mail = "1.6.2"
|
mail = "1.6.2"
|
||||||
|
node-gradle = "3.5.0"
|
||||||
postgres = "42.3.4"
|
postgres = "42.3.4"
|
||||||
shadow = "7.0.0"
|
shadow = "7.0.0"
|
||||||
|
|
||||||
|
@ -44,4 +45,5 @@ ktor-server = [
|
||||||
[plugins]
|
[plugins]
|
||||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
|
node-gradle = { id = "com.github.node-gradle.node", version.ref = "node-gradle" }
|
||||||
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
|
|
@ -1,3 +1,2 @@
|
||||||
rootProject.name = "twigs"
|
rootProject.name = "twigs"
|
||||||
include("core", "api", "app", "storage", "db", "web")
|
include("api", "app", "core", "db", "frontend", "storage", "testhelpers")
|
||||||
include("testhelpers")
|
|
2
web/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
||||||
build/
|
|
||||||
src/main/resources/twigs/
|
|
|
@ -1,51 +0,0 @@
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
`java-library`
|
|
||||||
alias(libs.plugins.kotlin.jvm)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(kotlin("stdlib"))
|
|
||||||
api(libs.ktor.server.core)
|
|
||||||
testImplementation(libs.junit.jupiter.api)
|
|
||||||
testRuntimeOnly(libs.junit.jupiter.engine)
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.getByName<Test>("test") {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Replace this hack with either a git submodule or an internal Kotlin-based UI
|
|
||||||
tasks.register("package") {
|
|
||||||
doLast {
|
|
||||||
val built = File(rootProject.rootDir.parent, "twigs-web/dist/twigs")
|
|
||||||
if (built.exists()) {
|
|
||||||
built.deleteRecursively()
|
|
||||||
}
|
|
||||||
val dest = File(project.projectDir, "src/main/resources/twigs")
|
|
||||||
if (dest.exists()) {
|
|
||||||
dest.deleteRecursively()
|
|
||||||
}
|
|
||||||
var command = listOf(
|
|
||||||
"cd", "../../twigs-web", ";",
|
|
||||||
"npm", "i", ";",
|
|
||||||
"npm", "run", "package"
|
|
||||||
)
|
|
||||||
command = if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows")) {
|
|
||||||
listOf("powershell", "-Command") + command
|
|
||||||
} else {
|
|
||||||
listOf("bash", "-c", "\"${command.joinToString(" ")}\"")
|
|
||||||
}
|
|
||||||
exec {
|
|
||||||
commandLine(command)
|
|
||||||
}
|
|
||||||
if (!built.copyRecursively(dest, true) || !dest.isDirectory) {
|
|
||||||
throw GradleException("Failed to copy files from ${built.absolutePath} to ${dest.absolutePath}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//tasks.getByName("processResources") {
|
|
||||||
// dependsOn.add("package")
|
|
||||||
//}
|
|