Add journal frontend
4
.gitignore
vendored
@ -1,4 +1,6 @@
|
||||
api/api_server/
|
||||
api/api_client/
|
||||
.vscode
|
||||
__debug_bin
|
||||
__debug_bin
|
||||
|
||||
frontend/node_modules/
|
||||
|
||||
18
api/generate_ts_client.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
shopt -s extglob
|
||||
|
||||
docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate \
|
||||
-i /local/dash_api.yaml \
|
||||
--git-host=github.com \
|
||||
--git-user-id=moustachioed \
|
||||
--git-repo-id=dash \
|
||||
--api-package dashclient \
|
||||
--artifact-id dashclient \
|
||||
--package-name dashclient \
|
||||
-g typescript-fetch \
|
||||
-o /local/api_client
|
||||
# -p packageName=dashapi,featureCORS=true,outputAsLibrary=true,sourceFolder=dashapi \
|
||||
# --invoker-package dashapi \
|
||||
|
||||
# rm -rf ../frontend/src/dashclient
|
||||
# cp -r ./api_client/ ../frontend/src/dashclient
|
||||
@ -1,104 +0,0 @@
|
||||
openapi: '3.0.2'
|
||||
info:
|
||||
title: Dash API
|
||||
version: '0.1'
|
||||
servers:
|
||||
- url: http://localhost:18010/api/v1
|
||||
- url: http://localhost:8080/api/v1
|
||||
- url: https://dash.pander.me/api/v1
|
||||
paths:
|
||||
/journal/entry/{date}:
|
||||
get:
|
||||
operationId: getJournalEntryForDate
|
||||
responses:
|
||||
'200':
|
||||
description: A journal entry for a specific day.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JournalEntry'
|
||||
parameters:
|
||||
- name: date
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
example: 2022-02-18
|
||||
post:
|
||||
operationId: writeJournalEntryForDate
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JournalEntry'
|
||||
responses:
|
||||
'200':
|
||||
description: Journal entry successfully written.
|
||||
parameters:
|
||||
- name: date
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
example: 2022-02-18
|
||||
delete:
|
||||
operationId: deleteJournalEntryForDate
|
||||
responses:
|
||||
'200':
|
||||
description: Journal entry successfully deleted.
|
||||
parameters:
|
||||
- name: date
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
example: 2022-02-18
|
||||
components:
|
||||
schemas:
|
||||
JournalEntry:
|
||||
type: object
|
||||
required:
|
||||
- date
|
||||
properties:
|
||||
date:
|
||||
type: string
|
||||
format: date
|
||||
example: 2022-02-18
|
||||
thankful:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
- "thankful 1"
|
||||
- "thankful 2"
|
||||
- "thankful 3"
|
||||
looking_forward:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
- "looking forward 1"
|
||||
- "looking forward 2"
|
||||
- "looking forward 3"
|
||||
been_great:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
- "been great 1"
|
||||
- "been great 2"
|
||||
- "been great 3"
|
||||
do_better:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example:
|
||||
- "do better 1"
|
||||
- "do better 2"
|
||||
- "do better 3"
|
||||
journal:
|
||||
type: string
|
||||
example: "journal entry 1"
|
||||
@ -41,5 +41,6 @@ func main() {
|
||||
router.Methods("GET", "POST", "DELETE", "OPTIONS")
|
||||
|
||||
log.Printf("Starting server.")
|
||||
log.Fatal(http.ListenAndServe(":8080", cors(router)))
|
||||
// TODO remove listening on all interfaces
|
||||
log.Fatal(http.ListenAndServe("0.0.0.0:8080", cors(router)))
|
||||
}
|
||||
|
||||
48
frontend/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# Svelte + TS + Vite
|
||||
|
||||
This template should help get you started developing with Svelte and TypeScript in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
||||
|
||||
## Need an official Svelte framework?
|
||||
|
||||
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
||||
|
||||
## Technical considerations
|
||||
|
||||
**Why use this over SvelteKit?**
|
||||
|
||||
- It brings its own routing solution which might not be preferable for some users.
|
||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
||||
`vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
|
||||
|
||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
||||
|
||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
||||
|
||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
||||
|
||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
||||
|
||||
**Why include `.vscode/extensions.json`?**
|
||||
|
||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
||||
|
||||
**Why enable `allowJs` in the TS template?**
|
||||
|
||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
||||
|
||||
**Why is HMR not preserving my local component state?**
|
||||
|
||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
||||
|
||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
||||
|
||||
```ts
|
||||
// store.ts
|
||||
// An extremely simple external store
|
||||
import { writable } from 'svelte/store'
|
||||
export default writable(0)
|
||||
```
|
||||
13
frontend/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Svelte + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
2351
frontend/package-lock.json
generated
Normal file
22
frontend/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "dash_app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"@tsconfig/svelte": "^3.0.0",
|
||||
"svelte": "^3.49.0",
|
||||
"svelte-check": "^2.8.0",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.0.7"
|
||||
}
|
||||
}
|
||||
1
frontend/public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
55
frontend/src/App.svelte
Normal file
@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import Main from './components/Main.svelte'
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<Main/>
|
||||
</main>
|
||||
|
||||
<!--
|
||||
<script>
|
||||
import svelteLogo from './assets/svelte.svg'
|
||||
import Counter from './lib/Counter.svelte'
|
||||
</script>
|
||||
|
||||
<main>
|
||||
<div>
|
||||
<a href="https://vitejs.dev" target="_blank">
|
||||
<img src="/vite.svg" class="logo" alt="Vite Logo" />
|
||||
</a>
|
||||
<a href="https://svelte.dev" target="_blank">
|
||||
<img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>Vite + Svelte</h1>
|
||||
|
||||
<div class="card">
|
||||
<Counter />
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out <a href="https://github.com/sveltejs/kit#readme" target="_blank">SvelteKit</a>, the official Svelte app framework powered by Vite!
|
||||
</p>
|
||||
|
||||
<p class="read-the-docs">
|
||||
Click on the Vite and Svelte logos to learn more
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.svelte:hover {
|
||||
filter: drop-shadow(0 0 2em #ff3e00aa);
|
||||
}
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
-->
|
||||
81
frontend/src/app.css
Normal file
@ -0,0 +1,81 @@
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.6em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
1
frontend/src/assets/book-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M448 360V24c0-13.3-10.7-24-24-24H96C43 0 0 43 0 96v320c0 53 43 96 96 96h328c13.3 0 24-10.7 24-24v-16c0-7.5-3.5-14.3-8.9-18.7-4.2-15.4-4.2-59.3 0-74.7 5.4-4.3 8.9-11.1 8.9-18.6zM128 134c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm0 64c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm253.4 250H96c-17.7 0-32-14.3-32-32 0-17.6 14.4-32 32-32h285.4c-1.9 17.1-1.9 46.9 0 64z"/></svg>
|
||||
|
After Width: | Height: | Size: 655 B |
1
frontend/src/assets/calendar-check-regular.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M400 64h-48V12c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v52H160V12c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v52H48C21.49 64 0 85.49 0 112v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 400H54a6 6 0 0 1-6-6V160h352v298a6 6 0 0 1-6 6zm-52.849-200.65L198.842 404.519c-4.705 4.667-12.303 4.637-16.971-.068l-75.091-75.699c-4.667-4.705-4.637-12.303.068-16.971l22.719-22.536c4.705-4.667 12.303-4.637 16.97.069l44.104 44.461 111.072-110.181c4.705-4.667 12.303-4.637 16.971.068l22.536 22.718c4.667 4.705 4.636 12.303-.069 16.97z"/></svg>
|
||||
|
After Width: | Height: | Size: 792 B |
1
frontend/src/assets/calendar-check-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M436 160H12c-6.627 0-12-5.373-12-12v-36c0-26.51 21.49-48 48-48h48V12c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v52h128V12c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v52h48c26.51 0 48 21.49 48 48v36c0 6.627-5.373 12-12 12zM12 192h424c6.627 0 12 5.373 12 12v260c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48V204c0-6.627 5.373-12 12-12zm333.296 95.947l-28.169-28.398c-4.667-4.705-12.265-4.736-16.97-.068L194.12 364.665l-45.98-46.352c-4.667-4.705-12.266-4.736-16.971-.068l-28.397 28.17c-4.705 4.667-4.736 12.265-.068 16.97l82.601 83.269c4.667 4.705 12.265 4.736 16.97.068l142.953-141.805c4.705-4.667 4.736-12.265.068-16.97z"/></svg>
|
||||
|
After Width: | Height: | Size: 851 B |
1
frontend/src/assets/chart-line-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M496 384H64V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-32c0-8.84-7.16-16-16-16zM464 96H345.94c-21.38 0-32.09 25.85-16.97 40.97l32.4 32.4L288 242.75l-73.37-73.37c-12.5-12.5-32.76-12.5-45.25 0l-68.69 68.69c-6.25 6.25-6.25 16.38 0 22.63l22.62 22.62c6.25 6.25 16.38 6.25 22.63 0L192 237.25l73.37 73.37c12.5 12.5 32.76 12.5 45.25 0l96-96 32.4 32.4c15.12 15.12 40.97 4.41 40.97-16.97V112c.01-8.84-7.15-16-15.99-16z"/></svg>
|
||||
|
After Width: | Height: | Size: 683 B |
1
frontend/src/assets/chevron-left-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z"/></svg>
|
||||
|
After Width: | Height: | Size: 455 B |
1
frontend/src/assets/chevron-right-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"/></svg>
|
||||
|
After Width: | Height: | Size: 498 B |
1
frontend/src/assets/file-alt-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm64 236c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-64c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12v8zm0-72v8c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-8c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm96-114.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 694 B |
1
frontend/src/assets/moon-regular.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M421.6 379.9c-.6641 0-1.35 .0625-2.049 .1953c-11.24 2.143-22.37 3.17-33.32 3.17c-94.81 0-174.1-77.14-174.1-175.5c0-63.19 33.79-121.3 88.73-152.6c8.467-4.812 6.339-17.66-3.279-19.44c-11.2-2.078-29.53-3.746-40.9-3.746C132.3 31.1 32 132.2 32 256c0 123.6 100.1 224 223.8 224c69.04 0 132.1-31.45 173.8-82.93C435.3 389.1 429.1 379.9 421.6 379.9zM255.8 432C158.9 432 80 353 80 256c0-76.32 48.77-141.4 116.7-165.8C175.2 125 163.2 165.6 163.2 207.8c0 99.44 65.13 183.9 154.9 212.8C298.5 428.1 277.4 432 255.8 432z"/></svg>
|
||||
|
After Width: | Height: | Size: 752 B |
1
frontend/src/assets/moon-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 518 B |
1
frontend/src/assets/pen-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"/></svg>
|
||||
|
After Width: | Height: | Size: 461 B |
1
frontend/src/assets/sun-regular.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M505.2 324.8l-47.73-68.78l47.75-68.81c7.359-10.62 8.797-24.12 3.844-36.06c-4.969-11.94-15.52-20.44-28.22-22.72l-82.39-14.88l-14.89-82.41c-2.281-12.72-10.76-23.25-22.69-28.22c-11.97-4.936-25.42-3.498-36.12 3.844L256 54.49L187.2 6.709C176.5-.6016 163.1-2.039 151.1 2.896c-11.92 4.971-20.4 15.5-22.7 28.19l-14.89 82.44L31.15 128.4C18.42 130.7 7.854 139.2 2.9 151.2C-2.051 163.1-.5996 176.6 6.775 187.2l47.73 68.78l-47.75 68.81c-7.359 10.62-8.795 24.12-3.844 36.06c4.969 11.94 15.52 20.44 28.22 22.72l82.39 14.88l14.89 82.41c2.297 12.72 10.78 23.25 22.7 28.22c11.95 4.906 25.44 3.531 36.09-3.844L256 457.5l68.83 47.78C331.3 509.7 338.8 512 346.3 512c4.906 0 9.859-.9687 14.56-2.906c11.92-4.969 20.4-15.5 22.7-28.19l14.89-82.44l82.37-14.88c12.73-2.281 23.3-10.78 28.25-22.75C514.1 348.9 512.6 335.4 505.2 324.8zM456.8 339.2l-99.61 18l-18 99.63L256 399.1L172.8 456.8l-18-99.63l-99.61-18L112.9 255.1L55.23 172.8l99.61-18l18-99.63L256 112.9l83.15-57.75l18.02 99.66l99.61 18L399.1 255.1L456.8 339.2zM256 143.1c-61.85 0-111.1 50.14-111.1 111.1c0 61.85 50.15 111.1 111.1 111.1s111.1-50.14 111.1-111.1C367.1 194.1 317.8 143.1 256 143.1zM256 319.1c-35.28 0-63.99-28.71-63.99-63.99S220.7 192 256 192s63.99 28.71 63.99 63.1S291.3 319.1 256 319.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
frontend/src/assets/sun-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM352 256c0 53-43 96-96 96s-96-43-96-96s43-96 96-96s96 43 96 96zm32 0c0-70.7-57.3-128-128-128s-128 57.3-128 128s57.3 128 128 128s128-57.3 128-128z"/></svg>
|
||||
|
After Width: | Height: | Size: 910 B |
33
frontend/src/components/Body.svelte
Normal file
@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { curTab } from '../stores/appStore';
|
||||
import Journal from './journal/Journal.svelte';
|
||||
import Plan from './Plan.svelte';
|
||||
</script>
|
||||
|
||||
<div class="main">
|
||||
<!--
|
||||
<svelte:component this={$curTab}/> -->
|
||||
{#if $curTab==="Journal"}
|
||||
<Journal/>
|
||||
{:else if $curTab=="Plan"}
|
||||
<Plan/>
|
||||
<!-- {:else if $curTab=="Tracking"} -->
|
||||
<!-- <Tracking/> -->
|
||||
<!-- {:else if $curTab=="Inbox"} -->
|
||||
<!-- <Note/> -->
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.main {
|
||||
/* margin: 1rem; */
|
||||
}
|
||||
</style>
|
||||
<!--
|
||||
<script lang="ts">
|
||||
import { curTab } from '../stores/appStore';
|
||||
import Tracking from './tracking/Tracking.svelte';
|
||||
import Note from './note/Note.svelte';
|
||||
</script>
|
||||
|
||||
-->
|
||||
10
frontend/src/components/Counter.svelte
Normal file
@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
let count: number = 0
|
||||
const increment = () => {
|
||||
count += 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<button on:click={increment}>
|
||||
count is {count}
|
||||
</button>
|
||||
33
frontend/src/components/DatePicker.svelte
Normal file
@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import leftImg from '../assets/chevron-left-solid.svg'
|
||||
import rightImg from '../assets/chevron-right-solid.svg'
|
||||
|
||||
import { currentDate, dateUpdate } from '../stores/appStore';
|
||||
import { getClientDateRep } from '../modules/dateHelpers';
|
||||
import InvertableButton from './inputs/InvertableButton.svelte';
|
||||
|
||||
async function setDate(day: number) {
|
||||
var newDate = new Date($currentDate);
|
||||
newDate.setDate(newDate.getDate() + day);
|
||||
currentDate.set(newDate);
|
||||
$dateUpdate();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="DatePicker">
|
||||
<InvertableButton label="left" icon={leftImg} clickHandler={() => {setDate(-1);}}/>
|
||||
{getClientDateRep($currentDate)}
|
||||
<InvertableButton label="right" icon={rightImg} clickHandler={() => {setDate(1);}}/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.DatePicker {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* for horizontal aligning of child divs */
|
||||
justify-content: center;
|
||||
/* for vertical aligning */
|
||||
align-items: center;
|
||||
/* width: 100%; */
|
||||
}
|
||||
</style>
|
||||
15
frontend/src/components/Main.svelte
Normal file
@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import TabBar from './tabs/TabBar.svelte';
|
||||
import Body from './Body.svelte';
|
||||
</script>
|
||||
|
||||
<div class="Main">
|
||||
<TabBar/>
|
||||
<Body/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.Main {
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
3
frontend/src/components/Plan.svelte
Normal file
@ -0,0 +1,3 @@
|
||||
<div style="width:100%;">
|
||||
<h1>Plan</h1>
|
||||
</div>
|
||||
25
frontend/src/components/inputs/Input.svelte
Normal file
@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import Text from './InputText.svelte';
|
||||
import Real from './InputReal.svelte';
|
||||
import Bool from './InputBool.svelte';
|
||||
import Select from './InputSelect.svelte';
|
||||
|
||||
export let type : string = '';
|
||||
export let selectables : string = '';
|
||||
export let val : any = null;
|
||||
export let onInput : () => void;
|
||||
export let onFocus : () => void;
|
||||
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#if type === "text"}
|
||||
<Text bind:val onInput={onInput} onFocus={onFocus} />
|
||||
{:else if type === "real"}
|
||||
<Real bind:val onInput={onInput}/>
|
||||
{:else if type === "bool"}
|
||||
<Bool bind:val onInput={onInput}/>
|
||||
{:else if type === "select"}
|
||||
<Select bind:val bind:selectables onInput={onInput}/>
|
||||
{/if}
|
||||
</div>
|
||||
8
frontend/src/components/inputs/InputBool.svelte
Normal file
@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
|
||||
export let val : boolean = false;
|
||||
export let onInput : () => void;
|
||||
|
||||
</script>
|
||||
|
||||
<input type="checkbox" on:input={onInput} bind:checked={val}/>
|
||||
21
frontend/src/components/inputs/InputReal.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
export let val : any;
|
||||
export let onInput : () => void;
|
||||
</script>
|
||||
|
||||
<input type="number" bind:value={val} on:keyup={onInput}/>
|
||||
|
||||
<style>
|
||||
input[type=number] {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-bottom: solid 1px grey;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
21
frontend/src/components/inputs/InputSelect.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
export let selectables : string = '';
|
||||
export let val : string = null;
|
||||
export let onInput : () => void;
|
||||
|
||||
$: {
|
||||
if (val === null) {
|
||||
val = "";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-onchange -->
|
||||
<select bind:value={val} on:change={onInput}>
|
||||
<option value=""></option>
|
||||
{#each selectables.split(';') as opt}
|
||||
<option value={opt}>
|
||||
{opt}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
16
frontend/src/components/inputs/InputText.svelte
Normal file
@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
export let val : any;
|
||||
export let onInput : () => void;
|
||||
</script>
|
||||
|
||||
<input type="text" bind:value={val} on:input={onInput} size=10/>
|
||||
|
||||
<style>
|
||||
input[type=text] {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-bottom: solid 1px grey;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
32
frontend/src/components/inputs/InvertableButton.svelte
Normal file
@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import InvertableIcon from "./InvertableIcon.svelte";
|
||||
|
||||
|
||||
export let label: string;
|
||||
export let icon: string;
|
||||
export let clickHandler: () => void;
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<button class="InvertableButton" on:click={clickHandler}>
|
||||
<InvertableIcon bind:label bind:icon/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.InvertableButton {
|
||||
border: None;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
padding: 0 0;
|
||||
}
|
||||
|
||||
.InvertableButton:active {
|
||||
filter:invert(50%);
|
||||
/* background-color: #3e8e41;
|
||||
box-shadow: 0 5px #666;
|
||||
transform: translateY(4px); */
|
||||
}
|
||||
</style>
|
||||
14
frontend/src/components/inputs/InvertableIcon.svelte
Normal file
@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
export let label: string;
|
||||
export let icon: string;
|
||||
</script>
|
||||
|
||||
<img class="InvertableIcon" alt={label} src={icon} height=100% width=100%/>
|
||||
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.InvertableIcon {
|
||||
filter:invert(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
frontend/src/components/inputs/MultiItemTextInput.svelte
Normal file
@ -0,0 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Text from './InputText.svelte';
|
||||
import * as TxtArr from '../../modules/arrayHelpers';
|
||||
|
||||
export let textArray : Array<string> = [];
|
||||
export let onInput : () => void;
|
||||
|
||||
|
||||
function update() {
|
||||
textArray = TxtArr.extendTextArrayWithEmptyString(
|
||||
TxtArr.cleanTextArrayFromEmptyString(textArray));
|
||||
}
|
||||
|
||||
function updateInput() {
|
||||
textArray = TxtArr.cleanTextArrayFromEmptyString(textArray);
|
||||
onInput();
|
||||
textArray = TxtArr.extendTextArrayWithEmptyString(textArray);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
update();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
{#each textArray as inTxt}
|
||||
<Text bind:val={inTxt} onInput={updateInput}/>
|
||||
{/each}
|
||||
</div>
|
||||
18
frontend/src/components/inputs/PlanTextArea.svelte
Normal file
@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
export let value : any;
|
||||
export let onInput : () => void;
|
||||
</script>
|
||||
|
||||
<!-- <div class="shadowed"> -->
|
||||
<div>
|
||||
<textarea bind:value on:keyup={onInput} cols=15 rows=3/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
textarea {
|
||||
margin: 10px;
|
||||
border: none;
|
||||
box-shadow: 1px 1px 4px grey;
|
||||
resize: none;
|
||||
}
|
||||
</style>
|
||||
141
frontend/src/components/journal/Journal.svelte
Normal file
@ -0,0 +1,141 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import sunImg from '../../assets/sun-solid.svg';
|
||||
import moonImg from '../../assets/moon-solid.svg';
|
||||
import { dashApi } from '../../stores/apiStore'
|
||||
import type { JournalEntry } from '../../dashclient'
|
||||
import { dateUpdate, currentDate } from '../../stores/appStore'
|
||||
import * as TxtArr from '../../modules/arrayHelpers';
|
||||
import MultiItemTextInput from '../inputs/MultiItemTextInput.svelte';
|
||||
import InvertableIcon from '../inputs/InvertableIcon.svelte';
|
||||
|
||||
let updateTimeout: NodeJS.Timeout;
|
||||
|
||||
let journalEntry : JournalEntry = {
|
||||
"date": $currentDate,
|
||||
"thankful": [],
|
||||
"lookingForward": [],
|
||||
"beenGreat": [],
|
||||
"doBetter": [],
|
||||
"journal": ""
|
||||
};
|
||||
|
||||
function initJournalEntry(je: JournalEntry) {
|
||||
journalEntry.date = je.date;
|
||||
journalEntry.thankful = TxtArr.initTextArray(je.thankful);
|
||||
journalEntry.lookingForward = TxtArr.initTextArray(je.lookingForward);
|
||||
journalEntry.beenGreat = TxtArr.initTextArray(je.beenGreat);
|
||||
journalEntry.doBetter = TxtArr.initTextArray(je.doBetter);
|
||||
if (je.journal) {
|
||||
journalEntry.journal = je.journal.concat("");
|
||||
} else {
|
||||
journalEntry.journal = "";
|
||||
}
|
||||
}
|
||||
|
||||
function prepareJournalEntryForWrite(entry: JournalEntry) {
|
||||
let je = {...entry};
|
||||
|
||||
je.thankful = TxtArr.prepareTextArrayForWrite(je.thankful);
|
||||
je.lookingForward = TxtArr.prepareTextArrayForWrite(je.lookingForward);
|
||||
je.beenGreat = TxtArr.prepareTextArrayForWrite(je.beenGreat);
|
||||
je.doBetter = TxtArr.prepareTextArrayForWrite(je.doBetter);
|
||||
if (je.journal && je.journal === "") {
|
||||
je.journal = undefined;
|
||||
}
|
||||
|
||||
return je;
|
||||
}
|
||||
|
||||
async function fetchJournal() {
|
||||
dashApi.getJournalEntryForDate({'date': $currentDate}).then(resp => (initJournalEntry(resp)));
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
$dateUpdate = fetchJournal;
|
||||
fetchJournal();
|
||||
});
|
||||
|
||||
function writeJournalEntry() {
|
||||
clearTimeout(updateTimeout);
|
||||
|
||||
updateTimeout = setTimeout(function() {
|
||||
dashApi.writeJournalEntry({journalEntry: prepareJournalEntryForWrite(journalEntry)});
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="journalTime">
|
||||
<div class="TimeIcon">
|
||||
<InvertableIcon label="morgens" icon={sunImg}/>
|
||||
</div>
|
||||
<div class="JournalSubcategory">
|
||||
<h2>Dankbar</h2>
|
||||
<MultiItemTextInput bind:textArray={journalEntry.thankful} onInput={writeJournalEntry}/>
|
||||
</div>
|
||||
|
||||
<div class="JournalSubcategory">
|
||||
<h2>Vorfreude</h2>
|
||||
<MultiItemTextInput bind:textArray={journalEntry.lookingForward} onInput={writeJournalEntry}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="journalTime">
|
||||
<div class="TimeIcon">
|
||||
<InvertableIcon label="abends" icon={moonImg}/>
|
||||
</div>
|
||||
|
||||
<div class="JournalSubcategory">
|
||||
<h2>Freude</h2>
|
||||
<MultiItemTextInput bind:textArray={journalEntry.beenGreat} onInput={writeJournalEntry}/>
|
||||
</div>
|
||||
|
||||
<div class="JournalSubcategory">
|
||||
<h2>Entwicklung</h2>
|
||||
<MultiItemTextInput bind:textArray={journalEntry.doBetter} onInput={writeJournalEntry}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="JournalText">
|
||||
<h1>Tagebuch</h1>
|
||||
<textarea bind:value={journalEntry.journal} on:input={writeJournalEntry}/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.journalTime {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.JournalSubcategory {
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 550px) {
|
||||
.journalTime {
|
||||
width: 550px;
|
||||
}
|
||||
}
|
||||
|
||||
.JournalText {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 6rem;
|
||||
border: none;
|
||||
box-shadow: 1px 1px 4px grey;
|
||||
resize: none;
|
||||
columns: 15;
|
||||
}
|
||||
|
||||
.TimeIcon {
|
||||
position: absolute;
|
||||
float: left;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
</style>
|
||||
36
frontend/src/components/tabs/TabBar.svelte
Normal file
@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import penImg from '../../assets/pen-solid.svg'
|
||||
import calendarImg from '../../assets/calendar-check-regular.svg'
|
||||
import chartImg from '../../assets/chart-line-solid.svg'
|
||||
import fileImg from '../../assets/file-alt-solid.svg'
|
||||
import { curTab } from '../../stores/appStore';
|
||||
|
||||
import InvertableButton from '../inputs/InvertableButton.svelte';
|
||||
import DatePicker from '../DatePicker.svelte';
|
||||
|
||||
function clickHandler(tab: string) {
|
||||
curTab.set(tab)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="TabBar">
|
||||
<DatePicker/>
|
||||
<InvertableButton label="Journal" icon={penImg} clickHandler={()=>{clickHandler("Journal")}}/>
|
||||
<InvertableButton label="Plan" icon={calendarImg} clickHandler={()=>{clickHandler("Plan")}}/>
|
||||
<InvertableButton label="Tracking" icon={chartImg} clickHandler={()=>{clickHandler("Tracking")}}/>
|
||||
<InvertableButton label="Inbox" icon={fileImg} clickHandler={()=>{clickHandler("Inbox")}}/>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<hr/>
|
||||
|
||||
<style>
|
||||
.TabBar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* for horizontal aligning of child divs */
|
||||
justify-content: center;
|
||||
/* for vertical aligning */
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
23
frontend/src/dashclient/.openapi-generator-ignore
Normal file
@ -0,0 +1,23 @@
|
||||
# OpenAPI Generator Ignore
|
||||
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||
|
||||
# Use this file to prevent files from being overwritten by the generator.
|
||||
# The patterns follow closely to .gitignore or .dockerignore.
|
||||
|
||||
# As an example, the C# client generator defines ApiClient.cs.
|
||||
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||
#ApiClient.cs
|
||||
|
||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||
#foo/*/qux
|
||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||
|
||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||
#foo/**/qux
|
||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||
|
||||
# You can also negate patterns with an exclamation (!).
|
||||
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||
#docs/*.md
|
||||
# Then explicitly reverse the ignore rule for a single file:
|
||||
#!docs/README.md
|
||||
6
frontend/src/dashclient/.openapi-generator/FILES
Normal file
@ -0,0 +1,6 @@
|
||||
apis/DefaultApi.ts
|
||||
apis/index.ts
|
||||
index.ts
|
||||
models/JournalEntry.ts
|
||||
models/index.ts
|
||||
runtime.ts
|
||||
1
frontend/src/dashclient/.openapi-generator/VERSION
Normal file
@ -0,0 +1 @@
|
||||
6.2.1-SNAPSHOT
|
||||
127
frontend/src/dashclient/apis/DefaultApi.ts
Normal file
@ -0,0 +1,127 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Dash API
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
import * as runtime from '../runtime';
|
||||
import type {
|
||||
JournalEntry,
|
||||
} from '../models';
|
||||
import {
|
||||
JournalEntryFromJSON,
|
||||
JournalEntryToJSON,
|
||||
} from '../models';
|
||||
|
||||
export interface DeleteJournalEntryForDateRequest {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export interface GetJournalEntryForDateRequest {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
export interface WriteJournalEntryRequest {
|
||||
journalEntry: JournalEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export class DefaultApi extends runtime.BaseAPI {
|
||||
|
||||
/**
|
||||
*/
|
||||
async deleteJournalEntryForDateRaw(requestParameters: DeleteJournalEntryForDateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
|
||||
if (requestParameters.date === null || requestParameters.date === undefined) {
|
||||
throw new runtime.RequiredError('date','Required parameter requestParameters.date was null or undefined when calling deleteJournalEntryForDate.');
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
const response = await this.request({
|
||||
path: `/journal/entry/{date}`.replace(`{${"date"}}`, encodeURIComponent(String(requestParameters.date.toISOString().substring(0,10)))),
|
||||
method: 'DELETE',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.VoidApiResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async deleteJournalEntryForDate(requestParameters: DeleteJournalEntryForDateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
|
||||
await this.deleteJournalEntryForDateRaw(requestParameters, initOverrides);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async getJournalEntryForDateRaw(requestParameters: GetJournalEntryForDateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<JournalEntry>> {
|
||||
if (requestParameters.date === null || requestParameters.date === undefined) {
|
||||
throw new runtime.RequiredError('date','Required parameter requestParameters.date was null or undefined when calling getJournalEntryForDate.');
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
const response = await this.request({
|
||||
path: `/journal/entry/{date}`.replace(`{${"date"}}`, encodeURIComponent(String(requestParameters.date.toISOString().substring(0,10)))),
|
||||
method: 'GET',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => JournalEntryFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async getJournalEntryForDate(requestParameters: GetJournalEntryForDateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<JournalEntry> {
|
||||
const response = await this.getJournalEntryForDateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async writeJournalEntryRaw(requestParameters: WriteJournalEntryRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
|
||||
if (requestParameters.journalEntry === null || requestParameters.journalEntry === undefined) {
|
||||
throw new runtime.RequiredError('journalEntry','Required parameter requestParameters.journalEntry was null or undefined when calling writeJournalEntry.');
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
const response = await this.request({
|
||||
path: `/journal/entry/`,
|
||||
method: 'POST',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: JournalEntryToJSON(requestParameters.journalEntry),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.VoidApiResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
async writeJournalEntry(requestParameters: WriteJournalEntryRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
|
||||
await this.writeJournalEntryRaw(requestParameters, initOverrides);
|
||||
}
|
||||
|
||||
}
|
||||
3
frontend/src/dashclient/apis/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export * from './DefaultApi';
|
||||
5
frontend/src/dashclient/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export * from './runtime';
|
||||
export * from './apis';
|
||||
export * from './models';
|
||||
106
frontend/src/dashclient/models/JournalEntry.ts
Normal file
@ -0,0 +1,106 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Dash API
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
import { exists, mapValues } from '../runtime';
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface JournalEntry
|
||||
*/
|
||||
export interface JournalEntry {
|
||||
/**
|
||||
*
|
||||
* @type {Date}
|
||||
* @memberof JournalEntry
|
||||
*/
|
||||
date: Date;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof JournalEntry
|
||||
*/
|
||||
thankful?: Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof JournalEntry
|
||||
*/
|
||||
lookingForward?: Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof JournalEntry
|
||||
*/
|
||||
beenGreat?: Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @memberof JournalEntry
|
||||
*/
|
||||
doBetter?: Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof JournalEntry
|
||||
*/
|
||||
journal?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given object implements the JournalEntry interface.
|
||||
*/
|
||||
export function instanceOfJournalEntry(value: object): boolean {
|
||||
let isInstance = true;
|
||||
isInstance = isInstance && "date" in value;
|
||||
|
||||
return isInstance;
|
||||
}
|
||||
|
||||
export function JournalEntryFromJSON(json: any): JournalEntry {
|
||||
return JournalEntryFromJSONTyped(json, false);
|
||||
}
|
||||
|
||||
export function JournalEntryFromJSONTyped(json: any, ignoreDiscriminator: boolean): JournalEntry {
|
||||
if ((json === undefined) || (json === null)) {
|
||||
return json;
|
||||
}
|
||||
return {
|
||||
|
||||
'date': (new Date(json['date'])),
|
||||
'thankful': !exists(json, 'thankful') ? undefined : json['thankful'],
|
||||
'lookingForward': !exists(json, 'looking_forward') ? undefined : json['looking_forward'],
|
||||
'beenGreat': !exists(json, 'been_great') ? undefined : json['been_great'],
|
||||
'doBetter': !exists(json, 'do_better') ? undefined : json['do_better'],
|
||||
'journal': !exists(json, 'journal') ? undefined : json['journal'],
|
||||
};
|
||||
}
|
||||
|
||||
export function JournalEntryToJSON(value?: JournalEntry | null): any {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (value === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
|
||||
'date': (value.date.toISOString().substr(0,10)),
|
||||
'thankful': value.thankful,
|
||||
'looking_forward': value.lookingForward,
|
||||
'been_great': value.beenGreat,
|
||||
'do_better': value.doBetter,
|
||||
'journal': value.journal,
|
||||
};
|
||||
}
|
||||
|
||||
3
frontend/src/dashclient/models/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export * from './JournalEntry';
|
||||
407
frontend/src/dashclient/runtime.ts
Normal file
@ -0,0 +1,407 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Dash API
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
export const BASE_PATH = "http://localhost:18010/api/v1".replace(/\/+$/, "");
|
||||
|
||||
export interface ConfigurationParameters {
|
||||
basePath?: string; // override base path
|
||||
fetchApi?: FetchAPI; // override for fetch implementation
|
||||
middleware?: Middleware[]; // middleware to apply before/after fetch requests
|
||||
queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings
|
||||
username?: string; // parameter for basic security
|
||||
password?: string; // parameter for basic security
|
||||
apiKey?: string | ((name: string) => string); // parameter for apiKey security
|
||||
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string | Promise<string>); // parameter for oauth2 security
|
||||
headers?: HTTPHeaders; //header params we want to use on every request
|
||||
credentials?: RequestCredentials; //value for the credentials param we want to use on each request
|
||||
}
|
||||
|
||||
export class Configuration {
|
||||
constructor(private configuration: ConfigurationParameters = {}) {}
|
||||
|
||||
set config(configuration: Configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
get basePath(): string {
|
||||
return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
|
||||
}
|
||||
|
||||
get fetchApi(): FetchAPI | undefined {
|
||||
return this.configuration.fetchApi;
|
||||
}
|
||||
|
||||
get middleware(): Middleware[] {
|
||||
return this.configuration.middleware || [];
|
||||
}
|
||||
|
||||
get queryParamsStringify(): (params: HTTPQuery) => string {
|
||||
return this.configuration.queryParamsStringify || querystring;
|
||||
}
|
||||
|
||||
get username(): string | undefined {
|
||||
return this.configuration.username;
|
||||
}
|
||||
|
||||
get password(): string | undefined {
|
||||
return this.configuration.password;
|
||||
}
|
||||
|
||||
get apiKey(): ((name: string) => string) | undefined {
|
||||
const apiKey = this.configuration.apiKey;
|
||||
if (apiKey) {
|
||||
return typeof apiKey === 'function' ? apiKey : () => apiKey;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined {
|
||||
const accessToken = this.configuration.accessToken;
|
||||
if (accessToken) {
|
||||
return typeof accessToken === 'function' ? accessToken : async () => accessToken;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get headers(): HTTPHeaders | undefined {
|
||||
return this.configuration.headers;
|
||||
}
|
||||
|
||||
get credentials(): RequestCredentials | undefined {
|
||||
return this.configuration.credentials;
|
||||
}
|
||||
}
|
||||
|
||||
export const DefaultConfig = new Configuration();
|
||||
|
||||
/**
|
||||
* This is the base class for all generated API classes.
|
||||
*/
|
||||
export class BaseAPI {
|
||||
|
||||
private middleware: Middleware[];
|
||||
|
||||
constructor(protected configuration = DefaultConfig) {
|
||||
this.middleware = configuration.middleware;
|
||||
}
|
||||
|
||||
withMiddleware<T extends BaseAPI>(this: T, ...middlewares: Middleware[]) {
|
||||
const next = this.clone<T>();
|
||||
next.middleware = next.middleware.concat(...middlewares);
|
||||
return next;
|
||||
}
|
||||
|
||||
withPreMiddleware<T extends BaseAPI>(this: T, ...preMiddlewares: Array<Middleware['pre']>) {
|
||||
const middlewares = preMiddlewares.map((pre) => ({ pre }));
|
||||
return this.withMiddleware<T>(...middlewares);
|
||||
}
|
||||
|
||||
withPostMiddleware<T extends BaseAPI>(this: T, ...postMiddlewares: Array<Middleware['post']>) {
|
||||
const middlewares = postMiddlewares.map((post) => ({ post }));
|
||||
return this.withMiddleware<T>(...middlewares);
|
||||
}
|
||||
|
||||
protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise<Response> {
|
||||
const { url, init } = await this.createFetchParams(context, initOverrides);
|
||||
const response = await this.fetchApi(url, init);
|
||||
if (response && (response.status >= 200 && response.status < 300)) {
|
||||
return response;
|
||||
}
|
||||
throw new ResponseError(response, 'Response returned an error code');
|
||||
}
|
||||
|
||||
private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) {
|
||||
let url = this.configuration.basePath + context.path;
|
||||
if (context.query !== undefined && Object.keys(context.query).length !== 0) {
|
||||
// only add the querystring to the URL if there are query parameters.
|
||||
// this is done to avoid urls ending with a "?" character which buggy webservers
|
||||
// do not handle correctly sometimes.
|
||||
url += '?' + this.configuration.queryParamsStringify(context.query);
|
||||
}
|
||||
|
||||
const headers = Object.assign({}, this.configuration.headers, context.headers);
|
||||
Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {});
|
||||
|
||||
const initOverrideFn =
|
||||
typeof initOverrides === "function"
|
||||
? initOverrides
|
||||
: async () => initOverrides;
|
||||
|
||||
const initParams = {
|
||||
method: context.method,
|
||||
headers,
|
||||
body: context.body,
|
||||
credentials: this.configuration.credentials,
|
||||
};
|
||||
|
||||
const overridedInit: RequestInit = {
|
||||
...initParams,
|
||||
...(await initOverrideFn({
|
||||
init: initParams,
|
||||
context,
|
||||
}))
|
||||
}
|
||||
|
||||
const init: RequestInit = {
|
||||
...overridedInit,
|
||||
body:
|
||||
isFormData(overridedInit.body) ||
|
||||
overridedInit.body instanceof URLSearchParams ||
|
||||
isBlob(overridedInit.body)
|
||||
? overridedInit.body
|
||||
: JSON.stringify(overridedInit.body),
|
||||
};
|
||||
|
||||
return { url, init };
|
||||
}
|
||||
|
||||
private fetchApi = async (url: string, init: RequestInit) => {
|
||||
let fetchParams = { url, init };
|
||||
for (const middleware of this.middleware) {
|
||||
if (middleware.pre) {
|
||||
fetchParams = await middleware.pre({
|
||||
fetch: this.fetchApi,
|
||||
...fetchParams,
|
||||
}) || fetchParams;
|
||||
}
|
||||
}
|
||||
let response = undefined;
|
||||
try {
|
||||
response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init);
|
||||
} catch (e) {
|
||||
for (const middleware of this.middleware) {
|
||||
if (middleware.onError) {
|
||||
response = await middleware.onError({
|
||||
fetch: this.fetchApi,
|
||||
url: fetchParams.url,
|
||||
init: fetchParams.init,
|
||||
error: e,
|
||||
response: response ? response.clone() : undefined,
|
||||
}) || response;
|
||||
}
|
||||
}
|
||||
if (response === undefined) {
|
||||
if (e instanceof Error) {
|
||||
throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response');
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const middleware of this.middleware) {
|
||||
if (middleware.post) {
|
||||
response = await middleware.post({
|
||||
fetch: this.fetchApi,
|
||||
url: fetchParams.url,
|
||||
init: fetchParams.init,
|
||||
response: response.clone(),
|
||||
}) || response;
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shallow clone of `this` by constructing a new instance
|
||||
* and then shallow cloning data members.
|
||||
*/
|
||||
private clone<T extends BaseAPI>(this: T): T {
|
||||
const constructor = this.constructor as any;
|
||||
const next = new constructor(this.configuration);
|
||||
next.middleware = this.middleware.slice();
|
||||
return next;
|
||||
}
|
||||
};
|
||||
|
||||
function isBlob(value: any): value is Blob {
|
||||
return typeof Blob !== 'undefined' && value instanceof Blob
|
||||
}
|
||||
|
||||
function isFormData(value: any): value is FormData {
|
||||
return typeof FormData !== "undefined" && value instanceof FormData
|
||||
}
|
||||
|
||||
export class ResponseError extends Error {
|
||||
override name: "ResponseError" = "ResponseError";
|
||||
constructor(public response: Response, msg?: string) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class FetchError extends Error {
|
||||
override name: "FetchError" = "FetchError";
|
||||
constructor(public cause: Error, msg?: string) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export class RequiredError extends Error {
|
||||
override name: "RequiredError" = "RequiredError";
|
||||
constructor(public field: string, msg?: string) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export const COLLECTION_FORMATS = {
|
||||
csv: ",",
|
||||
ssv: " ",
|
||||
tsv: "\t",
|
||||
pipes: "|",
|
||||
};
|
||||
|
||||
export type FetchAPI = WindowOrWorkerGlobalScope['fetch'];
|
||||
|
||||
export type Json = any;
|
||||
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
|
||||
export type HTTPHeaders = { [key: string]: string };
|
||||
export type HTTPQuery = { [key: string]: string | number | null | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery };
|
||||
export type HTTPBody = Json | FormData | URLSearchParams;
|
||||
export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody }
|
||||
export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original';
|
||||
|
||||
export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise<RequestInit>
|
||||
|
||||
export interface FetchParams {
|
||||
url: string;
|
||||
init: RequestInit;
|
||||
}
|
||||
|
||||
export interface RequestOpts {
|
||||
path: string;
|
||||
method: HTTPMethod;
|
||||
headers: HTTPHeaders;
|
||||
query?: HTTPQuery;
|
||||
body?: HTTPBody;
|
||||
}
|
||||
|
||||
export function exists(json: any, key: string) {
|
||||
const value = json[key];
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export function querystring(params: HTTPQuery, prefix: string = ''): string {
|
||||
return Object.keys(params)
|
||||
.map(key => querystringSingleKey(key, params[key], prefix))
|
||||
.filter(part => part.length > 0)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery, keyPrefix: string = ''): string {
|
||||
const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key);
|
||||
if (value instanceof Array) {
|
||||
const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue)))
|
||||
.join(`&${encodeURIComponent(fullKey)}=`);
|
||||
return `${encodeURIComponent(fullKey)}=${multiValue}`;
|
||||
}
|
||||
if (value instanceof Set) {
|
||||
const valueAsArray = Array.from(value);
|
||||
return querystringSingleKey(key, valueAsArray, keyPrefix);
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
|
||||
}
|
||||
if (value instanceof Object) {
|
||||
return querystring(value as HTTPQuery, fullKey);
|
||||
}
|
||||
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
|
||||
}
|
||||
|
||||
export function mapValues(data: any, fn: (item: any) => any) {
|
||||
return Object.keys(data).reduce(
|
||||
(acc, key) => ({ ...acc, [key]: fn(data[key]) }),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
export function canConsumeForm(consumes: Consume[]): boolean {
|
||||
for (const consume of consumes) {
|
||||
if ('multipart/form-data' === consume.contentType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface Consume {
|
||||
contentType: string
|
||||
}
|
||||
|
||||
export interface RequestContext {
|
||||
fetch: FetchAPI;
|
||||
url: string;
|
||||
init: RequestInit;
|
||||
}
|
||||
|
||||
export interface ResponseContext {
|
||||
fetch: FetchAPI;
|
||||
url: string;
|
||||
init: RequestInit;
|
||||
response: Response;
|
||||
}
|
||||
|
||||
export interface ErrorContext {
|
||||
fetch: FetchAPI;
|
||||
url: string;
|
||||
init: RequestInit;
|
||||
error: unknown;
|
||||
response?: Response;
|
||||
}
|
||||
|
||||
export interface Middleware {
|
||||
pre?(context: RequestContext): Promise<FetchParams | void>;
|
||||
post?(context: ResponseContext): Promise<Response | void>;
|
||||
onError?(context: ErrorContext): Promise<Response | void>;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
raw: Response;
|
||||
value(): Promise<T>;
|
||||
}
|
||||
|
||||
export interface ResponseTransformer<T> {
|
||||
(json: any): T;
|
||||
}
|
||||
|
||||
export class JSONApiResponse<T> {
|
||||
constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) {}
|
||||
|
||||
async value(): Promise<T> {
|
||||
return this.transformer(await this.raw.json());
|
||||
}
|
||||
}
|
||||
|
||||
export class VoidApiResponse {
|
||||
constructor(public raw: Response) {}
|
||||
|
||||
async value(): Promise<void> {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class BlobApiResponse {
|
||||
constructor(public raw: Response) {}
|
||||
|
||||
async value(): Promise<Blob> {
|
||||
return await this.raw.blob();
|
||||
};
|
||||
}
|
||||
|
||||
export class TextApiResponse {
|
||||
constructor(public raw: Response) {}
|
||||
|
||||
async value(): Promise<string> {
|
||||
return await this.raw.text();
|
||||
};
|
||||
}
|
||||
8
frontend/src/main.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import './app.css'
|
||||
import App from './App.svelte'
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app')
|
||||
})
|
||||
|
||||
export default app
|
||||
27
frontend/src/modules/arrayHelpers.ts
Normal file
@ -0,0 +1,27 @@
|
||||
export function initTextArray(ta: Array<string>): Array<string> {
|
||||
if (ta) {
|
||||
return ta.concat("");
|
||||
} else {
|
||||
return [""];
|
||||
}
|
||||
}
|
||||
|
||||
export function prepareTextArrayForWrite(ta: Array<string>): Array<string>|undefined {
|
||||
if (ta.length === 1 && ta[0] === "") {
|
||||
return undefined;
|
||||
} else {
|
||||
return cleanTextArrayFromEmptyString(ta);
|
||||
}
|
||||
}
|
||||
|
||||
export function cleanTextArrayFromEmptyString(ta: Array<string>): Array<string> {
|
||||
return ta.filter((val)=>{return val.trim()!=="";});
|
||||
}
|
||||
|
||||
export function extendTextArrayWithEmptyString(ta: Array<string>): Array<string> {
|
||||
if (!ta.includes("")) {
|
||||
return [...ta, ""];
|
||||
} else {
|
||||
return ta;
|
||||
}
|
||||
}
|
||||
7
frontend/src/modules/dateHelpers.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export function getServerDateRep(date: Date) : string {
|
||||
return date.toISOString().slice(0,10);
|
||||
}
|
||||
|
||||
export function getClientDateRep(date: Date) : string {
|
||||
return date.toLocaleDateString("de-DE");
|
||||
}
|
||||
9
frontend/src/stores/apiStore.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Configuration, DefaultApi } from "../dashclient";
|
||||
|
||||
console.log(window.location.origin)
|
||||
|
||||
const configuration = new Configuration({
|
||||
basePath: "http://192.168.0.181:8080/api/v1",
|
||||
});
|
||||
|
||||
export const dashApi = new DefaultApi(configuration);
|
||||
7
frontend/src/stores/appStore.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { writable, readable } from 'svelte/store';
|
||||
|
||||
export const curTab = writable("Journal");
|
||||
|
||||
export const currentDate = writable<Date>(new Date());
|
||||
|
||||
export const dateUpdate = writable(() => {});
|
||||
24
frontend/src/stores/serverStore.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export let authHeader : any = {};
|
||||
|
||||
export const serverBaseUrl = import.meta.env.VITE_SERVER_URL;
|
||||
|
||||
export const authBase = "auth";
|
||||
export const authRegister = "register";
|
||||
|
||||
export async function register() {
|
||||
console.log("Registering at server.")
|
||||
if (Object.entries(authHeader).length === 0) {
|
||||
return fetch(`${serverBaseUrl}/${authBase}/${authRegister}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({'client': 'dash'})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => { authHeader = {
|
||||
'Authorization': `Bearer ${data}`,
|
||||
'X-JWT': `Bearer ${data}`,
|
||||
}; });
|
||||
}
|
||||
}
|
||||
2
frontend/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
7
frontend/svelte.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
import sveltePreprocess from 'svelte-preprocess'
|
||||
|
||||
export default {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: sveltePreprocess()
|
||||
}
|
||||
21
frontend/tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
8
frontend/tsconfig.node.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
7
frontend/vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [svelte()]
|
||||
})
|
||||