Compare commits

..

2 Commits

29 changed files with 1140 additions and 524 deletions

View File

@ -6,8 +6,10 @@ namespace mirzaev\arming_bot\controllers;
// Files of the project // Files of the project
use mirzaev\arming_bot\controllers\core, use mirzaev\arming_bot\controllers\core,
mirzaev\arming_bot\models\cart as model,
mirzaev\arming_bot\models\product, mirzaev\arming_bot\models\product,
mirzaev\arming_bot\models\cart as model; mirzaev\arming_bot\models\menu,
mirzaev\arming_bot\models\enumerations\language;
// Framework for ArangoDB // Framework for ArangoDB
use mirzaev\arangodb\document; use mirzaev\arangodb\document;
@ -28,9 +30,74 @@ final class cart extends core
protected array $errors = [ protected array $errors = [
'session' => [], 'session' => [],
'account' => [], 'account' => [],
'cart' => [] 'cart' => [],
'menu' => [],
]; ];
/**
* Cart
*
* @param array $parameters Parameters of the request (POST + GET)
*/
public function index(array $parameters = []): ?string
{
if (isset($menu)) {
//
} else {
// Not received ... menu
// 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.style.order ASC, d.created DESC, d._key DESC',
amount: 4,
parameters: ['language' => $this->language->name],
errors: $this->errors['menu']
);
}
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// GET request
// Exit (success)
return $this->view->render('cart/page.html', [
'h2' => $this->language === language::ru ? 'Корзина' : 'Cart' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
]);
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST request
// Initializing a 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(
[
'main' => '',
'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;
}
/** /**
* Product * Product
* *
@ -51,7 +118,7 @@ final class cart extends core
// Validating @todo add throwing errors // Validating @todo add throwing errors
$identifier = null; $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)) { if (isset($identifier)) {
// Received and validated identfier of the product // Received and validated identfier of the product
@ -76,7 +143,7 @@ final class cart extends core
// Validating @todo add throwing errors // Validating @todo add throwing errors
$type = null; $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)) { if (isset($type)) {
// Received and validated type of action with the product // Received and validated type of action with the product
@ -106,7 +173,7 @@ final class cart extends core
// Validating @todo add throwing errors // Validating @todo add throwing errors
$_amount = null; $_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)) { if (isset($_amount)) {
// Received and validated amount parameter for action with the product // Received and validated amount parameter for action with the product
@ -114,20 +181,26 @@ final class cart extends core
if ($type === 'write') { if ($type === 'write') {
// Increase amount of the product in the cart // Increase amount of the product in the cart
if (101 > $amount += $_amount) { if ($amount + $_amount < 101) {
// Validated amount to wrting // Validated amount to wrting
// Writing the product to the cart // Writing the product to the cart
$this->cart->write(product: $product, amount: $_amount, errors: $this->errors['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') { } else if ($type === 'delete') {
// Decrease amount of the product in the cart // Decrease amount of the product in the cart
if (-1 < $amount -= $_amount) { if ($amount - $_amount > -1) {
// Validated amount to deleting // Validated amount to deleting
// Deleting the product from the cart // Deleting the product from the cart
$this->cart->delete(product: $product, amount: $_amount, errors: $this->errors['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') { } else if ($type === 'set') {
// Set amount of the product in the cart // Set amount of the product in the cart
@ -188,4 +261,49 @@ final class cart extends core
// Exit (fail) // Exit (fail)
return null; 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;
}
} }

View File

@ -43,22 +43,22 @@ final class catalog extends core
public function index(array $parameters = []): ?string public function index(array $parameters = []): ?string
{ {
// validating // 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)) { if (isset($product)) {
// received and validated identifier of the product // Received and validated identifier of the product
// Search for the product data and write to the buffer of global variables of view templater // Search for the product data and write to the buffer of global variables of view templater
$this->view->product = product::read( $this->view->product = product::read(
filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true", filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true",
sort: 'd.created DESC', sort: 'd.created DESC',
amount: 1, amount: 1,
return: '{name: d.name.@language, description: d.description.@language, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, cost: d.cost.@currency, images: d.images[*].storage}', return: '{identifier: d.identifier, name: d.name.@language, description: d.description.@language, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, cost: d.cost.@currency, images: d.images[*].storage}',
language: $this->language, language: $this->language,
currency: $this->currency, currency: $this->currency,
parameters: ['identifier' => $product], parameters: ['identifier' => $product],
errors: $this->errors['catalog'] errors: $this->errors['catalog']
)[0]?->getAll() ?? null; )?->getAll();
} }
// Intializing buffer of query parameters // Intializing buffer of query parameters
@ -68,7 +68,7 @@ final class catalog extends core
$_filters = 'd.deleted != true && d.hidden != true'; $_filters = 'd.deleted != true && d.hidden != true';
// Validating // 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)) { if (isset($brand)) {
// Received and validated filter by 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'; $_sort = 'd.position ASC, d.name ASC, d.created DESC';
// Validating // 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)) { if (isset($sort)) {
// Received and validated sort // Received and validated sort
@ -181,7 +181,7 @@ final class catalog extends core
} }
// Validating @todo add throwing errors // 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)) { if (isset($text)) {
// Received and validated text // Received and validated text
@ -234,7 +234,7 @@ final class catalog extends core
} }
// Validating // 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)) { if (isset($category)) {
// Received and validated identifier of the category // Received and validated identifier of the category
@ -296,19 +296,23 @@ final class catalog extends core
) ?? null; ) ?? null;
} }
// Search among products in the $category if (isset($brand) || (isset($this->view->products) && count($this->view->products) > 0)) {
if (isset($this->view->products) && count($this->view->products) > 0) { // Received and validated at least one of filters or amount of rendered products is more than 0
// Amount of rendered products is more than 0
// Search for filters and write to the buffer of global variables of view templater // Search for filters and write to the buffer of global variables of view templater
$this->view->filters = [ $this->view->filters = [
'brands' => product::collect( 'brands' => product::collect(
return: 'd.brand.@language', return: 'd.brand.@language',
products: array_map(fn(_document $document): string => $document->getId(), $this->view->products), products: array_map(fn(_document $document): string => $document->getId(), $this->view->products ?? []),
language: $this->language, language: $this->language,
errors: $this->errors['catalog'] errors: $this->errors['catalog']
) )
]; ];
}
// Search among products in the $category
if (isset($text) || isset($this->view->products) && count($this->view->products) > 0) {
// Amount of rendered products is more than 0
// Search for products and write to the buffer of global variables of view templater // Search for products and write to the buffer of global variables of view templater
$this->view->products = product::read( $this->view->products = product::read(
@ -333,7 +337,7 @@ final class catalog extends core
// Search for filters and write to the buffer of global variables of view templater // Search for filters and write to the buffer of global variables of view templater
$this->view->menu = menu::_read( $this->view->menu = menu::_read(
return: 'MERGE(d, { name: d.name.@language })', 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, amount: 4,
parameters: ['language' => $this->language->name], parameters: ['language' => $this->language->name],
errors: $this->errors['menu'] errors: $this->errors['menu']
@ -347,11 +351,16 @@ final class catalog extends core
// Initialized the product data // Initialized the product data
// Writing javascript code that has been executed after core and damper has been loaded // Writing javascript code that has been executed after core and damper has been loaded
$this->view->javascript = [<<<javascript $this->view->javascript = [
sprintf(
<<<javascript
if (typeof _window === 'undefined') { if (typeof _window === 'undefined') {
_window = setTimeout(() => core.catalog.product_system('$product'), 500); _window = setTimeout(() => core.catalog.product_system('%s'), 500);
} }
javascript] + ($this->view->javascript ?? []); javascript,
$this->view->product['identifier']
)
] + ($this->view->javascript ?? []);
} }
// Exit (success) // Exit (success)

View File

@ -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 * @return array|null Array with implementing objects of documents from ArangoDB, if found
*/ */
public function all( public function products(
?language $language = language::en, ?language $language = language::en,
?currency $currency = currency::usd, ?currency $currency = currency::usd,
array &$errors = [] array &$errors = []
@ -72,7 +72,7 @@ final class cart extends core implements document_interface, collection_interfac
RETURN { RETURN {
[d._id]: { [d._id]: {
amount, amount,
product: MERGE(d, { document: MERGE(d, {
name: d.name.@language, name: d.name.@language,
description: d.description.@language, description: d.description.@language,
compatibility: d.compatibility.@language, compatibility: d.compatibility.@language,
@ -124,6 +124,64 @@ final class cart extends core implements document_interface, collection_interfac
return null; return null;
} }
/**
* Search for summary of all products
*
* Search for summary of all products in the cart
*
* @param currency|null $currency Currency
* @param array &$errors Registry of errors
*
* @return array|null Array with implementing objects of documents from ArangoDB, if found
*/
public function summary(
?currency $currency = currency::usd,
array &$errors = []
): ?array {
try {
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) {
if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) {
// Initialized collections
// Search for all products in the cart
$result = @collection::execute(
<<<AQL
FOR v IN 1..1 INBOUND @cart GRAPH @graph
FILTER IS_SAME_COLLECTION(@collection, v._id)
COLLECT AGGREGATE amount = COUNT(v), cost = SUM(v.cost.@currency)
RETURN { amount, cost }
AQL,
[
'graph' => '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 * Count
* *

View File

@ -205,7 +205,7 @@ final class product extends core implements document_interface, collection_inter
); );
if ($amount === 1 && $result instanceof _document) { if ($amount === 1 && $result instanceof _document) {
// Found product @todo need to rebuild this // Found the product @todo need to rebuild this
// Initializing the object // Initializing the object
$product = new static; $product = new static;
@ -219,10 +219,12 @@ final class product extends core implements document_interface, collection_inter
// Exit (success) // Exit (success)
return $product; return $product;
} else throw new exception('Class ' . static::class . ' does not implement a document from ArangoDB'); } else throw new exception('Class ' . static::class . ' does not implement a document from ArangoDB');
} } else if (!empty($result)) {
// Found products
// Exit (success) // Exit (success)
return is_array($result) ? $result : [$result]; return is_array($result) ? $result : [$result];
}
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
} catch (exception $e) { } catch (exception $e) {
// Writing to the registry of errors // Writing to the registry of errors

View File

@ -43,6 +43,7 @@ $router
->write('/cart', 'cart', 'index', 'GET') ->write('/cart', 'cart', 'index', 'GET')
->write('/cart', 'cart', 'index', 'POST') ->write('/cart', 'cart', 'index', 'POST')
->write('/cart/product', 'cart', 'product', 'POST') ->write('/cart/product', 'cart', 'product', 'POST')
->write('/cart/summary', 'cart', 'summary', 'POST')
->write('/account/write', 'account', 'write', 'POST') ->write('/account/write', 'account', 'write', 'POST')
->write('/session/write', 'session', 'write', 'POST') ->write('/session/write', 'session', 'write', 'POST')
->write('/session/connect/telegram', 'session', 'telegram', 'POST') ->write('/session/connect/telegram', 'session', 'telegram', 'POST')

View File

@ -39,17 +39,26 @@ import("/js/core.js").then(() =>
* *
* Toggle the product in the cart (interface) * Toggle the product in the cart (interface)
* *
* @param {HTMLElement} button Button of the product <a> * @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) * @param {bool} force Ignore the damper? (false)
* *
* @return {bool} True if an error occurs to continue the event execution * @return {bool} True if an error occurs to continue the event execution
*/ */
static toggle(button, force = false) { static toggle(element, product, remove = false, force = false) {
// Blocking the button // Blocking the element
button.setAttribute("disabled", "true"); element.setAttribute("disabled", "true");
// Execute under damper // Execute under damper
this.toggle_damper(button, force); this.toggle_damper(
element,
product,
"toggle",
undefined,
remove,
force,
);
// Exit (success) // Exit (success)
return false; return false;
@ -60,109 +69,44 @@ import("/js/core.js").then(() =>
* *
* Toggle the product in the cart (damper) * Toggle the product in the cart (damper)
* *
* @param {HTMLElement} button Button of the product <a> * @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) * @param {bool} force Ignore the damper? (false)
* *
* @return {void} * @return {Promise}
*/ */
static toggle_damper = core.damper( static toggle_damper = core.damper(
(...variables) => this.toggle_system(...variables), (...variables) => this.product(...variables).then(this.summary),
300, 300,
2, 6,
); );
/**
* Toggle
*
* Toggle the product in the cart (system)
*
* @param {HTMLElement} button Button of the product <a>
*
* @return {Promise} Request to the server
*
* @todo add unblocking button by timer + everywhere
*/
static async toggle_system(button) {
if (button instanceof HTMLElement) {
// Validated
// Initializing of the wrap of buttons
const wrap = button.parentElement;
// Initializing of the product
const product = wrap.parentElement;
// 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
wrap.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 <span> element
const amount = wrap.querySelector(
'span[data-product-button-text="amount"]',
);
if (amount instanceof HTMLElement) {
// Initialized the amount <span> element
// Writing amount of the product in the cart
amount.innerText = json.amount;
}
}
});
}
}
}
/** /**
* Write * Write
* *
* Write the product in the cart (interface) * Write the product in the cart (interface)
* *
* @param {HTMLElement} button Button of the product <a> * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
* @param {HTMLElement} product The product
* @param {number} amount Amount of writings * @param {number} amount Amount of writings
* @param {bool} remove Remove the product element if json.amount === 0?
* @param {bool} force Ignore the damper? (false) * @param {bool} force Ignore the damper? (false)
* *
* @return {bool} True if an error occurs to continue the event execution * @return {bool} True if an error occurs to continue the event execution
*/ */
static write(button, amount = 1, force = false) { static write(
// Blocking the button element,
button.setAttribute("disabled", "true"); product,
amount = 1,
remove = false,
force = false,
) {
// Blocking the element
element.setAttribute("disabled", "true");
// Execute under damper // Execute under damper
this.write_damper(button, amount, force); this.write_damper(element, product, "write", amount, remove, force);
// Exit (success) // Exit (success)
return false; return false;
@ -173,116 +117,52 @@ import("/js/core.js").then(() =>
* *
* Write the product in the cart (damper) * Write the product in the cart (damper)
* *
* @param {HTMLElement} button Button of the product <a> * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
* @param {HTMLElement} product The product
* @param {number} amount Amount of writings * @param {number} amount Amount of writings
* @param {bool} remove Remove the product element if json.amount === 0?
* @param {bool} force Ignore the damper? (false) * @param {bool} force Ignore the damper? (false)
* *
* @return {void} * @return {Promise}
*/ */
static write_damper = core.damper( static write_damper = core.damper(
(...variables) => this.write_system(...variables), (...variables) => this.product(...variables).then(this.summary),
300, 300,
3, 6,
); );
/**
* Write
*
* Write the product in the cart (system)
*
* @param {HTMLElement} button Button of the product <a>
* @param {number} amount Amount of writings
*
* @return {Promise} Request to the server
*
* @todo add unblocking button by timer + everywhere
*/
static async write_system(button, amount = 1) {
if (
button instanceof HTMLElement &&
typeof amount === "number" &&
amount > -1 &&
amount < 100
) {
// Validated
// Initializing of the wrap of buttons
const wrap = button.parentElement;
// Initializing of the product
const product = wrap.parentElement;
// 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
wrap.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 <span> element
const amount = wrap.querySelector(
'span[data-product-button-text="amount"]',
);
if (amount instanceof HTMLElement) {
// Initialized the amount <span> element
// Writing amount of the product in the cart
amount.innerText = json.amount;
}
}
});
}
}
}
/** /**
* Delete * Delete
* *
* Delete the product from the cart (interface) * Delete the product from the cart (interface)
* *
* @param {HTMLElement} button Button of the product <a> * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
* @param {HTMLElement} product The product
* @param {number} amount Amount of deletings * @param {number} amount Amount of deletings
* @param {bool} remove Remove the product element if json.amount === 0?
* @param {bool} force Ignore the damper? (false) * @param {bool} force Ignore the damper? (false)
* *
* @return {bool} True if an error occurs to continue the event execution * @return {bool} True if an error occurs to continue the event execution
*/ */
static delete(button, amount = 1, force = false) { static delete(
// Blocking the button element,
button.setAttribute("disabled", "true"); product,
amount = 1,
remove = false,
force = false,
) {
// Blocking the element
element.setAttribute("disabled", "true");
// Execute under damper // Execute under damper
this.delete_damper(button, amount, force); this.delete_damper(
element,
product,
"delete",
amount,
remove,
force,
);
// Exit (success) // Exit (success)
return false; return false;
@ -293,116 +173,44 @@ import("/js/core.js").then(() =>
* *
* Delete the product from the cart (damper) * Delete the product from the cart (damper)
* *
* @param {HTMLElement} button Button of the product <a> * @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
* @param {number} amount Amount of deletings * @param {number} amount Amount of deletings
* @param {bool} remove Remove the product element if json.amount === 0?
* @param {bool} force Ignore the damper? (false) * @param {bool} force Ignore the damper? (false)
* *
* @return {void} * @return {Promise}
*/ */
static delete_damper = core.damper( static delete_damper = core.damper(
(...variables) => this.delete_system(...variables), (...variables) => this.product(...variables).then(this.summary),
300, 300,
3, 6,
); );
/**
* Delete
*
* Delete the product from the cart (system)
*
* @param {HTMLElement} button Button of the product <a>
* @param {number} amount Amount of deletings
*
* @return {Promise} Request to the server
*
* @todo add unblocking button by timer + everywhere
*/
static async delete_system(button, amount = 1) {
if (
button instanceof HTMLElement &&
typeof amount === "number" &&
amount > 0 &&
amount < 101
) {
// Validated
// Initializing of the wrap of buttons
const wrap = button.parentElement;
// Initializing of the product
const product = wrap.parentElement;
// 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
wrap.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 <span> element
const amount = wrap.querySelector(
'span[data-product-button-text="amount"]',
);
if (amount instanceof HTMLElement) {
// Initialized the amount <span> element
// Writing amount of the product in the cart
amount.innerText = json.amount;
}
}
});
}
}
}
/** /**
* Set * Set
* *
* Set amount of the product in the cart (interface) * Set amount of the product in the cart (interface)
* *
* @param {HTMLElement} button Button of the product <a> * @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 {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) * @param {bool} force Ignore the damper? (false)
* *
* @return {bool} True if an error occurs to continue the event execution * @return {bool} True if an error occurs to continue the event execution
*/ */
static set(button, amount = 1, force = false) { static set(
// Blocking the button element,
button.setAttribute("disabled", "true"); product,
amount = 1,
remove = false,
force = false,
) {
// Blocking the element
element.setAttribute("disabled", "true");
// Execute under damper // Execute under damper
this.set_damper(button, amount, force); this.set_damper(element, product, "set", amount, remove, force);
// Exit (success) // Exit (success)
return false; return false;
@ -413,56 +221,97 @@ import("/js/core.js").then(() =>
* *
* Set the product in the cart (damper) * Set the product in the cart (damper)
* *
* @param {HTMLElement} button Button of the product <a> * @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 {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) * @param {bool} force Ignore the damper? (false)
* *
* @return {void} * @return {Promise}
*/ */
static set_damper = core.damper( static set_damper = core.damper(
(...variables) => this.set_system(...variables), (...parameters) => this.product(...parameters).then(this.summary),
300, 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 <a> * @param {HTMLButtonElement|HTMLInputElement} element Handler element of the product
* @param {number} amount Amount of the product in the cart to be setted * @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 * @return {Promise|null}
*
* @todo add unblocking button by timer + everywhere
*/ */
static async set_system(button, amount = 1) { static async product(
element,
product,
type,
amount = null,
remove = false,
resolve = () => {},
reject = () => {},
) {
if ( if (
button instanceof HTMLElement && (element instanceof HTMLButtonElement ||
typeof amount === "number" && element instanceof HTMLInputElement) &&
amount > -1 && product instanceof HTMLElement
amount < 101
) { ) {
// Validated // Validated
// Initializing of the wrap of buttons // Initializing the buffer of request body
const wrap = button.parentElement; let request = "";
// Initializing of the product
const product = wrap.parentElement;
// Initializing of identifier of the product // Initializing of identifier of the product
const identifier = product.getAttribute( const identifier = +product.getAttribute(
"data-product-identifier", "data-product-identifier",
); );
if (typeof identifier === "string" && identifier.length > 0) { if (typeof identifier === "number") {
// Validated identifier // Validated identifier
// Writing to the buffer of request body
request += "&identifier=" + identifier;
if (
type === "toggle" ||
type === "write" ||
type === "delete" ||
type === "set"
) {
// Validated type
// Writing to the buffer of request body
request += "&type=" + type;
if (
(type === "toggle" &&
typeof amount === "undefined") ||
(type === "set" &&
amount === 0 ||
amount === 100) ||
typeof amount === "number" &&
amount > 0 &&
amount < 100
) {
// Validated amount
if (type !== "toggle") {
// Not a toggle request
// Writing to the buffer of request body
request += "&amount=" + amount;
}
// Request
return await core.request( return await core.request(
"/cart/product", "/cart/product",
`identifier=${identifier}&type=set&amount=${amount}`, request,
) )
.then((json) => { .then((json) => {
if ( if (
@ -474,13 +323,21 @@ import("/js/core.js").then(() =>
} else { } else {
// Success (not received errors) // Success (not received errors)
// Unblocking the button if (remove && json.amount === 0) {
button.removeAttribute("disabled"); // 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 // Writing offset of hue-rotate to indicate that the product is in the cart
wrap.style.setProperty( product.style.setProperty(
"--hue-rotate-offset", "--hue-rotate-offset",
json.amount + '0deg', json.amount + "0deg",
); );
// Writing attribute with amount of the product in the cart // Writing attribute with amount of the product in the cart
@ -490,21 +347,82 @@ import("/js/core.js").then(() =>
); );
// Initializing the amount <span> element // Initializing the amount <span> element
const amount = wrap.querySelector( const amounts = product.querySelectorAll(
'span[data-product-button-text="amount"]', '[data-product-parameter="amount"]',
); );
if (amount instanceof HTMLElement) { for (const amount of amounts) {
// Initialized the amount <span> element // Iterating over an amount elements
if (amount instanceof HTMLInputElement) {
// The <input> element
// Writing amount of the product in the cart
amount.value = json.amount;
} else {
// Not the <input> element
// Writing amount of the product in the cart // Writing amount of the product in the cart
amount.innerText = json.amount; 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 <span> element
const amount = document.getElementById("amount");
// Initializing the summary cost <span> 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;
}
}
});
}
}; };
} }
} }

View File

@ -50,8 +50,9 @@ import("/js/core.js").then(() =>
this.element_search.getElementsByTagName("input")[0]; this.element_search.getElementsByTagName("input")[0];
// Categories <section> // Categories <section>
static element_categories = static element_categories = document.getElementById(
document.getElementById("categories"); "categories",
);
// Filters <section> // Filters <section>
static element_filters = document.getElementById("filters"); static element_filters = document.getElementById("filters");
@ -75,7 +76,7 @@ import("/js/core.js").then(() =>
[ [
"category", "category",
new URLSearchParams(document.location.search).get( new URLSearchParams(document.location.search).get(
"category" "category",
), ),
], ],
[ [
@ -128,6 +129,12 @@ import("/js/core.js").then(() =>
if (event.keyCode === 13) { if (event.keyCode === 13) {
// Executed by "enter" button // Executed by "enter" button
// Blocking the search <input> element
this.element_search_input.setAttribute(
"disabled",
true,
);
// Execute // Execute
this.search_system(); this.search_system();
} else if (this.element_search_input.value.length < 3) { } else if (this.element_search_input.value.length < 3) {
@ -169,7 +176,7 @@ import("/js/core.js").then(() =>
static search_damper = core.damper( static search_damper = core.damper(
(...variables) => this.search_system(...variables), (...variables) => this.search_system(...variables),
1400, 1400,
1 1,
); );
/** /**
@ -182,14 +189,11 @@ import("/js/core.js").then(() =>
* @todo add animations of errors * @todo add animations of errors
*/ */
static async search_system() { static async search_system() {
// Blocking the search <input> element
this.element_search_input.setAttribute("disabled", true);
// @todo add timeout to unblock // @todo add timeout to unblock
// Initialize the buffer of URN parameters // Initialize the buffer of URN parameters
const parameters = new URLSearchParams( const parameters = new URLSearchParams(
window.location.search window.location.search,
); );
// Iterate parameters // Iterate parameters
@ -250,7 +254,7 @@ import("/js/core.js").then(() =>
// Writing the title <h2> element before the first element in the <main> element // Writing the title <h2> element before the first element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_title, this.element_title,
first first,
); );
} }
} }
@ -289,14 +293,16 @@ import("/js/core.js").then(() =>
this.element_search.outerHTML = json.search; this.element_search.outerHTML = json.search;
// Reinitializing the parameter with the search <search> element // Reinitializing the parameter with the search <search> element
this.element_search = this.element_search = document.getElementById(
document.getElementById("search"); "search",
);
} else { } else {
// Not found the search <search> element // Not found the search <search> element
// Initialize the search <search> element // Initialize the search <search> element
this.element_search = this.element_search = document.createElement(
document.createElement("search"); "search",
);
if (core.main instanceof HTMLElement) { if (core.main instanceof HTMLElement) {
// Found the <main> element // Found the <main> element
@ -307,7 +313,7 @@ import("/js/core.js").then(() =>
// Writing the search <search> element after the title <h2> element in the <main> element // Writing the search <search> element after the title <h2> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_search, this.element_search,
this.element_title.nextSibling this.element_title.nextSibling,
); );
} else { } else {
// Not initialized the title <h2> element in the <main> element // Not initialized the title <h2> element in the <main> element
@ -321,7 +327,7 @@ import("/js/core.js").then(() =>
// Writing the search <search> element before the first element in the <main> element // Writing the search <search> element before the first element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_search, this.element_search,
first first,
); );
} }
} }
@ -330,13 +336,14 @@ import("/js/core.js").then(() =>
this.element_search.outerHTML = json.search; this.element_search.outerHTML = json.search;
// Reinitializing the parameter with the search <search> element // Reinitializing the parameter with the search <search> element
this.element_search = this.element_search = document.getElementById(
document.getElementById("search"); "search",
);
// Reinitializing the parameter with the search <input> element // Reinitializing the parameter with the search <input> element
this.element_search_input = this.element_search_input =
this.element_search.getElementsByTagName( this.element_search.getElementsByTagName(
"input" "input",
)[0]; )[0];
} }
} }
@ -371,14 +378,16 @@ import("/js/core.js").then(() =>
this.element_categories.outerHTML = json.categories; this.element_categories.outerHTML = json.categories;
// Reinitializing the parameter with the categories <section> element // Reinitializing the parameter with the categories <section> element
this.element_categories = this.element_categories = document.getElementById(
document.getElementById("categories"); "categories",
);
} else { } else {
// Not found the categories <section> element // Not found the categories <section> element
// Initialize the categories <section> element // Initialize the categories <section> element
this.element_categories = this.element_categories = document.createElement(
document.createElement("section"); "section",
);
if (core.main instanceof HTMLElement) { if (core.main instanceof HTMLElement) {
// Found the <main> element // Found the <main> element
@ -389,7 +398,7 @@ import("/js/core.js").then(() =>
// Writing the categories <section> element after the search <search> element in the <main> element // Writing the categories <section> element after the search <search> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_categories, this.element_categories,
this.element_search.nextSibling this.element_search.nextSibling,
); );
} else if ( } else if (
this.element_title instanceof HTMLElement this.element_title instanceof HTMLElement
@ -399,7 +408,7 @@ import("/js/core.js").then(() =>
// Writing the categories <section> element after the title <h2> element in the <main> element // Writing the categories <section> element after the title <h2> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_categories, this.element_categories,
this.element_title.nextSibling this.element_title.nextSibling,
); );
} else { } else {
// Not initialized the title <h2> element in the <main> element // Not initialized the title <h2> element in the <main> element
@ -413,7 +422,7 @@ import("/js/core.js").then(() =>
// Writing the categories <section> before the first element in the <main> element // Writing the categories <section> before the first element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_categories, this.element_categories,
first first,
); );
} }
} }
@ -423,8 +432,9 @@ import("/js/core.js").then(() =>
json.categories; json.categories;
// Reinitializing the parameter with the categories <section> element // Reinitializing the parameter with the categories <section> element
this.element_categories = this.element_categories = document.getElementById(
document.getElementById("categories"); "categories",
);
} }
} }
} else { } else {
@ -454,14 +464,16 @@ import("/js/core.js").then(() =>
this.element_filters.outerHTML = json.filters; this.element_filters.outerHTML = json.filters;
// Reinitializing the parameter with the filters <section> element // Reinitializing the parameter with the filters <section> element
this.element_filters = this.element_filters = document.getElementById(
document.getElementById("filters"); "filters",
);
} else { } else {
// Not found the filters <section> element // Not found the filters <section> element
// Initialize the filters <section> element // Initialize the filters <section> element
this.element_filters = this.element_filters = document.createElement(
document.createElement("section"); "section",
);
if (core.main instanceof HTMLElement) { if (core.main instanceof HTMLElement) {
// Found the <main> element // Found the <main> element
@ -474,7 +486,7 @@ import("/js/core.js").then(() =>
// Writing the filters <section> element after the categories <section> element in the <main> element // Writing the filters <section> element after the categories <section> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_filters, this.element_filters,
this.element_categories.nextSibling this.element_categories.nextSibling,
); );
} else if ( } else if (
this.element_search instanceof HTMLElement this.element_search instanceof HTMLElement
@ -484,7 +496,7 @@ import("/js/core.js").then(() =>
// Writing the filters <section> element after the search <search> element in the <main> element // Writing the filters <section> element after the search <search> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_filters, this.element_filters,
this.element_search.nextSibling this.element_search.nextSibling,
); );
} else if ( } else if (
this.element_title instanceof HTMLElement this.element_title instanceof HTMLElement
@ -494,7 +506,7 @@ import("/js/core.js").then(() =>
// Writing the filters <section> element after the title <h2> element in the <main> element // Writing the filters <section> element after the title <h2> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_filters, this.element_filters,
this.element_title.nextSibling this.element_title.nextSibling,
); );
} else { } else {
// Not initialized the title <h2> element in the <main> element // Not initialized the title <h2> element in the <main> element
@ -508,7 +520,7 @@ import("/js/core.js").then(() =>
// Writing the filters <section> before the first element in the <main> element // Writing the filters <section> before the first element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_filters, this.element_filters,
first first,
); );
} }
} }
@ -517,8 +529,9 @@ import("/js/core.js").then(() =>
this.element_filters.outerHTML = json.filters; this.element_filters.outerHTML = json.filters;
// Reinitializing the parameter with the filters <section> element // Reinitializing the parameter with the filters <section> element
this.element_filters = this.element_filters = document.getElementById(
document.getElementById("filters"); "filters",
);
} }
} }
} else { } else {
@ -548,14 +561,16 @@ import("/js/core.js").then(() =>
this.element_sorting.outerHTML = json.sorting; this.element_sorting.outerHTML = json.sorting;
// Reinitializing the parameter with the sorting <section> element // Reinitializing the parameter with the sorting <section> element
this.element_sorting = this.element_sorting = document.getElementById(
document.getElementById("sorting"); "sorting",
);
} else { } else {
// Not found the sorting <section> element // Not found the sorting <section> element
// Initialize the sorting <section> element // Initialize the sorting <section> element
this.element_sorting = this.element_sorting = document.createElement(
document.createElement("section"); "section",
);
if (core.main instanceof HTMLElement) { if (core.main instanceof HTMLElement) {
// Found the <main> element // Found the <main> element
@ -566,7 +581,7 @@ import("/js/core.js").then(() =>
// Writing the sorting <section> element after the filters <section> element in the <main> element // Writing the sorting <section> element after the filters <section> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_sorting, this.element_sorting,
this.element_filters.nextSibling this.element_filters.nextSibling,
); );
} else if ( } else if (
this.element_categories instanceof HTMLElement this.element_categories instanceof HTMLElement
@ -576,7 +591,7 @@ import("/js/core.js").then(() =>
// Writing the sorting <section> element after the categories <section> element in the <main> element // Writing the sorting <section> element after the categories <section> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_sorting, this.element_sorting,
this.element_categories.nextSibling this.element_categories.nextSibling,
); );
} else if ( } else if (
this.element_search instanceof HTMLElement this.element_search instanceof HTMLElement
@ -586,7 +601,7 @@ import("/js/core.js").then(() =>
// Writing the sorting <section> element after the search <search> element in the <main> element // Writing the sorting <section> element after the search <search> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_sorting, this.element_sorting,
this.element_search.nextSibling this.element_search.nextSibling,
); );
} else if ( } else if (
this.element_title instanceof HTMLElement this.element_title instanceof HTMLElement
@ -596,7 +611,7 @@ import("/js/core.js").then(() =>
// Writing the sorting <section> element after the title <h2> element in the <main> element // Writing the sorting <section> element after the title <h2> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_sorting, this.element_sorting,
this.element_title.nextSibling this.element_title.nextSibling,
); );
} else { } else {
// Not initialized the title <h2> element in the <main> element // Not initialized the title <h2> element in the <main> element
@ -610,7 +625,7 @@ import("/js/core.js").then(() =>
// Writing the sorting <section> before the first element in the <main> element // Writing the sorting <section> before the first element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_sorting, this.element_sorting,
first first,
); );
} }
} }
@ -619,8 +634,9 @@ import("/js/core.js").then(() =>
this.element_sorting.outerHTML = json.sorting; this.element_sorting.outerHTML = json.sorting;
// Reinitializing the parameter with the sorting <section> element // Reinitializing the parameter with the sorting <section> element
this.element_sorting = this.element_sorting = document.getElementById(
document.getElementById("sorting"); "sorting",
);
} }
} }
} else { } else {
@ -650,14 +666,16 @@ import("/js/core.js").then(() =>
this.element_products.outerHTML = json.products; this.element_products.outerHTML = json.products;
// Reinitializing the parameter with the products <section> element // Reinitializing the parameter with the products <section> element
this.element_products = this.element_products = document.getElementById(
document.getElementById("products"); "products",
);
} else { } else {
// Not found the products <section> element element // Not found the products <section> element element
// Initialize the products <section> element // Initialize the products <section> element
this.element_products = this.element_products = document.createElement(
document.createElement("section"); "section",
);
if (core.main instanceof HTMLElement) { if (core.main instanceof HTMLElement) {
// Found the <main> element // Found the <main> element
@ -668,7 +686,7 @@ import("/js/core.js").then(() =>
// Writing the products <section> element after the sorting <section> element in the <main> element // Writing the products <section> element after the sorting <section> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_products, this.element_products,
this.element_sorting.nextSibling this.element_sorting.nextSibling,
); );
} else if ( } else if (
this.element_filters instanceof HTMLElement this.element_filters instanceof HTMLElement
@ -678,7 +696,7 @@ import("/js/core.js").then(() =>
// Writing the products <section> element after the filters <section> element in the <main> element // Writing the products <section> element after the filters <section> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_products, this.element_products,
this.element_filters.nextSibling this.element_filters.nextSibling,
); );
} else if ( } else if (
this.element_categories instanceof HTMLElement this.element_categories instanceof HTMLElement
@ -688,7 +706,7 @@ import("/js/core.js").then(() =>
// Writing the products <section> element after the categories <section> element in the <main> element // Writing the products <section> element after the categories <section> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_products, this.element_products,
this.element_categories.nextSibling this.element_categories.nextSibling,
); );
} else if ( } else if (
this.element_search instanceof HTMLElement this.element_search instanceof HTMLElement
@ -698,7 +716,7 @@ import("/js/core.js").then(() =>
// Writing the products <section> element after the search <search> element in the <main> element // Writing the products <section> element after the search <search> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_products, this.element_products,
this.element_search.nextSibling this.element_search.nextSibling,
); );
} else if ( } else if (
this.element_title instanceof HTMLElement this.element_title instanceof HTMLElement
@ -708,7 +726,7 @@ import("/js/core.js").then(() =>
// Writing the products <section> element after the title <h2> element in the <main> element // Writing the products <section> element after the title <h2> element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_products, this.element_products,
this.element_title.nextSibling this.element_title.nextSibling,
); );
} else { } else {
// Not initialized the title <h2> element in the <main> element // Not initialized the title <h2> element in the <main> element
@ -722,7 +740,7 @@ import("/js/core.js").then(() =>
// Writing the products <section> before the first element in the <main> element // Writing the products <section> before the first element in the <main> element
core.main.insertBefore( core.main.insertBefore(
this.element_products, this.element_products,
first first,
); );
} }
} }
@ -731,8 +749,9 @@ import("/js/core.js").then(() =>
this.element_products.outerHTML = json.products; this.element_products.outerHTML = json.products;
// Reinitializing the parameter with the products <section> element // Reinitializing the parameter with the products <section> element
this.element_products = this.element_products = document.getElementById(
document.getElementById("products"); "products",
);
} }
} }
} else { } else {
@ -761,8 +780,8 @@ import("/js/core.js").then(() =>
*/ */
static product(button, force = false) { static product(button, force = false) {
// Initializing identifier of the category // Initializing identifier of the category
const identifier = button.getAttribute( const identifier = button.parentElement.getAttribute(
"data-product-identifier" "data-product-identifier",
); );
// Execute under damper // Execute under damper
@ -785,7 +804,7 @@ import("/js/core.js").then(() =>
static product_damper = core.damper( static product_damper = core.damper(
(...variables) => this.product_system(...variables), (...variables) => this.product_system(...variables),
400, 400,
1 1,
); );
/** /**
@ -802,12 +821,17 @@ import("/js/core.js").then(() =>
// Validated identifier // Validated identifier
// Initialize the buffer of URN parameters @todo after opening window add to document.location.search // Initialize the buffer of URN parameters @todo after opening window add to document.location.search
const parameters = new URLSearchParams(); const parameters = new URLSearchParams(
document.location.search,
);
// Write parameter to the buffer of URN parameters // Write parameter to the buffer of URN parameters
parameters.set("product", identifier); parameters.set("product", identifier);
return await core.request("?" + parameters).then((json) => { // Intializing URI of the request
const uri = "?" + parameters;
return await core.request(uri).then((json) => {
if ( if (
json.errors !== null && json.errors !== null &&
typeof json.errors === "object" && typeof json.errors === "object" &&
@ -830,6 +854,9 @@ import("/js/core.js").then(() =>
) { ) {
// Received data of the product // Received data of the product
// Writing to the browser history
history.pushState({}, json.product.name, uri);
// Deinitializing of the old winow // Deinitializing of the old winow
const old = document.getElementById("window"); const old = document.getElementById("window");
if (old instanceof HTMLElement) old.remove(); if (old instanceof HTMLElement) old.remove();
@ -874,11 +901,9 @@ import("/js/core.js").then(() =>
{ {
let x = event.pageX || event.touches[0].pageX; let x = event.pageX || event.touches[0].pageX;
let y = event.pageY || event.touches[0].pageY; let y = event.pageY || event.touches[0].pageY;
let _x = let _x = images_from.pageX ||
images_from.pageX ||
images_from.touches[0].pageX; images_from.touches[0].pageX;
let _y = let _y = images_from.pageY ||
images_from.pageY ||
images_from.touches[0].pageY; images_from.touches[0].pageY;
if ( if (
@ -910,10 +935,10 @@ import("/js/core.js").then(() =>
const _close = (event) => { const _close = (event) => {
let x = event.pageX || event.touches[0].pageX; let x = event.pageX || event.touches[0].pageX;
let y = event.pageY || event.touches[0].pageY; let y = event.pageY || event.touches[0].pageY;
let _x = let _x = images_from.pageX ||
images_from.pageX || images_from.touches[0].pageX; images_from.touches[0].pageX;
let _y = let _y = images_from.pageY ||
images_from.pageY || images_from.touches[0].pageY; images_from.touches[0].pageY;
if ( if (
event.type === "touchstart" || event.type === "touchstart" ||
@ -1019,6 +1044,7 @@ import("/js/core.js").then(() =>
const cost = document.createElement("p"); const cost = document.createElement("p");
cost.classList.add("cost"); cost.classList.add("cost");
cost.innerText = json.product.cost + "р"; cost.innerText = json.product.cost + "р";
console.log(json.product.cost);
h3.append(name); h3.append(name);
exit.append(exit_icon); exit.append(exit_icon);
@ -1037,21 +1063,19 @@ import("/js/core.js").then(() =>
document.body.append(wrap); document.body.append(wrap);
// Reinitialize parameter // Reinitialize parameter
core.window = document.getElementById('window'); core.window = document.getElementById("window");
let width = 0; let width = 0;
let buffer; let buffer;
[...images.children].forEach( [...images.children].forEach(
(child) => (child) => (width += child.offsetWidth +
(width +=
child.offsetWidth +
(isNaN( (isNaN(
(buffer = parseFloat( buffer = parseFloat(
getComputedStyle(child).marginRight getComputedStyle(child).marginRight,
)) ),
) )
? 0 ? 0
: buffer)) : buffer)),
); );
// блокировка закрытия карточки // блокировка закрытия карточки
@ -1064,17 +1088,18 @@ import("/js/core.js").then(() =>
if ( if (
typeof event === "undefined" || typeof event === "undefined" ||
event.type !== "popstate" event.type !== "popstate"
) ) {
history.back(); history.back();
}
wrap.remove(); wrap.remove();
images.removeEventListener( images.removeEventListener(
"mousedown", "mousedown",
_images_from _images_from,
); );
images.removeEventListener( images.removeEventListener(
"touchstart", "touchstart",
_images_from _images_from,
); );
wrap.removeEventListener("mousedown", _from); wrap.removeEventListener("mousedown", _from);
wrap.removeEventListener("touchstart", _from); wrap.removeEventListener("touchstart", _from);
@ -1110,7 +1135,7 @@ import("/js/core.js").then(() =>
images.hotline = new core.hotline( images.hotline = new core.hotline(
json.product.identfier, json.product.identfier,
images images,
); );
images.hotline.step = -0.3; images.hotline.step = -0.3;
images.hotline.wheel = true; images.hotline.wheel = true;

View File

@ -28,6 +28,8 @@ import("/js/core.js").then(() => {
* @param {number} timeout Timer in milliseconds (ms) * @param {number} timeout Timer in milliseconds (ms)
* @param {number} force Argument number storing the status of enforcement execution (see @example) * @param {number} force Argument number storing the status of enforcement execution (see @example)
* *
* @return {Promise}
*
* @memberof core * @memberof core
* *
* @example * @example
@ -37,9 +39,13 @@ import("/js/core.js").then(() => {
* b, // 1 * b, // 1
* c, // 2 * c, // 2
* force = false, // 3 * force = false, // 3
* d // 4 * d, // 4
* resolve,
* reject
* ) => { * ) => {
* // Body of function * // Body of the function
*
* resolve();
* }, * },
* 500, * 500,
* 3, // 3 -> "force" argument * 3, // 3 -> "force" argument
@ -51,24 +57,39 @@ import("/js/core.js").then(() => {
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
core.damper = (func, timeout = 300, force) => { core.damper = (func, timeout = 300, force) => {
// Initializing of the timer // Declaring of the timer for executing the function
let timer; let timer;
return (...args) => { return ((...args) => {
return new Promise((resolve, reject) => {
// Deinitializing of the timer // Deinitializing of the timer
clearTimeout(timer); clearTimeout(timer);
if (typeof force === "number" && args[force]) { if (typeof force === "number" && args[force]) {
// Force execution (ignoring the timer) // Requested execution with ignoring the timer
// Deleting the force argument
if (typeof force === "number") delete args[force - 1];
// Writing promise handlers into the arguments variable
args.push(resolve, reject);
// Executing the function
func.apply(this, args); func.apply(this, args);
} else { } else {
// Normal execution // Normal execution
// Execute the handled function (entry into recursion) // 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); timer = setTimeout(() => func.apply(this, args), timeout);
} }
}; });
});
}; };
} }
} }

View File

@ -0,0 +1,139 @@
@charset "UTF-8";
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: 10rem;
display: flex;
border-radius: 0.75rem;
overflow: hidden;
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)>* {
transition: 0s;
}
main>section#products>article.product:not(:is(:hover, :focus))>* {
transition: 0.2s ease-out;
}
main>section#products>article.product>a {
display: contents;
}
main>section#products>article.product>a>img:first-of-type {
width: 5rem;
min-width: 5rem;
min-height: 100%;
object-fit: cover;
image-rendering: auto;
}
main>section#products>article.product>div {
width: 100%;
padding: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
overflow: hidden;
}
main>section#products>article.product>div>div {
display: inline-flex;
align-items: center;
}
main>section#products>article.product>div>div.head {
z-index: 50;
padding: 0 0.4rem;
gap: 1rem;
}
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>div.body>span {
padding: 0.2rem 0.4rem;
border-radius: 0.75rem;
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
main>section#products>article.product>div>div.footer {
z-index: 100;
padding: 0 0.4rem;
display: flex;
overflow: hidden;
}
main>section#products>article.product>div>div.footer>span[data-product-parameter] {
font-size: 0.8rem;
}
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>div.footer>input {
width: 2rem;
padding: 0 0.3rem;
text-align: center;
}

View File

@ -1,6 +1,6 @@
@charset "UTF-8"; @charset "UTF-8";
main>section:is(#products, #categories) { main>section#categories {
width: var(--width); width: var(--width);
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
@ -119,6 +119,7 @@ main>section#products>div.column>article.product>a {
main>section#products>div.column>article.product>a>img:first-of-type { main>section#products>div.column>article.product>a>img:first-of-type {
width: 100%; width: 100%;
height: 100%; height: 100%;
image-rendering: auto;
} }
main>section#products>div.column>article.product>a>img:first-of-type+* { main>section#products>div.column>article.product>a>img:first-of-type+* {
@ -155,12 +156,12 @@ main>section#products>div.column>article.product>div[data-product="buttons"]>but
flex-grow: 1; 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-button-text="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"]) { 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; display: none;
} }
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-button-text="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: '*'; content: '*';
margin: 0 0.2rem; margin: 0 0.2rem;
} }
@ -170,11 +171,11 @@ main>section#products>div.column>article.product[data-product-amount]:not([data-
} }
@container product-buttons (max-width: 200px) { @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-button-text="cost"], [data-product-button-text="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; display: none;
} }
main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-button-text="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; content: unset;
} }
} }

View File

@ -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;
}

View File

@ -1,6 +1,6 @@
@charset "UTF-8"; @charset "UTF-8";
i.icon.arrow.top.left { i.icon.arrow.circle {
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
display: block; display: block;
@ -10,28 +10,27 @@ i.icon.arrow.top.left {
border-radius: 20px; border-radius: 20px;
} }
i.icon.arrow.top.left::after, i.icon.arrow.circle::after,
i.icon.arrow.top.left::before { i.icon.arrow.circle::before {
content: ""; content: "";
display: block; display: block;
box-sizing: border-box; box-sizing: border-box;
position: absolute; position: absolute;
}
i.icon.arrow.top.left::after {
width: 10px;
height: 2px;
background: currentColor;
transform: rotate(45deg);
bottom: 8px;
right: 4px; right: 4px;
} }
i.icon.arrow.top.left::before { i.icon.arrow.circle::after {
width: 6px; width: 6px;
height: 6px; height: 6px;
left: 4px;
top: 4px;
border-top: 2px solid; 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;
} }

View File

@ -0,0 +1,61 @@
@charset "UTF-8";
i.icon.corner {
position: relative;
width: 22px;
height: 22px;
display: block;
box-sizing: border-box;
}
i.icon.corner::after,
i.icon.corner::before {
content: "";
position: absolute;
display: block;
box-sizing: border-box;
}
i.icon.corner.left::after,
i.icon.corner.left::before {
left: 3px;
}
i.icon.corner.right::after,
i.icon.corner.right::before {
right: 3px;
}
i.icon.corner::after {
bottom: 3px;
width: 8px;
height: 8px;
transform: rotate(45deg);
}
i.icon.corner.left::after {
border-left: 2px solid;
border-bottom: 2px solid;
}
i.icon.corner.right::after {
border-top: 2px solid;
border-right: 2px solid;
}
i.icon.corner::before {
bottom: 6px;
width: 16px;
height: 12px;
border-bottom: 2px solid;
}
i.icon.corner.left::before {
border-bottom-right-radius: 4px;
border-right: 2px solid;
}
i.icon.corner.right::before {
border-bottom-left-radius: 4px;
border-left: 2px solid;
}

View File

@ -0,0 +1,55 @@
@charset "UTF-8";
i.icon.list.add {
box-sizing: border-box;
position: relative;
display: block;
width: 12px;
height: 6px;
border-top: 0 solid transparent;
border-bottom: 2px solid transparent;
box-shadow:
inset 0 -2px 0,
-2px 4px 0 -2px,
0 -2px 0 0;
}
i.icon.list.add.small {
width: 10px;
box-shadow:
inset 0 -2px 0,
-1px 3px 0 -1px,
0 -2px 0 0;
}
i.icon.list.add::after,
i.icon.list.add::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 10px;
height: 2px;
background: currentColor;
top: 6px;
right: -8px;
}
i.icon.list.add.small::after,
i.icon.list.add.small::before {
width: 8px;
}
i.icon.list.add::before {
width: 2px;
height: 10px;
top: 2px;
right: -4px;
}
i.icon.list.add.small::before {
width: 2px;
height: 8px;
top: 3px;
right: -5px;
}

View File

@ -10,3 +10,6 @@ i.icon.minus {
border-radius: 10px; border-radius: 10px;
} }
i.icon.minus.small {
width: 10px;
}

View File

@ -7,12 +7,18 @@ i.icon.plus::after {
background: currentColor; background: currentColor;
border-radius: 10px; border-radius: 10px;
} }
i.icon.plus { i.icon.plus {
margin-top: -2px; margin-top: -2px;
position: relative; position: relative;
width: 16px; width: 16px;
height: 2px; height: 2px;
} }
i.icon.plus.small {
width: 10px;
}
i.icon.plus::after { i.icon.plus::after {
content: ""; content: "";
position: absolute; position: absolute;
@ -22,3 +28,8 @@ i.icon.plus::after {
left: 7px; left: 7px;
} }
i.icon.plus.small::after {
height: 10px;
top: -4px;
left: 4px;
}

View File

@ -0,0 +1,49 @@
@charset "UTF-8";
i.icon.trash {
box-sizing: border-box;
position: relative;
display: block;
width: 10px;
height: 12px;
border: 2px solid transparent;
box-shadow:
0 0 0 2px,
inset -2px 0 0,
inset 2px 0 0;
border-bottom-left-radius: 1px;
border-bottom-right-radius: 1px;
margin-top: 4px;
}
i.icon.trash.small {
height: 9px;
}
i.icon.trash::after,
i.icon.trash::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
}
i.icon.trash::after {
background: currentColor;
border-radius: 3px;
width: 16px;
height: 2px;
top: -4px;
left: -5px;
}
i.icon.trash::before {
width: 10px;
height: 4px;
border: 2px solid;
border-bottom: transparent;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
top: -7px;
left: -2px;
}

View File

@ -8,8 +8,6 @@
--socket-text: #b09999; --socket-text: #b09999;
} }
* { * {
text-decoration: none; text-decoration: none;
outline: none; outline: none;
@ -88,6 +86,8 @@ main>search {
overflow: clip; overflow: clip;
} }
footer {}
search:has(input:is(:focus, :active)) { search:has(input:is(:focus, :active)) {
border-color: var(--tg-theme-accent-text-color); border-color: var(--tg-theme-accent-text-color);
transition: unset; transition: unset;
@ -129,7 +129,8 @@ search:has(input:disabled) {
backdrop-filter: contrast(0.5); backdrop-filter: contrast(0.5);
} }
button, *[type="button"] { button,
*[type="button"] {
cursor: pointer; cursor: pointer;
} }
@ -166,7 +167,18 @@ h2 {
margin: 1rem 0 0; margin: 1rem 0 0;
} }
footer {} input {
background: unset;
}
.kabrio {
font-family: "Kabrio";
}
.cost.currency:after {
content: var(--currency);
margin-left: var(--currency-offset, 0.1rem);
}
.unselectable { .unselectable {
-webkit-touch-callout: none; -webkit-touch-callout: none;

View File

@ -3,6 +3,7 @@
header>nav#menu { header>nav#menu {
container-type: inline-size; container-type: inline-size;
container-name: menu; container-name: menu;
margin-bottom: 1rem;
width: var(--width); width: var(--width);
min-height: 3rem; min-height: 3rem;
display: flex; display: flex;
@ -16,8 +17,10 @@ header>nav#menu>a[type="button"] {
height: 3rem; height: 3rem;
padding: unset; padding: unset;
border-radius: 1.375rem; border-radius: 1.375rem;
color: var(--unsafe-color, var(--tg-theme-button-text-color)); /* color: var(--unsafe-color, var(--tg-theme-button-text-color));
background-color: var(--unsafe-background-color, var(--tg-theme-button-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 { header>nav#menu>a[type=button]>:first-child {

View File

@ -89,6 +89,7 @@ section#window>div.card>div.images>img {
flex-shrink: 0; flex-shrink: 0;
flex-grow: 0; flex-grow: 0;
object-fit: cover; object-fit: cover;
image-rendering: auto;
border-radius: 0.5rem; border-radius: 0.5rem;
transition: 0s; transition: 0s;
} }

View File

@ -75,7 +75,7 @@ final class templater extends controller implements ArrayAccess
if (!empty($account?->status())) $this->twig->addGlobal('account', $account); 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('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('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 // Initialize function of dimensions formatting
$this->twig->addFunction( $this->twig->addFunction(

View File

@ -0,0 +1,5 @@
{% if ASDASDASDASDASDASDSD is not empty %}
<section id="" class="unselectable">
</section>
{% endif %}

View File

@ -0,0 +1,48 @@
{% macro card(product, amount) %}
<article id="{{ product._id }}" class="product unselectable" data-product-identifier="{{ product.identifier }}"
data-product-amount="{{ amount }}" {% if amount> 0 %} style="--hue-rotate-offset: {{ amount }}0deg;"{% endif %}>
<a data-product="cover" href="?product={{ product.identifier }}" onclick="return core.catalog.product(this);"
onkeydown="event.keyCode === 13 && core.catalog.product(this)" tabindex="10">
<img src="{{ product.images.0.storage }}" alt="{{ product.name }}" ondrugstart="return false;">
</a>
<div>
<div class="head" title="{{ product.name }}">
{{ product.name | length > 65 ? product.name | slice(0, 65) ~ '...' : product.name }}
<!-- <button data-product-button="list" onclick="core.cart.list(this, document.getElementById('{{ product._id }}'))" title="{{ language.name == 'ru' ? 'Добавить в список' : 'Add to a list' }}"><i class="icon small list add"></i></button> -->
<button data-product-button="toggle"
onclick="core.cart.toggle(this, document.getElementById('{{ product._id }}'), true)"
title="{{ language.name == 'ru' ? 'Удалить' : 'Delete' }}"><i class="icon small trash"></i></button>
</div>
<div class="body">
{% 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 %}
<span>
{{ characteristic | length > 30 ? characteristic | slice(0, 30) ~ '...' : characteristic }}
</span>
{% endif %}
{% endfor %}
</div>
<div class="footer">
<span class="cost currency" data-product-parameter="cost">{{ product.cost }}</span>
<button data-product-button="delete"
onclick="core.cart.delete(this, document.getElementById('{{ product._id }}'), 1)"
title="{{ language.name == 'ru' ? 'Уменьшить' : 'Decrease' }}"><i class="icon small minus"></i></button>
<input type="text" value="{{ amount ?? 1 }}" title="{{ language.name == 'ru' ? 'Количество' : 'Amount' }}"
data-product-parameter="amount"
onchange="core.cart.set(this, document.getElementById('{{ product._id }}'), +this.value)"
oninput="this.value = (this.value = +this.value.replaceAll(/[^\d]/g, '')) > 100 ? 100 : (this.value < 0 ? 0 : this.value)"></input>
<button data-product-button="write"
onclick="core.cart.write(this, document.getElementById('{{ product._id }}'), 1)"
title="{{ language.name == 'ru' ? 'Увеличить' : 'Increase' }}"><i class="icon small plus"></i></button>
</div>
</div>
</article>
{% endmacro %}
{% if cart.products is not empty %}
<section id="products" class="unselectable">
{% for product in cart.products %}
{{ _self.card(product.document, product.amount) }}
{% endfor %}
</section>
{% endif %}

View File

@ -0,0 +1,11 @@
{% if cart.products is not empty %}
<section id="summary" class="unselectable">
<div>
<span id="amount">{{ cart.summary.amount ?? 0 }}</span>
<span>{{ language.name == "ru" ? "товаров на сумму" : "products worth" }}</span>
<span id="cost" class="cost currency">{{ cart.summary.cost ?? 0 }}</span>
<button id="order" onclick="core.cart.order(this)"
title="{{ language.name == 'ru' ? 'Оформить заказ' : 'Place an order' }}"><i class="icon arrow"></i></button>
</div>
</section>
{% endif %}

View File

@ -0,0 +1,24 @@
{% extends "/themes/default/index.html" %}
{% block css %}
{{ parent() }}
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/cart.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/trash.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/list_add.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/shopping_cart.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/plus.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/minus.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/arrow.css" />
{% endblock %}
{% block main %}
<h2 class="unselectable">{{ h2 }}</h2>
{% include "/themes/default/cart/elements/summary.html" %}
{% include "/themes/default/cart/elements/products.html" %}
{% endblock %}
{% block js %}
{{ parent() }}
<script src="/js/cart.js"></script>
<script src="/js/hotline.js"></script>
{% endblock %}

View File

@ -3,7 +3,7 @@
product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %} product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %}
{% set amount = cart[product.getId()].amount ?? 0 %} {% set amount = cart[product.getId()].amount ?? 0 %}
<article id="{{ product.getId() }}" class="product unselectable" data-product-identifier="{{ product.identifier }}" <article id="{{ product.getId() }}" class="product unselectable" data-product-identifier="{{ product.identifier }}"
data-product-amount="{{ amount }}"> data-product-amount="{{ amount }}"{% if amount > 0 %} style="--hue-rotate-offset: {{ amount }}0deg;"{% endif %}>
<a data-product="cover" href="?product={{ product.identifier }}" onclick="return core.catalog.product(this);" <a data-product="cover" href="?product={{ product.identifier }}" onclick="return core.catalog.product(this);"
onkeydown="event.keyCode === 13 && core.catalog.product(this)" tabindex="10"> onkeydown="event.keyCode === 13 && core.catalog.product(this)" tabindex="10">
<img src="{{ product.images.0.storage }}" alt="{{ product.name }}" ondrugstart="return false;"> <img src="{{ product.images.0.storage }}" alt="{{ product.name }}" ondrugstart="return false;">
@ -11,14 +11,14 @@ product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %
{{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }} {{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}
</p> </p>
</a> </a>
<div data-product="buttons"{% if amount > 0 %} style="--hue-rotate-offset: {{ amount }}0deg;"{% endif %}> <div data-product="buttons">
<button data-product-button="delete" onclick="core.cart.delete(this, 1)"><i class="icon minus"></i></button> <button data-product-button="delete" onclick="core.cart.delete(this, document.getElementById('{{ product.getId() }}'), 1)" title="{{ language.name == 'ru' ? 'Уменьшить' : 'Decrease' }}"><i class="icon small minus"></i></button>
<button data-product-button="toggle" onclick="core.cart.toggle(this)" tabindex="15"> <button data-product-button="toggle" onclick="core.cart.toggle(this, document.getElementById('{{ product.getId() }}'))" tabindex="15">
<span data-product-button-text="amount">{{ amount }}</span> <span data-product-parameter="amount">{{ amount }}</span>
<span data-product-button-text="cost">{{ product.cost }}</span> <span class="cost currency" data-product-parameter="cost">{{ product.cost }}</span>
<span data-product-button-text="currency">{{ currency.symbol }}</span> <span data-product-parameter="currency">{{ currency.symbol }}</span>
</button> </button>
<button data-product-button="write" onclick="core.cart.write(this, 1)"><i class="icon plus"></i></button> <button data-product-button="write" onclick="core.cart.write(this, document.getElementById('{{ product.getId() }}'), 1)" title="{{ language.name == 'ru' ? 'Увеличить' : 'Increase' }}"><i class="icon small plus"></i></button>
</div> </div>
</article> </article>
{% endmacro %} {% endmacro %}

View File

@ -16,4 +16,10 @@
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/loading.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/loading.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/window.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/window.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/fonts/dejavu.css" /> <link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/fonts/dejavu.css" />
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/fonts/kabrio.css" />
<style>
:root {
--currency: "{{ currency.symbol ?? '$' }}";
}
</style>
{% endblock %} {% endblock %}

View File

@ -14,8 +14,8 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{{ block('connection_body') }} <!-- {{ block('connection_body') }} -->
{{ block('account_body') }} <!-- {{ block('account_body') }} -->
{{ block('header') }} {{ block('header') }}
<main> <main>
{% block main %} {% block main %}

View File

@ -2,7 +2,7 @@
<link type="text/css" rel="stylesheet" href="/themes/default/css/menu.css"> <link type="text/css" rel="stylesheet" href="/themes/default/css/menu.css">
{% for button in menu %} {% for button in menu %}
{% if button.icon %} {% if button.icon %}
<link type="text/css" rel="stylesheet" href="/themes/default/css/icons/{{ button.icon|replace({' ': '-'}) }}.css"> <link type="text/css" rel="stylesheet" href="/themes/default/css/icons/{{ button.icon.class|replace({' ': '_'}) }}.css">
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
@ -11,10 +11,12 @@
<nav id="menu"> <nav id="menu">
{% for button in menu %} {% for button in menu %}
<a href='{{ button.urn }}' onclick="return core.loader.load('{{ button.urn }}');" type="button" class="unselectable" <a href='{{ button.urn }}' onclick="return core.loader.load('{{ button.urn }}');" type="button" class="unselectable"
title="{{ button.name }}" title="{{ button.name }}" {% if button.style %}
style="order: {{ button.position }};{% for target, color in button.color %} --unsafe-{{ target }}: {{ color|e }};{% endfor %}"> style="{% for parameter, value in button.style %}{{ parameter ~ ': ' ~ value ~ '; ' }}{% endfor %}" {% endif %}">
{% if button.icon %} {% if button.icon %}
<i class="icon {{ button.icon }}"></i> <i class="icon {{ button.icon.class }}" {% if button.icon.style %}
style="{% for parameter, value in button.icon.style %}{{ parameter ~ ': ' ~ value ~ '; ' }}{% endfor %}" {% endif
%}></i>
{% endif %} {% endif %}
<span>{{ button.name }}</span> <span>{{ button.name }}</span>
{% if button.image.storage %} {% if button.image.storage %}