Demo-Templates, Designanpassungen, Importverhalten geändert

This commit is contained in:
Dennis Heinrich 2025-04-07 17:51:19 +02:00
parent 9cffcec304
commit 3190b8dd92
19 changed files with 404 additions and 119 deletions

View file

@ -0,0 +1,62 @@
[
{
"id": 75086086956949000,
"name": "Wasser",
"price": "0.5",
"image": "./assets/example/images/wasser.webp"
},
{
"id": 403980494034923100,
"name": "Cola",
"price": "1",
"image": "./assets/example/images/cola.webp"
},
{
"id": 174111295271372860,
"name": "Fanta",
"price": "1",
"image": "./assets/example/images/fanta.webp"
},
{
"id": 13086165444583086,
"name": "Bier",
"price": "1.5",
"image": "./assets/example/images/bier.webp"
},
{
"id": 227920082494117630,
"name": "Helles Bier",
"price": "1.2",
"image": "./assets/example/images/bier_helles.webp"
},
{
"id": 27617349687000040,
"name": "Apfelsaft",
"price": "1.2",
"image": "./assets/example/images/apfelsaft.webp"
},
{
"id": 191783545996520450,
"name": "Eistee",
"price": "1.5",
"image": "./assets/example/images/eistee.webp"
},
{
"id": 203321763516746460,
"name": "Cider",
"price": "1.8",
"image": "./assets/example/images/cider.webp"
},
{
"id": 615216202376277900,
"name": "Zitronenlimonade",
"price": "1",
"image": "./assets/example/images/limonade.webp"
},
{
"id": 363150314621734500,
"name": "Wodka Lemon",
"price": "3",
"image": "./assets/example/images/wodka_lemon.webp"
}
]

View file

@ -0,0 +1,62 @@
[
{
"id": 75086086956949000,
"name": "Wasser",
"price": "0.5",
"image": "https://placehold.co/250x250?text=1"
},
{
"id": 403980494034923100,
"name": "Cola",
"price": "1",
"image": "https://placehold.co/250x250?text=2"
},
{
"id": 174111295271372860,
"name": "Fanta",
"price": "1",
"image": "https://placehold.co/250x250?text=3"
},
{
"id": 13086165444583086,
"name": "Bier",
"price": "1.5",
"image": "https://placehold.co/250x250?text=4"
},
{
"id": 227920082494117630,
"name": "Helles Bier",
"price": "1.2",
"image": "https://placehold.co/250x250?text=5"
},
{
"id": 27617349687000040,
"name": "Apfelsaft",
"price": "1.2",
"image": "https://placehold.co/250x250?text=6"
},
{
"id": 191783545996520450,
"name": "Eistee",
"price": "1.5",
"image": "https://placehold.co/250x250?text=7"
},
{
"id": 203321763516746460,
"name": "Cider",
"price": "1.8",
"image": "https://placehold.co/250x250?text=8"
},
{
"id": 615216202376277900,
"name": "Zitronenlimonade",
"price": "1",
"image": "https://placehold.co/250x250?text=9"
},
{
"id": 363150314621734500,
"name": "Wodka Lemon",
"price": "3",
"image": "https://placehold.co/250x250?text=10"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View file

@ -3,7 +3,6 @@
"short_name": "Durstrechner",
"description": "Der Durstrechner ist ein einfaches Tool zur Berechnung von Verkaufspreisen bei Veranstaltungen.",
"start_url": "../",
"scope": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196f3",

View file

@ -1,5 +1,5 @@
import './calculator.js'
import './../../node_modules/bootstrap/dist/js/bootstrap.min.js'
import './../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js'
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {

View file

@ -63,7 +63,20 @@ class Element {
* @returns {Element}
*/
static getNavbarBrand() {
return document.querySelector('nav a.navbar-brand');
return document.querySelector('.navbar-brand');
}
static getButtonsImportTestdata() {
return document.querySelectorAll('[data-action=import-testdata]');
}
static getButtonShowTestdata() {
return document.querySelectorAll('[data-action=import-show-testdata]');
}
static getButtonClearTestdata() {
return document.querySelectorAll('[data-action=import-clear]');
}
static getGitHubReferenceLink() {
return document.querySelector('[data-github-ref]');
}
}
@ -142,6 +155,7 @@ class Product {
const productElement = TemplateElement.getProductTemplate();
productElement.setAttribute('data-id', this.id);
productElement.querySelector('[data-attr=name]').textContent = this.name;
productElement.querySelector('[data-attr=price]').textContent = CartManager.getNumberFormatter().format(this.price);
productElement.querySelector('[data-attr=image]').src = this.image;
productElement.addEventListener('click', () => {
cartManager.addProduct(this);
@ -273,16 +287,41 @@ class ProductManager {
* Register events for the import/export textarea.
*/
registerImportFormEvents() {
Element.getImportExportTextarea().addEventListener('input', (e) => {
Element.getButtonsImportTestdata().forEach(button => {
button.addEventListener('click', async () => {
await fetch('assets/example/example.json').then(r => r.json()).then(async json => {
await this.convertAndSaveProductImages(json);
location.reload();
})
});
})
Element.getImportExportTextarea().addEventListener('input', async (e) => {
try {
localStorage.setItem('products', e.target.value);
window.location = window.location;
const json = JSON.parse(e.target.value);
await this.convertAndSaveProductImages(json);
location.reload();
} catch (e) {
console.error(e);
}
});
}
/**
* Convert and save product images from JSON string to base64 and save it to local storage.
* @param json
* @returns {Promise<void>}
*/
async convertAndSaveProductImages(json) {
const products = json.map(e => new Product(e.name, e.price, e.image));
for (const product of products) {
if (!product.image.toString().startsWith('data:image/')) {
product.image = await Base64Image.imageResize(await Base64Image.fromImageUrl(product.image))
}
}
localStorage.setItem('products', JSON.stringify(products));
}
/**
* Set the JSON value of the export field to the current products.
*/
@ -553,6 +592,16 @@ class ThemeManager {
constructor() {
this.setBootstrapTheme(localStorage.getItem('bootstrap-theme') || 'light');
this.registerThemeSwitchEvent();
this.showGitHubIconOnAlternateInstallation();
}
/**
* Show the GitHub icon on alternate installation if the URL does not start with the specified string.
*/
showGitHubIconOnAlternateInstallation() {
if (!location.href.startsWith('https://cloudmaker97.github.io/DurstRechner')) {
Element.getGitHubReferenceLink().classList.remove('visually-hidden');
}
}
/**

View file

@ -1,4 +1,40 @@
.line-item-details {
/**
* Here are some variables that are used in the whole application.
*/
:root {
--currency-symbol: '€';
--separator: '|';
--price-color-light-mode: var(--bs-primary-rgb);
--price-color-dark-mode: var(--bs-info-rgb);
}
/**
* Let the user never select the text in the product list, cart items, alert and navbar brand.
* This is used to prevent the user from selecting the text in the product list and cart items. (Usability)
*/
.cart-items, .product-list, .alert, .navbar-brand, .settings-product-list {
user-select: none;
-webkit-user-select: none;
-webkit-user-drag: none;
}
/**
* Make product boxes, cart items and settings product list clickable
* This is generally used for product list and cart items
*/
.product-box, .cart-items, .settings-product-list, .navbar-brand {
cursor: pointer;
}
/**
* This is a dummy element, that can be copied via JavaScript
* and will be filled dynamically with some data.
*/
[data-template] {
display: none !important;
}
/**
* This is the cart with its line items
*/
.cart {
.line-item-details {
.amount {
display: inline-block;
min-width: 1.5em;
@ -7,45 +43,68 @@
display: inline-block;
min-width: 3em;
}
}
.product-list {
padding-bottom: 1em;
}
.currency-value::after {
content: ' €';
}
.quantity-value::after {
content: 'x ';
}
.name-value {
.name {
margin-left: 1em;
font-weight: bold;
}
}
}
[data-template] {
display: none !important;
}
.cart-items, .product-list, .alert, .navbar-brand, .settings-product-list {
user-select: none;
-webkit-user-select: none;
-webkit-user-drag: none;
}
img {
-webkit-user-drag: none;
-khtml-user-drag: none;
user-drag: none;
}
.product-box, .cart-items, .settings-product-list {
cursor: pointer;
}
/**
* Product list contains all available products,
* which are displayed in a grid layout. The grid scaling is done by bootstrap.
*/
.product-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 4fr));
gap: .4em;
}
padding-bottom: 1em;
.product-box .card-body span {
/**
* Product box for product list
* @param {string} data-attr - product attribute
*/
.product-box {
/* Product box should be clickable */
cursor: pointer;
/* Images shouldn't drag in user interface */
img {
-webkit-user-drag: none;
-khtml-user-drag: none;
user-drag: none;
}
/* Break the words on the card body */
.card-body span {
word-wrap: break-word;
}
/* Slightly bold font for the price attribute */
.product-box small[data-attr=price] {
font-weight: 400;
}
}
}
/* When using dark mode, use info color for price */
[data-bs-theme="dark"] {
.product-box small[data-attr=price] {
color: rgba(var(--price-color-dark-mode));
}
}
/* When using light mode, use primary color for price */
[data-bs-theme="light"] {
.product-box small[data-attr=price] {
color: rgba(var(--price-color-light-mode));
}
}
/* Insert currency symbol after each value */
.currency-value::after {
content: ' ' var(--currency-symbol);
}
/* Insert separator */
.separator::after {
content: ' ' var(--separator) ' ';
}
/* Insert times x symbol after an amount */
.quantity-value::after {
content: 'x ';
}

View file

@ -14,19 +14,29 @@
<title>Durstrechner</title>
</head>
<body class="d-flex flex-column vh-100">
<nav class="navbar navbar-expand-lg border-bottom mb-3">
<nav class="navbar navbar-expand-lg border-bottom mb-3">
<div class="container-fluid">
<a class="navbar-brand" href="#">Der Durstrechner</a>
<span class="navbar-brand d-flex gap-2" href="#">
<span>Der Durstrechner</span>
<a href="https://github.com/cloudmaker97/DurstRechner" class="text-white visually-hidden" title="Projekt auf GitHub" data-github-ref style="width: 20px; height: 20px;">
<i class="bi bi-github"></i>
</a>
</span>
<form class="d-flex" role="search" method="dialog">
<button class="btn" data-toggle-bs-theme><i class="bi bi-lightbulb-fill"></i> <span class="d-none d-md-inline-block">Tag / Nacht</span></button>
<button class="btn" data-toggle-tab><i class="bi bi-gear"></i> <span class="d-none d-md-inline-block">Einstellungen</span></button>
<button class="btn" data-toggle-bs-theme>
<i class="bi bi-lightbulb-fill"></i> <span class="d-none d-md-inline-block">Tag / Nacht</span>
</button>
<button class="btn" data-toggle-tab>
<i class="bi bi-gear"></i> <span class="d-none d-md-inline-block">Einstellungen</span>
</button>
</form>
</div>
</nav>
</nav>
<main class="container-fluid flex-grow-1">
<main class="container-fluid flex-grow-1">
<div data-tab="products" class="row">
<div class="col-12 col-md-4">
<aside class="col-12 col-md-4 cart">
<li class="list-group-item d-flex justify-content-between" data-template="product-line-item">
<span class="line-item-details">
<span class="amount quantity-value" data-attr="quantity">5</span>
@ -41,13 +51,17 @@
<button class="btn btn-secondary btn-lg w-100 cart-value mb-3">
<span class="currency-value" data-total-value>0,00</span>
</button>
</div>
</aside>
<div class="col-12 col-md-8">
<div class="product-list">
<div class="col product-box" data-template="product" data-id="0">
<div class="card">
<img data-attr="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+BCQAHBQICJmhD1AAAAABJRU5ErkJggg==" alt="Produktbild" class="card-img-top" loading="eager">
<img data-attr="image"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+BCQAHBQICJmhD1AAAAABJRU5ErkJggg=="
alt="Produktbild" class="card-img-top" loading="eager">
<div class="card-body">
<small data-attr="price" class="currency-value">0,00</small>
<i class="separator"></i>
<small data-attr="name">Wasser mit Kohlensäure</small>
</div>
</div>
@ -55,9 +69,8 @@
</div>
</div>
</div>
<div data-tab="settings" class="row d-none">
<div class="col-12 col-md-6">
<aside class="col-12 col-md-6">
<form class="card mb-2" method="dialog" id="new-product">
<h5 class="card-header">Neues Produkt</h5>
<div class="card-body">
@ -83,13 +96,44 @@
<div class="card">
<h5 class="card-header">Export / Import</h5>
<div class="card-body">
<label class="w-100">
Daten importieren oder exportieren (Kopieren und Einfügen)
<details class="mb-2">
<summary>Exportieren und Importieren</summary>
<p>
Die Daten können im JSON-Format exportiert und importiert werden.
Um Speicherplatz zu sparen, sollte das Bild im Format 1:1 vorliegen.
Ideal ist eine Bildgröße von 250x250 Pixel. Wenn ein Bild über eine URL bereitgestellt wird,
lädt die Software das Bild herunter und speichert es in der JSON-Datei als Base64-kodierte
Version. Die ursprüngliche URL wird dabei nicht mehr gespeichert.
</p>
<label class="w-100 form-label">
<b>Text zum Kopieren und Einfügen:</b>
<textarea class="form-control mt-1" id="input-export-import" spellcheck="false"></textarea>
</label>
</details>
<details>
<summary>Testdaten installieren</summary>
<p>
Über den Knopf "Testdaten einspielen" können Testdaten in die Software importiert werden
oder auch angezeigt werden. Die Daten können nach eigenen Belieben bearbeitet werden. Die
Testdaten könnten einen kleinen Augenblick laden, da sie erst heruntergeladen werden müssen.
</p>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-secondary" data-action="import-testdata">
Testdaten einspielen
</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false" data-bs-reference="parent">
<span class="visually-hidden">Testdaten einspielen</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/assets/example/example.json">Import-Datei anzeigen</a></li>
<li><a class="dropdown-item" href="/assets/example/example_urls.json">Import-Datei mit URLs anzeigen</a></li>
</ul>
</div>
</details>
</div>
</div>
</div>
</aside>
<div class="col-12 col-md-6 mt-2 mt-md-0">
<div class="card">
<h5 class="card-header">Produkte entfernen</h5>
@ -102,8 +146,8 @@
</div>
</div>
</div>
</main>
</main>
<script src="assets/script/all.js" type="module"></script>
<script src="assets/script/all.js" type="module"></script>
</body>
</html>

View file

@ -2,10 +2,17 @@
"name": "durst-rechner",
"version": "1.0.0",
"scripts": {},
"keywords": ["calculator", "feuerwehr", "firefighter", "pos", "thirst"],
"keywords": [
"calculator",
"feuerwehr",
"firefighter",
"pos",
"thirst"
],
"author": "Dennis Heinrich",
"license": "BSD-3-Clause",
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "5.3.5",
"bootstrap-icons": "^1.11.3"
}

View file

@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
bootstrap:
specifier: 5.3.5
version: 5.3.5(@popperjs/core@2.11.8)