THE JAVASCRIPT SYSTEM TOTAL REBUILD

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2024-10-29 13:01:15 +03:00
parent b07d60d8e5
commit cf0e32e954
56 changed files with 5456 additions and 3503 deletions

View File

@ -28,7 +28,10 @@
"twig/extra-bundle": "^3.7",
"twig/intl-extra": "^3.10",
"avadim/fast-excel-reader": "^2.19",
"openswoole/core": "22.1.5"
"openswoole/core": "22.1.5",
"ttatpuot/cdek-sdk2.0": "^1.2",
"guzzlehttp/guzzle": "^7.9",
"php-http/guzzle7-adapter": "^1.0"
},
"autoload": {
"psr-4": {

1126
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -40,23 +40,31 @@ final class account extends core
public function write(array $parameters = []): void
{
if (!empty($parameters) && $this->account instanceof model) {
// Found data of the program and active account
// Received parameters and initialized account
// Declaring the buffer of deserialized parameters
$deserialized = [];
foreach ($parameters as $name => $value) {
// Iterate over parameters
// Validation of the parameter
// Validation of the parameter value
if (mb_strlen($value) > 4096) continue;
// Convert name to multidimensional array
foreach (array_reverse(explode('_', $name)) as $key) $parameter = [$key => $parameter ?? json_validate($value) ? json_decode($value, true, 10) : $value];
// Declaring the buffer of deserialized parameter
$parameter = null;
// Write data of to the buffer parameter in the implement object of account document from ArangoDB
$this->account->buffer = $parameter + $this->account->buffer ?? [];
// Deserializing name to multidimensional array
foreach (array_reverse(explode('_', $name)) as $key)
$parameter = [$key => $parameter ?? (json_validate($value) ? json_decode($value, true, 10) : $value)];
// Writing into the buffer of deserialized parameters
$deserialized = array_merge_recursive($parameter, $deserialized);
}
// Write from implement object to account document from ArangoDB
document::update($this->account->__document(), $this->errors['account']);
// Write to the account document from ArangoDB
if (!empty($deserialized)) $this->account->write($deserialized, $this->errors['account']);
}
}
}

View File

@ -24,6 +24,11 @@ use mirzaev\arangodb\document;
*/
final class cart extends core
{
/**
* Instance of the cart
*/
protected readonly ?model $cart;
/**
* Registry of errors
*/
@ -57,6 +62,22 @@ final class cart extends core
);
}
// Initializing the cart
$this->cart = $this->account?->cart() ?? $this->session?->cart();
// Initializing the cart data
$this->view->cart = [
'summary' => $this->cart?->summary(currency: $this->currency),
'products' => $this->cart?->products(language: $this->language, currency: $this->currency)
];
// Initializing types of avaiabld deliveries
$this->view->deliveries = [
'cdek' => [
'label' => 'CDEK'
]
];
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// GET request
@ -135,6 +156,12 @@ final class cart extends core
if ($product instanceof product) {
// Initialized the product
// Initializing the cart
$this->cart = $this->account?->cart() ?? $this->session?->cart();
if ($this->cart instanceof model) {
// Initialized the cart
// Initializing the buffer with amount of the product in the cart
$amount = $this->cart->count(product: $product, limit: 100, errors: $this->errors['cart']) ?? 0;
@ -230,6 +257,7 @@ final class cart extends core
}
}
}
}
// Initializing a response headers
header('Content-Type: application/json');
@ -272,6 +300,12 @@ final class cart extends core
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST request
// Initializing the cart
$this->cart = $this->account?->cart() ?? $this->session?->cart();
if ($this->cart instanceof model) {
// Initialized the cart
// Initializing summary data of the cart
$summary = $this->cart?->summary(currency: $this->currency, errors: $this->errors['cart']);
@ -302,6 +336,7 @@ final class cart extends core
// Exit (success)
return null;
}
}
// Exit (fail)
return null;

View File

@ -53,7 +53,7 @@ final class catalog extends core
filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true",
sort: 'd.created DESC',
amount: 1,
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}',
return: '{identifier: d.identifier, name: d.name.@language, description: d.description.@language, cost: d.cost.@currency, 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,
currency: $this->currency,
parameters: ['identifier' => $product],
@ -186,7 +186,7 @@ final class catalog extends core
if (isset($text)) {
// Received and validated text
// Writing to the account buffer
// Writing to the account buffer (useless becouse rewrite itself to null with every request)
$this->account?->write(
[
'catalog' => [
@ -197,7 +197,7 @@ final class catalog extends core
]
);
// Writing to the session buffer
// Writing to the session buffer (useless becouse rewrite itself to null with every request)
$this->session?->write(
[
'catalog' => [
@ -296,7 +296,7 @@ final class catalog extends core
) ?? null;
}
if (isset($brand) || (isset($this->view->products) && count($this->view->products) > 0)) {
if (isset($brand) || isset($text) || (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
// Search for filters and write to the buffer of global variables of view templater
@ -355,7 +355,7 @@ final class catalog extends core
sprintf(
<<<javascript
if (typeof _window === 'undefined') {
_window = setTimeout(() => core.catalog.product_system('%s'), 500);
_window = setTimeout(() => core.catalog.product.system('%s'), 500);
}
javascript,
$this->view->product['identifier']

View File

@ -138,15 +138,11 @@ class core extends controller
// Initializing of the currency
$this->currency = $this->account?->currency ?? $this->session?->buffer['currency'] ?? $this->settings?->currency ?? currency::usd;
// Initializing of the cart
$this->cart = $this->account?->cart() ?? $this->session?->cart();
// Initializing of preprocessor of views
$this->view = new templater(
session: $this->session,
account: $this->account,
settings: $this->settings,
cart: $this->cart
settings: $this->settings
);
// @todo перенести в middleware

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\controllers;
// Files of the project
use mirzaev\arming_bot\controllers\core,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\cart,
mirzaev\arming_bot\models\product,
mirzaev\arming_bot\models\menu;
// Framework for ArangoDB
use mirzaev\arangodb\document;
/**
* Controller of cdek
*
* @package mirzaev\arming_bot\controllers
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class cdek extends core
{
/**
* Registry of errors
*/
protected array $errors = [
'delivery' => []
];
/**
* Calculate
*
* @param array $parameters Parameters of the request (POST + GET)
*/
public function calculate(array $parameters = []): ?string
{
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST request
$this->model::calculate();
die;
// 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;
}
}

View File

@ -167,23 +167,30 @@ final class session extends core
public function write(array $parameters = []): void
{
if (!empty($parameters) && $this->session instanceof model) {
// Found data of the program and active session
// Received parameters and initialized session
// Declaring the buffer of deserialized parameters
$deserialized = [];
foreach ($parameters as $name => $value) {
// Iterate over parameters
// Validation of the parameter
// Validation of the parameter value
if (mb_strlen($value) > 4096) continue;
// Convert name to multidimensional array
foreach (array_reverse(explode('_', $name)) as $key) $parameter = [$key => $parameter ?? json_validate($value) ? json_decode($value, true, 10) : $value];
// Declaring the buffer of deserialized parameter
$parameter = null;
// Write data of to the buffer parameter in the implement object of session document from ArangoDB
$this->session->buffer = $parameter + ($this->session->buffer ?? []);
// Deserializing name to multidimensional array
foreach (array_reverse(explode('_', $name)) as $key)
$parameter = [$key => $parameter ?? (json_validate($value) ? json_decode($value, true, 10) : $value)];
// Writing into the buffer of deserialized parameters
$deserialized = array_merge_recursive($parameter, $deserialized);
}
// Write from implement object to session document from ArangoDB
document::update($this->session->__document(), $this->errors['session']);
// Write to the session document from ArangoDB
if (!empty($deserialized)) $this->session->write($deserialized, $this->errors['session']);
}
}
}

View File

@ -106,7 +106,7 @@ final class cart extends core implements document_interface, collection_interfac
}
// Exit (success)
return $products;
return isset($products['amount']) ? [$products['document']['_id'] => $products] : $products;
} 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);

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace mirzaev\arming_bot\models\deliveries;
// Files of the project
use mirzaev\arming_bot\models\core,
mirzaev\arming_bot\models\traits\document as document_trait,
mirzaev\arming_bot\models\interfaces\document as document_interface,
mirzaev\arming_bot\models\interfaces\collection as collection_interface;
// The HTTP PSR-18 adapter for Guzzle HTTP client
use Http\Adapter\Guzzle7\Client as guzzle;
// Framework for CDEK
use CdekSDK2\Client as client,
CdekSDK2\Dto\CityList as cities;
// Built-in libraries
use exception;
/**
* Model of CDEK
*
* @package mirzaev\arming_bot\models\deliveries
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class cdek extends core implements document_interface, collection_interface
{
use document_trait;
/**
* Name of the collection in ArangoDB
*/
final public const string COLLECTION = 'delivery';
/**
* Calculate
*
* Calculate delivery by CDEK
*
* @param array &$errors Registry of errors
*
* @return
*/
public static function calculate(array &$errors = []): static|null
{
try {
//
/* $client = new client(new guzzle, 'account', 'secure'); */
$client = new client(new guzzle);
$client->setTest(true);
$result = $client->cities()->getFiltered(['country_codes' => 'RU', 'city' => 'зеленогорск']);
if ($result->isOk()) {
//
//Запрос успешно выполнился
$cities = $client->formatResponseList($result, cities::class);
foreach ($cities->items as $city) {
var_dump($city);
}
die;
}
} 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;
}
}

View File

@ -5,7 +5,9 @@ declare(strict_types=1);
namespace mirzaev\arming_bot\models\traits;
// Files of the project
use mirzaev\arming_bot\models\interfaces\collection as collection_interface;
use mirzaev\arming_bot\models\interfaces\collection as collection_interface,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
// Library for ArangoDB
use ArangoDBClient\Document as _document;
@ -47,12 +49,19 @@ trait buffer
if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
// Initialized the collection
// The instance of the document from ArangoDB is initialized?
isset($this->document) || throw new exception('The instance of the sessoin document from ArangoDB is not initialized');
// Is the instance of the document from ArangoDB are initialized?
if (!isset($this->document)) throw new exception('The instance of the sessoin document from ArangoDB is not initialized');
// Writing data into buffer of the instance of the document from ArangoDB
$this->document->buffer = array_replace_recursive($this->document->buffer ?? [], $data);
// Is the buffer of the instance of the document from ArangoDB exceed 10 megabytes?
if (mb_strlen(json_encode($this->document->buffer)) > 10485760) throw new exception('The buffer size exceeds 10 megabytes');
// Serializing parameters
if ($this->document->language instanceof language) $this->document->language = $this->document->language->name;
if ($this->document->currency instanceof currency) $this->document->currency = $this->document->currency->name;
// Writing to ArangoDB and exit (success)
return document::update($this->document, errors: $errors);
} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);

View File

@ -47,6 +47,7 @@ $router
->write('/account/write', 'account', 'write', 'POST')
->write('/session/write', 'session', 'write', 'POST')
->write('/session/connect/telegram', 'session', 'telegram', 'POST')
->write('/cdek/calculate', 'deliveries\\cdek', 'calculate', 'POST')
/* ->write('/category/$identifier', 'catalog', 'index', 'POST') */
/* ->write('/category', 'catalog', 'index', 'POST') */
/* ->write('/product/$identifier', 'catalog', 'product', 'POST') */

View File

@ -1,156 +0,0 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() =>
import("/js/damper.js").then(() =>
import("/js/telegram.js").then(() => {
const dependencies = setInterval(() => {
if (
typeof core === "function" &&
typeof core.damper === "function" &&
typeof core.telegram === "function"
) {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
if (typeof core.account === "undefined") {
// Not initialized
/**
* @name Account
*
* @description
* Implements actions with accounts
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.account = class account {
// Wrap of indicator of the account
static wrap = document.getElementById("account");
// Indicator of the account
static indicator = this.wrap.getElementsByTagName("i")[0];
// Description of the account
static description = this.wrap.getElementsByTagName("small")[0];
// Statuc of the account
static connected = false;
// Duration of the disconnected status
static timeout = 0;
// Instance of the time counter in disconnected status
static counter;
// Socket address (xn--e1ajlli это сокет)
static socket = "wss://arming.dev.mirzaev.sexy:9502";
// Instance of account to the socket
static session;
// Iterval for reconnect
static interval;
// Attempts to connect (when core.account.readyState === 0)
static attempts = 0;
// Interval for block
static block;
/**
* Authentication
*
* @return {void}
*/
static authentication() {
core.status_loading.removeAttribute("disabled");
const timer_for_response = setTimeout(() => {
core.status_loading.setAttribute("disabled", true);
}, 3000);
if (core.telegram.api.initData.length > 0) {
core
.request(
"/session/connect/telegram",
core.telegram.api.initData,
)
.then((json) => {
if (
json.errors !== null &&
typeof json.errors === "object" &&
json.errors.length > 0
) {
// Errors received
} else {
// Errors not received
if (json.connected === true) {
core.status_loading.setAttribute("disabled", true);
clearTimeout(timer_for_response);
const a =
core.status_account.getElementsByTagName("a")[0];
a.setAttribute("onclick", "core.account.profile()");
a.innerText = json.domain.length > 0
? "@" + json.domain
: "ERROR";
}
if (
json.language !== null &&
typeof json.language === "string" &&
json.langiage.length === 2
) {
core.language = json.language;
}
}
});
}
}
/**
* Buffer
*/
static buffer = class buffer {
/**
* Write to the account buffer
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
*
* @return {void}
*/
static write(name, value) {
if (typeof name === "string" && typeof value === "string") {
//
// Send request to the server
core.request(
"/account/write",
`${name}=${value}`,
"POST",
{},
null,
);
}
}
};
};
}
}
})
)
);

View File

@ -1,28 +1,7 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() =>
import("/js/damper.js").then(() =>
import("/js/telegram.js").then(() =>
import("/js/account.js").then(() => {
const dependencies = setInterval(() => {
if (
typeof core === "function" &&
typeof core.damper === "function" &&
typeof core.telegram === "function" &&
typeof core.account === "function"
) {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
core.modules.connect(["session", "account", "telegram"])
.then(() => {
//
const { initData, initDataUnsafe, ...data } = core.telegram.api;
@ -38,8 +17,4 @@ import("/js/core.js").then(() =>
//
core.telegram.api.ready();
}
})
)
)
);
});

View File

@ -1,430 +0,0 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() =>
import("/js/damper.js").then(() => {
const dependencies = setInterval(() => {
if (
typeof core === "function" &&
typeof core.damper === "function"
) {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
if (typeof core.cart === "undefined") {
// Not initialized
/**
* @name Cart
*
* @description
* Implements actions with cart
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.cart = class cart {
/**
* Toggle
*
* Toggle the product in the cart (interface)
*
* @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(element, product, remove = false, force = false) {
// Blocking the element
element.setAttribute("disabled", "true");
// Execute under damper
this.toggle_damper(
element,
product,
"toggle",
undefined,
remove,
force,
);
// Exit (success)
return false;
}
/**
* Toggle
*
* Toggle the product in the cart (damper)
*
* @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 {Promise}
*/
static toggle_damper = core.damper(
(...variables) => this.product(...variables).then(this.summary),
300,
6,
);
/**
* Write
*
* Write the product in the cart (interface)
*
* @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(
element,
product,
amount = 1,
remove = false,
force = false,
) {
// Blocking the element
element.setAttribute("disabled", "true");
// Execute under damper
this.write_damper(element, product, "write", amount, remove, force);
// Exit (success)
return false;
}
/**
* Write
*
* Write the product in the cart (damper)
*
* @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 {Promise}
*/
static write_damper = core.damper(
(...variables) => this.product(...variables).then(this.summary),
300,
6,
);
/**
* Delete
*
* Delete the product from the cart (interface)
*
* @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(
element,
product,
amount = 1,
remove = false,
force = false,
) {
// Blocking the element
element.setAttribute("disabled", "true");
// Execute under damper
this.delete_damper(
element,
product,
"delete",
amount,
remove,
force,
);
// Exit (success)
return false;
}
/**
* Delete
*
* Delete the product from the cart (damper)
*
* @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 {Promise}
*/
static delete_damper = core.damper(
(...variables) => this.product(...variables).then(this.summary),
300,
6,
);
/**
* Set
*
* Set amount of the product in the cart (interface)
*
* @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(
element,
product,
amount = 1,
remove = false,
force = false,
) {
// Blocking the element
element.setAttribute("disabled", "true");
// Execute under damper
this.set_damper(element, product, "set", amount, remove, force);
// Exit (success)
return false;
}
/**
* Set
*
* Set the product in the cart (damper)
*
* @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 {Promise}
*/
static set_damper = core.damper(
(...parameters) => this.product(...parameters).then(this.summary),
300,
6,
);
/**
* The product
*
* Handle the product in the cart (system)
*
* @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|null}
*/
static async product(
element,
product,
type,
amount = null,
remove = false,
resolve = () => {},
reject = () => {},
) {
if (
(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(
"data-product-identifier",
);
if (typeof identifier === "number") {
// 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(
"/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 <span> element
const amounts = product.querySelectorAll(
'[data-product-parameter="amount"]',
);
for (const amount of amounts) {
// 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
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;
}
}
});
}
};
}
}
})
);

File diff suppressed because it is too large Load Diff

View File

@ -1,324 +0,0 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() =>
import("/js/damper.js").then(() => {
const dependencies = setInterval(() => {
if (
typeof core === "function" &&
typeof core.damper === "function"
) {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
if (typeof core.connection === "undefined") {
// Not initialized
/**
* @name Connection
*
* @description
* Implements actions with websocket connection
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.connection = class connection {
// Wrap of indicator of the connection
static wrap = document.getElementById("connection");
// Indicator of the connection
static indicator = this.wrap.getElementsByTagName("i")[0];
// Description of the connection
static description = this.wrap.getElementsByTagName("small")[0];
// Statuc of the connection
static connected = false;
// Duration of the disconnected status
static timeout = 0;
// Instance of the time counter in disconnected status
static counter;
// Socket address (xn--e1ajlli это сокет)
static socket = "wss://arming.dev.mirzaev.sexy:9502";
// Instance of connection to the socket
static session;
// Iterval for reconnect
static interval;
// Attempts to connect (when core.connection.readyState === 0)
static attempts = 0;
// Interval for block
static block;
/**
* Initialize status of the connection to socket
*
* @param {bool} connected Connected?
*
* @return {void}
*/
static status(connected = false) {
if (this.indicator instanceof HTMLElement) {
// Initialized the indicator
if (this.connected = connected) {
// Connected
this.wrap.setAttribute("title", "Connected");
this.indicator.classList.remove("disconnected");
this.indicator.classList.add("connected");
clearInterval(this.counter);
this.description.innerText = "";
this.counter = undefined;
this.timeout = 0;
} else {
// Disconnected
this.wrap.setAttribute("title", "Disconnected");
this.indicator.classList.remove("connected");
this.indicator.classList.add("disconnected");
if (typeof this.counter === "undefined") {
this.counter = setInterval(() => {
this.timeout += 0.01;
this.description.innerText = this.timeout.toFixed(2);
}, 10);
}
}
}
}
/**
* Connect to the socket
*
* @param {bool|number} interval Connection check interval (ms)
* @param {function} preprocessing Will be executed every cycle
* @param {function} onmessage New message
* @param {function} onopen Connection opened
* @param {function} onclose Connection closed
* @param {function} onerror An error has occurred
*
* @return {Promise}
*/
static connect(
interval = false,
preprocessing,
onmessage,
onopen,
onclose,
onerror,
) {
return new Promise((resolve, reject) => {
try {
if (typeof interval === "number" && interval > 0) {
// Connect with automatic reconnect
if (typeof this.interval === "undefined") {
this.interval = setInterval(() => {
preprocessing();
if (
!(this.session instanceof WebSocket) ||
(this.session.readyState === 3 ||
this.session.readyState === 4) ||
(this.session.readyState === 0 &&
++this.attempts > 10)
) {
this.attempts = 0;
if (this.session instanceof WebSocket) {
this.session.close();
}
this.session = new WebSocket(this.socket);
this.session.addEventListener("message", (e) => {
try {
const json = JSON.parse(e.data);
if (json.type === "registration") {
// Подключение сокета к сессии
fetch("/socket/registration", {
method: "POST",
headers: {
"Content-Type":
"application/x-www-form-urlencoded",
},
body: `key=${json.key}`,
});
}
} catch (_e) {}
});
this.session.addEventListener("message", onmessage);
this.session.addEventListener("open", onopen);
this.session.addEventListener("close", onclose);
this.session.addEventListener("error", onerror);
resolve(this.session);
} else resolve(this.session);
}, interval);
}
} else {
// Connect without reconnecting
if (
!(this.session instanceof WebSocket) ||
(this.session.readyState === 3 ||
this.session.readyState === 4)
) {
if (this.session instanceof WebSocket) {
this.session.close();
}
this.session = new WebSocket(this.socket);
this.session.addEventListener("message", onmessage);
this.session.addEventListener("open", onopen);
this.session.addEventListener("close", onclose);
this.session.addEventListener("error", onerror);
resolve(this.session);
} else resolve(this.session);
}
} catch (_e) {}
});
}
/**
* Core is connected to the socket?
*
* @return {bool}
*/
static connected() {
return this.session instanceof WebSocket &&
this.session.readyState === 1;
}
};
}
core.connection.connect(
3000,
() =>
core.connection.status(
core.connection.session instanceof WebSocket &&
core.connection.session.readyState === 1,
),
(e) => {
try {
const json = JSON.parse(e.data);
if (json.target === "task") {
// Заявка
// Инициализация строки
const row = document.getElementById(json._key);
if (row instanceof HTMLElement) {
// Инициализирована строка
if (json.type === "blocked") {
// Заблокирована заявка
// Запись статуса: "заблокирована"
row.setAttribute("data-blocked", json.account._key);
row.setAttribute(
"title",
"Редактирует: " + json.account.name,
);
// Удалить блокировку (60000 === 1 минута) (в базе данных стоит expires 1 минута тоже)
setTimeout(() => {
// Удаление статуса: "заблокирована"
row.removeAttribute("data-blocked");
row.removeAttribute("title");
// Обновление строки
tasks.row(row);
}, 60000);
} else if (json.type === "unblocked") {
// Разблокирована заявка
// Удаление статуса: "заблокирована"
row.removeAttribute("data-blocked");
row.removeAttribute("title");
// Обновление строки
tasks.row(row);
} else if (json.type === "updated") {
// Обновлена заявка
// Обновление строки
tasks.row(row);
} else if (json.type === "deleted") {
// Удалена заявка
// Удаление строки
row.remove();
}
}
}
} catch (_e) {}
// Инициализация идентифиатора
//const id = row.getAttribute("id");
// Инициализация количества непрочитанных сообщений
//const messages = row.lastElementChild.innerText;
// Инициализация статуса активной строки
//const selected = row.getAttribute("data-selected");
// Реинициализация строки
//row.outerHTML = data.rows;
// Реинициализация перезаписанной строки
//row = document.getElementById(id);
// Копирование статуса активной строки
//if (
// typeof selected === "string" &&
// selected === "true" &&
// document.body.contains(document.getElementById("popup"))
//) {
// row.setAttribute("data-selected", "true");
//}
},
(e) => {
//connection.status(
// core.connection instanceof WebSocket &&
// core.connection.readyState === 1,
//)
//console.log("Connected to WebSocket!");
},
(e) => {
//connection.status(
// core.connection instanceof WebSocket &&
// core.connection.readyState === 1,
//)
//console.log("Connection closed");
},
(e) => {
//console.log("Error happens");
},
);
}
})
);

View File

@ -1,5 +1,3 @@
"use strict";
/**
* @name Core
*
@ -9,7 +7,7 @@
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
const core = class core {
class core {
// Domain
static domain = window.location.hostname;
@ -66,31 +64,51 @@ const core = class core {
}
/**
* Buffer
* @name Modules
*
* @method connect(modules) Connect modules
*
* @return {Array} List of initialized modules
*/
static modules() {
return Object.keys(this).filter((module) =>
this[module]?.type === "module"
);
}
/**
* @name Buffer
*/
static buffer = class buffer {
/**
* Write to buffers
* @name Write to buffers
*
* @description
* Write to buffers (interface)
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
*
* @return {void}
* @return {bool} Execution completed with an error?
*/
static write(name, value) {
if (typeof this.session === "function") {
// Initialized the session implement object
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Write to the session buffer
core.session.buffer.write(name, value);
}
// Execute under damper
this.write.damper(name, value);
},
() => {
// Not imported the damper module
if (typeof this.account === "function") {
// Initialized the account implement object
// Execute
this.write.system(name, value);
},
);
// Write to the account buffer
core.account.buffer.write(name, value);
}
// Exit (success)
return false;
}
};
@ -274,4 +292,112 @@ const core = class core {
},
300,
); */
};
}
Object.assign(
core.modules,
{
/**
* @name Connect modules
*
* @param {Array|string} modules Names of modules or name of the module
*
* @return {Prommise}
*/
async connect(modules) {
// Normalisation required argiments
if (typeof modules === "string") modules = [modules];
if (modules instanceof Array) {
// Received and validated required arguments
// Initializing the registry of loaded modules
const loaded = [];
for (const module of modules) {
// Iterating over modules
// Downloading, importing and writing the module into a core property and into registry of loaded modules
core[module] =
loaded[module] =
await (await import(`./modules/${module}.js`)).default;
}
// Exit (success)
return loaded;
}
},
},
);
core.modules.connect("damper").then(() => {
// Imported the damper module
Object.assign(
core.buffer.write,
{
/**
* @name Write to buffers
*
* @description
* Write to buffers (damper)
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
* @param {bool} force Ignore the damper? (false)
*
* @return {Promise}
*/
damper: core.damper(
(...variables) => core.buffer.write.system(...variables),
300,
3,
),
},
);
});
/**
* @name Write to buffers
*
* @description
* Write to buffers (system)
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
*
* @return {Promise}
*/
Object.assign(
core.buffer.write,
{
system(
name,
value,
resolve = () => {},
reject = () => {},
) {
try {
core.modules.connect("session").then(() => {
// Imported the session module
// Write to the session buffer
session.default.buffer?.write(name, value);
});
core.modules.connect("account").then(() => {
// Imported the account module
// Write to the account buffer
account.default.buffer?.write(name, value);
});
// Exit (success)
resolve();
} catch (e) {
// Exit (fail)
reject(e);
}
},
},
);

View File

@ -1,96 +0,0 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() => {
const dependencies = setInterval(() => {
if (typeof core === "function") {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
if (typeof core.damper === "undefined") {
// Not initialized
/**
* @name Damper
*
* @description
* Execute multiple "function" calls in a "timeout" amount of time just once
*
* @param {function} function Function to execute after damping
* @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
* a = damper(
* async (
* a, // 0
* b, // 1
* c, // 2
* force = false, // 3
* d, // 4
* resolve,
* reject
* ) => {
* // Body of the function
*
* resolve();
* },
* 500,
* 3, // 3 -> "force" argument
* );
*
* a('for a', 'for b', 'for c', true, 'for d'); // Force execute is enabled
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.damper = (func, timeout = 300, force) => {
// Declaring of the timer for executing the function
let timer;
return ((...args) => {
return new Promise((resolve, reject) => {
// Deinitializing of the timer
clearTimeout(timer);
if (typeof force === "number" && args[force]) {
// 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);
} 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);
}
});
});
};
}
}
});

View File

@ -1,24 +1,6 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() => {
const dependencies = setInterval(() => {
if (typeof core === "function") {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
if (typeof core.hotline === "undefined") {
// Not initialized
/**
/**
* @name Бегущая строка
*
* @description
@ -31,8 +13,6 @@ import("/js/core.js").then(() => {
* события при выбранных действиях для того, чтобы пользователь имел возможность
* дорабатывать функционал без изучения и изменения моего кода
*
* @memberof core
*
* @example
* сonst hotline = new hotline();
* hotline.step = '-5';
@ -46,7 +26,7 @@ import("/js/core.js").then(() => {
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.hotline = class hotline {
export default class hotline {
// Идентификатор
#id = 0;
@ -257,14 +237,14 @@ import("/js/core.js").then(() => {
// Вертикальная бегущая строка
// Удаление отступов (движения)
_this.#shell.lastElementChild.style.marginTop =
-coords.height - separator + "px";
_this.#shell.lastElementChild.style.marginTop = -coords.height -
separator + "px";
} else {
// Горизонтальная бегущая строка
// Удаление отступов (движения)
_this.#shell.lastElementChild.style.marginLeft =
-coords.width - separator + "px";
_this.#shell.lastElementChild.style.marginLeft = -coords.width -
separator + "px";
}
// Копирование последнего элемента в начало строки
@ -286,8 +266,7 @@ import("/js/core.js").then(() => {
new CustomEvent(`hotline.${_this.#id}.transfer.start`, {
detail: {
element: _this.#shell.lastElementChild,
offset:
(_this.vertical ? coords.height : coords.width) +
offset: (_this.vertical ? coords.height : coords.width) +
separator,
},
}),
@ -434,8 +413,7 @@ import("/js/core.js").then(() => {
const to = _y - (y + offset - first.offset);
// Движение
_this.#shell.firstElementChild.style.marginTop =
to +
_this.#shell.firstElementChild.style.marginTop = to +
"px";
} else {
// Горизонтальная бегущая строка
@ -446,8 +424,7 @@ import("/js/core.js").then(() => {
const to = _x - (x + offset - first.offset);
// Движение
_this.#shell.firstElementChild.style.marginLeft =
to +
_this.#shell.firstElementChild.style.marginLeft = to +
"px";
}
@ -673,8 +650,7 @@ import("/js/core.js").then(() => {
configure(attribute) {
// Инициализация названия параметра
const parameter =
(/^data-hotline-(\w+)$/.exec(attribute) ?? [, null])[1];
const parameter = (/^data-hotline-(\w+)$/.exec(attribute) ?? [, null])[1];
if (typeof parameter === "string") {
// Параметр найден
@ -771,7 +747,4 @@ import("/js/core.js").then(() => {
);
}
}
};
}
}
});
}

View File

@ -1,265 +0,0 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() =>
import("/js/damper.js").then(() => {
const dependencies = setInterval(() => {
if (typeof core === "function" && typeof core.damper === "function") {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
if (typeof core.loader === "undefined") {
// Not initialized
/**
* @name Loader
*
* @description
* Implements actions with loading and rendering content
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.loader = class loader {
/**
* Load
*
* @param {string} uri
* @param {string} body
*
* @return {Promise}
*/
static async load(uri = "/", body) {
if (typeof uri === "string") {
// Received and validated uri
return await core
.request(
uri,
body,
"POST",
{
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
"json"
)
.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 browser history
history.pushState({}, json.title ?? uri, uri);
/**
* The <title>
*
* The title of the page
*/
if (
typeof json.title === "string" &&
json.title.length > 0
) {
// Received text for the <title> of the page
// Search for the <title> element (document.title)
const title = document.getElementsByTagName("title")[0];
if (title instanceof HTMLElement) {
// Found the <title> element
// Writing into the <title> element
title.innerText = json.title;
} else {
// Not found the <title> element
// Initialize the <title> element
const title = document.createElement("title");
// Inititalize the <head> element (document.head)
const head = document.getElementsByTagName("head")[0];
if (head instanceof HTMLElement) {
// Found the <head> element
// Writing the <title> element into the <head> element
head.appendChild(title);
}
// Writing title into the <title> element
title.innerText = json.title;
}
}
/**
* The <header> element
*/
if (
typeof json.header === "string" &&
json.header.length > 0
) {
// Received and validated the header HTML-code
if (core.header instanceof HTMLElement) {
// Found the <header> element
// Writing into the <header> element
core.header.outerHTML = json.header;
// Reinitializing the parameter with the <header> element
core.header =
document.getElementsByTagName("header")[0];
} else {
// Not found the <header> element
// Initialize the <header> element
core.header = document.createElement("header");
// Inititalize the <body> element (document.body)
const body = document.getElementsByTagName("body")[0];
if (body instanceof HTMLElement) {
// Found the <body> element
if (core.main instanceof HTMLElement) {
// Found the <main> element
// Writing the <header> element before the <main> element
body.insertBefore(core.header, core.main);
} else if (core.footer instanceof HTMLElement) {
// Fount the <footer> element
// Writing the <header> element before the <footer> element
body.insertBefore(core.header, core.footer);
} else {
// Not found the <main> element and the <footer> element
// Search for the last <section> element inside the <body> element
const section = document.body.querySelector(
"body > section:last-of-type"
);
if (section instanceof HTMLElement) {
// Found the last <section> element inside the <body> element
// Writing the <header> element after the last <section> element inside the <body> element
body.insertBefore(
core.header,
section.nextSibling
);
} else {
// Not found section elements <section> inside the <body> element
// Writing the <header> element into the <body> element
body.appendChild(core.header);
}
}
// Writing into the <header> element
core.header.outerHTML = json.header;
// Reinitializing the parameter with the <header> element
core.header =
document.getElementsByTagName("header")[0];
}
}
}
/**
* The <main> element
*
* The main content of the page
*/
if (typeof json.main === "string" && json.main.length > 0) {
// Received and validated the <main> HTML-code
if (core.main instanceof HTMLElement) {
// Found the <main> element
// Writing into the <main> element
core.main.outerHTML = json.main;
// Reinitializing the parameter with the <main> element
core.main = document.getElementsByTagName("main")[0];
} else {
// Not found the <main> element
// Initialize the <main> element
core.main = document.createElement("main");
// Inititalize the <body> element (document.body)
const body = document.getElementsByTagName("body")[0];
if (body instanceof HTMLElement) {
// Found the <body> element
if (core.header instanceof HTMLElement) {
// Found the <header> element
// Writing the <main> element after the <header> element
body.insertBefore(
core.main,
core.header.nextSibling
);
} else if (core.footer instanceof HTMLElement) {
// Fount the <footer> element
// Writing the <main> element before the <footer> element
body.insertBefore(core.main, core.footer);
} else {
// Not found the <header> element and the <footer> element
// Search for the last <section> element inside the <body> element
const section = document.body.querySelector(
"body > section:last-of-type"
);
if (section instanceof HTMLElement) {
// Found the last <section> element inside the <body> element
// Writing the <main> element after the last <section> element inside the <body> element
body.insertBefore(core.main, section.nextSibling);
} else {
// Not found section elements <section> inside the <body> element
// Writing the <main> element into the <body> element
body.appendChild(core.main);
}
}
// Writing into the <main> element
core.main.outerHTML = json.main;
// Reinitializing the parameter with the <main> element
core.main = document.getElementsByTagName("main")[0];
}
}
}
// Exit (success)
return json;
}
});
}
}
};
}
}
})
);

View File

@ -0,0 +1,202 @@
"use strict";
/**
* @name Account
*
* @description
* Implements actions with accounts
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
export default class account {
/**
* @name Type of the program
*/
static type = "module";
// Wrap of indicator of the account
static wrap = document.getElementById("account");
// Indicator of the account
static indicator = this.wrap?.getElementsByTagName("i")[0] ?? null;
// Description of the account
static description = this.wrap?.getElementsByTagName("small")[0] ?? null;
/**
* Authentication
*
* @return {void}
*/
static authentication() {
core.status_loading.removeAttribute("disabled");
const timer_for_response = setTimeout(() => {
core.status_loading.setAttribute("disabled", true);
}, 3000);
core.modules.connect("telegram").then(() => {
// Imported the telegram module
if (core.telegram.api.initData.length > 0) {
core
.request(
"/session/connect/telegram",
core.telegram.api.initData,
)
.then((json) => {
if (json) {
// Received a JSON-response
if (
json.errors !== null &&
typeof json.errors === "object" &&
json.errors.length > 0
) {
// Errors received
} else {
// Errors not received
if (json.connected === true) {
core.status_loading.setAttribute("disabled", true);
clearTimeout(timer_for_response);
const a = core.status_account.getElementsByTagName("a")[0];
a.setAttribute("onclick", "core.account.profile()");
a.innerText = json.domain.length > 0
? "@" + json.domain
: "ERROR";
}
if (
json.language !== null &&
typeof json.language === "string" &&
json.langiage.length === 2
) {
core.language = json.language;
}
}
}
});
}
});
}
/**
* @name Buffer
*/
static buffer = class buffer {
/**
* @name Write
*
* @description
* Write to the account buffer (interface)
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
* @param {bool} force Ignore the damper? (false)
*
* @return {bool} Execution completed with an error?
*/
static write = (name, value, force = false) => {
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Execute under damper
this.write.damper(name, value, force);
},
() => {
// Not imported the damper module
// Execute
this.write.system(name, value, force);
},
);
// Exit (success)
return false;
};
};
}
core.modules.connect("damper").then(() => {
// Imported the damper module
Object.assign(
account.buffer.write,
{
/**
* @name Write
*
* @description
* Write to the account buffer (damper)
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
* @param {bool} force Ignore the damper? (false)
*
* @return {Promise}
*/
damper: core.damper(
(...variables) => account.buffer.write.system(...variables),
300,
3,
),
},
);
});
Object.assign(
account.buffer.write,
{
/**
* @name Write
*
* @description
* Write to the account buffer (system)
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
*
* @return {Promise}
*/
async system(
name,
value,
resolve = () => {},
reject = () => {},
) {
try {
if (typeof name === "string" && typeof value === "string") {
// Received and validated required arguments
// Sending request to the server
return await core.request(
"/account/write",
`${name}=${value}`,
"POST",
)
.then(
(json) => {
if (json) {
// Received a JSON-response
// Exit (success)
resolve(json);
}
},
() => reject(),
);
}
} catch (e) {
// Exit (fail)
reject(e);
}
},
},
);
// Connecting to the core
if (!core.account) core.account = account;

View File

@ -0,0 +1,528 @@
"use strict";
/**
* @name Cart
*
* @description
* Implements actions with cart
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
export default class cart {
/**
* @name Type of the program
*/
static type = "module";
/**
* Toggle
*
* Toggle the product in the cart (interface)
*
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
* @param {HTMLElement} product The product element
* @param {bool} remove Remove the product element if json.amount === 0?
* @param {bool} force Ignore the damper? (false)
*
* @return {bool} Execution completed with an error?
*/
static toggle(element, product, remove = false, force = false) {
// Blocking the element
element.setAttribute("disabled", "true");
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Execute under damper
this.product.damper(
element,
product,
"toggle",
undefined,
remove,
force,
);
},
() => {
// Not imported the damper module
// Execute
this.product.system(
element,
product,
"toggle",
undefined,
remove,
force,
);
},
);
// Exit (success)
return false;
}
/**
* Write
*
* Write the product in the cart (interface)
*
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
* @param {HTMLElement} product The product element
* @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} Execution completed with an error?
*/
static write(
element,
product,
amount = 1,
remove = false,
force = false,
) {
// Blocking the element
element.setAttribute("disabled", "true");
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Execute under damper
this.product.damper(
element,
product,
"write",
amount,
remove,
force,
);
},
() => {
// Not imported the damper module
// Execute
this.product.system(
element,
product,
"write",
amount,
remove,
force,
);
},
);
// Exit (success)
return false;
}
/**
* Delete
*
* Delete the product from the cart (interface)
*
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
* @param {HTMLElement} product The product element
* @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} Execution completed with an error?
*/
static delete(
element,
product,
amount = 1,
remove = false,
force = false,
) {
// Blocking the element
element.setAttribute("disabled", "true");
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Execute under damper
this.product.damper(
element,
product,
"delete",
amount,
remove,
force,
);
},
() => {
// Not imported the damper module
// Execute
this.product.system(
element,
product,
"delete",
amount,
remove,
force,
);
},
);
// Exit (success)
return false;
}
/**
* Set
*
* Set amount of the product in the cart (interface)
*
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
* @param {HTMLElement} product The product element
* @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} Execution completed with an error?
*/
static set(
element,
product,
amount = 1,
remove = false,
force = false,
) {
// Blocking the element
element.setAttribute("disabled", "true");
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Execute under damper
this.product.damper(
element,
product,
"set",
amount,
remove,
force,
);
},
() => {
// Not imported the damper module
// Execute
this.product.system(
element,
product,
"set",
amount,
remove,
force,
);
},
);
// Exit (success)
return false;
}
/**
* The product
*
* Handle the product in the cart (system)
*
* @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 {bool} Execution completed with an error?
*/
static product(
element,
product,
type,
amount = null,
remove = false,
) {
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Execute under damper
this.product.damper(
element,
product,
type,
amount,
remove,
force,
);
},
() => {
// Not imported the damper module
// Execute
this.product.system(
element,
product,
type,
amount,
remove,
force,
);
},
);
// Exit (success)
return false;
}
/**
* @name Summary
*
* @description
* Initialize summary of products the cart (system)
*
* @return {Promise}
*/
static async summary() {
// Request
return await core.request("/cart/summary")
.then((json) => {
if (json) {
// Received a JSON-response
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");
if (amount instanceof HTMLElement) {
// Initialized the summary amount element
// Writing summmary amount into the summary amount element
amount.innerText = json.amount;
}
// Initializing the summary cost <span> element
const cost = document.getElementById("cost");
if (cost instanceof HTMLElement) {
// Initialized the summary cost element
// Writing summmary cost into the summary cost element
cost.innerText = json.cost;
}
}
}
});
}
}
core.modules.connect("damper").then(() => {
// Imported the damper module
Object.assign(
cart.product,
{
/**
* Toggle
*
* Toggle the product in the cart (damper)
*
* @param {HTMLButtonElement|HTMLInputElement} element Handler elememnt of the product
* @param {HTMLElement} product The product element
* @param {bool} remove Remove the product element if json.amount === 0?
* @param {bool} force Ignore the damper? (false)
*
* @return {Promise}
*/
damper: core.damper(
(...variables) => cart.product.system(...variables).then(cart.summary),
300,
6,
),
},
);
});
Object.assign(
cart.product,
{
/**
* The product
*
* Handle the product in the cart (system)
*
* @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|null}
*/
async system(
element,
product,
type,
amount = null,
remove = false,
resolve = () => {},
reject = () => {},
) {
try {
if (
(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(
"data-product-identifier",
);
if (typeof identifier === "number") {
// 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;
console.log(type, amount);
if (
(type === "toggle" &&
amount === null ||
typeof amount === "undefined") ||
(type === "set" &&
amount === 0 ||
amount === 100) ||
typeof amount === "number" &&
amount > 0 &&
amount < 100
) {
// Validated amount
console.log(amount);
if (type !== "toggle") {
// Not a toggle request
// Writing to the buffer of request body
request += "&amount=" + amount;
}
// Request
return await core.request(
"/cart/product",
request,
)
.then(
(json) => {
if (json) {
// Received a JSON-response
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 <span> element
const amounts = product.querySelectorAll(
'[data-product-parameter="amount"]',
);
for (const amount of amounts) {
// 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
amount.innerText = json.amount;
}
}
}
// Exit (success)
resolve();
}
}
},
() => reject(),
);
}
}
}
}
} catch (e) {
// Exit (fail)
reject(e);
}
},
},
);
// Connecting to the core
if (!core.cart) core.cart = cart;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,305 @@
"use strict";
/**
* @name Connection
*
* @description
* Implements actions with websocket connection
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
export default class connection {
/**
* @name Type of the program
*/
static type = "module";
// Wrap of indicator of the connection
static wrap = document.getElementById("connection");
// Indicator of the connection
static indicator = this.wrap?.getElementsByTagName("i")[0];
// Description of the connection
static description = this.wrap?.getElementsByTagName("small")[0];
// Statuc of the connection
static connected = false;
// Duration of the disconnected status
static timeout = 0;
// Instance of the time counter in disconnected status
static counter;
// Socket address (xn--e1ajlli это сокет)
static socket = "wss://arming.dev.mirzaev.sexy:9502";
// Instance of connection to the socket
static session;
// Iterval for reconnect
static interval;
// Attempts to connect (when connection.readyState === 0)
static attempts = 0;
// Interval for block
static block;
/**
* Initialize status of the connection to socket
*
* @param {bool} connected Connected?
*
* @return {void}
*/
static status(connected = false) {
if (this.indicator instanceof HTMLElement) {
// Initialized the indicator
this.connected = connected;
if (this.connected) {
// Connected
this.wrap.setAttribute("title", "Connected");
this.indicator.classList.remove("disconnected");
this.indicator.classList.add("connected");
clearInterval(this.counter);
this.description.innerText = "";
this.counter = undefined;
this.timeout = 0;
} else {
// Disconnected
this.wrap.setAttribute("title", "Disconnected");
this.indicator.classList.remove("connected");
this.indicator.classList.add("disconnected");
if (typeof this.counter === "undefined") {
this.counter = setInterval(() => {
this.timeout += 0.01;
this.description.innerText = this.timeout.toFixed(2);
}, 10);
}
}
}
}
/**
* Connect to the socket
*
* @param {bool|number} interval Connection check interval (ms)
* @param {function} preprocessing Will be executed every cycle
* @param {function} onmessage New message
* @param {function} onopen Connection opened
* @param {function} onclose Connection closed
* @param {function} onerror An error has occurred
*
* @return {Promise}
*/
static connect(
interval = false,
preprocessing,
onmessage,
onopen,
onclose,
onerror,
) {
return new Promise((resolve, reject) => {
try {
if (typeof interval === "number" && interval > 0) {
// Connect with automatic reconnect
if (typeof this.interval === "undefined") {
this.interval = setInterval(() => {
preprocessing();
if (
!(this.session instanceof WebSocket) ||
(this.session.readyState === 3 ||
this.session.readyState === 4) ||
(this.session.readyState === 0 &&
++this.attempts > 10)
) {
this.attempts = 0;
if (this.session instanceof WebSocket) {
this.session.close();
}
this.session = new WebSocket(this.socket);
this.session.addEventListener("message", (e) => {
try {
const json = JSON.parse(e.data);
if (json.type === "registration") {
// Подключение сокета к сессии
fetch("/socket/registration", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: `key=${json.key}`,
});
}
} catch (_e) {}
});
this.session.addEventListener("message", onmessage);
this.session.addEventListener("open", onopen);
this.session.addEventListener("close", onclose);
this.session.addEventListener("error", onerror);
resolve(this.session);
} else resolve(this.session);
}, interval);
}
} else {
// Connect without reconnecting
if (
!(this.session instanceof WebSocket) ||
(this.session.readyState === 3 ||
this.session.readyState === 4)
) {
if (this.session instanceof WebSocket) {
this.session.close();
}
this.session = new WebSocket(this.socket);
this.session.addEventListener("message", onmessage);
this.session.addEventListener("open", onopen);
this.session.addEventListener("close", onclose);
this.session.addEventListener("error", onerror);
resolve(this.session);
} else resolve(this.session);
}
} catch (_e) {}
});
}
/**
* Core is connected to the socket?
*
* @return {bool}
*/
static connected() {
return this.session instanceof WebSocket &&
this.session.readyState === 1;
}
}
connection.connect(
3000,
() =>
connection.status(
connection.session instanceof WebSocket &&
connection.session.readyState === 1,
),
(e) => {
try {
const json = JSON.parse(e.data);
if (json.target === "task") {
// Заявка
// Инициализация строки
const row = document.getElementById(json._key);
if (row instanceof HTMLElement) {
// Инициализирована строка
if (json.type === "blocked") {
// Заблокирована заявка
// Запись статуса: "заблокирована"
row.setAttribute("data-blocked", json.account._key);
row.setAttribute(
"title",
"Редактирует: " + json.account.name,
);
// Удалить блокировку (60000 === 1 минута) (в базе данных стоит expires 1 минута тоже)
setTimeout(() => {
// Удаление статуса: "заблокирована"
row.removeAttribute("data-blocked");
row.removeAttribute("title");
// Обновление строки
tasks.row(row);
}, 60000);
} else if (json.type === "unblocked") {
// Разблокирована заявка
// Удаление статуса: "заблокирована"
row.removeAttribute("data-blocked");
row.removeAttribute("title");
// Обновление строки
tasks.row(row);
} else if (json.type === "updated") {
// Обновлена заявка
// Обновление строки
tasks.row(row);
} else if (json.type === "deleted") {
// Удалена заявка
// Удаление строки
row.remove();
}
}
}
} catch (_e) {}
// Инициализация идентифиатора
//const id = row.getAttribute("id");
// Инициализация количества непрочитанных сообщений
//const messages = row.lastElementChild.innerText;
// Инициализация статуса активной строки
//const selected = row.getAttribute("data-selected");
// Реинициализация строки
//row.outerHTML = data.rows;
// Реинициализация перезаписанной строки
//row = document.getElementById(id);
// Копирование статуса активной строки
//if (
// typeof selected === "string" &&
// selected === "true" &&
// document.body.contains(document.getElementById("popup"))
//) {
// row.setAttribute("data-selected", "true");
//}
},
(e) => {
//connection.status(
// connection instanceof WebSocket &&
// connection.readyState === 1,
//)
//console.log("Connected to WebSocket!");
},
(e) => {
//connection.status(
// connection instanceof WebSocket &&
// connection.readyState === 1,
//)
//console.log("Connection closed");
},
(e) => {
//console.log("Error happens");
},
);
// Connecting to the core
if (!core.connection) core.connection = connection;

View File

@ -0,0 +1,76 @@
"use strict";
/**
* @name Damper
*
* @description
* Execute multiple "function" calls in a "timeout" amount of time just once
*
* @param {function} function Function to execute after damping
* @param {number} timeout Timer in milliseconds (ms)
* @param {number} force Argument number storing the status of enforcement execution (see @example)
*
* @return {Promise}
*
* @example
* a = damper(
* async (
* a, // 0
* b, // 1
* c, // 2
* force = false, // 3
* d, // 4
* resolve,
* reject
* ) => {
* // Body of the function
*
* resolve();
* },
* 500,
* 3, // 3 -> "force" argument
* );
*
* a('for a', 'for b', 'for c', true, 'for d'); // Force execute is enabled
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
export default function damper(func, timeout = 300, force) {
// Declaring of the timer for executing the function
let timer;
return ((...args) => {
return new Promise((resolve, reject) => {
// Deinitializing of the timer
clearTimeout(timer);
if (typeof force === "number" && args[force]) {
// 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);
} 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);
}
});
});
}
// Connecting to the core
if (!core.damper) core.damper = damper;

View File

@ -0,0 +1,161 @@
"use strict";
/**
* @name Delivery
*
* @description
* Implements actions with delivery
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
export default class delivery {
/**
* @name Type of the program
*/
static type = "module";
/**
* @name Company
*
* @description
* Choose a delivery company (interface)
*
* @param {HTNLElement} element Handler element
* @param {string} identifier Identifier of the delivery company
* @param {bool} force Ignore the damper? (false)
*
* @return {bool} Execution completed with an error?
*/
static company = (element, identifier, force = false) => {
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Execute under damper
this.company.damper(element, identifier, force);
},
() => {
// Not imported the damper module
// Execute
this.company.system(element, identifier, force);
},
);
// Exit (success)
return false;
};
/**
* @name City
*
* @description
* Write name of a city for delivery (interface)
*
* @param {HTNLInputElement} element Handler element <input>
* @param {bool} force Ignore the damper? (false)
*
* @return {bool} Execution completed with an error?
*/
static company = (element, force = false) => {
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Execute under damper
this.city.damper(element, identifier, force);
},
() => {
// Not imported the damper module
// Execute
this.company.system(element, identifier, force);
},
);
// Exit (success)
return false;
};
}
core.modules.connect("damper").then(() => {
// Imported the damper module
Object.assign(
delivery.company,
{
/**
* @name Write
*
* @description
* Choose a delivery company (damper)
*
* @param {HTNLElement} element Handler element
* @param {string} identifier Identifier of the delivery company
* @param {bool} force Ignore the damper? (false)
*
* @return {Promise}
*/
damper: core.damper(
(...variables) => delivery.company.system(...variables),
300,
3,
),
},
);
});
Object.assign(
delivery.company,
{
/**
* @name Write
*
* @description
* Choose a delivery company (system)
*
* @param {string} identifier Identifier of the delivery company
*
* @return {Promise}
*/
async system(
type,
value,
resolve = () => {},
reject = () => {},
) {
try {
if (
typeof type === "string" &&
(typeof value === "string" || typeof valye === "number")
) {
// Received and validated required arguments
// Sending request to the server
return await core.buffer.write(`delivery_${type}`, value)
.then(
(json) => {
try {
// Exit (success)
resolve(json);
} catch (e) {
// Exit (fail)
reject(e);
}
},
() => reject(),
);
}
} catch (e) {
// Exit (fail)
reject(e);
}
},
},
);
// Connecting to the core
if (!core.delivery) core.delivery = delivery;

View File

@ -0,0 +1,250 @@
"use strict";
/**
* @name Loader
*
* @description
* Implements actions with loading and rendering content
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
export default class loader {
/**
* @name Type of the program
*/
static type = "module";
/**
* @name Load
*
* @param {string} uri
* @param {string} body
*
* @return {Promise}
*/
static async load(uri = "/", body) {
if (typeof uri === "string") {
// Received and validated uri
return await core
.request(
uri,
body,
"POST",
{
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
"json",
)
.then((json) => {
if (json) {
// Received a JSON-response
if (
json.errors !== null &&
typeof json.errors === "object" &&
json.errors.length > 0
) {
// Fail (received errors)
} else {
// Success (not received errors)
// Writing to the browser history
history.pushState({}, json.title ?? uri, uri);
/**
* The <title>
*
* The title of the page
*/
if (
typeof json.title === "string" &&
json.title.length > 0
) {
// Received text for the <title> of the page
// Search for the <title> element (document.title)
const title = document.getElementsByTagName("title")[0];
if (title instanceof HTMLElement) {
// Found the <title> element
// Writing into the <title> element
title.innerText = json.title;
} else {
// Not found the <title> element
// Initialize the <title> element
const title = document.createElement("title");
// Inititalize the <head> element (document.head)
const head = document.getElementsByTagName("head")[0];
if (head instanceof HTMLElement) {
// Found the <head> element
// Writing the <title> element into the <head> element
head.appendChild(title);
}
// Writing title into the <title> element
title.innerText = json.title;
}
}
/**
* The <header> element
*/
if (
typeof json.header === "string" &&
json.header.length > 0
) {
// Received and validated the header HTML-code
if (core.header instanceof HTMLElement) {
// Found the <header> element
// Writing into the <header> element
core.header.outerHTML = json.header;
// Reinitializing the parameter with the <header> element
core.header = document.getElementsByTagName("header")[0];
} else {
// Not found the <header> element
// Initialize the <header> element
core.header = document.createElement("header");
// Inititalize the <body> element (document.body)
const body = document.getElementsByTagName("body")[0];
if (body instanceof HTMLElement) {
// Found the <body> element
if (core.main instanceof HTMLElement) {
// Found the <main> element
// Writing the <header> element before the <main> element
body.insertBefore(core.header, core.main);
} else if (core.footer instanceof HTMLElement) {
// Fount the <footer> element
// Writing the <header> element before the <footer> element
body.insertBefore(core.header, core.footer);
} else {
// Not found the <main> element and the <footer> element
// Search for the last <section> element inside the <body> element
const section = document.body.querySelector(
"body > section:last-of-type",
);
if (section instanceof HTMLElement) {
// Found the last <section> element inside the <body> element
// Writing the <header> element after the last <section> element inside the <body> element
body.insertBefore(
core.header,
section.nextSibling,
);
} else {
// Not found section elements <section> inside the <body> element
// Writing the <header> element into the <body> element
body.appendChild(core.header);
}
}
// Writing into the <header> element
core.header.outerHTML = json.header;
// Reinitializing the parameter with the <header> element
core.header = document.getElementsByTagName("header")[0];
}
}
}
/**
* The <main> element
*
* The main content of the page
*/
if (typeof json.main === "string" && json.main.length > 0) {
// Received and validated the <main> HTML-code
if (core.main instanceof HTMLElement) {
// Found the <main> element
// Writing into the <main> element
core.main.outerHTML = json.main;
// Reinitializing the parameter with the <main> element
core.main = document.getElementsByTagName("main")[0];
} else {
// Not found the <main> element
// Initialize the <main> element
core.main = document.createElement("main");
// Inititalize the <body> element (document.body)
const body = document.getElementsByTagName("body")[0];
if (body instanceof HTMLElement) {
// Found the <body> element
if (core.header instanceof HTMLElement) {
// Found the <header> element
// Writing the <main> element after the <header> element
body.insertBefore(
core.main,
core.header.nextSibling,
);
} else if (core.footer instanceof HTMLElement) {
// Fount the <footer> element
// Writing the <main> element before the <footer> element
body.insertBefore(core.main, core.footer);
} else {
// Not found the <header> element and the <footer> element
// Search for the last <section> element inside the <body> element
const section = document.body.querySelector(
"body > section:last-of-type",
);
if (section instanceof HTMLElement) {
// Found the last <section> element inside the <body> element
// Writing the <main> element after the last <section> element inside the <body> element
body.insertBefore(core.main, section.nextSibling);
} else {
// Not found section elements <section> inside the <body> element
// Writing the <main> element into the <body> element
body.appendChild(core.main);
}
}
// Writing into the <main> element
core.main.outerHTML = json.main;
// Reinitializing the parameter with the <main> element
core.main = document.getElementsByTagName("main")[0];
}
}
}
// Exit (success)
return json;
}
}
});
}
}
}
// Connecting to the core
if (!core.loader) core.loader = loader;

View File

@ -0,0 +1,129 @@
"use strict";
/**
* @name Session
*
* @description
* Implements actions with sessions
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
export default class session {
/**
* @name Type of the program
*/
static type = "module";
/**
* @name Buffer
*/
static buffer = class buffer {
/**
* @name Write
*
* @description
* Write to the session buffer (interface)
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
* @param {bool} force Ignore the damper? (false)
*
* @return {bool} Execution completed with an error?
*/
static write = (name, value, force = false) => {
core.modules.connect("damper").then(
() => {
// Imported the damper module
// Execute under damper
this.write.damper(name, value, force);
},
() => {
// Not imported the damper module
// Execute
this.write.system(name, value, force);
},
);
// Exit (success)
return false;
};
};
}
core.modules.connect("damper").then(() => {
// Imported the damper module
Object.assign(
session.buffer.write,
{
/**
* @name Write
*
* @description
* Write to the session buffer (damper)
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
* @param {bool} force Ignore the damper? (false)
*
* @return {Promise}
*/
damper: core.damper(
(...variables) => session.buffer.write.system(...variables),
300,
3,
),
},
);
});
Object.assign(
session.buffer.write,
{
/**
* @name Write
*
* @description
* Write to the session buffer (system)
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
*
* @return {Promise}
*/
async system(
name,
value,
resolve = () => {},
reject = () => {},
) {
if (typeof name === "string" && typeof value === "string") {
// Received and validated required arguments
// Sending request to the server
return await core.request(
"/session/write",
`${name}=${value}`,
"POST",
)
.then(
(json) => {
if (json) {
// Received a JSON-response
// Exit (success)
resolve(json);
}
},
() => reject(),
);
}
},
},
);
// Connecting to the core
if (!core.session) core.session = session;

View File

@ -0,0 +1,27 @@
"use strict";
/**
* @name Telegram
*
* @description
* Implements actions with data of the telegram account
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
export default class telegram {
/**
* @name Type of the program
*/
static type = "module";
/**
* Telegram "WebApp" API
*
* @see {@link https://core.telegram.org/bots/webapps#initializing-mini-apps}
*/
static api = window.Telegram.WebApp;
}
// Connecting to the core
if (!core.telegram) core.telegram = telegram;

View File

@ -1,71 +0,0 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() =>
import("/js/damper.js").then(() =>
import("/js/telegram.js").then(() => {
const dependencies = setInterval(() => {
if (
typeof core === "function" &&
typeof core.damper === "function" &&
typeof core.telegram === "function"
) {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
if (typeof core.session === "undefined") {
// Not initialized
/**
* @name Session
*
* @description
* Implements actions with sessions
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.session = class session {
/**
* Buffer
*/
static buffer = class buffer {
/**
* Write to the session buffer
*
* @param {string} name Name of the parameter
* @param {string} value Value of the parameter (it can be JSON)
*
* @return {void}
*/
static write(name, value) {
if (typeof name === "string" && typeof value === "string") {
//
// Send request to the server
core.request(
"/session/write",
`${name}=${value}`,
"POST",
{},
null,
);
}
}
};
};
}
}
})
)
);

View File

@ -1,53 +0,0 @@
"use strict";
// Import dependencies
import("/js/core.js").then(() =>
import("/js/damper.js").then(() => {
const dependencies = setInterval(() => {
if (
typeof core === "function" &&
typeof core.damper === "function"
) {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
if (typeof core.telegram === "undefined") {
// Not initialized
/**
* @name Telegram
*
* @description
* Implements actions with data of the telegram account
*
* @memberof core
*
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
core.telegram = class telegram {
/**
* Telegram "WebApp" API
*
* @see {@link https://core.telegram.org/bots/webapps#initializing-mini-apps}
*/
static api = window.Telegram.WebApp;
};
/* telegram.MainButton.text =
typeof core === "object" && core.language === "ru"
? "Корзина"
: "Cart";
telegram.MainButton.show(); */
}
}
})
);

View File

@ -33,8 +33,12 @@ define('CATALOG_EXAMPLE', STORAGE . DIRECTORY_SEPARATOR . 'example.xlsx');
// Файл в формате xlsx для импорта каталога
define('CATALOG_IMPORT', STORAGE . DIRECTORY_SEPARATOR . 'import.xlsx');
// Ключ чат-робота Telegram
define('KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'key.php'));
/**
* Ключ чат-робота Telegram
* @deprecated
*/
define('KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
define('TELEGRAM_KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'telegram.php'));
// Initialize dependencies
require __DIR__ . DIRECTORY_SEPARATOR
@ -55,7 +59,7 @@ $config = new Config();
$config->setParseMode(Config::PARSE_MODE_MARKDOWN);
$config->useReactFileSystem(true);
$bot = new Zanzara(KEY, $config);
$bot = new Zanzara(TELEGRAM_KEY, $config);
/* $bot->onUpdate(function (Context $ctx): void {
var_dump($ctx->getMessage()->getWebAppData());

View File

@ -1,22 +1,86 @@
@charset "UTF-8";
main>section:is(#summary, #products) {
main>section:is(#summary, #products, #delivery) {
width: var(--width);
display: flex;
flex-direction: column;
gap: var(--gap);
overflow: hidden;
}
main>section#delivery>div.column {
padding: 1rem;
gap: var(--gap);
background-color: var(--tg-theme-secondary-bg-color);
}
main>section#delivery>div.column>div#deliveries>input {
display: none;
}
main>section#delivery>div.column>div#deliveries>input+label {
/* backdrop-filter: brightness(1.2); */
/* background-color: unset; */
}
main>section#delivery>div.column>div#deliveries>input+label:is(:hover, :focus) {
filter: brightness(1.1) hue-rotate(30deg);
/* filter: unset; */
/* backdrop-filter: brightness(1.4); */
}
main>section#delivery>div.column>div#deliveries>input+label:active {
filter: unset;
/* backdrop-filter: brightness(0.8); */
}
main>section#delivery>div.column>div#deliveries>input:checked+label {
filter: hue-rotate(30deg);
}
main>section#delivery>div.column>div#deliveries>input:checked+label:is(:hover, :focus) {
filter: brightness(1.1) hue-rotate(30deg);
}
main>section#delivery>div.column>div#deliveries>input:checked+label:active {
filter: brightness(0.9) hue-rotate(30deg);
}
main>section#delivery>div.column>div#address {
flex-flow: row wrap;
gap: 0.7rem;
}
main>section#delivery>div.column>div#address>input#city {
min-width: max(6rem, 20%);
width: min(6rem, 100%);
flex-grow: 1;
}
main>section#delivery>div.column>div#address>input#street {
min-width: max(10rem, 30%);
width: min(10rem, 100%);
flex-grow: 10;
}
main:has(section#summary.disabled:hover)>section#delivery>div {
filter: brightness(1.2);
}
main>section#summary>div {
container-type: inline-size;
container-name: summary;
padding-left: 1rem;
display: flex;
align-items: center;
gap: 0.4rem;
overflow: hidden;
pointer-events: auto;
border-radius: 1.375rem;
background-color: var(--tg-theme-secondary-bg-color);
}
main>section#summary>div>span {
flex-shrink: 0;
}
main>section#summary>div>span:first-of-type {
/* margin-left: auto; */
}
@ -27,6 +91,19 @@ main>section#summary>div>button#order {
padding-left: 0.7rem;
}
main>section#summary.disabled,
main>section#summary>div:has(button#order:disabled),
main:not(:has(section#delivery>div.column>div#deliveries>input:checked))>section#summary>div:has(button#order:enabled) {
cursor: not-allowed;
}
main>section#summary.disabled,
main>section#summary>div>button#order:disabled,
main:not(:has(section#delivery>div.column>div#deliveries>input:checked))>section#summary>div>button#order:enabled {
pointer-events: none;
filter: grayscale(1);
}
main>section#products>article.product {
position: relative;
width: 100%;
@ -137,3 +214,9 @@ main>section#products>article.product>div>div.footer>input {
padding: 0 0.3rem;
text-align: center;
}
@container summary (max-width: 350px) {
main>section#summary>div>span:not(#cost) {
display: none;
}
}

View File

@ -180,6 +180,20 @@ input {
margin-left: var(--currency-offset, 0.1rem);
}
.rounded {
border-radius: 0.75rem;
}
.row {
display: flex;
flex-direction: row;
}
.column {
display: flex;
flex-direction: column;
}
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;

View File

@ -17,8 +17,6 @@ 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)); */
color: var(--tg-theme-button-text-color);
background-color: var(--tg-theme-button-color);
}
@ -32,25 +30,25 @@ header>nav#menu>a[type="button"]>* {
}
@container header (max-width: 450px) {
header>nav#menu > a[type="button"]:nth-child(1)> i.icon+span {
header>nav#menu>a[type="button"]:nth-child(1)>i.icon+span {
display: none;
}
}
@container header (max-width: 350px) {
header>nav#menu > a[type="button"]:nth-child(2)> i.icon+span {
header>nav#menu>a[type="button"]:nth-child(2)>i.icon+span {
display: none;
}
}
@container header (max-width: 250px) {
header>nav#menu > a[type="button"]:nth-child(3)> i.icon+span {
header>nav#menu>a[type="button"]:nth-child(3)>i.icon+span {
display: none;
}
}
@container header (max-width: 150px) {
header>nav#menu > a[type="button"]> i.icon+span {
header>nav#menu>a[type="button"]>i.icon+span {
display: none;
}
}

View File

@ -133,15 +133,17 @@ section#window>div.card>p:last-of-type {
}
section#window>div.card>div.footer {
container-type: inline-size;
container-name: window-footer;
padding: 0.8rem 2.3rem;
display: flex;
flex-flow: row wrap;
align-items: baseline;
gap: 0 0.8rem;
gap: 0.8rem;
background-color: var(--tg-theme-header-bg-color);
}
section#window>div.card>div.footer>small.dimensions {
margin-left: 1.5rem;
color: var(--tg-theme-section-header-text-color);
}
@ -151,7 +153,6 @@ section#window>div.card>div.footer>small.weight {
section#window>div.card>div.footer>p.cost {
margin-left: auto;
margin-right: 1.5rem;
font-weight: bold;
}
@ -159,3 +160,20 @@ section#window>div.card>div.footer>button.buy {
width: 100%;
height: 3.5rem;
}
@container window-footer (max-width: 350px) {
section#window>div.card>div.footer>small:first-of-type {
margin-left: auto;
}
section#window>div.card>div.footer>small:last-of-type {
margin-right: auto;
}
section#window>div.card>div.footer>p.cost {
margin: unset;
width: 100%;
text-align: center;
}
}

View File

@ -0,0 +1,6 @@
<?php
return [
'account' => '',
'secret' => ''
];

View File

@ -8,7 +8,6 @@ namespace mirzaev\arming_bot\views;
use mirzaev\arming_bot\models\session,
mirzaev\arming_bot\models\account,
mirzaev\arming_bot\models\settings,
mirzaev\arming_bot\models\cart,
mirzaev\arming_bot\models\enumerations\language,
mirzaev\arming_bot\models\enumerations\currency;
@ -50,7 +49,6 @@ final class templater extends controller implements ArrayAccess
* @param session|null $session The object implementing a session instance from ArangoDB
* @param account|null $account The object implementing an account instance from ArangoDB
* @param settings|null $settings The object implementing a settings instance from ArangoDB
* @param cart|null $cart The object implementing a cart instance from ArangoDB
*
* @return void
*/
@ -58,7 +56,6 @@ final class templater extends controller implements ArrayAccess
?session $session = null,
?account $account = null,
?settings $settings = null,
?cart $cart = null
) {
// Initializing an instance of twig
$this->twig = new twig(new FilesystemLoader(VIEWS));
@ -75,7 +72,8 @@ 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', ['summary' => $cart->summary(currency: $currency), 'products' => $cart->products(language: $language, currency: $currency)]);
// @todo move functions into controllers
// Initialize function of dimensions formatting
$this->twig->addFunction(

View File

@ -6,7 +6,7 @@
{% if account is empty %}
<section id="account">
<a onclick="core.account.authentication()">
<!-- {{ language == 'ru' ? "Аутентификация" : "Authentication" }} -->
{{ language == 'ru' ? "Аутентификация" : "Authentication" }}
</a>
</section>
{% else %}
@ -19,5 +19,5 @@
{% endblock %}
{% block js %}
<script src="/js/account.js"></script>
<script src="/js/modules/account.js" type="module"></script>
{% endblock %}

View File

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

View File

@ -0,0 +1,25 @@
{% if deliveries is not empty %}
<section id="delivery" class="rounded column unselectable">
<div class="column">
<div id="deliveries" class="row">
{% for identifier, delivery in deliveries %}
<input id="{{ identifier }}" name="delivery" type="radio" {% if identifier==session.buffer.delivery.company %}
checked{% endif %}>
<label class="rounded" for="{{ identifier }}" type="button"
title="{{ (language.name == 'ru' ? 'Выбрать ' : 'Choose ') ~ delivery.label }}"
onclick="core.buffer.write.damper('delivery_company', '{{ identifier }}').then(() => core.delivery.check())" tabindex="5">
{{ delivery.label }}
</label>
{% endfor %}
</div>
<div id="address" class="row">
<input id="city" placeholder="{{ language.name == 'ru' ? 'Город' : 'City' }}"
value="{{ session.buffer.delivery.city ?? account.buffer.delivery.city }}"
onkeydown="core.buffer.write.damper('delivery_city', this.value).then(() => core.delivery.check())" tabindex="6" />
<input id="street" placeholder="{{ language.name == 'ru' ? 'Улица' : 'Street' }}"
value="{{ session.buffer.delivery.street ?? session.buffer.delivery.street }}"
onkeydown="core.buffer.write.damper('delivery_street', this.value).then(() => core.delivery.check())" tabindex="6" />
</div>
</div>
</section>
{% endif %}

View File

@ -1,5 +1,5 @@
{% macro card(product, amount) %}
<article id="{{ product._id }}" class="product unselectable" data-product-identifier="{{ product.identifier }}"
<article id="{{ product._id }}" class="product" 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">
@ -40,8 +40,9 @@
</article>
{% endmacro %}
{% if cart.products is not empty %}
<section id="products" class="unselectable">
<section id="products" class="column unselectable">
{% for product in cart.products %}
{{ product.summary }}
{{ _self.card(product.document, product.amount) }}
{% endfor %}
</section>

View File

@ -1,10 +1,13 @@
{% if cart.products is not empty %}
<section id="summary" class="unselectable">
<div>
<section id="summary"
class="column {% if delivery is empty or delivery.city is empty or delivery.street is empty %}disabled{% endif %} unselectable"
{% if delivery is empty %}title="{{ language.name == " ru" ? "Выберите тип доставки" : "Choose delivery type" }}"{%
endif %}>
<div class="row">
<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)"
<button id="order" onclick="core.cart.order(this)" {% if delivery is empty %} disabled="true" {% endif %}
title="{{ language.name == 'ru' ? 'Оформить заказ' : 'Place an order' }}"><i class="icon arrow"></i></button>
</div>
</section>

View File

@ -12,13 +12,15 @@
{% endblock %}
{% block main %}
<h2 class="unselectable">{{ h2 }}</h2>
<h2 id="title" class="unselectable">{{ h2 }}</h2>
{% include "/themes/default/cart/elements/delivery.html" %}
{% 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>
<script src="/js/modules/delivery.js" type="module"></script>
<script src="/js/modules/cart.js" type="module"></script>
<script src="/js/modules/hotline.js" type="module"></script>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% if filters is not empty %}
<section id="filters" class="unselectble">
<section class="unselectable" data-type="select" tabindex="4">
{% set buffer_brand = account.buffer.catalog.filters.brand ?? session.buffer.catalog.filters.brand %}
{% set buffer_brand = session.buffer.catalog.filters.brand ?? account.buffer.catalog.filters.brand %}
<input name="brand" type="radio" id="brand_title" {% if buffer_brand is empty %} checked{% endif %}>
<label for="brand_title" type="button" onclick="core.catalog.parameters.set('brand', null); core.catalog.search(event)">
{{ language.name == 'ru' ? 'Бренд' : 'Brand' }}

View File

@ -16,7 +16,6 @@ product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %
<button data-product-button="toggle" onclick="core.cart.toggle(this, document.getElementById('{{ product.getId() }}'))" tabindex="15">
<span data-product-parameter="amount">{{ amount }}</span>
<span class="cost currency" data-product-parameter="cost">{{ product.cost }}</span>
<span data-product-parameter="currency">{{ currency.symbol }}</span>
</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>

View File

@ -1,6 +1,6 @@
<search id="search">
<label class="unselectable"><i class="icon search"></i></label>
{% set buffer_search = account.buffer.catalog.search.text ?? session.buffer.catalog.search.text %}
{% set buffer_search = session.buffer.catalog.search.text ?? account.buffer.catalog.search.text %}
<input placeholder="{{ language.name == 'ru' ? 'Поиск по каталогу' : 'Search in the catalog' }}" type="search" tabindex="1"
onkeyup="event.keyCode === 9 || core.catalog.parameters.set('text', this.value); core.catalog.search(event, this)"
{% if buffer_search is not empty %} value="{{ buffer_search }}" {% endif %} />

View File

@ -12,7 +12,7 @@
{% endblock %}
{% block main %}
<h2 class="unselectable">{{ h2 }}</h2>
<h2 id="title" class="unselectable">{{ h2 }}</h2>
{% include "/themes/default/catalog/elements/search.html" %}
{% include "/themes/default/catalog/elements/categories.html" %}
{% include "/themes/default/catalog/elements/filters.html" %}
@ -21,7 +21,7 @@
{% block js %}
{{ parent() }}
<script src="/js/catalog.js"></script>
<script src="/js/cart.js"></script>
<script src="/js/hotline.js"></script>
<script src="/js/modules/catalog.js" type="module"></script>
<script src="/js/modules/cart.js" type="module"></script>
<script src="/js/hotline.js" type="module"></script>
{% endblock %}

View File

@ -10,5 +10,5 @@
{% endblock %}
{% block js %}
<script src="/js/connection.js"></script>
<script src="/js/modules/connection.js" type="module"></script>
{% endblock %}

View File

@ -1,38 +1,20 @@
{% block js %}
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<script src="/js/modules/damper.js" type="module"></script>
<script src="/js/core.js"></script>
<script src="/js/damper.js"></script>
<script src="/js/loader.js"></script>
<script src="/js/telegram.js"></script>
<script src="/js/session.js"></script>
<script src="/js/modules/loader.js" type="module"></script>
<script src="/js/modules/telegram.js" type="module"></script>
<script src="/js/modules/session.js" type="module"></script>
<script src="/js/authentication.js"></script>
{% if javascript is not empty %}
<script>
import("/js/core.js").then(() =>
import("/js/damper.js").then(() => {
const dependencies = setInterval(() => {
if (
typeof core === "function" &&
typeof core.damper === "function"
) {
clearInterval(dependencies);
clearTimeout(timeout);
initialization();
}
}, 10);
const timeout = setTimeout(() => {
clearInterval(dependencies);
initialization();
}, 5000);
function initialization() {
core.modules.connect("damper").then(() => {
let _window;
{% for code in javascript %}
{{ code|raw }}
{% endfor %}
}
})
);
});
</script>
{% endif %}
{% endblock %}

View File

@ -12,7 +12,7 @@
{% for button in menu %}
<a href='{{ button.urn }}' onclick="return core.loader.load('{{ button.urn }}');" type="button" class="unselectable"
title="{{ button.name }}" {% if button.style %}
style="{% for parameter, value in button.style %}{{ parameter ~ ': ' ~ value ~ '; ' }}{% endfor %}" {% endif %}">
style="{% for parameter, value in button.style %}{{ parameter ~ ': ' ~ value ~ '; ' }}{% endfor %}" {% endif %}>
{% if button.icon %}
<i class="icon {{ button.icon.class }}" {% if button.icon.style %}
style="{% for parameter, value in button.icon.style %}{{ parameter ~ ': ' ~ value ~ '; ' }}{% endfor %}" {% endif

View File

@ -0,0 +1,11 @@
{% if deliveries is not empty %}
<section id="deliveries" class="unselectable">
{% for identifier, delivery in deliveries %}
<div id="{{ identifier }}">
<h4>{{ identifier[:1]|upper ~ identifier[1:] }}</h4>
<button onclick="core.delivery.choose(this, '{{ identifier }}')"
title="{{ language.name == 'ru' ? 'Выбрать {{ identifier }}' : 'Choose {{ identifier }}' }}"></button>
</div>
{% 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 id="title" class="unselectable">{{ h2 }}</h2>
{% include "/themes/default/cart/elements/delivery.html" %}
{% include "/themes/default/cart/elements/summary.html" %}
{% endblock %}
{% block js %}
{{ parent() }}
<script src="/js/modules/cart.js" type="module"></script>
<script src="/js/modules/hotline.js" type="module"></script>
{% endblock %}