diff --git a/assets/example/example.json b/assets/example/example.json new file mode 100644 index 0000000..e5e310a --- /dev/null +++ b/assets/example/example.json @@ -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" + } +] \ No newline at end of file diff --git a/assets/example/example_urls.json b/assets/example/example_urls.json new file mode 100644 index 0000000..2ab1913 --- /dev/null +++ b/assets/example/example_urls.json @@ -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" + } +] \ No newline at end of file diff --git a/assets/example/images/apfelsaft.webp b/assets/example/images/apfelsaft.webp new file mode 100644 index 0000000..5189a19 Binary files /dev/null and b/assets/example/images/apfelsaft.webp differ diff --git a/assets/example/images/bier.webp b/assets/example/images/bier.webp new file mode 100644 index 0000000..ed12c80 Binary files /dev/null and b/assets/example/images/bier.webp differ diff --git a/assets/example/images/bier_helles.webp b/assets/example/images/bier_helles.webp new file mode 100644 index 0000000..150be8a Binary files /dev/null and b/assets/example/images/bier_helles.webp differ diff --git a/assets/example/images/cider.webp b/assets/example/images/cider.webp new file mode 100644 index 0000000..be0419c Binary files /dev/null and b/assets/example/images/cider.webp differ diff --git a/assets/example/images/cola.webp b/assets/example/images/cola.webp new file mode 100644 index 0000000..9b12376 Binary files /dev/null and b/assets/example/images/cola.webp differ diff --git a/assets/example/images/eistee.webp b/assets/example/images/eistee.webp new file mode 100644 index 0000000..b0762f1 Binary files /dev/null and b/assets/example/images/eistee.webp differ diff --git a/assets/example/images/fanta.webp b/assets/example/images/fanta.webp new file mode 100644 index 0000000..cff3d19 Binary files /dev/null and b/assets/example/images/fanta.webp differ diff --git a/assets/example/images/limonade.webp b/assets/example/images/limonade.webp new file mode 100644 index 0000000..b5c4eaa Binary files /dev/null and b/assets/example/images/limonade.webp differ diff --git a/assets/example/images/wasser.webp b/assets/example/images/wasser.webp new file mode 100644 index 0000000..086649e Binary files /dev/null and b/assets/example/images/wasser.webp differ diff --git a/assets/example/images/wodka_lemon.webp b/assets/example/images/wodka_lemon.webp new file mode 100644 index 0000000..f079245 Binary files /dev/null and b/assets/example/images/wodka_lemon.webp differ diff --git a/assets/manifest.json b/assets/manifest.json index 5336f69..c606168 100644 --- a/assets/manifest.json +++ b/assets/manifest.json @@ -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", diff --git a/assets/script/all.js b/assets/script/all.js index 328d09c..7b89d9f 100644 --- a/assets/script/all.js +++ b/assets/script/all.js @@ -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', () => { diff --git a/assets/script/calculator.js b/assets/script/calculator.js index 2552650..c9c9f13 100644 --- a/assets/script/calculator.js +++ b/assets/script/calculator.js @@ -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} + */ + 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'); + } } /** diff --git a/assets/style/stylesheet.css b/assets/style/stylesheet.css index 876ed70..9f95657 100644 --- a/assets/style/stylesheet.css +++ b/assets/style/stylesheet.css @@ -1,51 +1,110 @@ -.line-item-details { - .amount { - display: inline-block; - min-width: 1.5em; - } - .price { - display: inline-block; - min-width: 3em; - } +/** + * 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); } -.product-list { - padding-bottom: 1em; -} -.currency-value::after { - content: ' €'; -} -.quantity-value::after { - content: 'x '; -} -.name-value { - margin-left: 1em; - font-weight: bold; -} - -[data-template] { - display: none !important; -} - +/** + * 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; } -img { - -webkit-user-drag: none; - -khtml-user-drag: none; - user-drag: none; -} -.product-box, .cart-items, .settings-product-list { +/** + * 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; + } + .price { + display: inline-block; + min-width: 3em; + } + .name { + margin-left: 1em; + font-weight: bold; + } + } +} +/** + * 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 { - word-wrap: break-word; + /** + * 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 '; } \ No newline at end of file diff --git a/index.html b/index.html index 20daf74..5940df6 100644 --- a/index.html +++ b/index.html @@ -14,96 +14,140 @@ Durstrechner - + + +
+
+ +
+
+
+
+ Produktbild +
+ 0,00 + + Wasser mit Kohlensäure
- -
-
-
-
Neues Produkt
-
-
- - -
-
- -
- - -
-
-
- - -
- +
+
+ +
+
+
Produkte entfernen
+
+

Hier können Produkte entfernt werden, die nicht mehr benötigt werden.

+
  • Test
  • +
      +
    -
    + + - + diff --git a/package.json b/package.json index e94c06a..66e9bee 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3021a59..e38b292 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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)