mirror of
https://github.com/cloudmaker97/DurstRechner.git
synced 2025-12-06 07:58:39 +00:00
Compare commits
9 commits
ab65fa7a73
...
9eba8c47f4
| Author | SHA1 | Date | |
|---|---|---|---|
| 9eba8c47f4 | |||
| 2dfe3543fc | |||
| 10ffcf9509 | |||
| 16c9cc1f8a | |||
| 8612acbf1e | |||
| 66a244f37a | |||
|
|
82a07fa652 | ||
| deef49a560 | |||
| 05de62c87a |
3 changed files with 169 additions and 12 deletions
|
|
@ -21,6 +21,30 @@ class Element {
|
||||||
return document.querySelector('#input-export-import');
|
return document.querySelector('#input-export-import');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file export button element
|
||||||
|
* @returns {Element}
|
||||||
|
*/
|
||||||
|
static getFileExportButton() {
|
||||||
|
return document.querySelector('#export-file');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file import button element
|
||||||
|
* @returns {Element}
|
||||||
|
*/
|
||||||
|
static getFileImportButton() {
|
||||||
|
return document.querySelector('#import-file-button');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file import input element
|
||||||
|
* @returns {Element}
|
||||||
|
*/
|
||||||
|
static getFileImportFileInput() {
|
||||||
|
return document.querySelector('#import-file');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the product list element
|
* Get the product list element
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
|
|
@ -79,7 +103,7 @@ class Element {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the buttons for importing test data
|
* Get the buttons for importing test data
|
||||||
* @returns {Element}
|
* @returns {Element[]}
|
||||||
*/
|
*/
|
||||||
static getButtonsImportTestdata() {
|
static getButtonsImportTestdata() {
|
||||||
return document.querySelectorAll('[data-action=import-testdata]');
|
return document.querySelectorAll('[data-action=import-testdata]');
|
||||||
|
|
@ -211,12 +235,46 @@ class Product {
|
||||||
getSettingsProductElement() {
|
getSettingsProductElement() {
|
||||||
const productElement = TemplateElement.getSettingsProductTemplate();
|
const productElement = TemplateElement.getSettingsProductTemplate();
|
||||||
productElement.setAttribute('data-id', this.id);
|
productElement.setAttribute('data-id', this.id);
|
||||||
productElement.textContent = this.name;
|
productElement.querySelector('[data-attr=name]').textContent = this.name;
|
||||||
productElement.addEventListener('click', () => {
|
productElement.querySelector('[data-action=delete]').addEventListener('click', () => {
|
||||||
productManager.removeProduct(this);
|
productManager.removeProduct(this);
|
||||||
productManager.setExportFieldJsonValue();
|
productManager.setExportFieldJsonValue();
|
||||||
productElement.remove();
|
productElement.remove();
|
||||||
})
|
})
|
||||||
|
productElement.querySelector('[data-action=edit]').addEventListener('click', () => {
|
||||||
|
const nameInputField = document.querySelector('#product-name');
|
||||||
|
const priceInputField = document.querySelector('#product-price');
|
||||||
|
const depositInputField = document.querySelector('#product-deposit');
|
||||||
|
const imageInputField = document.querySelector('#product-image');
|
||||||
|
const editProductId = document.querySelector("[name=edit-product-id]");
|
||||||
|
nameInputField.value = this.name;
|
||||||
|
priceInputField.value = this.price;
|
||||||
|
depositInputField.value = this.deposit;
|
||||||
|
editProductId.value = this.id;
|
||||||
|
|
||||||
|
// Set the image input field, create a new file object and set it to the input field by base64
|
||||||
|
const base64Image = this.image.split(',')[1];
|
||||||
|
const byteCharacters = atob(base64Image);
|
||||||
|
const byteNumbers = new Array(byteCharacters.length);
|
||||||
|
for (let i = 0; i < byteCharacters.length; i++) {
|
||||||
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
const blob = new Blob([byteArray], { type: 'image/png' });
|
||||||
|
const file = new File([blob], 'image.png', { type: 'image/png' });
|
||||||
|
const dataTransfer = new DataTransfer();
|
||||||
|
dataTransfer.items.add(file);
|
||||||
|
imageInputField.files = dataTransfer.files;
|
||||||
|
imageInputField.dispatchEvent(new Event('change'));
|
||||||
|
|
||||||
|
// Remove the product element from the settings list if the product has been saved
|
||||||
|
Element.getCreateNewProductButton().addEventListener('click', (e) => {
|
||||||
|
if(document.querySelector("[name=edit-product-id]").value === this.id.toString()) {
|
||||||
|
productManager.removeProduct(this);
|
||||||
|
productElement.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
return productElement;
|
return productElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,8 +332,8 @@ class Base64Image {
|
||||||
img.crossOrigin = 'Anonymous';
|
img.crossOrigin = 'Anonymous';
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = 250;
|
canvas.width = 150;
|
||||||
canvas.height = 250;
|
canvas.height = 150;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
const dataUrl = canvas.toDataURL();
|
const dataUrl = canvas.toDataURL();
|
||||||
|
|
@ -346,6 +404,34 @@ class ProductManager {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Element.getFileExportButton().addEventListener('click', () => {
|
||||||
|
const blob = new Blob([Element.getImportExportTextarea().value], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'products.json';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
setTimeout(() => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
a.remove();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
Element.getFileImportButton().addEventListener('click', () => {
|
||||||
|
let inputJsonFile = Element.getFileImportFileInput();
|
||||||
|
let fileContent = inputJsonFile.files[0];
|
||||||
|
if (fileContent) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async (e) => {
|
||||||
|
const json = JSON.parse(e.target.result);
|
||||||
|
await productManager.convertAndSaveProductImages(json);
|
||||||
|
location.reload();
|
||||||
|
};
|
||||||
|
reader.readAsText(fileContent);
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -420,7 +506,7 @@ class ProductManager {
|
||||||
* Reset the product settings form by clearing all input fields.
|
* Reset the product settings form by clearing all input fields.
|
||||||
*/
|
*/
|
||||||
static resetProductSettingsForm() {
|
static resetProductSettingsForm() {
|
||||||
document.querySelectorAll('form input:not([type=submit])').forEach(inputField => inputField.value = '');
|
document.querySelectorAll('form input:not([type=submit], [type=reset])').forEach(inputField => inputField.value = '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -682,14 +768,26 @@ class ThemeManager {
|
||||||
* Define the CartHistoryManager class to manage the cart history.
|
* Define the CartHistoryManager class to manage the cart history.
|
||||||
*/
|
*/
|
||||||
class CartHistoryManager {
|
class CartHistoryManager {
|
||||||
|
/**
|
||||||
|
* Get the cart history from local storage.
|
||||||
|
* @returns {string|number}
|
||||||
|
*/
|
||||||
static getTotal() {
|
static getTotal() {
|
||||||
return localStorage.getItem('cart-total-value') || 0;
|
return localStorage.getItem('cart-total-value') || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the cart history value in local storage.
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
static setTotal(value) {
|
static setTotal(value) {
|
||||||
localStorage.setItem('cart-total-value', value);
|
localStorage.setItem('cart-total-value', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add to the cart history value in local storage.
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
static addToTotal(value) {
|
static addToTotal(value) {
|
||||||
const total = parseFloat(CartHistoryManager.getTotal()) + value;
|
const total = parseFloat(CartHistoryManager.getTotal()) + value;
|
||||||
CartHistoryManager.setTotal(total);
|
CartHistoryManager.setTotal(total);
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
* Make product boxes, cart items and settings product list clickable
|
* Make product boxes, cart items and settings product list clickable
|
||||||
* This is generally used for product list and cart items
|
* This is generally used for product list and cart items
|
||||||
*/
|
*/
|
||||||
.product-box, .cart-items, .settings-product-list, .navbar-brand {
|
.product-box, .cart-items, .navbar-brand {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,13 +52,26 @@
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the cart sticky on screens wider than 768px
|
||||||
|
*/
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.cart {
|
||||||
|
position: sticky;
|
||||||
|
top: 1rem;
|
||||||
|
max-height: calc(100vh - 2rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Product list contains all available products,
|
* Product list contains all available products,
|
||||||
* which are displayed in a grid layout. The grid scaling is done by bootstrap.
|
* which are displayed in a grid layout. The grid scaling is done by bootstrap.
|
||||||
*/
|
*/
|
||||||
.product-list {
|
.product-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, 4fr));
|
grid-template-columns: repeat(auto-fill, minmax(140px, 4fr));
|
||||||
gap: .4em;
|
gap: .4em;
|
||||||
padding-bottom: 1em;
|
padding-bottom: 1em;
|
||||||
|
|
||||||
|
|
@ -80,7 +93,10 @@
|
||||||
-khtml-user-drag: none;
|
-khtml-user-drag: none;
|
||||||
user-drag: none;
|
user-drag: none;
|
||||||
}
|
}
|
||||||
|
/* Make the product box responsive */
|
||||||
|
.card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
/* Break the words on the card body */
|
/* Break the words on the card body */
|
||||||
.card-body span {
|
.card-body span {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
|
@ -89,6 +105,10 @@
|
||||||
.product-box small[data-attr=price] {
|
.product-box small[data-attr=price] {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
/* Make the product box clickable */
|
||||||
|
&:active{
|
||||||
|
transform:scale(0.96);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* When using dark mode, use info color for price */
|
/* When using dark mode, use info color for price */
|
||||||
|
|
|
||||||
45
index.html
45
index.html
|
|
@ -101,7 +101,9 @@
|
||||||
<label for="product-image" class="form-label">Bild auswählen</label>
|
<label for="product-image" class="form-label">Bild auswählen</label>
|
||||||
<input type="file" class="form-control" placeholder="Tolles Produkt" id="product-image">
|
<input type="file" class="form-control" placeholder="Tolles Produkt" id="product-image">
|
||||||
</div>
|
</div>
|
||||||
|
<input type="hidden" name="edit-product-id">
|
||||||
<input type="submit" class="btn btn-success" value="Speichern" id="create-product">
|
<input type="submit" class="btn btn-success" value="Speichern" id="create-product">
|
||||||
|
<input type="reset" class="btn btn-secondary" value="Zurücksetzen" onclick="document.querySelector('[name=edit-product-id]').value = ''">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
@ -110,6 +112,35 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<details class="mb-2">
|
<details class="mb-2">
|
||||||
<summary>Exportieren und Importieren</summary>
|
<summary>Exportieren und Importieren</summary>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-2 mt-2">
|
||||||
|
<p>
|
||||||
|
Hier kann die JSON-Datei exportiert werden. Dafür auf "Produkte herunterladen" klicken.
|
||||||
|
Die Datei wird dann im Download-Ordner gespeichert. Die Daten können nach eigenen
|
||||||
|
Belieben bearbeitet werden. Die Daten können auch wieder importiert werden.
|
||||||
|
</p>
|
||||||
|
<button class="btn btn-primary" id="export-file">
|
||||||
|
Produkte herunterladen
|
||||||
|
</button>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<p>
|
||||||
|
Hier kann die JSON-Datei importiert werden. Dafür die Datei
|
||||||
|
auswählen und auf "Hochladen" klicken. Die Daten werden dann in die Software
|
||||||
|
importiert.
|
||||||
|
</p>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" type="file" id="import-file">
|
||||||
|
<button class="btn btn-primary ms-2" data-action="import" id="import-file-button">
|
||||||
|
Hochladen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details class="mb-2">
|
||||||
|
<summary>Testdaten einspielen</summary>
|
||||||
<p>
|
<p>
|
||||||
Die Daten können im JSON-Format exportiert und importiert werden.
|
Die Daten können im JSON-Format exportiert und importiert werden.
|
||||||
Um Speicherplatz zu sparen, sollte das Bild im Format 1:1 vorliegen.
|
Um Speicherplatz zu sparen, sollte das Bild im Format 1:1 vorliegen.
|
||||||
|
|
@ -148,10 +179,18 @@
|
||||||
</aside>
|
</aside>
|
||||||
<div class="col-12 col-md-6 mt-2 mt-md-0">
|
<div class="col-12 col-md-6 mt-2 mt-md-0">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">Produkte entfernen</h5>
|
<h5 class="card-header">Produkte</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="mb-2">Hier können Produkte entfernt werden, die nicht mehr benötigt werden.</p>
|
<p class="mb-2">Hier können Produkte entfernt oder bearbeitet werden.</p>
|
||||||
<li class="list-group-item" data-template="settings-product">Test</li>
|
<li class="list-group-item" data-template="settings-product">
|
||||||
|
<span class="d-flex justify-content-between">
|
||||||
|
<span data-attr="name"></span>
|
||||||
|
<span>
|
||||||
|
<button class="mx-auto btn btn-secondary btn-sm" data-action="edit"><i class="bi bi-pen"></i></button>
|
||||||
|
<button class="mx-auto btn btn-warning btn-sm" data-action="delete"><i class="bi bi-trash"></i></button>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
<ul class="list-group settings-product-list">
|
<ul class="list-group settings-product-list">
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue