Split code into frontend/backend maybe?
25
.devcontainer/Dockerfile
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/java/.devcontainer/base.Dockerfile
|
||||||
|
|
||||||
|
# [Choice] Java version (use -bullseye variants on local arm64/Apple Silicon): 11, 17, 11-bullseye, 17-bullseye, 11-buster, 17-buster
|
||||||
|
ARG VARIANT="17-bullseye"
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT}
|
||||||
|
|
||||||
|
# [Option] Install Maven
|
||||||
|
ARG INSTALL_MAVEN="false"
|
||||||
|
ARG MAVEN_VERSION=""
|
||||||
|
# [Option] Install Gradle
|
||||||
|
ARG INSTALL_GRADLE="false"
|
||||||
|
ARG GRADLE_VERSION=""
|
||||||
|
RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \
|
||||||
|
&& if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi
|
||||||
|
|
||||||
|
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
|
||||||
|
ARG NODE_VERSION="none"
|
||||||
|
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
|
||||||
|
|
||||||
|
# [Optional] Uncomment this section to install additional OS packages.
|
||||||
|
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
|
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||||
|
|
||||||
|
# [Optional] Uncomment this line to install global node packages.
|
||||||
|
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
|
32
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||||
|
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/java
|
||||||
|
{
|
||||||
|
"name": "Recipes",
|
||||||
|
"dockerComposeFile": "docker-compose.yml",
|
||||||
|
"service": "app",
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
"customizations": {
|
||||||
|
// Configure properties specific to VS Code.
|
||||||
|
"vscode": {
|
||||||
|
// Set *default* container specific settings.json values on container create.
|
||||||
|
"settings": {
|
||||||
|
},
|
||||||
|
|
||||||
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
|
"extensions": [
|
||||||
|
"vscjava.vscode-java-pack",
|
||||||
|
"PWABuilder.pwa-studio"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
"forwardPorts": [8080],
|
||||||
|
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "java -version",
|
||||||
|
|
||||||
|
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||||
|
"remoteUser": "vscode",
|
||||||
|
"workspaceFolder": "/workspace"
|
||||||
|
}
|
24
.devcontainer/docker-compose.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
VARIANT: 17-bullseye
|
||||||
|
NODE_VERSION: "lts/*"
|
||||||
|
environment:
|
||||||
|
RECIPES_DB_HOST: 'db'
|
||||||
|
volumes:
|
||||||
|
- ..:/workspace:cached
|
||||||
|
# Overrides default command so things don't shut down after the process ends.
|
||||||
|
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||||
|
network_mode: service:db
|
||||||
|
user: vscode
|
||||||
|
db:
|
||||||
|
image: mongo
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: recipes
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: recipes
|
1
backend/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
src/main/resources/static
|
36
backend/build.gradle
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
plugins {
|
||||||
|
id 'org.springframework.boot' version '2.6.7'
|
||||||
|
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'com.wbrawner'
|
||||||
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||||
|
implementation 'org.springframework.session:spring-session-core'
|
||||||
|
compileOnly 'org.springframework.boot:spring-boot-devtools'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo'
|
||||||
|
testImplementation 'io.projectreactor:reactor-test'
|
||||||
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
task copyPublicResources(type: Copy) {
|
||||||
|
from '../frontend/dist'
|
||||||
|
into 'src/main/resources/static'
|
||||||
|
}
|
||||||
|
copyPublicResources.dependsOn(':frontend:build')
|
||||||
|
processResources.dependsOn(copyPublicResources)
|
|
@ -1,4 +1,4 @@
|
||||||
spring.data.mongodb.host=localhost
|
spring.data.mongodb.host=${RECIPES_DB_HOST:localhost}
|
||||||
spring.data.mongodb.database=recipes
|
spring.data.mongodb.database=recipes
|
||||||
spring.data.mongodb.password=recipes
|
spring.data.mongodb.password=recipes
|
||||||
spring.data.mongodb.username=recipes
|
spring.data.mongodb.username=recipes
|
32
build.gradle
|
@ -1,29 +1,9 @@
|
||||||
plugins {
|
buildscript {
|
||||||
id 'org.springframework.boot' version '2.6.7'
|
|
||||||
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
|
||||||
id 'java'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'com.wbrawner'
|
allprojects {
|
||||||
version = '0.0.1-SNAPSHOT'
|
repositories {
|
||||||
sourceCompatibility = '17'
|
mavenCentral()
|
||||||
|
}
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
|
||||||
implementation 'org.springframework.session:spring-session-core'
|
|
||||||
compileOnly 'org.springframework.boot:spring-boot-devtools'
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
|
||||||
testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo'
|
|
||||||
testImplementation 'io.projectreactor:reactor-test'
|
|
||||||
testImplementation 'org.springframework.security:spring-security-test'
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.named('test') {
|
|
||||||
useJUnitPlatform()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
db:
|
|
||||||
image: mongo
|
|
||||||
ports:
|
|
||||||
- "27017:27017"
|
|
||||||
environment:
|
|
||||||
MONGO_INITDB_ROOT_USERNAME: recipes
|
|
||||||
MONGO_INITDB_ROOT_PASSWORD: recipes
|
|
8
frontend/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dev-dist
|
||||||
|
build
|
||||||
|
types
|
||||||
|
.idea
|
||||||
|
.github
|
13
frontend/LICENSE.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
ManifoldJS
|
||||||
|
|
||||||
|
Copyright (c) Microsoft Corporation
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
37
frontend/README.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# pwa-starter
|
||||||
|
|
||||||
|
Please use our [main repository for any issues/bugs/features suggestion](https://github.com/pwa-builder/PWABuilder/issues/new/choose).
|
||||||
|
|
||||||
|
[Documentation](https://docs.pwabuilder.com/#/starter/quick-start)
|
||||||
|
|
||||||
|
The PWABuilder pwa-starter is our opinionated, best practices, production tested starter that we use to build all of our PWAs, including [PWABuilder itself](https://blog.pwabuilder.com/posts/introducing-the-brand-new-pwa-builder/)! The pwa-starter is a starter codebase, just like create-react-app or the Angular CLI can generate, that uses the PWABuilder team's preferred front-end tech stack.
|
||||||
|
|
||||||
|
[![Get started with the pwa-starter!](https://img.youtube.com/vi/u3pWKpmic_k/0.jpg)](https://www.youtube.com/watch?v=u3pWKpmic_k)
|
||||||
|
|
||||||
|
With it you get an app that:
|
||||||
|
- Has no build system to set up and no boilerplate code to add. Everything is included out of the box.
|
||||||
|
- Has a Service Worker system using [Workbox](https://developers.google.com/web/tools/workbox/)
|
||||||
|
- Scores close to 100 on Lighthouse out of the box
|
||||||
|
- Has everything needed to be installable in the browser
|
||||||
|
- Is ready to be package for the app stores using [PWABuilder](https://www.pwabuilder.com)
|
||||||
|
- Uses the [Azure Static Web Apps CLI](https://azure.github.io/static-web-apps-cli) which enables emulating your production environment locally, and gets you ready for deploying to Azure Static Web Apps!
|
||||||
|
|
||||||
|
and all with just a few button clicks 😊.
|
||||||
|
|
||||||
|
[Get Started!](https://docs.pwabuilder.com/#/starter/quick-start)
|
||||||
|
|
||||||
|
## Sample PWAs built with the starter!
|
||||||
|
|
||||||
|
- SimpleEdit: Simple Image editing and collage making app!
|
||||||
|
- Github: https://github.com/jgw96/simple-edit-2
|
||||||
|
- Web: https://gray-pond-01ccec410.azurestaticapps.net/
|
||||||
|
- Microsoft Store: https://www.microsoft.com/store/productId/9P53Q9BF3MV6
|
||||||
|
- Google Play: https://play.google.com/store/apps/details?id=net.azurestaticapps.thankful_tree_07da4921e.twa&hl=en&gl=US
|
||||||
|
|
||||||
|
- Mail GO: Full featured email client. This app aims to show the power of the web by integrating many of the advanced APIs now avilable to PWAs, such as [receiving content shared from another app](https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/share#receiving-shared-content), [a custom titlebar](https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/window-controls-overlay), [sycing data in the background](https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/background-syncs) and more!
|
||||||
|
- Github: https://github.com/jgw96/graph-app
|
||||||
|
- Web: https://www.memosapp.app
|
||||||
|
- Microsoft Store: https://www.microsoft.com/store/productId/9NQW566N4866
|
||||||
|
|
||||||
|
## More Resources
|
||||||
|
- [The pwa-starter docs](https://docs.pwabuilder.com/#/starter/quick-start)
|
21
frontend/build.gradle
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
plugins {
|
||||||
|
id "com.github.node-gradle.node" version "3.5.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
task build(type: NpmTask) {
|
||||||
|
args = ['run', 'build']
|
||||||
|
inputs.files(fileTree('node_modules'))
|
||||||
|
inputs.files(fileTree('public'))
|
||||||
|
inputs.files(fileTree('src'))
|
||||||
|
inputs.file('package.json')
|
||||||
|
inputs.file('vite.config.ts')
|
||||||
|
outputs.dir('dist')
|
||||||
|
dependsOn npm_install
|
||||||
|
args = ['run', 'build']
|
||||||
|
}
|
||||||
|
|
||||||
|
// build.dependsOn(npmBuild)
|
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>
|
7274
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/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 |
61
frontend/public/manifest.json
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"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/icon_512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/icons/icon_192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/icons/icon_48.png",
|
||||||
|
"sizes": "48x48",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/icons/icon_24.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/icon_192.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 { LitElement, css, html } 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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
79
frontend/src/components/header.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { LitElement, css, html } from 'lit';
|
||||||
|
import { property, customElement } 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 { LitElement, html } 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 { LitElement, css, html } from 'lit';
|
||||||
|
import { property, customElement } 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>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
32
frontend/src/styles/global.css
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
frontend/tsconfig.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"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"]
|
||||||
|
}
|
27
frontend/vite.config.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
base: "/",
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
assetsDir: "code",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
VitePWA({
|
||||||
|
strategies: "injectManifest",
|
||||||
|
injectManifest: {
|
||||||
|
swSrc: 'public/sw.js',
|
||||||
|
swDest: 'dist/sw.js',
|
||||||
|
globDirectory: 'dist',
|
||||||
|
globPatterns: [
|
||||||
|
'**/*.{html,js,css,json, png}',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devOptions: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
0
gradlew
vendored
Normal file → Executable file
|
@ -1 +1,3 @@
|
||||||
rootProject.name = 'recipes'
|
rootProject.name = 'recipes'
|
||||||
|
include 'backend'
|
||||||
|
include 'frontend'
|
||||||
|
|