Compare commits
1 commit
main
...
frontend-r
Author | SHA1 | Date | |
---|---|---|---|
73bfc793c8 |
|
@ -1,5 +1,4 @@
|
|||
import java.net.URI
|
||||
import java.util.*
|
||||
|
||||
plugins {
|
||||
java
|
||||
|
@ -20,7 +19,7 @@ dependencies {
|
|||
implementation(project(":api"))
|
||||
implementation(project(":core"))
|
||||
implementation(project(":db"))
|
||||
implementation(project(":web"))
|
||||
implementation(project(":frontend"))
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.bundles.ktor.server)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
|
|
|
@ -5,7 +5,6 @@ import com.wbrawner.twigs.*
|
|||
import com.wbrawner.twigs.db.*
|
||||
import com.wbrawner.twigs.model.Session
|
||||
import com.wbrawner.twigs.storage.*
|
||||
import com.wbrawner.twigs.web.webRoutes
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
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.http.content.*
|
||||
|
@ -9,12 +9,13 @@ import io.ktor.server.routing.*
|
|||
fun Application.webRoutes() {
|
||||
routing {
|
||||
static {
|
||||
resources("twigs")
|
||||
default("index.html")
|
||||
staticBasePackage = "static"
|
||||
defaultResource("index.html")
|
||||
resources(".")
|
||||
}
|
||||
intercept(ApplicationCallPipeline.Setup) {
|
||||
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)
|
||||
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"
|
||||
logback = "1.2.11"
|
||||
mail = "1.6.2"
|
||||
node-gradle = "3.5.0"
|
||||
postgres = "42.3.4"
|
||||
shadow = "7.0.0"
|
||||
|
||||
|
@ -44,4 +45,5 @@ ktor-server = [
|
|||
[plugins]
|
||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", 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" }
|
|
@ -1,3 +1,2 @@
|
|||
rootProject.name = "twigs"
|
||||
include("core", "api", "app", "storage", "db", "web")
|
||||
include("testhelpers")
|
||||
include("api", "app", "core", "db", "frontend", "storage", "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")
|
||||
//}
|