sessions + websockets + telegram program data + bug fixes + filters
This commit is contained in:
parent
32cc78da1c
commit
5509b97148
|
@ -27,7 +27,8 @@
|
|||
"twig/twig": "^3.10",
|
||||
"twig/extra-bundle": "^3.7",
|
||||
"twig/intl-extra": "^3.10",
|
||||
"avadim/fast-excel-reader": "^2.19"
|
||||
"avadim/fast-excel-reader": "^2.19",
|
||||
"openswoole/core": "22.1.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e580e133abf1c4a60898f255e006a3e9",
|
||||
"content-hash": "2a3687f9c81d7a26a57b654136653c18",
|
||||
"packages": [
|
||||
{
|
||||
"name": "avadim/fast-excel-helper",
|
||||
|
@ -918,6 +918,77 @@
|
|||
],
|
||||
"time": "2023-11-13T09:31:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "openswoole/core",
|
||||
"version": "22.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/openswoole/core.git",
|
||||
"reference": "06dae68fdac73341ccf565ecef388434bd893141"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/openswoole/core/zipball/06dae68fdac73341ccf565ecef388434bd893141",
|
||||
"reference": "06dae68fdac73341ccf565ecef388434bd893141",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-openswoole": ">=22.0",
|
||||
"php": ">=7.4",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"psr/http-server-middleware": "^1.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"ext-sockets": "*",
|
||||
"friendsofphp/php-cs-fixer": "^3.6",
|
||||
"openswoole/ide-helper": "^22.0",
|
||||
"php-http/psr7-integration-tests": "^1.1",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mysqli": "*",
|
||||
"ext-pdo": "*",
|
||||
"ext-redis": "Required to use redis database, and the required version is greater than or equal to 3.1.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Coroutine/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"OpenSwoole\\Core\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "OpenSwoole Group",
|
||||
"email": "hello@openswoole.com"
|
||||
}
|
||||
],
|
||||
"description": "Openswoole core library",
|
||||
"homepage": "https://openswoole.com",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http2",
|
||||
"mqtt",
|
||||
"openswoole",
|
||||
"php",
|
||||
"tcp",
|
||||
"websocket"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://openswoole.com/docs",
|
||||
"issues": "https://github.com/openswoole/openswoole/issues",
|
||||
"pull-request": "https://github.com/openswoole/openswoole/pulls",
|
||||
"source": "https://github.com/openswoole/openswoole"
|
||||
},
|
||||
"time": "2023-12-10T19:02:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "opis/closure",
|
||||
"version": "3.6.3",
|
||||
|
@ -1596,6 +1667,119 @@
|
|||
},
|
||||
"time": "2023-04-04T09:50:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-server-handler",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-server-handler.git",
|
||||
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Server\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP server-side request handler",
|
||||
"keywords": [
|
||||
"handler",
|
||||
"http",
|
||||
"http-interop",
|
||||
"psr",
|
||||
"psr-15",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response",
|
||||
"server"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
|
||||
},
|
||||
"time": "2023-04-10T20:06:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-server-middleware",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-server-middleware.git",
|
||||
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"psr/http-server-handler": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Server\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP server-side middleware",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-interop",
|
||||
"middleware",
|
||||
"psr",
|
||||
"psr-15",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/http-server-middleware/issues",
|
||||
"source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2"
|
||||
},
|
||||
"time": "2023-04-11T06:14:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.1.4",
|
||||
|
|
|
@ -6,7 +6,6 @@ namespace mirzaev\arming_bot\controllers;
|
|||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core,
|
||||
mirzaev\arming_bot\models\catalog as model,
|
||||
mirzaev\arming_bot\models\entry,
|
||||
mirzaev\arming_bot\models\category,
|
||||
mirzaev\arming_bot\models\product;
|
||||
|
@ -39,6 +38,9 @@ final class catalog extends core
|
|||
preg_match('/[\d]+/', $parameters['identifier'] ?? '', $matches);
|
||||
$identifier = $matches[0] ?? null;
|
||||
|
||||
// Initializint the buffer of respnse
|
||||
$html = [];
|
||||
|
||||
if (!empty($parameters['identifier'])) {
|
||||
// Передана категория (идентификатор)
|
||||
|
||||
|
@ -70,21 +72,6 @@ final class catalog extends core
|
|||
|
||||
// Запись товаров из буфера в глобальную переменную шаблонизатора
|
||||
$this->view->products = $product;
|
||||
}
|
||||
} else {
|
||||
// Не передана категория
|
||||
|
||||
// Поиск категорий: "categories" (самый верхний уровень)
|
||||
$this->view->categories = entry::ascendants(descendant: new category, errors: $this->errors['catalog']);
|
||||
|
||||
// Search for products
|
||||
/* $this->view->products = product::read(
|
||||
filter: 'd.deleted != true && d.hidden != true',
|
||||
sort: 'd.promoting ASC, d.position ASC, d.created DESC',
|
||||
amount: 6,
|
||||
errors: $this->errors['catalog']
|
||||
); */
|
||||
}
|
||||
|
||||
// Generating filters
|
||||
$this->view->filters = [
|
||||
|
@ -94,6 +81,19 @@ final class catalog extends core
|
|||
)
|
||||
];
|
||||
|
||||
// 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')
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// Не передана категория
|
||||
|
||||
// Поиск категорий: "categories" (самый верхний уровень)
|
||||
$this->view->categories = entry::ascendants(descendant: new category, errors: $this->errors['catalog']);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
// GET request
|
||||
|
||||
|
@ -114,10 +114,8 @@ final class catalog extends core
|
|||
echo json_encode(
|
||||
[
|
||||
'title' => $title ?? '',
|
||||
'html' => [
|
||||
'html' => $html + [
|
||||
'categories' => $this->view->render('catalog/elements/categories.html'),
|
||||
'products' => $this->view->render('catalog/elements/products/2columns.html'),
|
||||
'filters' => $this->view->render('catalog/elements/filters.html')
|
||||
],
|
||||
'errors' => $this->errors
|
||||
]
|
||||
|
|
|
@ -117,8 +117,8 @@ class core extends controller
|
|||
$this->settings = settings::active();
|
||||
|
||||
// Initializing of language
|
||||
if ($this->account?->language) $this->language = $this->account->language ?? language::en;
|
||||
else if ($this->settings?->language) $this->language = $this->settings->language ?? language::en;
|
||||
if ($this->account?->language) $this->language = $this->account->language ?? language::en->name;
|
||||
else if ($this->settings?->language) $this->language = $this->settings->language ?? language::en->name;
|
||||
|
||||
// Initializing of preprocessor of views
|
||||
$this->view = new templater(
|
||||
|
|
|
@ -6,8 +6,12 @@ namespace mirzaev\arming_bot\controllers;
|
|||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core,
|
||||
mirzaev\arming_bot\models\session as model,
|
||||
mirzaev\arming_bot\models\account;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\document;
|
||||
|
||||
/**
|
||||
* Controller of session
|
||||
*
|
||||
|
@ -34,11 +38,20 @@ final class session extends core
|
|||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// POST request
|
||||
|
||||
// Declaring variables in the correct scope
|
||||
$identifier = $domain = $language = null;
|
||||
|
||||
if ($connected = isset($this->account)) {
|
||||
// Found the account
|
||||
|
||||
// Initializing identifier of the account
|
||||
$identifier = $this->account->identifier;
|
||||
|
||||
// Initializing language of the account
|
||||
$language = $this->account->language;
|
||||
|
||||
// Initializing domain of the account
|
||||
$domain = $this->account->domain;
|
||||
} else {
|
||||
// Not found the account
|
||||
|
||||
|
@ -46,7 +59,7 @@ final class session extends core
|
|||
|
||||
$buffer = $parameters;
|
||||
|
||||
unset($buffer['authentication'], $buffer['hash']);
|
||||
unset($buffer['hash']);
|
||||
ksort($buffer);
|
||||
|
||||
$prepared = [];
|
||||
|
@ -71,10 +84,10 @@ final class session extends core
|
|||
$data = json_decode($parameters['user']);
|
||||
|
||||
// Initializing of the account
|
||||
$account = account::initialization(
|
||||
$account = account::initialize(
|
||||
$data->id,
|
||||
[
|
||||
'id' => $data->id,
|
||||
'identifier' => $data->id,
|
||||
'name' => [
|
||||
'first' => $data->first_name,
|
||||
'last' => $data->last_name
|
||||
|
@ -92,8 +105,14 @@ final class session extends core
|
|||
// Connecting the account to the session
|
||||
$connected = $this->session->connect($account, $this->errors['session']);
|
||||
|
||||
// Initializing identifier of the account
|
||||
$identifier = $account->identifier;
|
||||
|
||||
// Initializing language of the account
|
||||
$language = $account->language;
|
||||
|
||||
// Initializing domain of the account
|
||||
$domain = $account->domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +131,9 @@ final class session extends core
|
|||
echo json_encode(
|
||||
[
|
||||
'connected' => (bool) $connected,
|
||||
'language' => $language ?? null,
|
||||
'identifier' => $identifier ?? null,
|
||||
'domain' => $domain ?? null,
|
||||
'language' => $language?->name ?? null,
|
||||
'errors' => $this->errors
|
||||
]
|
||||
);
|
||||
|
@ -131,4 +152,37 @@ final class session extends core
|
|||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Connect session to the telegram account
|
||||
*
|
||||
* @param array $parameters Parameters of the request (POST + GET)
|
||||
*/
|
||||
public function write(array $parameters = []): ?string
|
||||
{
|
||||
if (!empty($parameters) && $this->session instanceof model) {
|
||||
// Found data of the program and active session
|
||||
|
||||
foreach ($parameters as $name => $value) {
|
||||
// Iterate over parameters
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,12 +104,6 @@ final class account extends core implements arangodb_document_interface
|
|||
],
|
||||
'domain' => $registration->getUsername(),
|
||||
'robot' => $registration->isBot(),
|
||||
'banned' => false,
|
||||
'tester' => false,
|
||||
'developer' => false,
|
||||
'access' => [
|
||||
'settings' => false
|
||||
],
|
||||
'menus' => [
|
||||
'attachments' => $registration->getAddedToAttachmentMenu()
|
||||
],
|
||||
|
@ -124,6 +118,12 @@ final class account extends core implements arangodb_document_interface
|
|||
'inline' => $registration->getSupportsInlineQueries()
|
||||
]
|
||||
]) + [
|
||||
'banned' => false,
|
||||
'tester' => false,
|
||||
'developer' => false,
|
||||
'access' => [
|
||||
'settings' => false
|
||||
],
|
||||
'version' => ROBOT_VERSION,
|
||||
'active' => true
|
||||
],
|
||||
|
|
|
@ -213,13 +213,13 @@ final class product extends core
|
|||
/**
|
||||
* Collect parameter from all products
|
||||
*
|
||||
* @param string $name Name of the parameter (AQL path)
|
||||
* @param string $return Return (AQL path)
|
||||
* @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 $name = 'd._key',
|
||||
string $return = 'd._key',
|
||||
array &$errors = []
|
||||
): array {
|
||||
try {
|
||||
|
@ -227,13 +227,15 @@ final class product extends core
|
|||
// Initialized the collection
|
||||
|
||||
if ($result = collection::execute(
|
||||
sprintf(
|
||||
<<<'AQL'
|
||||
FOR d IN @@collection
|
||||
RETURN DISTINCT @parameter
|
||||
RETURN DISTINCT %s
|
||||
AQL,
|
||||
empty($return) ? 'd._key' : $return
|
||||
),
|
||||
[
|
||||
'@collection' => static::COLLECTION,
|
||||
'parameter' => $name
|
||||
],
|
||||
errors: $errors
|
||||
)) {
|
||||
|
|
|
@ -10,7 +10,8 @@ use mirzaev\arming_bot\models\account,
|
|||
mirzaev\arming_bot\models\enumerations\session as verification,
|
||||
mirzaev\arming_bot\models\traits\status,
|
||||
mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
|
||||
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface;
|
||||
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface,
|
||||
mirzaev\arming_bot\models\enumerations\language;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\collection,
|
||||
|
@ -158,11 +159,14 @@ final class session extends core implements arangodb_document_interface
|
|||
// Found active settings
|
||||
|
||||
// Initializing the object
|
||||
$account = new static;
|
||||
$account = new account;
|
||||
|
||||
if (method_exists($account, '__document')) {
|
||||
// Object can implement a document from ArangoDB
|
||||
|
||||
// Abstractioning of parameters
|
||||
$result->language = language::{$result->language} ?? 'en';
|
||||
|
||||
// Writing the instance of account document from ArangoDB to the implement object
|
||||
$account->__document($result);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ define('SETTINGS', realpath('..' . DIRECTORY_SEPARATOR . 'settings'));
|
|||
define('INDEX', __DIR__);
|
||||
define('THEME', 'default');
|
||||
|
||||
// Инициализация библиотек
|
||||
// Initialize dependencies
|
||||
require __DIR__ . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
|
@ -33,13 +33,14 @@ require __DIR__ . DIRECTORY_SEPARATOR
|
|||
. 'vendor' . DIRECTORY_SEPARATOR
|
||||
. 'autoload.php';
|
||||
|
||||
// Инициализация маршрутизатора
|
||||
// Initialize the router
|
||||
$router = new router;
|
||||
|
||||
// Initializing of routes
|
||||
// Initialize routes
|
||||
$router
|
||||
->write('/', 'catalog', 'index', 'GET')
|
||||
->write('/search', 'catalog', 'search', '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')
|
||||
|
@ -65,8 +66,8 @@ $router
|
|||
->sort();
|
||||
var_dump($router->routes); */
|
||||
|
||||
// Инициализация ядра
|
||||
// Initialize the core
|
||||
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
|
||||
|
||||
// Обработка запроса
|
||||
// Handle the request
|
||||
echo $core->start();
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
"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
|
||||
|
||||
// Write to the core
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
// Import dependencies
|
||||
import("/js/core.js").then(() =>
|
||||
import("/js/damper.js").then(() => {
|
||||
import("/js/telegram.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.telegram === "function" &&
|
||||
typeof core.account === "function"
|
||||
) {
|
||||
clearInterval(dependencies);
|
||||
clearTimeout(timeout);
|
||||
|
@ -21,45 +23,13 @@ import("/js/core.js").then(() =>
|
|||
}, 5000);
|
||||
|
||||
function initialization() {
|
||||
const timer_for_response = setTimeout(() => {
|
||||
core.loading.setAttribute("disabled", true);
|
||||
|
||||
const p = document.createElement("p");
|
||||
p.innerText = "Not authenticated";
|
||||
|
||||
core.footer.appendChild(p);
|
||||
}, 3000);
|
||||
|
||||
core.request(
|
||||
"/session/connect/telegram",
|
||||
"authentication=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.loading.setAttribute("disabled", true);
|
||||
|
||||
clearTimeout(timer_for_response);
|
||||
core.session.telegram();
|
||||
if (core.telegram.api.initData.length > 0) {
|
||||
core.account.authentication();
|
||||
}
|
||||
|
||||
if (
|
||||
json.language !== null &&
|
||||
typeof json.language === "string" &&
|
||||
json.langiage.length === 2
|
||||
) {
|
||||
core.language = json.language;
|
||||
core.telegram.api.ready();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -38,7 +38,7 @@ import("/js/core.js").then(() =>
|
|||
/**
|
||||
* Select a category (interface)
|
||||
*
|
||||
* @param {HTMLElement} button Button of category <a>
|
||||
* @param {HTMLElement} button Button of a category <a>
|
||||
* @param {bool} clean Clear search bar?
|
||||
* @param {bool} force Ignore the damper?
|
||||
*
|
||||
|
@ -76,13 +76,13 @@ import("/js/core.js").then(() =>
|
|||
*
|
||||
* @return {Promise} Request to the server
|
||||
*/
|
||||
static __category(category, clean = true) {
|
||||
if (typeof category === "string") {
|
||||
static __category(identifier, clean = true) {
|
||||
if (typeof identifier === "string") {
|
||||
// Received required parameters
|
||||
|
||||
return core.request(
|
||||
"/category" +
|
||||
("/" + category).replace(/^\/*/, "/").trim().replace(
|
||||
("/" + identifier).replace(/^\/*/, "/").trim().replace(
|
||||
/\/*$/,
|
||||
"",
|
||||
),
|
||||
|
@ -146,21 +146,70 @@ import("/js/core.js").then(() =>
|
|||
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
|
||||
|
@ -179,32 +228,8 @@ import("/js/core.js").then(() =>
|
|||
// Not found list of products
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
core.main.append(element);
|
||||
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)
|
||||
|
@ -234,14 +259,14 @@ import("/js/core.js").then(() =>
|
|||
static search(event, element, force = false) {
|
||||
element.classList.remove("error");
|
||||
|
||||
if (element.innerText.length === 1) {
|
||||
if (element.value.length === 1) {
|
||||
return;
|
||||
} else if (event.keyCode === 13) {
|
||||
// Button: "enter"
|
||||
|
||||
element.setAttribute("disabled", true);
|
||||
|
||||
this.__search(element);
|
||||
this._search(element, force);
|
||||
} else {
|
||||
// Button: any
|
||||
|
||||
|
@ -375,12 +400,17 @@ import("/js/core.js").then(() =>
|
|||
/**
|
||||
* Open product card (interface)
|
||||
*
|
||||
* @param {string} identifier Identifier of a product
|
||||
* @param {HTMLElement} button Button of a product <a>
|
||||
* @param {bool} force Ignore the damper?
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
static product(identifier, force = false) {
|
||||
static product(button, force = false) {
|
||||
// Initializing identifier of the category
|
||||
const identifier = button.getAttribute(
|
||||
"data-product-identifier",
|
||||
);
|
||||
|
||||
this._product(identifier, force);
|
||||
}
|
||||
|
||||
|
@ -406,7 +436,7 @@ import("/js/core.js").then(() =>
|
|||
* @return {Promise} Request to the server
|
||||
*/
|
||||
static __product(identifier) {
|
||||
if (typeof identifier === "number") {
|
||||
if (typeof identifier === "string") {
|
||||
//
|
||||
|
||||
return core.request(`/product/${identifier}`)
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
"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
|
||||
|
||||
// Write to the core
|
||||
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");
|
||||
},
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
|
@ -8,8 +8,11 @@ const core = class core {
|
|||
// Language
|
||||
static language = "ru";
|
||||
|
||||
// Label for the "loding" element
|
||||
static loading = document.getElementById("loading");
|
||||
// Label for the "loading" element
|
||||
static status_loading = document.getElementById("loading");
|
||||
|
||||
// Label for the "account" element
|
||||
static status_account = document.getElementById("account");
|
||||
|
||||
// Label for the <header> element
|
||||
static header = document.body.getElementsByTagName("header")[0];
|
||||
|
@ -49,4 +52,185 @@ const core = class core {
|
|||
return await fetch(encodeURI(address), { method, headers, body })
|
||||
.then((response) => response[type]());
|
||||
}
|
||||
|
||||
/**
|
||||
* Сгенерировать окно выбора действия
|
||||
*
|
||||
* @param {string} title Верхний колонтинул
|
||||
* @param {string} text Основное содержимое окна
|
||||
* @param {string} left Содержимое левой кнопки
|
||||
* @param {object} left_css Перечисление CSS-классов левой кнопки (массив)
|
||||
* @param {function} left_click Действие после нажатия на левую кнопку
|
||||
* @param {string} right Содержимое правой кнопки
|
||||
* @param {object} right_css Перечисление CSS-классов правой кнопки (массив)
|
||||
* @param {function} right_click Действие после нажатия на правую кнопку
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
/* static choose = core.damper(
|
||||
(
|
||||
title = "Выбор действия",
|
||||
text = "",
|
||||
left = "Да",
|
||||
left_css = ["grass"],
|
||||
left_click = () => {},
|
||||
right = "Нет",
|
||||
right_css = ["clay"],
|
||||
right_click = () => {},
|
||||
) => {
|
||||
// Инициализация оболочки всплывающего окна
|
||||
this.popup_body.wrap = document.createElement("div");
|
||||
this.popup_body.wrap.setAttribute("id", "popup");
|
||||
|
||||
// Инициализация всплывающего окна
|
||||
const popup = document.createElement("section");
|
||||
popup.classList.add("list", "small");
|
||||
|
||||
// Инициализация заголовка всплывающего окна
|
||||
const title_h3 = document.createElement("h3");
|
||||
title_h3.classList.add("unselectable");
|
||||
title_h3.innerText = title;
|
||||
|
||||
// Инициализация оболочки с основной информацией
|
||||
const main = document.createElement("section");
|
||||
main.classList.add("main");
|
||||
|
||||
// Инициализация колонки
|
||||
const column = document.createElement("div");
|
||||
column.classList.add("column");
|
||||
|
||||
// Инициализация текста
|
||||
const text_p = document.createElement("p");
|
||||
text_p.innerText = text;
|
||||
|
||||
// Инициализация строки
|
||||
const row = document.createElement("div");
|
||||
row.classList.add("row", "buttons");
|
||||
|
||||
// Инициализация левой кнопки
|
||||
const left_button = document.createElement("button");
|
||||
left_button.classList.add(...left_css);
|
||||
left_button.innerText = left;
|
||||
left_button.addEventListener("click", left_click);
|
||||
|
||||
// Инициализация правой кнопки
|
||||
const right_button = document.createElement("button");
|
||||
right_button.classList.add(...right_css);
|
||||
right_button.innerText = right;
|
||||
right_button.addEventListener("click", right_click);
|
||||
|
||||
// Инициализация окна с ошибками
|
||||
this.popup_body.errors = document.createElement("section");
|
||||
this.popup_body.errors.classList.add(
|
||||
"errors",
|
||||
"window",
|
||||
"list",
|
||||
"calculated",
|
||||
"hidden",
|
||||
);
|
||||
this.popup_body.errors.setAttribute("data-errors", true);
|
||||
|
||||
// Инициализация элемента-тела (оболочки) окна с ошибками
|
||||
const errors = document.createElement("section");
|
||||
errors.classList.add("body");
|
||||
|
||||
// Инициализация элемента-списка ошибок
|
||||
const dl = document.createElement("dl");
|
||||
|
||||
// Инициализация активного всплывающего окна
|
||||
const old = document.getElementById("popup");
|
||||
|
||||
if (old instanceof HTMLElement) {
|
||||
// Найдено активное окно
|
||||
|
||||
// Деинициализация быстрых действий по кнопкам
|
||||
document.removeEventListener("keydown", this.buttons);
|
||||
|
||||
// Сброс блокировки
|
||||
this.freeze = false;
|
||||
|
||||
// Удаление активного окна
|
||||
old.remove();
|
||||
}
|
||||
|
||||
// Запись в документ
|
||||
popup.appendChild(title_h3);
|
||||
|
||||
column.appendChild(text_p);
|
||||
|
||||
row.appendChild(left_button);
|
||||
row.appendChild(right_button);
|
||||
column.appendChild(row);
|
||||
|
||||
main.appendChild(column);
|
||||
popup.appendChild(main);
|
||||
|
||||
this.popup_body.wrap.appendChild(popup);
|
||||
document.body.appendChild(this.popup_body.wrap);
|
||||
|
||||
errors.appendChild(dl);
|
||||
this.popup_body.errors.appendChild(errors);
|
||||
this.popup_body.wrap.appendChild(this.popup_body.errors);
|
||||
|
||||
// Инициализация ширины окна с ошибками
|
||||
this.popup_body.errors.style.setProperty(
|
||||
"--calculated-width",
|
||||
popup.offsetWidth + "px",
|
||||
);
|
||||
|
||||
// Инициализация переменных для окна с ошибками (12 - это значение gap из div#popup)
|
||||
function top(errors) {
|
||||
errors.style.setProperty("transition", "0s");
|
||||
errors.style.setProperty(
|
||||
"--top",
|
||||
popup.offsetTop + popup.offsetHeight + 12 + "px",
|
||||
);
|
||||
setTimeout(() => errors.style.removeProperty("transition"), 100);
|
||||
}
|
||||
top(this.popup_body.errors);
|
||||
const resize = new ResizeObserver(() => top(this.popup_body.errors));
|
||||
resize.observe(this.popup_body.wrap);
|
||||
|
||||
// Инициализация функции закрытия всплывающего окна
|
||||
const click = () => {
|
||||
// Блокировка
|
||||
if (this.freeze) return;
|
||||
|
||||
// Удаление всплывающего окна
|
||||
this.popup_body.wrap.remove();
|
||||
|
||||
// Удаление статуса активной строки
|
||||
row.removeAttribute("data-selected");
|
||||
|
||||
// Деинициализация быстрых действий по кнопкам
|
||||
document.removeEventListener("keydown", this.buttons);
|
||||
|
||||
// Сброс блокировки
|
||||
this.freeze = false;
|
||||
};
|
||||
|
||||
// Инициализация функции добавления функции закрытия всплывающего окна
|
||||
const enable = () =>
|
||||
this.popup_body.wrap.addEventListener("click", click);
|
||||
|
||||
// Инициализация функции удаления функции закрытия всплывающего окна
|
||||
const disable = () =>
|
||||
this.popup_body.wrap.removeEventListener("click", click);
|
||||
|
||||
// Первичная активация функции удаления всплывающего окна
|
||||
enable();
|
||||
|
||||
// Добавление функции удаления всплывающего окна по событиям
|
||||
popup.addEventListener("mouseenter", disable);
|
||||
popup.addEventListener("mouseleave", enable);
|
||||
|
||||
// Добавление функции удаления всплывающего окна по кнопкам
|
||||
left_button.addEventListener("click", click);
|
||||
right_button.addEventListener("click", click);
|
||||
|
||||
// Фокусировка
|
||||
right_button.focus();
|
||||
},
|
||||
300,
|
||||
); */
|
||||
};
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
// Import dependencies
|
||||
import("/js/core.js").then(() =>
|
||||
import("/js/damper.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.damper === "function" &&
|
||||
typeof core.telegram === "function"
|
||||
) {
|
||||
clearInterval(dependencies);
|
||||
clearTimeout(timeout);
|
||||
|
@ -25,21 +27,22 @@ import("/js/core.js").then(() =>
|
|||
// Write to the core
|
||||
core.session = class session {
|
||||
/**
|
||||
* Current position in hierarchy of the categories
|
||||
*/
|
||||
static categories = [];
|
||||
|
||||
/**
|
||||
* Send data of Telegram program settings to the session
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
static connect() {
|
||||
static telegram() {
|
||||
//
|
||||
const { initData, initDataUnsafe, ...data } = core.telegram.api;
|
||||
|
||||
core.request(
|
||||
"/session/connect/telegram",
|
||||
window.Telegram.WebApp.initData,
|
||||
"/session/write",
|
||||
"telegram=" + JSON.stringify(data),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
|
|
@ -36,7 +36,7 @@ define('CATALOG_IMPORT', STORAGE . DIRECTORY_SEPARATOR . 'import.xlsx');
|
|||
// Ключ чат-робота Telegram
|
||||
define('KEY', require(SETTINGS . DIRECTORY_SEPARATOR . 'key.php'));
|
||||
|
||||
// Инициализация библиотек
|
||||
// Initialize dependencies
|
||||
require __DIR__ . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
|
|
|
@ -0,0 +1,384 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\arming_bot;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\arming_bot\controllers\core as controller,
|
||||
mirzaev\arming_bot\models\core as model,
|
||||
mirzaev\arming_bot\models\socket;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\core;
|
||||
|
||||
// Framework for ArangoDB
|
||||
use mirzaev\arangodb\connection as arangodb,
|
||||
mirzaev\arangodb\collection,
|
||||
mirzaev\arangodb\document;
|
||||
|
||||
// Library for ArangoDB
|
||||
use ArangoDBClient\Document as _document;
|
||||
|
||||
// Built-in libraries
|
||||
use exception;
|
||||
|
||||
// Server of WebSocket
|
||||
use OpenSwoole\WebSocket\{Server, Frame};
|
||||
use OpenSwoole\Constant,
|
||||
OpenSwoole\Http\Request,
|
||||
OpenSwoole\Table;
|
||||
|
||||
// Initialize dependencies
|
||||
require __DIR__ . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. 'vendor' . DIRECTORY_SEPARATOR
|
||||
. 'autoload.php';
|
||||
|
||||
define('INDEX', __DIR__);
|
||||
|
||||
// Initialize the core
|
||||
$core = new core(namespace: __NAMESPACE__, controller: new controller(false), model: $model = new model(false));
|
||||
|
||||
// https://dev.to/robertobutti/websocket-with-php-4k2c
|
||||
|
||||
$server = new Server("armng.dev.mirzaev.sexy", 9502, Server::SIMPLE_MODE, Constant::SOCK_TCP | Constant::SSL);
|
||||
|
||||
$accounts = new Table(1024);
|
||||
$accounts->column('id', Table::TYPE_INT, 4);
|
||||
$accounts->column('type', Table::TYPE_STRING, 16);
|
||||
$accounts->create();
|
||||
|
||||
$server->set([
|
||||
// Process
|
||||
'daemonize' => 1,
|
||||
'user' => 'www-data',
|
||||
'group' => 'www-data',
|
||||
/* 'chroot' => '/data/server/', */
|
||||
'open_cpu_affinity' => true,
|
||||
/* 'cpu_affinity_ignore' => [0, 1], */
|
||||
'pid_file' => '/var/run/php/ebala-socket.pid',
|
||||
|
||||
// Server
|
||||
/* 'reactor_num' => 8,
|
||||
'worker_num' => 2, */
|
||||
'message_queue_key' => 'mq1',
|
||||
'dispatch_mode' => 4,
|
||||
'discard_timeout_request' => true,
|
||||
/* 'dispatch_func' => 'my_dispatch_function', */
|
||||
|
||||
// Worker
|
||||
/* 'max_request' => 0,
|
||||
'max_request_grace' => $max_request / 2, */
|
||||
|
||||
// HTTP Server max execution time, since v4.8.0
|
||||
'max_request_execution_time' => 5, // 30s
|
||||
|
||||
// Task worker
|
||||
/* 'task_ipc_mode' => 2,
|
||||
'task_max_request' => 100,
|
||||
'task_tmpdir' => '/tmp',
|
||||
'task_worker_num' => 8,
|
||||
'task_enable_coroutine' => true,
|
||||
'task_use_object' => true, */
|
||||
|
||||
// Logging
|
||||
'log_level' => 1,
|
||||
'log_file' => __DIR__ . '/../logs/openswoole.log',
|
||||
'log_rotation' => Constant::LOG_ROTATION_DAILY,
|
||||
'log_date_format' => '%Y-%m-%d %H:%M:%S',
|
||||
'log_date_with_microseconds' => false,
|
||||
/* 'request_slowlog_file' => false, */
|
||||
|
||||
// Enable trace logs
|
||||
'trace_flags' => Constant::TRACE_ALL,
|
||||
|
||||
// TCP
|
||||
'input_buffer_size' => 2097152,
|
||||
'buffer_output_size' => 32 * 1024 * 1024, // byte in unit
|
||||
'tcp_fastopen' => true,
|
||||
'max_conn' => 10000,
|
||||
'tcp_defer_accept' => false,
|
||||
'open_tcp_keepalive' => true,
|
||||
'tcp_keepidle' => 30, // Check if there is no data for 4s.
|
||||
/* 'tcp_keepinterval' => 1, // Check if there is data every 1s */
|
||||
'tcp_keepcount' => 10,
|
||||
'open_tcp_nodelay' => true,
|
||||
/* 'pipe_buffer_size' => 32 * 1024 * 1024, */
|
||||
'socket_buffer_size' => 128 * 1024 * 1024,
|
||||
|
||||
// Kernel
|
||||
'backlog' => 512,
|
||||
'kernel_socket_send_buffer_size' => 65535,
|
||||
'kernel_socket_recv_buffer_size' => 65535,
|
||||
|
||||
// TCP Parser
|
||||
'open_eof_check' => true,
|
||||
'open_eof_split' => true,
|
||||
'package_eof' => '\r\n',
|
||||
'open_length_check' => true,
|
||||
'package_length_type' => 'N',
|
||||
'package_body_offset' => 8,
|
||||
'package_length_offset' => 8,
|
||||
'package_max_length' => 2 * 1024 * 1024, // 2MB
|
||||
/* 'package_length_func' => 'my_package_length_func', */
|
||||
|
||||
// Coroutine
|
||||
'enable_coroutine' => true,
|
||||
'max_coroutine' => 3000,
|
||||
'send_yield' => true,
|
||||
|
||||
// tcp server
|
||||
'heartbeat_idle_time' => 600,
|
||||
'heartbeat_check_interval' => 30,
|
||||
'enable_delay_receive' => false,
|
||||
'enable_reuse_port' => false,
|
||||
'enable_unsafe_event' => false,
|
||||
|
||||
// Protocol
|
||||
'open_http_protocol' => true,
|
||||
'open_http2_protocol' => true,
|
||||
'open_websocket_protocol' => true,
|
||||
'open_mqtt_protocol' => false,
|
||||
|
||||
// HTTP2
|
||||
'http2_header_table_size' => 4095,
|
||||
'http2_initial_window_size' => 65534,
|
||||
'http2_max_concurrent_streams' => 1281,
|
||||
'http2_max_frame_size' => 16383,
|
||||
'http2_max_header_list_size' => 4095,
|
||||
|
||||
// SSL
|
||||
'ssl_cert_file' => '/etc/letsencrypt/live/xn--80aksgi6f.xn--24-mlca2chbdebu6a.xn--p1ai/fullchain.pem',
|
||||
'ssl_key_file' => '/etc/letsencrypt/live/xn--80aksgi6f.xn--24-mlca2chbdebu6a.xn--p1ai/privkey.pem',
|
||||
'ssl_ciphers' => 'ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP',
|
||||
'ssl_protocols' => Constant::SSL_TLSv1_3, // added from v4.5.4
|
||||
'ssl_verify_peer' => false,
|
||||
/* 'ssl_sni_certs' => [
|
||||
"cs.php.net" => [
|
||||
'ssl_cert_file' => __DIR__ . "/config/sni_server_cs_cert.pem",
|
||||
'ssl_key_file' => __DIR__ . "/config/sni_server_cs_key.pem"
|
||||
],
|
||||
"uk.php.net" => [
|
||||
'ssl_cert_file' => __DIR__ . "/config/sni_server_uk_cert.pem",
|
||||
'ssl_key_file' => __DIR__ . "/config/sni_server_uk_key.pem"
|
||||
],
|
||||
"us.php.net" => [
|
||||
'ssl_cert_file' => __DIR__ . "/config/sni_server_us_cert.pem",
|
||||
'ssl_key_file' => __DIR__ . "/config/sni_server_us_key.pem",
|
||||
],
|
||||
], */
|
||||
|
||||
// Static Files
|
||||
'document_root' => __DIR__,
|
||||
'enable_static_handler' => true,
|
||||
/* 'static_handler_locations' => ['/static', '/app/images'], */
|
||||
'http_index_files' => ['index.html', 'index.txt'],
|
||||
|
||||
// Source File Reloading
|
||||
'reload_async' => true,
|
||||
'max_wait_time' => 30,
|
||||
|
||||
// HTTP Server
|
||||
'http_parse_post' => true,
|
||||
'http_parse_cookie' => true,
|
||||
'upload_tmp_dir' => '/tmp',
|
||||
|
||||
// Compression
|
||||
'http_compression' => true,
|
||||
'http_compression_level' => 3, // 1 - 9
|
||||
'compression_min_length' => 20,
|
||||
|
||||
// Websocket
|
||||
'websocket_compression' => true,
|
||||
'open_websocket_close_frame' => true,
|
||||
'open_websocket_ping_frame' => true, // added from v4.5.4
|
||||
'open_websocket_pong_frame' => true, // added from v4.5.4
|
||||
|
||||
// TCP User Timeout
|
||||
'tcp_user_timeout' => 120,
|
||||
|
||||
// DNS Server
|
||||
'dns_server' => '1.1.1.1',
|
||||
/* 'dns_cache_refresh_time' => 60, */
|
||||
/* 'enable_preemptive_scheduler' => 0, */
|
||||
|
||||
/* 'open_fastcgi_protocol' => 0, */
|
||||
'open_redis_protocol' => 0,
|
||||
|
||||
'event_object' => false,
|
||||
]);
|
||||
|
||||
/* $server->set([
|
||||
'ssl_cert_file' => __DIR__
|
||||
. DIRECTORY_SEPARATOR . '..'
|
||||
. DIRECTORY_SEPARATOR . 'settings'
|
||||
. DIRECTORY_SEPARATOR . 'certificates'
|
||||
. DIRECTORY_SEPARATOR . '84.22.137.106.pem',
|
||||
'ssl_key_file' => __DIR__
|
||||
. DIRECTORY_SEPARATOR . '..'
|
||||
. DIRECTORY_SEPARATOR . 'settings'
|
||||
. DIRECTORY_SEPARATOR . 'certificates'
|
||||
. DIRECTORY_SEPARATOR . '84.22.137.106-key.pem'
|
||||
]); */
|
||||
|
||||
$server->on("Start", function (Server $server) use ($core, $model) {
|
||||
// Очистка баз данных для сокетов
|
||||
collection::truncate($model->arangodb->session, 'socket');
|
||||
collection::truncate($model->arangodb->session, 'session_edge_socket');
|
||||
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo "Swoole WebSocket Server is started at " . $server->host . ":" . $server->port . "\n";
|
||||
});
|
||||
|
||||
$server->on('Open', function (Server $server, Request $request) use ($accounts, $core, $model) {
|
||||
// Инициализация идентификатора
|
||||
$id = $request->fd;
|
||||
|
||||
// Запись в базу данных
|
||||
$accounts->set((string) $id, [
|
||||
'id' => $id
|
||||
]);
|
||||
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo '[' . date('Y.m.d h:i:s', time()) . "][$id] Connected ({$accounts->count()})" . PHP_EOL;
|
||||
|
||||
// Регистрация
|
||||
if (document::write($model->arangodb->session, 'socket', [
|
||||
'id' => (int) $id,
|
||||
'key' => $key = sodium_bin2hex(sodium_crypto_generichash((string) $id)),
|
||||
'expires' => (int) strtotime('+1 hour')
|
||||
])) {
|
||||
// Создана инстанция сокета (регистрация)
|
||||
|
||||
// Отправка данных для подключения
|
||||
$server->push($id, sprintf(
|
||||
<<<JSON
|
||||
{
|
||||
"type": "registration",
|
||||
"key": "%s"
|
||||
}
|
||||
JSON,
|
||||
$key
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
$server->on('Message', function (Server $server, Frame $frame) use ($accounts, $core, $model) {
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo '[' . date('Y.m.d h:i:s', time()) . "][$frame->fd] Message: $frame->data" . PHP_EOL;
|
||||
|
||||
if ($account = socket::account($frame->fd)) {
|
||||
// Авторизован аккаунт
|
||||
|
||||
// Инициализация полученных данных
|
||||
$message = json_decode($frame->data, false, 2);
|
||||
|
||||
// Инициализация имени
|
||||
$name = trim((!empty($account->name['first']) ? mb_strtoupper(mb_substr($account->name['first'], 0, 1)) . '.' : '') . (!empty($account->name['last']) ? ' ' . mb_strtoupper(mb_substr($account->name['last'], 0, 1)) . '.' : '') . (!empty($account->name['second']) ? ' ' . $account->name['second'] : ''), ' ');
|
||||
|
||||
try {
|
||||
if ($message->target === 'task') {
|
||||
// Заявка
|
||||
|
||||
if ($message->type === 'block') {
|
||||
// Блокировка редактирования
|
||||
|
||||
if ($account->status() && ($account->type === 'administrator' || $account->type === 'operator'))
|
||||
if (task::block((int) $message->_key, (int) $account->getKey())) {
|
||||
// Заблокирована заявка
|
||||
|
||||
// Отправка сообщения всем сокетам
|
||||
foreach ($accounts as $key => $value)
|
||||
if ((int) $key !== $frame->fd)
|
||||
$server->push((int) $key, <<<JSON
|
||||
{
|
||||
"type": "blocked",
|
||||
"target": "task",
|
||||
"_key": $message->_key,
|
||||
"account": {
|
||||
"_key": {$account->getKey()},
|
||||
"name": "$name"
|
||||
}
|
||||
}
|
||||
JSON);
|
||||
}
|
||||
} else if ($message->type === 'unblock') {
|
||||
// Разблокировка редактирования
|
||||
|
||||
if ($account->status() && ($account->type === 'administrator' || $account->type === 'operator'))
|
||||
if (task::unblock((int) $message->_key, (int) $account->getKey())) {
|
||||
// Заблокирована заявка
|
||||
|
||||
|
||||
// Отправка сообщения всем сокетам
|
||||
foreach ($accounts as $key => $value)
|
||||
if ((int) $key !== $frame->fd)
|
||||
$server->push((int) $key, <<<JSON
|
||||
{
|
||||
"type": "unblocked",
|
||||
"target": "task",
|
||||
"_key": $message->_key,
|
||||
"account": {
|
||||
"_key": {$account->getKey()},
|
||||
"name": "$name"
|
||||
}
|
||||
}
|
||||
JSON);
|
||||
}
|
||||
} else if ($message->type === 'update') {
|
||||
// Обновление
|
||||
|
||||
// Отправка сообщения всем сокетам
|
||||
foreach ($accounts as $key => $value)
|
||||
if ((int) $key !== $frame->fd)
|
||||
$server->push((int) $key, <<<JSON
|
||||
{
|
||||
"type": "updated",
|
||||
"target": "task",
|
||||
"_key": $message->_key
|
||||
}
|
||||
JSON);
|
||||
} else if ($message->type === 'delete') {
|
||||
// Обновление
|
||||
|
||||
// Отправка сообщения всем сокетам
|
||||
foreach ($accounts as $key => $value)
|
||||
if ((int) $key !== $frame->fd)
|
||||
$server->push((int) $key, <<<JSON
|
||||
{
|
||||
"type": "deleted",
|
||||
"target": "task",
|
||||
"_key": $message->_key
|
||||
}
|
||||
JSON);
|
||||
}
|
||||
}
|
||||
} catch (exception $e) {
|
||||
var_dump($e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$server->on('Close', function (Server $server, int $id) use ($accounts, $core, $model) {
|
||||
// Удаление из базы данных
|
||||
$accounts->del((string) $id);
|
||||
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo '[' . date('Y.m.d h:i:s', time()) . "][$id] Disconnect (server) ({$accounts->count()})" . PHP_EOL;
|
||||
});
|
||||
|
||||
$server->on('Disconnect', function (Server $server, int $id) use ($accounts, $core, $model) {
|
||||
// Удаление из базы данных
|
||||
$accounts->del((string) $id);
|
||||
|
||||
// Запись в буфер вывода (журнал)
|
||||
echo '[' . date('Y.m.d h:i:s', time()) . "][$id] Disconnect (client) ({$accounts->count()})" . PHP_EOL;
|
||||
});
|
||||
|
||||
// Запуск (точка входа)
|
||||
$server->start();
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
section#account {
|
||||
z-index: 999;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ main>section[data-section="catalog"]>article.product>a {
|
|||
padding: 4px 8px 4px 8px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
font-weight: bold;
|
||||
backdrop-filter: brightness(0.4) contrast(1.2);
|
||||
color: var(--text-light, var(--tg-theme-text-color));
|
||||
|
|
|
@ -44,7 +44,7 @@ main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[
|
|||
filter: brightness(60%);
|
||||
}
|
||||
|
||||
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:hover>img {
|
||||
main>section[data-section="catalog"][data-catalog-type="categories"]>a.category[type="button"]:is(:hover, :focus)>img {
|
||||
filter: unset;
|
||||
}
|
||||
|
||||
|
@ -92,16 +92,16 @@ main>section[data-section="catalog"][data-catalog-type="products"]>div.column>ar
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:hover {
|
||||
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:is(:hover, :focus) {
|
||||
/* flex-grow: 0.1; */
|
||||
/* background-color: var(--tg-theme-secondary-bg-color); */
|
||||
}
|
||||
|
||||
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:hover>* {
|
||||
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:is(:hover, :focus)>* {
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:not(:hover)>* {
|
||||
main>section[data-section="catalog"][data-catalog-type="products"]>div.column>article.product:not(:is(:hover, :focus))>* {
|
||||
transition: 0.2s ease-out;
|
||||
}
|
||||
|
||||
|
@ -163,19 +163,7 @@ main>section[data-section="cart"]>i.icon.shopping.cart {
|
|||
}
|
||||
|
||||
main>section[data-section="filters"] {
|
||||
--diameter: 4rem;
|
||||
z-index: 999;
|
||||
right: 5vw;
|
||||
bottom: 5vw;
|
||||
position: fixed;
|
||||
width: var(--diameter);
|
||||
height: var(--diameter);
|
||||
max-height: 2.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-radius: 100%;
|
||||
background-color: var(--tg-theme-button-color);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
main>section[data-section="filters"][data-filter="brand"] {}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
section#connection {
|
||||
z-index: 999;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
section#connection>i#indicator {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
cursor: help;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
section#connection>small {
|
||||
margin-right: 7px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
color: var(--socket-text);
|
||||
}
|
||||
|
||||
section#connection>i#indicator.disconnected:not(.connected) {
|
||||
background-color: var(--socket-disconnected);
|
||||
}
|
||||
|
||||
section#connection>i#indicator.connected:not(.disconnected) {
|
||||
background-color: var(--socket-connected);
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
section[data-type="select"] {
|
||||
--width: max(14rem, 20vw);
|
||||
--height-element: 2rem;
|
||||
--height-close: var(--height-element);
|
||||
--height-open: max-content;
|
||||
position: relative;
|
||||
width: var(--width);
|
||||
height: var(--height-close);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: context-menu;
|
||||
border-radius: 0.75rem;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--tg-theme-button-color);
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section[data-type="select"]:not(:focus):after {
|
||||
z-index: 30;
|
||||
content: '';
|
||||
top: calc(50% - 2.5px);
|
||||
right: 1rem;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
border-left: 5px solid transparent;
|
||||
border-top: 5px solid var(--tg-theme-button-text-color, black);
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
|
||||
section[data-type="select"]>input {
|
||||
left: -99999px;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
section[data-type="select"]>label {
|
||||
z-index: 10;
|
||||
order: 2;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: var(--height-element);
|
||||
padding: 0 1rem;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
color: var(--tg-theme-button-text-color);
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
section[data-type="select"]:is([data-select="open"], :focus)>input:not(:checked)+label[for$='title'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section[data-type="select"]:is([data-select="open"], :focus)>input:not(:checked)+label[for$='all']:not(:hover, :active, :focus) {
|
||||
filter: brightness(90%);
|
||||
}
|
||||
|
||||
section[data-type="select"]>input:not(:checked)+label {
|
||||
cursor: pointer;
|
||||
filter: brightness(80%);
|
||||
}
|
||||
|
||||
section[data-type="select"]:is([data-select="open"], :focus)>input+label:hover {
|
||||
filter: brightness(110%);
|
||||
}
|
||||
|
||||
section[data-type="select"]:is([data-select="open"], :focus)>input:checked+label:hover {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
section[data-type="select"]:is([data-select="open"], :focus)>input+label:is(:active, :focus) {
|
||||
filter: brightness(60%);
|
||||
}
|
||||
|
||||
section[data-type="select"]:is([data-select="open"], :focus)>input:checked+label:is(:active, :focus) {
|
||||
filter: brightness(70%);
|
||||
}
|
||||
|
||||
section[data-type="select"]>input:checked+label {
|
||||
order: 1;
|
||||
max-width: calc(var(--width) - 2rem - 10px);
|
||||
display: inline;
|
||||
line-height: var(--height-element);
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
section[data-type="select"]:is([data-select="open"], :focus)>input:checked+label {
|
||||
max-width: initial;
|
||||
padding-right: initial;
|
||||
}
|
||||
|
||||
section[data-type="select"]:is([data-select="open"], :focus) {
|
||||
height: var(--height-open, max-content);
|
||||
}
|
||||
|
||||
section[data-type="select"]:is([data-select="open"], :focus)>label {
|
||||
position: relative;
|
||||
display: inline;
|
||||
line-height: var(--height-element);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
section[data-type="select"]:only-child {
|
||||
--width: 100%
|
||||
}
|
||||
}
|
|
@ -2,8 +2,14 @@
|
|||
|
||||
:root {
|
||||
--text-light: #fafaff;
|
||||
|
||||
--socket-connected: #2be851;
|
||||
--socket-disconnected: #8e8181;
|
||||
--socket-text: #b09999;
|
||||
}
|
||||
|
||||
|
||||
|
||||
* {
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
|
@ -45,6 +51,10 @@ main {
|
|||
transition: 0s;
|
||||
}
|
||||
|
||||
main>section:last-child {
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
main>*[data-section] {
|
||||
--gap: 16px;
|
||||
--width: calc(100% - var(--gap) * 2);
|
||||
|
@ -112,7 +122,7 @@ search:has(input:disabled) {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
:is(button, a[type="button"]) {
|
||||
:is(button, :is(a, label)[type="button"]) {
|
||||
padding: 8px 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -129,11 +139,11 @@ a[type="button"] {
|
|||
height: 23px;
|
||||
}
|
||||
|
||||
:is(button, a[type="button"]):is(:hover) {
|
||||
:is(button, :is(a, label)[type="button"]):is(:hover, :focus) {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
:is(button, a[type="button"]):active {
|
||||
:is(button, :is(a, label)[type="button"]):active {
|
||||
filter: brightness(80%);
|
||||
transition: 0s;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{% block css %}
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/account.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% if account is empty %}
|
||||
<section id="account">
|
||||
<a onclick="core.account.authentication()">
|
||||
<!-- {{ language == 'ru' ? "Аутентификация" : "Authentication" }} -->
|
||||
</a>
|
||||
</section>
|
||||
{% else %}
|
||||
<section id="account">
|
||||
<a onclick="core.account.profile()">
|
||||
@{{ account.domain }}
|
||||
</a>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script type="text/javascript" src="/js/account.js"></script>
|
||||
{% endblock %}
|
|
@ -3,15 +3,15 @@
|
|||
data-catalog-level="{{ level ?? 0 }}">
|
||||
{% for category in categories %}
|
||||
{% if category.images %}
|
||||
<a id="{{ category.getId() }}" class="category" type="button" onclick="return core.catalog.category(this);"
|
||||
data-category-identifier="{{ category.identifier }}">
|
||||
<a id="{{ category.getId() }}" class="category" type="button" onclick="core.catalog.category(this)" onkeydown="event.keyCode === 13 && core.catalog.category(this)"
|
||||
data-category-identifier="{{ category.identifier }}" tabindex="3">
|
||||
<img src="{{ category.images.0.storage }}"
|
||||
alt="{{ category.name[language] }}" ondrugstart="return false;">
|
||||
<p>{{ category.name[language] }}</p>
|
||||
</a>
|
||||
{% else %}
|
||||
<a id="{{ category.getId() }}" class="category" type="button" onclick="return core.catalog.category(this);"
|
||||
data-category-identifier="{{ category.identifier }}">
|
||||
<a id="{{ category.getId() }}" class="category" type="button" onclick="core.catalog.category(this)" onkeydown="event.keyCode === 13 && core.catalog.category(this)"
|
||||
data-category-identifier="{{ category.identifier }}" tabindex="3">
|
||||
<p>{{ category.name[language] }}</p>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
{% if filters is not empty %}
|
||||
<section data-section="filters" data-filter="brand">
|
||||
<section class="unselectble" data-section="filters">
|
||||
<section class="unselectable" data-type="select" data-filter="brand" tabindex="4">
|
||||
<input name="brand" type="radio" id="brand_title"{% if session.filters.brand is empty %} checked{% endif %}>
|
||||
<label for="brand_title" type="button">{{ language == 'ru' ? 'Бренд' : 'Brand' }}</label>
|
||||
<input name="brand" type="radio" id="brand_all">
|
||||
<label for="brand_all" type="button">{{ language == 'ru' ? 'Все бренды' : 'All brands' }}</label>
|
||||
{% for brand in filters.brands %}
|
||||
<input class="menu" name="brand" type="radio" id="brand_{{ loop.index }}" checked>
|
||||
<label for="brand_{{ loop.index }}" class="menu option">{{ brand }}</label>
|
||||
<input name="brand" type="radio" id="brand_{{ loop.index }}"{% if session.filters.brand == brand %} checked{% endif %}>
|
||||
<label for="brand_{{ loop.index }}" type="button">{{ brand }}</label>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{% macro card(product) %}
|
||||
{% set title = product.name[language] ~ ' ' ~ product.brand[language] ~ format_dimensions(product.dimensions.x, product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %}
|
||||
<article id="{{ product.getId() }}" class="product unselectable">
|
||||
<a onclick="core.catalog.product({{ product.identifier }})">
|
||||
<a data-product-identifier="{{ product.identifier }}" onclick="core.catalog.product(this)" onkeydown="event.keyCode === 13 && core.catalog.product(this)" tabindex="10">
|
||||
<img src="{{ product.images.0.storage }}" alt="{{ product.name[language] }}" ondrugstart="return false;">
|
||||
<p class="title" title="{{ product.name[language] }}">
|
||||
{{ title | length > 45 ? title | slice(0, 45) ~ '...' : title }}
|
||||
</p>
|
||||
</a>
|
||||
<button title="Добавить в корзину" onclick="catalog.cart.add({{ product.getKey() }})">
|
||||
<button title="Добавить в корзину" onclick="catalog.cart.add(this)" tabindex="15">
|
||||
{{ product.cost }}р
|
||||
</button>
|
||||
</article>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<search data-section="search">
|
||||
<label><i class="icon search"></i></label>
|
||||
<input placeholder="Поиск по каталогу" type="search" onkeyup="return core.catalog.search(event, this);" />
|
||||
<input id="search" placeholder="Поиск по каталогу" type="search" onkeyup="event.keyCode !== 9 && core.catalog.search(event, this)" tabindex="1"/>
|
||||
</search>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
{% block css %}
|
||||
{{ parent() }}
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/catalog/2columns.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/interface/select.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/search.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/close.css" />
|
||||
|
@ -13,9 +14,9 @@
|
|||
|
||||
{% include "/themes/default/catalog/elements/search.html" %}
|
||||
{% include "/themes/default/catalog/elements/categories.html" %}
|
||||
{% include "/themes/default/catalog/elements/filters.html" %}
|
||||
{% include "/themes/default/catalog/elements/products/2columns.html" %}
|
||||
<!-- {% include "/themes/default/catalog/elements/cart.html" %} -->
|
||||
{% include "/themes/default/catalog/elements/filters.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{% block css %}
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/connection.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<section id='connection' class="unselectable" title="Disconnected">
|
||||
<small>0.00</small>
|
||||
<i id="indicator" class="disconnected"></i>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script type="text/javascript" src="/js/connection.js"></script>
|
||||
{% endblock %}
|
|
@ -19,7 +19,7 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<section id="loading">
|
||||
<section id="loading" disabled>
|
||||
<i class="icon loading spinner animated"></i>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
{% extends "/themes/default/core.html" %}
|
||||
|
||||
{% use "/themes/default/header.html" with css as header_css, body as header, js as header_js %}
|
||||
{% use "/themes/default/connection.html" with css as connection_css, body as connection_body, js as connection_js %}
|
||||
{% use "/themes/default/account.html" with css as account_css, body as account_body, js as account_js %}
|
||||
{% use "/themes/default/footer.html" with css as footer_css, body as footer, js as footer_js %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ block('header_css') }}
|
||||
{{ block('connection_css') }}
|
||||
{{ block('account_css') }}
|
||||
{{ block('footer_css') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{{ block('header') }}
|
||||
{{ block('connection_body') }}
|
||||
{{ block('account_body') }}
|
||||
<main>
|
||||
{% block main %}
|
||||
{{ main|raw }}
|
||||
|
@ -21,6 +27,8 @@
|
|||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ block('footer_js') }}
|
||||
{{ block('header_js') }}
|
||||
{{ block('connection_js') }}
|
||||
{{ block('account_js') }}
|
||||
{{ block('footer_js') }}
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
<script src="/js/core.js" defer></script>
|
||||
<script src="/js/damper.js"></script>
|
||||
<script src="/js/telegram.js"></script>
|
||||
<script src="/js/session.js"></script>
|
||||
<script src="/js/authentication.js"></script>
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in New Issue