From 16be453b07a611cdbb99541521e530c5e0efafab Mon Sep 17 00:00:00 2001 From: Arsen Mirzaev Tatyano-Muradovich Date: Sun, 20 Oct 2024 00:17:11 +0300 Subject: [PATCH] menu + catelog REBUILD + new icons --- .../arming_bot/system/controllers/account.php | 60 +++ .../arming_bot/system/controllers/catalog.php | 365 ++++++------- .../arming_bot/system/controllers/session.php | 21 +- mirzaev/arming_bot/system/models/menu.php | 29 ++ mirzaev/arming_bot/system/models/product.php | 9 +- mirzaev/arming_bot/system/public/index.php | 10 +- .../arming_bot/system/public/js/account.js | 42 +- .../system/public/js/authentication.js | 12 +- .../arming_bot/system/public/js/catalog.js | 490 +++++++----------- mirzaev/arming_bot/system/public/js/core.js | 32 +- .../arming_bot/system/public/js/session.js | 35 +- .../default/css/icons/arrow-top-left.css | 37 ++ .../default/css/icons/shopping_cart.css | 1 - .../system/public/themes/default/css/main.css | 21 +- .../system/public/themes/default/css/menu.css | 53 ++ .../default/catalog/elements/categories.html | 18 +- .../default/catalog/elements/filters.html | 9 +- .../default/catalog/elements/search.html | 4 +- .../system/views/themes/default/header.html | 5 + .../system/views/themes/default/index.html | 6 +- .../system/views/themes/default/menu.html | 30 ++ .../system/views/themes/default/search.html | 14 - 22 files changed, 747 insertions(+), 556 deletions(-) create mode 100755 mirzaev/arming_bot/system/controllers/account.php create mode 100755 mirzaev/arming_bot/system/models/menu.php create mode 100755 mirzaev/arming_bot/system/public/themes/default/css/icons/arrow-top-left.css create mode 100755 mirzaev/arming_bot/system/public/themes/default/css/menu.css create mode 100644 mirzaev/arming_bot/system/views/themes/default/menu.html delete mode 100755 mirzaev/arming_bot/system/views/themes/default/search.html diff --git a/mirzaev/arming_bot/system/controllers/account.php b/mirzaev/arming_bot/system/controllers/account.php new file mode 100755 index 0000000..9a3c160 --- /dev/null +++ b/mirzaev/arming_bot/system/controllers/account.php @@ -0,0 +1,60 @@ + + */ +final class account extends core +{ + /** + * Registry of errors + */ + protected array $errors = [ + 'session' => [], + 'account' => [] + ]; + + /** + * Write to the buffer + * + * @param array $parameters Parameters of the request (POST + GET) + * + * @return void + */ + public function write(array $parameters = []): void + { + if (!empty($parameters) && $this->account instanceof model) { + // Found data of the program and active account + + foreach ($parameters as $name => $value) { + // Iterate over parameters + + // Validation of the parameter + 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]; + + // Write data of to the buffer parameter in the implement object of account document from ArangoDB + $this->account->buffer = $parameter + $this->account->buffer ?? []; + } + + // Write from implement object to account document from ArangoDB + document::update($this->account->__document(), $this->errors['account']); + } + } +} diff --git a/mirzaev/arming_bot/system/controllers/catalog.php b/mirzaev/arming_bot/system/controllers/catalog.php index 77a27f3..1c39118 100755 --- a/mirzaev/arming_bot/system/controllers/catalog.php +++ b/mirzaev/arming_bot/system/controllers/catalog.php @@ -8,7 +8,11 @@ namespace mirzaev\arming_bot\controllers; use mirzaev\arming_bot\controllers\core, mirzaev\arming_bot\models\entry, mirzaev\arming_bot\models\category, - mirzaev\arming_bot\models\product; + mirzaev\arming_bot\models\product, + mirzaev\arming_bot\models\menu; + +// Library for ArangoDB +use ArangoDBClient\Document as _document; /** * Controller of catalog @@ -24,6 +28,7 @@ final class catalog extends core protected array $errors = [ 'session' => [], 'account' => [], + 'menu' => [], 'catalog' => [] ]; @@ -34,66 +39,162 @@ final class catalog extends core */ public function index(array $parameters = []): ?string { - // Initializing identifier of a category - preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches); - $identifier = $matches[0] ?? null; + // Validating + if (!empty($parameters['category']) && preg_match('/[\d]+/', $parameters['category'], $matches)) $category = (int) $matches[0]; - // Initializint the buffer of respnse - $html = []; + if (isset($category)) { + // Received and validated identifier of the category - if (!empty($parameters['identifier'])) { - // Передана категория (идентификатор) - - // Инициализация актуальной категории - $category = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $identifier], errors: $this->errors['catalog']); + // Initialize of category + $category = category::_read('d.identifier == @identifier', parameters: ['identifier' => $category], errors: $this->errors['catalog']); if ($category instanceof category) { // Found the category - // Поиск категорий или товаров входящих в актуальную категорию + // Write to the response buffer + $response['category'] = ['name' => $category->name ?? null]; + + // Search for categories that are descendants of $category $entries = entry::search( document: $category, amount: 30, errors: $this->errors['catalog'] ); - // Объявление буферов категорий и товаров (важно - в единственном числе, по параметру из базы данных) + // Initialize buffers of entries (in singular, by parameter from ArangoDB) $category = $product = []; foreach ($entries as $entry) { - // Перебор вхождений + // Iterate over entries (descendands) - // Запись массивов категорий и товаров ($category и $product) в буфер глобальной переменной шаблонизатора + // Write entry to the buffer of entries (sort by $category and $product) ${$entry->_type}[] = $entry; } - // Запись категорий из буфера в глобальную переменную шаблонизатора + // Write to the buffer of global variables of view templater $this->view->categories = $category; - // Запись товаров из буфера в глобальную переменную шаблонизатора + // Write to the buffer of global variables of view templater $this->view->products = $product; - // Generating filters - $this->view->filters = [ - 'brands' => product::collect( - 'd.brand.' . $this->language->name, - errors: $this->errors['catalog'] - ) - ]; - - // Generating HTML and writing to the buffer of response - $html = [ - 'filters' => count($this->view->products) > 0 ? $this->view->render('catalog/elements/filters.html') : null, - 'products' => $this->view->render('catalog/elements/products/2columns.html') - ]; + // Delete buffers + unset($category, $product); } - } else { - // Не передана категория + } else if (!isset($parameters['category'])) { + // Not received identifie of the category - // Поиск категорий: "categories" (самый верхний уровень) + // Search for root ascendants categories $this->view->categories = entry::ascendants(descendant: new category, errors: $this->errors['catalog']); } + // Validating + if (!empty($parameters['product']) && preg_match('/[\d]+/', $parameters['product'], $matches)) $product = (int) $matches[0]; + + if (isset($product)) { + // Received and validated identifier of the product + + // Search for product and write to the buffer of global variables of view templater + $this->view->product = product::read( + filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true", + sort: 'd.created DESC', + amount: 1, + return: '{name: d.name.@language, description: d.description.@language, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, images: d.images[*].storage}', + parameters: ['identifier' => $product], + language: $this->language, + errors: $this->errors['catalog'] + )[0]?->getAll() ?? null; + } + + // Validating + if (!empty($parameters['text']) && preg_match('/[\w\s]+/u', $parameters['text'], $matches)) $text = $matches[0]; + + if (isset($text)) { + // Received and validated text for search + + // Intialize buffer of query parameters + $_parameters = []; + + // Initialize buffer of filters query (AQL) + $_filters = 'd.deleted != true && d.hidden != true'; + + if (isset($this->view->products) && count($this->view->products) > 0) { + // Amount of rendered products is more than 0 + + // Write to the buffer of filters query (AQL) + $_filters .= ' && POSITION(["' . implode('", "', array_map(fn(_document $document): string => $document->getId(), $this->view->products)) . '"], d._id)'; + } + + // Validating + if (!empty($parameters['brand']) && preg_match('/[\w]+/', $parameters['brand'], $matches)) $brand = $matches[0]; + + if (isset($brand)) { + // Received and validated filter by brand + + // Write to the buffer of filters query (AQL) + $_filters .= ' && d.brand.@language == @brand'; + + // Write to the buffer of query parameters + $_parameters['brand'] = $brand; + } + + // Initialize buffer of filters query (AQL) + $_sort = 'd.position ASC, d.name ASC, d.created DESC'; + + // Validating + if (!empty($parameters['sort']) && preg_match('/[\w]+/', $parameters['sort'], $matches)) $sort = $matches[0]; + + if (isset($sort)) { + // Received and validated sort + + // Write to the buffer of sort query (AQL) + $_sort = "d.@sort DESC, $_sort"; + + // Write to the buffer of query parameters + $_parameters['sort'] = $sort; + } + + // Search for products and write to the buffer of global variables of view templater + $this->view->products = product::read( + search: $text, + filter: $_filters, + sort: $_sort, + amount: 30, + language: $this->language, + parameters: $_parameters, + errors: $this->errors['catalog'] + ); + } + + if (isset($this->view->products) && count($this->view->products) > 0) { + // Amount of rendered products is more than 0 + + // Search for filters and write to the buffer of global variables of view templater + $this->view->filters = [ + 'brands' => product::collect( + return: 'd.brand.@language', + products: array_map(fn(_document $document): string => $document->getId(), $this->view->products), + language: $this->language, + errors: $this->errors['catalog'] + ) + ]; + } + + if (isset($menu)) { + // + + } else { + // Not received ... menu + + // Search for filters and write to the buffer of global variables of view templater + $this->view->menu = menu::_read( + return: 'MERGE(d, { name: d.name.@language })', + sort: 'd.position ASC, d.created DESC, d._key DESC', + amount: 4, + parameters: ['language' => $this->language->name], + errors: $this->errors['menu'] + ); + } + if ($_SERVER['REQUEST_METHOD'] === 'GET') { // GET request @@ -102,152 +203,64 @@ final class catalog extends core } else if ($_SERVER['REQUEST_METHOD'] === 'POST') { // POST request - // Initializing a response headers - header('Content-Type: application/json'); - header('Content-Encoding: none'); - header('X-Accel-Buffering: no'); + // Initializing the buffer of response + $response = [ + 'title' => $title ?? null + ]; - // Initializing of the output buffer - ob_start(); + if (isset($this->view->categories)) { + // Initialized categories - // Generating the reponse - echo json_encode( - [ - 'title' => $title ?? '', - 'html' => $html + [ - 'categories' => $this->view->render('catalog/elements/categories.html'), - ], - '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; - } - - /** - * Search - * - * @param array $parameters Parameters of the request (POST + GET) - */ - public function search(array $parameters = []): ?string - { - // Initializing of text fore search - preg_match('/[\w\s]+/u', $parameters['text'] ?? '', $matches); - $text = $matches[0] ?? null; - - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - // POST request - - // Search for products - $this->view->products = isset($text) ? product::read( - search: $text, - filter: 'd.deleted != true && d.hidden != true', - sort: 'd.position ASC, d.name ASC, d.created DESC', - amount: 30, - language: $this->language, - errors: $this->errors['catalog'] - ) : []; - - // 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( - [ - 'title' => $title ?? '', - 'html' => [ - 'products' => $this->view->render('catalog/elements/products/2columns.html') - ], - 'errors' => $this->errors - ] - ); - - // Initializing a response headers - header('Content-Length: ' . ob_get_length()); - - // Sending and deinitializing of the output buffer - ob_end_flush(); - flush(); - - // Exit (success) - return null; - } - - // Exit (fail) - return null; - } - - /** - * Product - * - * @param array $parameters Parameters of the request (POST + GET) - */ - public function product(array $parameters = []): ?string - { - // Initializing identifier of a product - preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches); - $identifier = $matches[0] ?? null; - - if (!empty($identifier)) { - // Received identifier of the product - - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - // POST request - - // Search for products - $product = product::read( - 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, images: d.images[*].storage}', - parameters: ['identifier' => (int) $identifier], - language: $this->language, - errors: $this->errors['catalog'] - )[0]?->getAll(); - - // 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( - [ - 'product' => $product, - '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; + // Render HTML-code of categories and write to the response buffer + $response['html'] ??= []; + $response['html']['categories'] = $this->view->render('catalog/elements/categories.html'); } + + if (isset($this->view->product)) { + // Initialized product + + } + + if (isset($this->view->products)) { + // Initialized products + + // Render HTML-code of products and write to the response buffer + $response['html'] ??= []; + $response['html']['products'] = $this->view->render('catalog/elements/products/2columns.html'); + } + + if (isset($this->view->filters)) { + // Initialized filters + + // Render HTML-code of filters and write to the response buffer + $response['html'] ??= []; + $response['html']['filters'] = $this->view->render('catalog/elements/filters.html'); + } + + // 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( + $response + [ + '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) diff --git a/mirzaev/arming_bot/system/controllers/session.php b/mirzaev/arming_bot/system/controllers/session.php index 1ff7ecb..75c499f 100755 --- a/mirzaev/arming_bot/system/controllers/session.php +++ b/mirzaev/arming_bot/system/controllers/session.php @@ -153,14 +153,14 @@ final class session extends core return null; } - - /** - * Connect session to the telegram account + * Write to the buffer * * @param array $parameters Parameters of the request (POST + GET) + * + * @return void */ - public function write(array $parameters = []): ?string + public function write(array $parameters = []): void { if (!empty($parameters) && $this->session instanceof model) { // Found data of the program and active session @@ -171,18 +171,15 @@ final class session extends core // Validation of the parameter if (mb_strlen($value) > 4096) continue; - // Write data of the program to the implement object of session document from ArangoDB - $this->session->{$name} = json_decode($value, true, 10); + // Convert name to multidimensional array + foreach (array_reverse(explode('_', $name)) as $key) $parameter = [$key => $parameter ?? json_validate($value) ? json_decode($value, true, 10) : $value]; + + // Write data of to the buffer parameter in the implement object of session document from ArangoDB + $this->session->buffer = $parameter + ($this->session->buffer ?? []); } // Write from implement object to session document from ArangoDB document::update($this->session->__document(), $this->errors['session']); - - // Exit (success) - return null; } - - // Exit (fail) - return null; } } diff --git a/mirzaev/arming_bot/system/models/menu.php b/mirzaev/arming_bot/system/models/menu.php new file mode 100755 index 0000000..8f0069e --- /dev/null +++ b/mirzaev/arming_bot/system/models/menu.php @@ -0,0 +1,29 @@ + + */ +final class menu extends core implements arangodb_document_interface +{ + use arangodb_document_trait; + + /** + * Name of the collection in ArangoDB + */ + final public const string COLLECTION = 'menu'; +} diff --git a/mirzaev/arming_bot/system/models/product.php b/mirzaev/arming_bot/system/models/product.php index 50ab827..7b3f921 100755 --- a/mirzaev/arming_bot/system/models/product.php +++ b/mirzaev/arming_bot/system/models/product.php @@ -214,12 +214,16 @@ final class product extends core * Collect parameter from all products * * @param string $return Return (AQL path) + * @param array $products Array with products system identifiers ["_id", "_id", "_id"...] * @param array &$errors Registry of errors * * @return array Array with found unique parameter values from all products (can be empty) */ public static function collect( string $return = 'd._key', + array $products = [], + language $language = language::en, + array $parameters = [], array &$errors = [] ): array { try { @@ -230,13 +234,16 @@ final class product extends core sprintf( <<<'AQL' FOR d IN @@collection + %s RETURN DISTINCT %s AQL, + empty($products) ? '' : 'FILTER POSITION(["' . implode('", "', $products) . '"], d._id)', empty($return) ? 'd._key' : $return ), [ '@collection' => static::COLLECTION, - ], + 'language' => $language->name, + ] + $parameters, errors: $errors )) { // Found parameters diff --git a/mirzaev/arming_bot/system/public/index.php b/mirzaev/arming_bot/system/public/index.php index 64ed9c1..483509e 100755 --- a/mirzaev/arming_bot/system/public/index.php +++ b/mirzaev/arming_bot/system/public/index.php @@ -39,12 +39,14 @@ $router = new router; // Initialize routes $router ->write('/', 'catalog', 'index', 'GET') - ->write('/search', 'catalog', 'search', 'POST') + ->write('/', 'catalog', 'index', 'POST') + ->write('/account/write', 'account', 'write', 'POST') ->write('/session/write', 'session', 'write', 'POST') ->write('/session/connect/telegram', 'session', 'telegram', 'POST') - ->write('/category/$identifier', 'catalog', 'index', 'POST') - ->write('/category', 'catalog', 'index', 'POST') - ->write('/product/$identifier', 'catalog', 'product', 'POST'); + /* ->write('/category/$identifier', 'catalog', 'index', 'POST') */ + /* ->write('/category', 'catalog', 'index', 'POST') */ + /* ->write('/product/$identifier', 'catalog', 'product', 'POST') */ +; /* diff --git a/mirzaev/arming_bot/system/public/js/account.js b/mirzaev/arming_bot/system/public/js/account.js index 583d21e..47fdc7a 100755 --- a/mirzaev/arming_bot/system/public/js/account.js +++ b/mirzaev/arming_bot/system/public/js/account.js @@ -72,10 +72,11 @@ import("/js/core.js").then(() => }, 3000); if (core.telegram.api.initData.length > 0) { - core.request( - "/session/connect/telegram", - core.telegram.api.initData, - ) + core + .request( + "/session/connect/telegram", + core.telegram.api.initData + ) .then((json) => { if ( json.errors !== null && @@ -93,9 +94,8 @@ import("/js/core.js").then(() => const a = core.status_account.getElementsByTagName("a")[0]; a.setAttribute("onclick", "core.account.profile()"); - a.innerText = json.domain.length > 0 - ? "@" + json.domain - : "ERROR"; + a.innerText = + json.domain.length > 0 ? "@" + json.domain : "ERROR"; } if ( @@ -109,6 +109,34 @@ import("/js/core.js").then(() => }); } } + + /** + * 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 + ); + } + } + }; }; } } diff --git a/mirzaev/arming_bot/system/public/js/authentication.js b/mirzaev/arming_bot/system/public/js/authentication.js index 8ad006f..db1cefc 100755 --- a/mirzaev/arming_bot/system/public/js/authentication.js +++ b/mirzaev/arming_bot/system/public/js/authentication.js @@ -23,10 +23,20 @@ import("/js/core.js").then(() => }, 5000); function initialization() { - core.session.telegram(); + // + const { initData, initDataUnsafe, ...data } = core.telegram.api; + + // + core.session.buffer.write("telegram_program", JSON.stringify(data)); + if (core.telegram.api.initData.length > 0) { + // + + // core.account.authentication(); } + + // core.telegram.api.ready(); } }) diff --git a/mirzaev/arming_bot/system/public/js/catalog.js b/mirzaev/arming_bot/system/public/js/catalog.js index 4277f4f..fdee79b 100755 --- a/mirzaev/arming_bot/system/public/js/catalog.js +++ b/mirzaev/arming_bot/system/public/js/catalog.js @@ -29,226 +29,19 @@ import("/js/core.js").then(() => // Write to the core core.catalog = class catalog { /** - * Registry of filters (instead of cookies) + * Parameters of search + * + * Will be converted to the body of the GET request ("?name=value&name2=value2") */ - static filters = new Map([ + static parameters = new Map([ + ["text", null], + ["category", null], + ["sort", null], ["brand", null], ]); /** - * Select a category (interface) - * - * @param {HTMLElement} button Button of a category - * @param {bool} clean Clear search bar? - * @param {bool} force Ignore the damper? - * - * @return {void} - */ - static category(button, clean = true, force = false) { - // Initializing identifier of the category - const identifier = button.getAttribute( - "data-category-identifier", - ); - - this._category(identifier, clean, force); - } - - /** - * Select a category (damper) - * - * @param {HTMLElement} button Button of category - * @param {bool} clean Clear search bar? - * @param {bool} force Ignore the damper? - * - * @return {void} - */ - static _category = core.damper( - (...variables) => this.__category(...variables), - 400, - 2, - ); - - /** - * Select a category (system) - * - * @param {string} identifier Identifier of the category - * @param {bool} clean Clear search bar? - * - * @return {Promise} Request to the server - */ - static __category(identifier, clean = true) { - if (typeof identifier === "string") { - // Received required parameters - - return core.request( - "/category" + - ("/" + identifier).replace(/^\/*/, "/").trim().replace( - /\/*$/, - "", - ), - ) - .then((json) => { - if ( - json.errors !== null && - typeof json.errors === "object" && - json.errors.length > 0 - ) { - // Errors received - } else { - // Errors not received - - if (clean) { - // Clearing the search bar - const search = core.main.querySelector( - 'search[data-section="search"]>input', - ); - if (search instanceof HTMLElement) search.value = ""; - } - - if ( - typeof json.title === "string" && - json.title.length > 0 - ) { - // Received the page title - - // Initialize a link to the categories list - const title = core.main.getElementsByTagName("h2")[0]; - - // Write the title - title.innerText = json.title; - } - - if ( - typeof json.html.categories === "string" && - json.html.categories.length > 0 - ) { - // Received categories (reinitialization of the categories) - - const categories = core.main.querySelector( - 'section[data-catalog-type="categories"]', - ); - - if (categories instanceof HTMLElement) { - // Found list of categories - - categories.outerHTML = json.html.categories; - } else { - // Not found list of categories - - const element = document.createElement("section"); - - const search = core.main.querySelector( - 'search[data-section="search"]', - ); - - if (search instanceof HTMLElement) { - core.main.insertBefore( - element, - search.nextSibling, - ); - } else { - core.main.append(element); - } - - element.outerHTML = json.html.categories; - } - } else { - // Not received categories (deinitialization of the categories) - - const categories = core.main.querySelector( - 'section[data-catalog-type="categories"', - ); - - if (categories instanceof HTMLElement) { - categories.remove(); - } - } - - if ( - typeof json.html.filters === "string" && - json.html.filters.length > 0 - ) { - // Received filters (reinitialization of the filters) - - const filters = core.main.querySelector( - 'section[data-section="filters"]', - ); - - if (filters instanceof HTMLElement) { - // Found list of filters - - filters.outerHTML = json.html.filters; - } else { - // Not found list of categories - - const element = document.createElement("section"); - - const categories = core.main.querySelector( - 'section[data-catalog-type="categories"]', - ); - - if (categories instanceof HTMLElement) { - core.main.insertBefore( - element, - categories.nextSibling, - ); - } else { - core.main.append(element); - } - - element.outerHTML = json.html.filters; - } - } else { - // Not received categories (deinitialization of the categories) - - const filters = core.main.querySelector( - 'section[data-section="filters"', - ); - - if (filters instanceof HTMLElement) { - filters.remove(); - } - } - - if ( - typeof json.html.products === "string" && - json.html.products.length > 0 - ) { - // Received products (reinitialization of the products) - - const products = core.main.querySelector( - 'section[data-catalog-type="products"]', - ); - - if (products instanceof HTMLElement) { - // Found list of products - - products.outerHTML = json.html.products; - } else { - // Not found list of products - - const element = document.createElement("section"); - core.main.append(element); - element.outerHTML = json.html.products; - } - } else { - // Not received products (deinitialization of the products) - - const products = core.main.querySelector( - 'section[data-catalog-type="products"', - ); - if (products instanceof HTMLElement) { - products - .remove(); - } - } - } - }); - } - } - - /** - * Select a category (interface) + * Search (interface) * * @param {Event} event Event (keyup) * @param {HTMLElement} element Search bar @@ -257,28 +50,37 @@ import("/js/core.js").then(() => * @return {void} */ static search(event, element, force = false) { + if (typeof element === "undefined") { + element = document.getElementById("search"); + } + element.classList.remove("error"); if (element.value.length === 1) { return; - } else if (event.keyCode === 13) { - // Button: "enter" - - element.setAttribute("disabled", true); - - this._search(element, force); } else { - // Button: any + if (typeof event === "undefined") { + // - this._search(element, force); + this._search(element, true); + } else if (event.keyCode === 13) { + // Button: "enter" + + element.setAttribute("disabled", true); + + this._search(element, force); + } else { + // Button: any + + this._search(element, force); + } } } /** - * Search in the catalog (damper) + * Search (damper) * - * @param {HTMLElement} button Button of category - * @param {bool} clean Clear search bar? + * @param {HTMLElement} element Search bar * @param {bool} force Ignore the damper? * * @return {void} @@ -290,7 +92,7 @@ import("/js/core.js").then(() => ); /** - * Search in the catalog (system) + * Search (system) * * @param {HTMLElement} element Search bar * @@ -299,101 +101,170 @@ import("/js/core.js").then(() => * @todo add animations of errors */ static __search(element) { - return this.__category("/", false) - .then(function () { - core.request("/search", `text=${element.value}`) - .then((json) => { - element.removeAttribute("disabled"); - element.focus(); + if (typeof element === "undefined") { + element = document.getElementById("search"); + } - if ( - json.errors !== null && - typeof json.errors === "object" && - json.errors.length > 0 - ) { - // Errors received + const urn = Array.from(this.parameters) + .filter(([k, v]) => + typeof v === "string" || typeof v === "number" + ) + .map(([k, v]) => `${k}=${v}`) + .join("&"); - element.classList.add("error"); + return core.request( + "/", + urn, + ) + .then((json) => { + element.removeAttribute("disabled"); + element.focus(); + + if ( + json.errors !== null && + typeof json.errors === "object" && + json.errors.length > 0 + ) { + // Errors received + + element.classList.add("error"); + } else { + // Errors not received + + history.pushState( + {}, + urn, + urn, + ); + + if ( + typeof json.title === "string" && + json.title.length > 0 + ) { + // Received the page title + + // Initialize a link to the categories list + const title = core.main.getElementsByTagName("h2")[0]; + + // Write the title + title.innerText = json.title; + } + + // Deinitialization of the categories + const categories = core.main.querySelector( + 'section[data-catalog-type="categories"]', + ); + // if (categories instanceof HTMLElement) categories.remove(); + + if ( + typeof json.html.categories === "string" && + json.html.categories.length > 0 + ) { + // Received categories (reinitialization of the categories) + + const categories = core.main.querySelector( + 'section[data-catalog-type="categories"]', + ); + + if (categories instanceof HTMLElement) { + // Found list of categories + + categories.outerHTML = json.html.categories; } else { - // Errors not received + // Not found list of categories - if ( - typeof json.title === "string" && - json.title.length > 0 - ) { - // Received the page title + const element = document.createElement("section"); - // Initialize a link to the categories list - const title = - core.main.getElementsByTagName("h2")[0]; + const search = core.main.querySelector( + 'search[data-section="search"]', + ); - // Write the title - title.innerText = json.title; + if (search instanceof HTMLElement) { + core.main.insertBefore( + element, + search.nextSibling, + ); + } else { + core.main.append(element); } - // Deinitialization of the categories + element.outerHTML = json.html.categories; + } + } else { + // Not received categories (deinitialization of the categories) + + const categories = core.main.querySelector( + 'section[data-catalog-type="categories"', + ); + + if (categories instanceof HTMLElement) { + categories.remove(); + } + } + + if ( + typeof json.html.products === "string" && + json.html.products.length > 0 + ) { + // Received products (reinitialization of the products) + + const products = core.main.querySelector( + 'section[data-catalog-type="products"]', + ); + + if (products instanceof HTMLElement) { + // Found list of products + + products.outerHTML = json.html.products; + } else { + // Not found list of products + + const element = document.createElement("section"); + const categories = core.main.querySelector( - 'section[data-catalog-type="categories"]', + 'section[data-catalog-type="categories"', ); - // if (categories instanceof HTMLElement) categories.remove(); - if ( - typeof json.html.products === "string" && - json.html.products.length > 0 - ) { - // Received products (reinitialization of the products) + if (categories instanceof HTMLElement) { + // - const products = core.main.querySelector( - 'section[data-catalog-type="products"]', + // + core.main.insertBefore( + element, + categories.nextSibling, ); - if (products instanceof HTMLElement) { - // Found list of products + element.outerHTML = json.html.products; + } else { + // - products.outerHTML = json.html.products; - } else { - // Not found list of products + // + const search = core.main.querySelector( + 'search[data-section="search"]', + ); - const element = document.createElement("section"); - - const categories = core.main.querySelector( - 'section[data-catalog-type="categories"', + if (search instanceof HTMLElement) { + core.main.insertBefore( + element, + search.nextSibling, ); - if (categories instanceof HTMLElement) { - core.main.insertBefore( - element, - categories.nextSibling, - ); - - element.outerHTML = json.html.products; - } else { - const search = core.main.querySelector( - 'search[data-section="search"]', - ); - - if (search instanceof HTMLElement) { - core.main.insertBefore( - element, - search.nextSibling, - ); - - element.outerHTML = json.html.products; - } - } - } - } else { - // Not received products (deinitialization of the products) - - const products = core.main.querySelector( - 'section[data-catalog-type="products"]', - ); - if (products instanceof HTMLElement) { - products.remove(); + element.outerHTML = json.html.products; } } } - }); + } else { + // Not received products (deinitialization of the products) + + const products = core.main.querySelector( + 'section[data-catalog-type="products"]', + ); + + if (products instanceof HTMLElement) { + products.remove(); + } + } + } }); } @@ -439,7 +310,10 @@ import("/js/core.js").then(() => if (typeof identifier === "string") { // - return core.request(`/product/${identifier}`) + // + const urn = `?product=${identifier}`; + + return core.request(urn) .then((json) => { if ( json.errors !== null && @@ -471,7 +345,7 @@ import("/js/core.js").then(() => const name = document.createElement("span"); name.classList.add("name"); - name.setAttribute("title", json.product.identifier); + name.setAttribute("title", identifier); name.innerText = json.product.name; const exit = document.createElement("a"); @@ -671,8 +545,9 @@ import("/js/core.js").then(() => ); history.pushState( - { product_card: json.product.identifier }, + { identifier }, json.product.name, + urn, ); // блокировка закрытия карточки @@ -681,7 +556,12 @@ import("/js/core.js").then(() => wrap.addEventListener("mousedown", _from); wrap.addEventListener("touchstart", _from); - const remove = () => { + const remove = (event) => { + if ( + typeof event === "undefined" || + event.type !== "popstate" + ) history.back(); + wrap.remove(); images.removeEventListener( "mousedown", diff --git a/mirzaev/arming_bot/system/public/js/core.js b/mirzaev/arming_bot/system/public/js/core.js index dd0468f..3ee7031 100755 --- a/mirzaev/arming_bot/system/public/js/core.js +++ b/mirzaev/arming_bot/system/public/js/core.js @@ -46,13 +46,43 @@ const core = class core { method = "POST", headers = { "Content-Type": "application/x-www-form-urlencoded", + "Accept": "application/json", }, type = "json", ) { return await fetch(encodeURI(address), { method, headers, body }) - .then((response) => response[type]()); + .then((response) => type === null || response[type]()); } + /** + * Buffer + */ + static buffer = class buffer { + /** + * Write to buffers + * + * @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 this.session === "function") { + // Initialized the session implement object + + // Write to the session buffer + core.session.buffer.write(name, value); + } + + if (typeof this.account === "function") { + // Initialized the account implement object + + // Write to the account buffer + core.account.buffer.write(name, value); + } + } + }; + /** * Сгенерировать окно выбора действия * diff --git a/mirzaev/arming_bot/system/public/js/session.js b/mirzaev/arming_bot/system/public/js/session.js index 8774c5f..ea72791 100755 --- a/mirzaev/arming_bot/system/public/js/session.js +++ b/mirzaev/arming_bot/system/public/js/session.js @@ -27,19 +27,32 @@ import("/js/core.js").then(() => // Write to the core core.session = class session { /** - * Send data of Telegram program settings to the session - * - * @return {void} + * Buffer */ - static telegram() { - // - const { initData, initDataUnsafe, ...data } = core.telegram.api; + 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") { + // - core.request( - "/session/write", - "telegram=" + JSON.stringify(data), - ); - } + // Send request to the server + core.request( + "/session/write", + `${name}=${value}`, + "POST", + {}, + null + ); + } + } + }; }; } } diff --git a/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow-top-left.css b/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow-top-left.css new file mode 100755 index 0000000..c05805a --- /dev/null +++ b/mirzaev/arming_bot/system/public/themes/default/css/icons/arrow-top-left.css @@ -0,0 +1,37 @@ +@charset "UTF-8"; + +i.icon.arrow.top.left { + box-sizing: border-box; + position: relative; + display: block; + width: 22px; + height: 22px; + border: 2px solid; + border-radius: 20px; +} + +i.icon.arrow.top.left::after, +i.icon.arrow.top.left::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; +} + +i.icon.arrow.top.left::after { + width: 10px; + height: 2px; + background: currentColor; + transform: rotate(45deg); + bottom: 8px; + right: 4px; +} + +i.icon.arrow.top.left::before { + width: 6px; + height: 6px; + left: 4px; + top: 4px; + border-top: 2px solid; + border-left: 2px solid; +} diff --git a/mirzaev/arming_bot/system/public/themes/default/css/icons/shopping_cart.css b/mirzaev/arming_bot/system/public/themes/default/css/icons/shopping_cart.css index 8351e54..0606f17 100755 --- a/mirzaev/arming_bot/system/public/themes/default/css/icons/shopping_cart.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/icons/shopping_cart.css @@ -4,7 +4,6 @@ i.icon.shopping.cart { display: block; box-sizing: border-box; position: relative; - transform: scale(var(--ggs, 1)); width: 20px; height: 21px; background: diff --git a/mirzaev/arming_bot/system/public/themes/default/css/main.css b/mirzaev/arming_bot/system/public/themes/default/css/main.css index dbbc0cc..5f7eb97 100755 --- a/mirzaev/arming_bot/system/public/themes/default/css/main.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/main.css @@ -25,6 +25,9 @@ a { } body { + --gap: 16px; + --width: calc(100% - var(--gap) * 2); + --offset-x: 2%; width: 100%; height: 100%; margin: 0; @@ -39,10 +42,20 @@ body { aside {} -header {} +header { + container-type: inline-size; + container-name: header; + margin: 2rem 0 1rem; + padding: 0 var(--offset-x); + display: flex; + flex-direction: column; + align-items: center; + gap: 26px; +} main { - --offset-x: 2%; + container-type: inline-size; + container-name: main; padding: 0 var(--offset-x); display: flex; flex-direction: column; @@ -56,8 +69,6 @@ main>section:last-child { } main>*[data-section] { - --gap: 16px; - --width: calc(100% - var(--gap) * 2); width: var(--width); } @@ -150,7 +161,7 @@ a[type="button"] { h1, h2 { - margin: 28px 0 0; + margin: 1rem 0 0; } footer {} diff --git a/mirzaev/arming_bot/system/public/themes/default/css/menu.css b/mirzaev/arming_bot/system/public/themes/default/css/menu.css new file mode 100755 index 0000000..6e87042 --- /dev/null +++ b/mirzaev/arming_bot/system/public/themes/default/css/menu.css @@ -0,0 +1,53 @@ +@charset "UTF-8"; + +header>nav#menu { + container-type: inline-size; + container-name: menu; + width: var(--width); + min-height: 3rem; + display: flex; + flex-flow: row wrap; + gap: 1rem; + border-radius: 1.375rem; + overflow: hidden; +} + +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)); +} + +header>nav#menu>a[type=button]>:first-child { + margin-left: 1rem; +} + +header>nav#menu>a[type="button"]>* { + margin-right: 1rem; +} + +@container header (max-width: 450px) { + 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 { + display: none; + } +} + +@container header (max-width: 250px) { + 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 { + display: none; + } +} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html index 521e844..76af4f1 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html @@ -2,19 +2,15 @@
{% for category in categories %} - {% if category.images %} - - {{ category.name[language] }} + + {% if category.images %} + {{ category.name[language] }} + {% endif %}

{{ category.name[language] }}

- {% else %} - -

{{ category.name[language] }}

-
- {% endif %} {% endfor %}
{% endif %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html index 0ee4cf6..c550e10 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html @@ -1,12 +1,15 @@ {% if filters is not empty %}
- + {% set cheked_brand = account.buffer.filters.brand ?? session.buffer.filters.brand %} + - + {% for brand in filters.brands %} - + {% endfor %}
diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html index 526314b..195266a 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html @@ -1,4 +1,6 @@ - + {% set search = account.buffer.search ?? session.buffer.search %} + diff --git a/mirzaev/arming_bot/system/views/themes/default/header.html b/mirzaev/arming_bot/system/views/themes/default/header.html index 430650a..551dff2 100755 --- a/mirzaev/arming_bot/system/views/themes/default/header.html +++ b/mirzaev/arming_bot/system/views/themes/default/header.html @@ -1,10 +1,15 @@ +{% use "/themes/default/menu.html" with css as menu_css, body as menu_body, js as menu_js %} + {% block css %} +{{ block('menu_css') }} {% endblock %} {% block body %}
+ {{ block('menu_body') }}
{% endblock %} {% block js %} +{{ block('menu_js') }} {% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/index.html b/mirzaev/arming_bot/system/views/themes/default/index.html index a849d91..07bfa86 100755 --- a/mirzaev/arming_bot/system/views/themes/default/index.html +++ b/mirzaev/arming_bot/system/views/themes/default/index.html @@ -7,16 +7,16 @@ {% block css %} {{ parent() }} -{{ block('header_css') }} {{ block('connection_css') }} {{ block('account_css') }} +{{ block('header_css') }} {{ block('footer_css') }} {% endblock %} {% block body %} -{{ block('header') }} {{ block('connection_body') }} {{ block('account_body') }} +{{ block('header') }}
{% block main %} {{ main|raw }} @@ -27,8 +27,8 @@ {% block js %} {{ parent() }} -{{ block('header_js') }} {{ block('connection_js') }} {{ block('account_js') }} +{{ block('header_js') }} {{ block('footer_js') }} {% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/menu.html b/mirzaev/arming_bot/system/views/themes/default/menu.html new file mode 100644 index 0000000..2cd0800 --- /dev/null +++ b/mirzaev/arming_bot/system/views/themes/default/menu.html @@ -0,0 +1,30 @@ +{% block css %} + +{% for button in menu %} +{% if button.icon %} + +{% endif %} +{% endfor %} +{% endblock %} + +{% block body %} + +{% endblock %} + +{% block js %} + +{% endblock %} diff --git a/mirzaev/arming_bot/system/views/themes/default/search.html b/mirzaev/arming_bot/system/views/themes/default/search.html deleted file mode 100755 index 77c8956..0000000 --- a/mirzaev/arming_bot/system/views/themes/default/search.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "/themes/default/index.html" %} - -{% block css %} -{{ parent() }} - -{% endblock %} - -{% block main %} -{% endblock %} - -{% block js %} -{{ parent() }} - -{% endblock %}