diff --git a/mirzaev/arming_bot/system/controllers/cart.php b/mirzaev/arming_bot/system/controllers/cart.php index ad2ad21..d9c2639 100755 --- a/mirzaev/arming_bot/system/controllers/cart.php +++ b/mirzaev/arming_bot/system/controllers/cart.php @@ -50,7 +50,7 @@ final class cart extends core // Search for filters and write to the buffer of global variables of view templater $this->view->menu = menu::_read( return: 'MERGE(d, { name: d.name.@language })', - sort: 'd.position ASC, d.created DESC, d._key DESC', + sort: 'd.style.order ASC, d.created DESC, d._key DESC', amount: 4, parameters: ['language' => $this->language->name], errors: $this->errors['menu'] @@ -118,7 +118,7 @@ final class cart extends core // Validating @todo add throwing errors $identifier = null; - if (!empty($parameters['identifier']) && preg_match('/[\d]+/', urldecode($parameters['identifier']), $matches)) $identifier = (int) $matches[0]; + if (isset($parameters['identifier']) && preg_match('/[\d]+/', urldecode($parameters['identifier']), $matches)) $identifier = (int) $matches[0]; if (isset($identifier)) { // Received and validated identfier of the product @@ -143,7 +143,7 @@ final class cart extends core // Validating @todo add throwing errors $type = null; - if (!empty($parameters['type']) && preg_match('/[\w]+/', urldecode($parameters['type']), $matches)) $type = $matches[0]; + if (isset($parameters['type']) && preg_match('/[\w]+/', urldecode($parameters['type']), $matches)) $type = $matches[0]; if (isset($type)) { // Received and validated type of action with the product @@ -173,7 +173,7 @@ final class cart extends core // Validating @todo add throwing errors $_amount = null; - if (!empty($parameters['amount']) && preg_match('/[\d]+/', urldecode($parameters['amount']), $matches)) $_amount = (int) $matches[0]; + if (isset($parameters['amount']) && preg_match('/[\d]+/', urldecode($parameters['amount']), $matches)) $_amount = (int) $matches[0]; if (isset($_amount)) { // Received and validated amount parameter for action with the product @@ -181,20 +181,26 @@ final class cart extends core if ($type === 'write') { // Increase amount of the product in the cart - if (101 > $amount += $_amount) { + if ($amount + $_amount < 101) { // Validated amount to wrting // Writing the product to the cart $this->cart->write(product: $product, amount: $_amount, errors: $this->errors['cart']); + + // Reinitialize the buffer with amount of the product in the cart + $amount += $_amount; } } else if ($type === 'delete') { // Decrease amount of the product in the cart - if (-1 < $amount -= $_amount) { + if ($amount - $_amount > -1) { // Validated amount to deleting // Deleting the product from the cart $this->cart->delete(product: $product, amount: $_amount, errors: $this->errors['cart']); + + // Reinitialize the buffer with amount of the product in the cart + $amount -= $_amount; } } else if ($type === 'set') { // Set amount of the product in the cart @@ -255,4 +261,49 @@ final class cart extends core // Exit (fail) return null; } + + /** + * Summary + * + * @param array $parameters Parameters of the request (POST + GET) + */ + public function summary(array $parameters = []): ?string + { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // POST request + + // Initializing summary data of the cart + $summary = $this->cart?->summary(currency: $this->currency, errors: $this->errors['cart']); + + // Initializing response headers + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Initializing of the output buffer + ob_start(); + + // Generating the reponse + echo json_encode( + [ + 'cost' => $summary['cost'] ?? 0, + 'amount' => $summary['amount'] ?? 0, + 'errors' => $this->errors + ] + ); + + // Initializing a response headers + header('Content-Length: ' . ob_get_length()); + + // Sending and deinitializing of the output buffer + ob_end_flush(); + flush(); + + // Exit (success) + return null; + } + + // Exit (fail) + return null; + } } diff --git a/mirzaev/arming_bot/system/controllers/catalog.php b/mirzaev/arming_bot/system/controllers/catalog.php index 194cbef..a9cf5bb 100755 --- a/mirzaev/arming_bot/system/controllers/catalog.php +++ b/mirzaev/arming_bot/system/controllers/catalog.php @@ -43,7 +43,7 @@ final class catalog extends core public function index(array $parameters = []): ?string { // validating - if (!empty($parameters['product']) && preg_match('/[\d]+/', $parameters['product'], $matches)) $product = (int) $matches[0]; + if (isset($parameters['product']) && preg_match('/[\d]+/', $parameters['product'], $matches)) $product = (int) $matches[0]; if (isset($product)) { // Received and validated identifier of the product @@ -68,7 +68,7 @@ final class catalog extends core $_filters = 'd.deleted != true && d.hidden != true'; // Validating - if (!empty($parameters['brand']) && preg_match('/[\w\s]+/u', urldecode($parameters['brand']), $matches)) $brand = $matches[0]; + if (isset($parameters['brand']) && preg_match('/[\w\s]+/u', urldecode($parameters['brand']), $matches)) $brand = $matches[0]; if (isset($brand)) { // Received and validated filter by brand @@ -130,7 +130,7 @@ final class catalog extends core $_sort = 'd.position ASC, d.name ASC, d.created DESC'; // Validating - if (!empty($parameters['sort']) && preg_match('/[\w\s]+/u', $parameters['sort'], $matches)) $sort = $matches[0]; + if (isset($parameters['sort']) && preg_match('/[\w\s]+/u', $parameters['sort'], $matches)) $sort = $matches[0]; if (isset($sort)) { // Received and validated sort @@ -181,7 +181,7 @@ final class catalog extends core } // Validating @todo add throwing errors - if (!empty($parameters['text']) && preg_match('/[\w\s]+/u', urldecode($parameters['text']), $matches) && mb_strlen($matches[0]) > 2) $text = $matches[0]; + if (isset($parameters['text']) && preg_match('/[\w\s]+/u', urldecode($parameters['text']), $matches) && mb_strlen($matches[0]) > 2) $text = $matches[0]; if (isset($text)) { // Received and validated text @@ -234,7 +234,7 @@ final class catalog extends core } // Validating - if (!empty($parameters['category']) && preg_match('/[\d]+/', $parameters['category'], $matches)) $category = (int) $matches[0]; + if (isset($parameters['category']) && preg_match('/[\d]+/', $parameters['category'], $matches)) $category = (int) $matches[0]; if (isset($category)) { // Received and validated identifier of the category @@ -337,7 +337,7 @@ final class catalog extends core // Search for filters and write to the buffer of global variables of view templater $this->view->menu = menu::_read( return: 'MERGE(d, { name: d.name.@language })', - sort: 'd.position ASC, d.created DESC, d._key DESC', + sort: 'd.style.order ASC, d.created DESC, d._key DESC', amount: 4, parameters: ['language' => $this->language->name], errors: $this->errors['menu'] diff --git a/mirzaev/arming_bot/system/models/cart.php b/mirzaev/arming_bot/system/models/cart.php index f908c38..543154b 100755 --- a/mirzaev/arming_bot/system/models/cart.php +++ b/mirzaev/arming_bot/system/models/cart.php @@ -52,7 +52,7 @@ final class cart extends core implements document_interface, collection_interfac * * @return array|null Array with implementing objects of documents from ArangoDB, if found */ - public function all( + public function products( ?language $language = language::en, ?currency $currency = currency::usd, array &$errors = [] @@ -66,13 +66,13 @@ final class cart extends core implements document_interface, collection_interfac // Search for all products in the cart $result = @collection::execute( << 'catalog', + 'cart' => $this->getId(), + 'collection' => product::COLLECTION, + 'currency' => $currency->name + ], + flat: true, + errors: $errors + ); + + // Exit (success) + return $result; + } else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION); + } else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** * Count * diff --git a/mirzaev/arming_bot/system/public/index.php b/mirzaev/arming_bot/system/public/index.php index dc4563f..28b4b21 100755 --- a/mirzaev/arming_bot/system/public/index.php +++ b/mirzaev/arming_bot/system/public/index.php @@ -43,6 +43,7 @@ $router ->write('/cart', 'cart', 'index', 'GET') ->write('/cart', 'cart', 'index', 'POST') ->write('/cart/product', 'cart', 'product', 'POST') + ->write('/cart/summary', 'cart', 'summary', 'POST') ->write('/account/write', 'account', 'write', 'POST') ->write('/session/write', 'session', 'write', 'POST') ->write('/session/connect/telegram', 'session', 'telegram', 'POST') diff --git a/mirzaev/arming_bot/system/public/js/cart.js b/mirzaev/arming_bot/system/public/js/cart.js index 8166060..757253e 100755 --- a/mirzaev/arming_bot/system/public/js/cart.js +++ b/mirzaev/arming_bot/system/public/js/cart.js @@ -39,17 +39,26 @@ import("/js/core.js").then(() => * * Toggle the product in the cart (interface) * - * @param {HTMLElement} button Button of the product + * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product + * @param {HTMLElement} product The product + * @param {bool} remove Remove the product element if json.amount === 0? * @param {bool} force Ignore the damper? (false) * * @return {bool} True if an error occurs to continue the event execution */ - static toggle(button, product, force = false) { - // Blocking the button - button.setAttribute("disabled", "true"); + static toggle(element, product, remove = false, force = false) { + // Blocking the element + element.setAttribute("disabled", "true"); // Execute under damper - this.toggle_damper(button, product, force); + this.toggle_damper( + element, + product, + "toggle", + undefined, + remove, + force, + ); // Exit (success) return false; @@ -60,115 +69,44 @@ import("/js/core.js").then(() => * * Toggle the product in the cart (damper) * - * @param {HTMLElement} button Button of the product + * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product + * @param {HTMLElement} product The product + * @param {bool} remove Remove the product element if json.amount === 0? * @param {bool} force Ignore the damper? (false) * - * @return {void} + * @return {Promise} */ static toggle_damper = core.damper( - (...variables) => this.toggle_system(...variables), + (...variables) => this.product(...variables).then(this.summary), 300, - 2, + 6, ); - /** - * Toggle - * - * Toggle the product in the cart (system) - * - * @param {HTMLElement} button Button of the product - * - * @return {Promise} Request to the server - * - * @todo add unblocking button by timer + everywhere - */ - static async toggle_system(button, product) { - if ( - product instanceof HTMLElement && - button instanceof HTMLElement - ) { - // Validated - - // Initializing of identifier of the product - const identifier = product.getAttribute( - "data-product-identifier", - ); - - if (typeof identifier === "string" && identifier.length > 0) { - // Validated identifier - - return await core.request( - "/cart/product", - `identifier=${identifier}&type=toggle`, - ) - .then((json) => { - if ( - json.errors !== null && - typeof json.errors === "object" && - json.errors.length > 0 - ) { - // Fail (received errors) - } else { - // Success (not received errors) - - // Unblocking the button - button.removeAttribute("disabled"); - - // Writing offset of hue-rotate to indicate that the product is in the cart - product.style.setProperty( - "--hue-rotate-offset", - json.amount + "0deg", - ); - - // Writing attribute with amount of the product in the cart - product.setAttribute( - "data-product-amount", - json.amount, - ); - - // Initializing the amount element - const amounts = product.querySelectorAll( - 'span[data-product-parameter="amount"]', - ); - - for (const amount of amounts) { - // Iterating over an amount elements - - if (amount instanceof HTMLInputElement) { - // The element - - // Writing amount of the product in the cart - amount.value = json.amount; - } else { - // Not the element - - // Writing amount of the product in the cart - amount.innerText = json.amount; - } - } - } - }); - } - } - } - /** * Write * * Write the product in the cart (interface) * - * @param {HTMLElement} button Button of the product + * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product + * @param {HTMLElement} product The product * @param {number} amount Amount of writings + * @param {bool} remove Remove the product element if json.amount === 0? * @param {bool} force Ignore the damper? (false) * * @return {bool} True if an error occurs to continue the event execution */ - static write(button, product, amount = 1, force = false) { - // Blocking the button - button.setAttribute("disabled", "true"); + static write( + element, + product, + amount = 1, + remove = false, + force = false, + ) { + // Blocking the element + element.setAttribute("disabled", "true"); // Execute under damper - this.write_damper(button, amount, force); + this.write_damper(element, product, "write", amount, remove, force); // Exit (success) return false; @@ -179,120 +117,52 @@ import("/js/core.js").then(() => * * Write the product in the cart (damper) * - * @param {HTMLElement} button Button of the product + * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product + * @param {HTMLElement} product The product * @param {number} amount Amount of writings + * @param {bool} remove Remove the product element if json.amount === 0? * @param {bool} force Ignore the damper? (false) * - * @return {void} + * @return {Promise} */ static write_damper = core.damper( - (...variables) => this.write_system(...variables), + (...variables) => this.product(...variables).then(this.summary), 300, - 3, + 6, ); - /** - * Write - * - * Write the product in the cart (system) - * - * @param {HTMLElement} button Button of the product - * @param {number} amount Amount of writings - * - * @return {Promise} Request to the server - * - * @todo add unblocking button by timer + everywhere - */ - static async write_system(button, product, amount = 1) { - if ( - product instanceof HTMLElement && - button instanceof HTMLElement && - typeof amount === "number" && - amount > -1 && - amount < 100 - ) { - // Validated - - // Initializing of identifier of the product - const identifier = product.getAttribute( - "data-product-identifier", - ); - - if (typeof identifier === "string" && identifier.length > 0) { - // Validated identifier - - return await core.request( - "/cart/product", - `identifier=${identifier}&type=write&amount=${amount}`, - ) - .then((json) => { - if ( - json.errors !== null && - typeof json.errors === "object" && - json.errors.length > 0 - ) { - // Fail (received errors) - } else { - // Success (not received errors) - - // Unblocking the button - button.removeAttribute("disabled"); - - // Writing offset of hue-rotate to indicate that the product is in the cart - product.style.setProperty( - "--hue-rotate-offset", - json.amount + "0deg", - ); - - // Writing attribute with amount of the product in the cart - product.setAttribute( - "data-product-amount", - json.amount, - ); - - // Initializing the amount element - const amounts = product.querySelectorAll( - 'span[data-product-parameter="amount"]', - ); - - for (const amount of amounts) { - // Iterating over an amount elements - - if (amount instanceof HTMLInputElement) { - // The element - - // Writing amount of the product in the cart - amount.value = json.amount; - } else { - // Not the element - - // Writing amount of the product in the cart - amount.innerText = json.amount; - } - } - } - }); - } - } - } - /** * Delete * * Delete the product from the cart (interface) * - * @param {HTMLElement} button Button of the product + * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product + * @param {HTMLElement} product The product * @param {number} amount Amount of deletings + * @param {bool} remove Remove the product element if json.amount === 0? * @param {bool} force Ignore the damper? (false) * * @return {bool} True if an error occurs to continue the event execution */ - static delete(button, product, amount = 1, force = false) { - // Blocking the button - button.setAttribute("disabled", "true"); + static delete( + element, + product, + amount = 1, + remove = false, + force = false, + ) { + // Blocking the element + element.setAttribute("disabled", "true"); // Execute under damper - this.delete_damper(button, amount, force); + this.delete_damper( + element, + product, + "delete", + amount, + remove, + force, + ); // Exit (success) return false; @@ -303,120 +173,44 @@ import("/js/core.js").then(() => * * Delete the product from the cart (damper) * - * @param {HTMLElement} button Button of the product + * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product * @param {number} amount Amount of deletings + * @param {bool} remove Remove the product element if json.amount === 0? * @param {bool} force Ignore the damper? (false) * - * @return {void} + * @return {Promise} */ static delete_damper = core.damper( - (...variables) => this.delete_system(...variables), + (...variables) => this.product(...variables).then(this.summary), 300, - 3, + 6, ); - /** - * Delete - * - * Delete the product from the cart (system) - * - * @param {HTMLElement} button Button of the product - * @param {number} amount Amount of deletings - * - * @return {Promise} Request to the server - * - * @todo add unblocking button by timer + everywhere - */ - static async delete_system(button, product, amount = 1) { - if ( - product instanceof HTMLElement && - button instanceof HTMLElement && - typeof amount === "number" && - amount > 0 && - amount < 101 - ) { - // Validated - - // Initializing of identifier of the product - const identifier = product.getAttribute( - "data-product-identifier", - ); - - if (typeof identifier === "string" && identifier.length > 0) { - // Validated identifier - - return await core.request( - "/cart/product", - `identifier=${identifier}&type=delete&amount=${amount}`, - ) - .then((json) => { - if ( - json.errors !== null && - typeof json.errors === "object" && - json.errors.length > 0 - ) { - // Fail (received errors) - } else { - // Success (not received errors) - - // Unblocking the button - button.removeAttribute("disabled"); - - // Writing offset of hue-rotate to indicate that the product is in the cart - product.style.setProperty( - "--hue-rotate-offset", - json.amount + "0deg", - ); - - // Writing attribute with amount of the product in the cart - product.setAttribute( - "data-product-amount", - json.amount, - ); - - // Initializing the amount element - const amounts = product.querySelectorAll( - 'span[data-product-parameter="amount"]', - ); - - for (const amount of amounts) { - // Iterating over an amount elements - - if (amount instanceof HTMLInputElement) { - // The element - - // Writing amount of the product in the cart - amount.value = json.amount; - } else { - // Not the element - - // Writing amount of the product in the cart - amount.innerText = json.amount; - } - } - } - }); - } - } - } - /** * Set * * Set amount of the product in the cart (interface) * - * @param {HTMLElement} button Button of the product + * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product + * @param {HTMLElement} product The product * @param {number} amount Amount of the product in the cart to be setted + * @param {bool} remove Remove the product element if json.amount === 0? * @param {bool} force Ignore the damper? (false) * * @return {bool} True if an error occurs to continue the event execution */ - static set(button, product, amount = 1, force = false) { - // Blocking the button - button.setAttribute("disabled", "true"); + static set( + element, + product, + amount = 1, + remove = false, + force = false, + ) { + // Blocking the element + element.setAttribute("disabled", "true"); // Execute under damper - this.set_damper(button, amount, force); + this.set_damper(element, product, "set", amount, remove, force); // Exit (success) return false; @@ -427,101 +221,207 @@ import("/js/core.js").then(() => * * Set the product in the cart (damper) * - * @param {HTMLElement} button Button of the product + * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product + * @param {HTMLElement} product The product * @param {number} amount Amount of the product in the cart to be setted + * @param {bool} remove Remove the product element if json.amount === 0? * @param {bool} force Ignore the damper? (false) * - * @return {void} + * @return {Promise} */ static set_damper = core.damper( - (...variables) => this.set_system(...variables), + (...parameters) => this.product(...parameters).then(this.summary), 300, - 3, + 6, ); /** - * Set + * The product * - * Set the product in the cart (system) + * Handle the product in the cart (system) * - * @param {HTMLElement} button Button of the product - * @param {number} amount Amount of the product in the cart to be setted + * @param {HTMLButtonElement|HTMLInputElement} element Handler element of the product + * @param {HTMLElement} product The product element + * @param {string} type Type of action with the product + * @param {number} amount Amount of product to handle + * @param {bool} remove Remove the product element if json.amount === 0? * - * @return {Promise} Request to the server - * - * @todo add unblocking button by timer + everywhere + * @return {Promise|null} */ - static async set_system(button, product, amount = 1) { + static async product( + element, + product, + type, + amount = null, + remove = false, + resolve = () => {}, + reject = () => {}, + ) { if ( - product instanceof HTMLElement && - button instanceof HTMLElement && - typeof amount === "number" && - amount > -1 && - amount < 101 + (element instanceof HTMLButtonElement || + element instanceof HTMLInputElement) && + product instanceof HTMLElement ) { // Validated + // Initializing the buffer of request body + let request = ""; + // Initializing of identifier of the product - const identifier = product.getAttribute( + const identifier = +product.getAttribute( "data-product-identifier", ); - if (typeof identifier === "string" && identifier.length > 0) { + if (typeof identifier === "number") { // Validated identifier - return await core.request( - "/cart/product", - `identifier=${identifier}&type=set&amount=${amount}`, - ) - .then((json) => { - if ( - json.errors !== null && - typeof json.errors === "object" && - json.errors.length > 0 - ) { - // Fail (received errors) - } else { - // Success (not received errors) + // Writing to the buffer of request body + request += "&identifier=" + identifier; - // Unblocking the button - button.removeAttribute("disabled"); + if ( + type === "toggle" || + type === "write" || + type === "delete" || + type === "set" + ) { + // Validated type - // Writing offset of hue-rotate to indicate that the product is in the cart - product.style.setProperty( - "--hue-rotate-offset", - json.amount + "0deg", - ); + // Writing to the buffer of request body + request += "&type=" + type; - // Writing attribute with amount of the product in the cart - product.setAttribute( - "data-product-amount", - json.amount, - ); + if ( + (type === "toggle" && + typeof amount === "undefined") || + (type === "set" && + amount === 0 || + amount === 100) || + typeof amount === "number" && + amount > 0 && + amount < 100 + ) { + // Validated amount - // Initializing the amount element - const amounts = product.querySelectorAll( - 'span[data-product-parameter="amount"]', - ); + if (type !== "toggle") { + // Not a toggle request - for (const amount of amounts) { - // Iterating over an amount elements - - if (amount instanceof HTMLInputElement) { - // The element - - // Writing amount of the product in the cart - amount.value = json.amount; - } else { - // Not the element - - // Writing amount of the product in the cart - amount.innerText = json.amount; - } - } + // Writing to the buffer of request body + request += "&amount=" + amount; } - }); + + // Request + return await core.request( + "/cart/product", + request, + ) + .then((json) => { + if ( + json.errors !== null && + typeof json.errors === "object" && + json.errors.length > 0 + ) { + // Fail (received errors) + } else { + // Success (not received errors) + + if (remove && json.amount === 0) { + // Requested deleting of the product element when there is no the product in the cart + + // Deleting the product element + product.remove(); + } else { + // Not requested deleting the product element when there is no the product in the cart + + // Unblocking the element + element.removeAttribute("disabled"); + + // Writing offset of hue-rotate to indicate that the product is in the cart + product.style.setProperty( + "--hue-rotate-offset", + json.amount + "0deg", + ); + + // Writing attribute with amount of the product in the cart + product.setAttribute( + "data-product-amount", + json.amount, + ); + + // Initializing the amount element + const amounts = product.querySelectorAll( + '[data-product-parameter="amount"]', + ); + + for (const amount of amounts) { + // Iterating over an amount elements + + if (amount instanceof HTMLInputElement) { + // The element + + // Writing amount of the product in the cart + amount.value = json.amount; + } else { + // Not the element + + // Writing amount of the product in the cart + amount.innerText = json.amount; + } + } + + // Exit (success) + resolve(); + } + } + }); + } + } } } + + // Exit (fail) + reject(); + } + + /** + * Summary + * + * Initialize summary of products the cart (system) + * + * @return {void} + */ + static async summary() { + // Request + return await core.request("/cart/summary") + .then((json) => { + if ( + json.errors !== null && + typeof json.errors === "object" && + json.errors.length > 0 + ) { + // Fail (received errors) + } else { + // Success (not received errors) + + // Initializing the summary amount element + const amount = document.getElementById("amount"); + + // Initializing the summary cost element + const cost = document.getElementById("cost"); + + if (amount instanceof HTMLElement) { + // Initialized the summary amount element + + // Writing summmary amount into the summary amount element + amount.innerText = json.amount; + } + + if (cost instanceof HTMLElement) { + // Initialized the summary cost element + + // Writing summmary cost into the summary cost element + cost.innerText = json.cost; + } + } + }); } }; } diff --git a/mirzaev/arming_bot/system/public/js/damper.js b/mirzaev/arming_bot/system/public/js/damper.js index ce9dec3..7bf4107 100755 --- a/mirzaev/arming_bot/system/public/js/damper.js +++ b/mirzaev/arming_bot/system/public/js/damper.js @@ -28,6 +28,8 @@ import("/js/core.js").then(() => { * @param {number} timeout Timer in milliseconds (ms) * @param {number} force Argument number storing the status of enforcement execution (see @example) * + * @return {Promise} + * * @memberof core * * @example @@ -37,9 +39,13 @@ import("/js/core.js").then(() => { * b, // 1 * c, // 2 * force = false, // 3 - * d // 4 + * d, // 4 + * resolve, + * reject * ) => { - * // Body of function + * // Body of the function + * + * resolve(); * }, * 500, * 3, // 3 -> "force" argument @@ -51,24 +57,39 @@ import("/js/core.js").then(() => { * @author Arsen Mirzaev Tatyano-Muradovich */ core.damper = (func, timeout = 300, force) => { - // Initializing of the timer + // Declaring of the timer for executing the function let timer; - return (...args) => { - // Deinitializing of the timer - clearTimeout(timer); + return ((...args) => { + return new Promise((resolve, reject) => { + // Deinitializing of the timer + clearTimeout(timer); - if (typeof force === "number" && args[force]) { - // Force execution (ignoring the timer) + if (typeof force === "number" && args[force]) { + // Requested execution with ignoring the timer - func.apply(this, args); - } else { - // Normal execution + // Deleting the force argument + if (typeof force === "number") delete args[force - 1]; - // Execute the handled function (entry into recursion) - timer = setTimeout(() => func.apply(this, args), timeout); - } - }; + // Writing promise handlers into the arguments variable + args.push(resolve, reject); + + // Executing the function + func.apply(this, args); + } else { + // Normal execution + + // Deleting the force argument + if (typeof force === "number") delete args[force - 1]; + + // Writing promise handlers into the arguments variable + args.push(resolve, reject); + + // Resetting the timer and executing the function when the timer expires + timer = setTimeout(() => func.apply(this, args), timeout); + } + }); + }); }; } } diff --git a/mirzaev/arming_bot/system/public/themes/default/css/cart.css b/mirzaev/arming_bot/system/public/themes/default/css/cart.css index f17f79f..0098164 100644 --- a/mirzaev/arming_bot/system/public/themes/default/css/cart.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/cart.css @@ -1,21 +1,45 @@ @charset "UTF-8"; -main>section#products { +main>section:is(#summary, #products) { width: var(--width); display: flex; flex-direction: column; gap: var(--gap); } +main>section#summary>div { + padding-left: 1rem; + display: flex; + align-items: center; + gap: 0.4rem; + overflow: hidden; + border-radius: 1.375rem; + background-color: var(--tg-theme-secondary-bg-color); +} + +main>section#summary>div>span:first-of-type { + /* margin-left: auto; */ +} + +main>section#summary>div>button#order { + /* margin-left: 0.3rem; */ + margin-left: auto; + padding-left: 0.7rem; +} + main>section#products>article.product { position: relative; width: 100%; min-height: 5rem; - max-height: 8rem; + max-height: 10rem; display: flex; border-radius: 0.75rem; overflow: hidden; - backdrop-filter: brightness(0.7); + background-color: var(--tg-theme-secondary-bg-color); +} + +main>section#products>article.product[data-product-amount]:not([data-product-amount="0"]) * { + backdrop-filter: hue-rotate(calc(120deg + var(--hue-rotate-offset, 0deg))); } main>section#products>article.product:is(:hover, :focus)>* { @@ -38,7 +62,7 @@ main>section#products>article.product>a>img:first-of-type { image-rendering: auto; } -main>section#products>article.product>div[data-product="body"] { +main>section#products>article.product>div { width: 100%; padding: 0.5rem; display: flex; @@ -47,69 +71,69 @@ main>section#products>article.product>div[data-product="body"] { overflow: hidden; } -main>section#products>article.product>div[data-product="body"]>p { - margin: unset; +main>section#products>article.product>div>div { + display: inline-flex; + align-items: center; } -main>section#products>article.product>div[data-product="body"]>p.title { +main>section#products>article.product>div>div.head { z-index: 50; padding: 0 0.4rem; - font-size: 0.8rem; - hyphens: auto; - overflow-wrap: anywhere; + gap: 1rem; } -main>section#products>article.product>div[data-product="body"]>p.characteristics { +main>section#products>article.product>div>div>button:first-of-type { + margin-left: auto; +} + +main>section#products>article.product>div>div>button { + padding: 0.4rem; + background: unset; +} + +main>section#products>article.product>div>div.head>button+button { + margin-left: 0.4rem; +} + +main>section#products>article.product>div>div.head>button>i.icon.trash { + color: var(--tg-theme-destructive-text-color); +} + +main>section#products>article.product>div>div.body { z-index: 30; + flex-grow: 1; display: inline-flex; flex-flow: row wrap; + align-items: start; gap: 0.3rem; font-size: 0.6rem; overflow: hidden; } -main>section#products>article.product>div[data-product="body"]>p.characteristics>span { +main>section#products>article.product>div>div.body>span { padding: 0.2rem 0.4rem; border-radius: 0.75rem; - color: var(--tg-theme-accent_text_color); - background-color: var(--tg-theme-secondary-bg-color); + color: var(--tg-theme-button-text-color); + background-color: var(--tg-theme-button-color); } -main>section#products>article.product>div[data-product="body"]>p.cost { - z-index: 20; - margin-top: auto; - padding: 0 0.4rem; - font-size: 0.8rem; -} - -main>section#products>article.product>div[data-product="buttons"]:last-of-type { +main>section#products>article.product>div>div.footer { z-index: 100; - flex-shrink: 0; - padding: 0.2rem 0rem; + padding: 0 0.4rem; display: flex; - flex-direction: column; - justify-content: space-between; overflow: hidden; } -main>section#products>article.product>div[data-product="buttons"]:last-of-type>div.row { - padding: 0 0.6rem; - display: flex; - gap: 0.2rem; - justify-content: end; +main>section#products>article.product>div>div.footer>span[data-product-parameter] { + font-size: 0.8rem; } -main>section#products>article.product>div[data-product="buttons"]:last-of-type>div.row>button { - padding: 0.4rem; - background: unset; +main>section#products>article.product>div>div.footer>span[data-product-parameter]+span[data-product-parameter="currency"] { + margin-left: 0.1rem; } -main>section#products>article.product>div[data-product="buttons"]:last-of-type>div.row>button + button { - margin-left: 0.4rem; -} - -main>section#products>article.product>div[data-product="buttons"]:last-of-type>div.row>input { +main>section#products>article.product>div>div.footer>input { width: 2rem; - padding: 0 0.3rem; - text-align: center; + padding: 0 0.3rem; + text-align: center; } diff --git a/mirzaev/arming_bot/system/public/themes/default/css/catalog.css b/mirzaev/arming_bot/system/public/themes/default/css/catalog.css index a5b997f..18abab6 100644 --- a/mirzaev/arming_bot/system/public/themes/default/css/catalog.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/catalog.css @@ -156,12 +156,12 @@ main>section#products>div.column>article.product>div[data-product="buttons"]>but flex-grow: 1; } -main>section#products>div.column>article.product:is([data-product-amount="0"], [data-product-amount="1"])>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parametert="amount"], +main>section#products>div.column>article.product:is([data-product-amount="0"], [data-product-amount="1"])>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"], main>section#products>div.column>article.product[data-product-amount="0"]>div[data-product="buttons"]>button:is([data-product-button="write"], [data-product-button="delete"]) { display: none; } -main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parametert="amount"]:after { +main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"]:after { content: '*'; margin: 0 0.2rem; } @@ -171,11 +171,11 @@ main>section#products>div.column>article.product[data-product-amount]:not([data- } @container product-buttons (max-width: 200px) { - main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span:is([data-product-parametert="cost"], [data-product-parametert="currency"]) { + main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span:is([data-product-parameter="cost"], [data-product-parameter="currency"]) { display: none; } - main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parametert="amount"]:after { + main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-parameter="amount"]:after { content: unset; } } diff --git a/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow.css b/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow.css new file mode 100755 index 0000000..acecc5c --- /dev/null +++ b/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow.css @@ -0,0 +1,34 @@ +@charset "UTF-8"; + +i.icon.arrow:not(.circle, .square) { + box-sizing: border-box; + position: relative; + display: block; + width: 22px; + height: 22px; +} + +i.icon.arrow:not(.circle, .square)::after, +i.icon.arrow:not(.circle, .square)::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + right: 3px; +} + +i.icon.arrow:not(.circle, .square)::after { + width: 8px; + height: 8px; + border-top: 2px solid; + border-right: 2px solid; + transform: rotate(45deg); + bottom: 7px; +} + +i.icon.arrow:not(.circle, .square)::before { + width: 16px; + height: 2px; + bottom: 10px; + background: currentColor; +} diff --git a/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow-top-left.css b/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow_circle.css similarity index 67% rename from mirzaev/arming_bot/system/public/themes/default/css/icons/arrow-top-left.css rename to mirzaev/arming_bot/system/public/themes/default/css/icons/arrow_circle.css index c05805a..3920757 100755 --- a/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow-top-left.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow_circle.css @@ -1,6 +1,6 @@ @charset "UTF-8"; -i.icon.arrow.top.left { +i.icon.arrow.circle { box-sizing: border-box; position: relative; display: block; @@ -10,28 +10,27 @@ i.icon.arrow.top.left { border-radius: 20px; } -i.icon.arrow.top.left::after, -i.icon.arrow.top.left::before { +i.icon.arrow.circle::after, +i.icon.arrow.circle::before { content: ""; display: block; box-sizing: border-box; position: absolute; -} - -i.icon.arrow.top.left::after { - width: 10px; - height: 2px; - background: currentColor; - transform: rotate(45deg); - bottom: 8px; right: 4px; } -i.icon.arrow.top.left::before { +i.icon.arrow.circle::after { width: 6px; height: 6px; - left: 4px; - top: 4px; border-top: 2px solid; - border-left: 2px solid; + border-right: 2px solid; + transform: rotate(45deg); + bottom: 6px; +} + +i.icon.arrow.circle::before { + width: 10px; + height: 2px; + bottom: 8px; + background: currentColor; } diff --git a/mirzaev/arming_bot/system/public/themes/default/css/main.css b/mirzaev/arming_bot/system/public/themes/default/css/main.css index aca3222..0eea4ea 100755 --- a/mirzaev/arming_bot/system/public/themes/default/css/main.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/main.css @@ -175,6 +175,11 @@ input { font-family: "Kabrio"; } +.cost.currency:after { + content: var(--currency); + margin-left: var(--currency-offset, 0.1rem); +} + .unselectable { -webkit-touch-callout: none; -webkit-user-select: none; diff --git a/mirzaev/arming_bot/system/public/themes/default/css/menu.css b/mirzaev/arming_bot/system/public/themes/default/css/menu.css index 6e87042..40b3a72 100755 --- a/mirzaev/arming_bot/system/public/themes/default/css/menu.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/menu.css @@ -3,6 +3,7 @@ header>nav#menu { container-type: inline-size; container-name: menu; + margin-bottom: 1rem; width: var(--width); min-height: 3rem; display: flex; @@ -15,9 +16,11 @@ header>nav#menu { header>nav#menu>a[type="button"] { height: 3rem; padding: unset; - border-radius: 1.375rem; - color: var(--unsafe-color, var(--tg-theme-button-text-color)); - background-color: var(--unsafe-background-color, var(--tg-theme-button-color)); + border-radius: 1.375rem; + /* color: var(--unsafe-color, var(--tg-theme-button-text-color)); + background-color: var(--unsafe-background-color, var(--tg-theme-button-color)); */ + color: var(--tg-theme-button-text-color); + background-color: var(--tg-theme-button-color); } header>nav#menu>a[type=button]>:first-child { diff --git a/mirzaev/arming_bot/system/views/templater.php b/mirzaev/arming_bot/system/views/templater.php index df8138e..bb62902 100755 --- a/mirzaev/arming_bot/system/views/templater.php +++ b/mirzaev/arming_bot/system/views/templater.php @@ -75,7 +75,7 @@ final class templater extends controller implements ArrayAccess if (!empty($account?->status())) $this->twig->addGlobal('account', $account); $this->twig->addGlobal('language', $language = $account?->language ?? $session?->buffer['language'] ?? $settings?->language ?? language::en); $this->twig->addGlobal('currency', $currency = $account?->currency ?? $session?->buffer['currency'] ?? $settings?->currency ?? currency::usd); - $this->twig->addGlobal('cart', $cart->all(language: $language, currency: $currency)); + $this->twig->addGlobal('cart', ['summary' => $cart->summary(currency: $currency), 'products' => $cart->products(language: $language, currency: $currency)]); // Initialize function of dimensions formatting $this->twig->addFunction( diff --git a/mirzaev/arming_bot/system/views/themes/default/cart/elements/products.html b/mirzaev/arming_bot/system/views/themes/default/cart/elements/products.html index 91296e0..af4ae4d 100755 --- a/mirzaev/arming_bot/system/views/themes/default/cart/elements/products.html +++ b/mirzaev/arming_bot/system/views/themes/default/cart/elements/products.html @@ -1,44 +1,48 @@ {% macro card(product, amount) %}
+ data-product-amount="{{ amount }}" {% if amount> 0 %} style="--hue-rotate-offset: {{ amount }}0deg;"{% endif %}> {{ product.name }} -
-

+

+
{{ product.name | length > 65 ? product.name | slice(0, 65) ~ '...' : product.name }} -

-

+ + +

+
{% for characteristic in [product.brand, format_dimensions(product.dimensions.x, product.dimensions.y, product.dimensions.z, ' '), product.weight ~ (language.name == 'ru' ? 'г' : 'g')] %} + {% if characteristic is not empty %} {{ characteristic | length > 30 ? characteristic | slice(0, 30) ~ '...' : characteristic }} + {% endif %} {% endfor %} -

-

- {{ product.cost }} - {{ currency.symbol }} -

-
-
-
- -
-
0 %} style="--hue-rotate-offset: {{ amount }}0deg;"{% endif %}> - - - +
{% endmacro %} -{% if cart is not empty %} +{% if cart.products is not empty %}
- {% for entry in cart %} - {{ _self.card(entry.product, amount) }} + {% for product in cart.products %} + {{ _self.card(product.document, product.amount) }} {% endfor %}
{% endif %} diff --git a/mirzaev/arming_bot/system/views/themes/default/cart/elements/summary.html b/mirzaev/arming_bot/system/views/themes/default/cart/elements/summary.html new file mode 100755 index 0000000..4b8a779 --- /dev/null +++ b/mirzaev/arming_bot/system/views/themes/default/cart/elements/summary.html @@ -0,0 +1,11 @@ +{% if cart.products is not empty %} +
+
+ {{ cart.summary.amount ?? 0 }} + {{ language.name == "ru" ? "товаров на сумму" : "products worth" }} + {{ cart.summary.cost ?? 0 }} + +
+
+{% endif %} diff --git a/mirzaev/arming_bot/system/views/themes/default/cart/page.html b/mirzaev/arming_bot/system/views/themes/default/cart/page.html index 7d2e197..02d806a 100755 --- a/mirzaev/arming_bot/system/views/themes/default/cart/page.html +++ b/mirzaev/arming_bot/system/views/themes/default/cart/page.html @@ -8,10 +8,12 @@ + {% endblock %} {% block main %}

{{ h2 }}

+{% include "/themes/default/cart/elements/summary.html" %} {% include "/themes/default/cart/elements/products.html" %} {% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html index ecd08ad..8fc7089 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html @@ -3,7 +3,7 @@ product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %} {% set amount = cart[product.getId()].amount ?? 0 %}
+ data-product-amount="{{ amount }}"{% if amount > 0 %} style="--hue-rotate-offset: {{ amount }}0deg;"{% endif %}> {{ product.name }} @@ -11,11 +11,11 @@ product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' % {{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}

-
0 %} style="--hue-rotate-offset: {{ amount }}0deg;"{% endif %}> +
diff --git a/mirzaev/arming_bot/system/views/themes/default/head.html b/mirzaev/arming_bot/system/views/themes/default/head.html index ff7af2e..148a688 100755 --- a/mirzaev/arming_bot/system/views/themes/default/head.html +++ b/mirzaev/arming_bot/system/views/themes/default/head.html @@ -17,4 +17,9 @@ + {% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/index.html b/mirzaev/arming_bot/system/views/themes/default/index.html index 07bfa86..f0f2603 100755 --- a/mirzaev/arming_bot/system/views/themes/default/index.html +++ b/mirzaev/arming_bot/system/views/themes/default/index.html @@ -14,8 +14,8 @@ {% endblock %} {% block body %} -{{ block('connection_body') }} -{{ block('account_body') }} + + {{ block('header') }}
{% block main %} diff --git a/mirzaev/arming_bot/system/views/themes/default/menu.html b/mirzaev/arming_bot/system/views/themes/default/menu.html index a2bd1a5..6323734 100644 --- a/mirzaev/arming_bot/system/views/themes/default/menu.html +++ b/mirzaev/arming_bot/system/views/themes/default/menu.html @@ -2,7 +2,7 @@ {% for button in menu %} {% if button.icon %} - + {% endif %} {% endfor %} {% endblock %} @@ -11,10 +11,12 @@