generated from mirzaev/pot-php
Compare commits
13 Commits
0.1.0
...
0f09b2de71
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f09b2de71 | |||
| 3ef75a5814 | |||
| 4d2e419018 | |||
| 91d5883885 | |||
| 4203b1e9f8 | |||
| 658a95877c | |||
| 3ce62f13e2 | |||
| 1361f8cd5e | |||
| 323beeb886 | |||
| 9378f022b4 | |||
| 51e8214cd2 | |||
| 99daad5074 | |||
| eeca8fb535 |
53
README.md
53
README.md
@@ -1,2 +1,51 @@
|
||||
# pechatalka
|
||||
Chat-robot constructor for the Svoboda typography
|
||||
# Pechatalka
|
||||
Site and chat-robot constructor for the Svoboda typography
|
||||
|
||||
# Installation
|
||||
Before process any command think about **what it does** and whether the **paths** are specified correctly<br>
|
||||
|
||||
## NGINX
|
||||
### Create the NGINX server
|
||||
You can copy an example of the server file from here: `/examples/nginx/pechatalka.conf`<br>
|
||||
<small><i>I prefer to rename nginx config files to domain names. For example: `pechatalka.kodorvan.tech`</i></small><br>
|
||||
|
||||
<i>The file will be half commented out specifically to start the server for generating the TLS/SSL certificate</i><br>
|
||||
|
||||
1. `cd /examples/nginx`<br>
|
||||
2. `sudo cp pechatalka.conf /etc/nginx/sites-avaiable/pechatalka.conf`<br>
|
||||
3. `sudo nvim /etc/nginx/sites-avaiable/pechatalka.conf`<br>
|
||||
<small>3.1. <i>Customize the file according to your requirements</i></small><br>
|
||||
4. `sudo ln -s /etc/nginx/sites-avaiable/pechatalka.conf /etc/nginx/sites-enabled/pechatalka.conf`<br>
|
||||
5. `sudo nginx -t`<br>
|
||||
<small>5.1. <i>Make sure that <b>NGINX does not throw errors</b>, otherwise proceed to the instructions for generate a TLS/SSL certificate</i></small><br>
|
||||
6. `sudo service nginx restart`<br>
|
||||
|
||||
### Add mime-type recognition for javascript modules
|
||||
<small>Edit the file: `/etc/nginx/mime.types`</small><br>
|
||||
**From:** `application/javascript js;`<br>
|
||||
**To:** `application/javascript js mjs;`
|
||||
|
||||
### Generate a TLS/SSL sertificate (via [certbot](http://certbot.eff.org/) for [ubuntu](https://ubuntu.com/))
|
||||
1. `sudo apt install certbot python3-certbot-nginx`<br>
|
||||
2. `sudo certbot certonly --nginx`<br>
|
||||
<small><i>The **domain** must already be **bound** to the **IP-address** of the server by `CNAME`, `A` or `AAAA` record</i></small><br>
|
||||
3. Uncomment and reconfigure the nginx server file
|
||||
4. `sudo nginx -t`<br>
|
||||
<small>4.1. <i>Make sure that <b>NGINX does not throw errors</b></i></small><br>
|
||||
5. `sudo service nginx restart`
|
||||
|
||||
### Set up firewall rules for HTTP and HTTPS requests (for [ubuntu](https://ubuntu.com/))
|
||||
1. `sudo ufw allow "NGINX Full"`<br>
|
||||
<small>1.1. <i>Make sure that the <b>port for SSH connection</b> is open</i></small><br>
|
||||
2. `sudo ufw allow 22`<br>
|
||||
<small>2.1. <i>Use your SSH port instead of 22</i></small><br>
|
||||
3. `sudo ufw enable`
|
||||
|
||||
## SystemD (or any alternative you like)
|
||||
You can copy an example of the systemd file from here: `/examples/systemd/arming.service`<br>
|
||||
1. `cd examples/systemd`<br>
|
||||
2. `sudo cp pechatalka.service /etc/systemd/system/pechatalka.service && sudo chmod +x /etc/systemd/system/pechatalka.service`<br>
|
||||
3. `sudo nvim /etc/systemd/system/pechatalka.service`<br>
|
||||
<small>3.1. <i>Customize the file according to your requirements</i></small><br>
|
||||
4. `sudo systemctl daemon-reload`<br>
|
||||
3. `sudo systemctl enable pechatalka`<br>
|
||||
@@ -15,7 +15,7 @@
|
||||
"name": "Arsen Mirzaev Tatyano-Muradovich",
|
||||
"email": "arsen@mirzaev.sexy",
|
||||
"homepage": "https://mirzaev.sexy",
|
||||
"role": "Creator"
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
@@ -23,16 +23,19 @@
|
||||
"issues": "https://git.svoboda.works/svoboda/pechatalka/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.4",
|
||||
"mirzaev/minimal": "^3",
|
||||
"mirzaev/baza": "^3.3",
|
||||
"php": "^8.5",
|
||||
"ext-imagick": "^3.8",
|
||||
"mirzaev/minimal": "^3.10",
|
||||
"mirzaev/baza": "^3.4.3",
|
||||
"twig/twig": "^3.2",
|
||||
"twig/extra-bundle": "^3.7",
|
||||
"twig/intl-extra": "^3.10",
|
||||
"svoboda/time": "^1.0",
|
||||
"imagine/imagine": "^1.5",
|
||||
"badfarm/zanzara": "^0.9.1",
|
||||
"nyholm/psr7": "^1.8",
|
||||
"react/filesystem": "^0.1.2"
|
||||
"react/filesystem": "^0.1.2",
|
||||
"mirzaev/record": "^2.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
||||
Submodule damper.mjs updated: 68589e968c...81d208b964
21
install.sh
21
install.sh
@@ -1,5 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
git submodule update --init --recursive
|
||||
|
||||
if [ -d author/project ]; then
|
||||
mv author/project author/pechatalka
|
||||
fi
|
||||
@@ -8,3 +10,22 @@ if [ -d author ]; then
|
||||
mv author mirzaev
|
||||
fi
|
||||
|
||||
for i in svoboda/pechatalka/system/settings/*.sample; do
|
||||
echo $i;
|
||||
if [ ! -f "${i/.sample/}" ]; then
|
||||
cp -n "$i" "${i/.sample/}";
|
||||
echo ${i/.sample/};
|
||||
fi
|
||||
done
|
||||
|
||||
if ! [ -d svoboda/pechatalka/system/public/js/modules ]; then
|
||||
mkdir svoboda/pechatalka/system/public/js/modules -p
|
||||
fi
|
||||
|
||||
if ! [ -L svoboda/pechatalka/system/public/js/modules/damper.mjs ]; then
|
||||
ln -s ../../../../../../damper.mjs/damper.mjs svoboda/pechatalka/system/public/js/modules/damper.mjs;
|
||||
fi
|
||||
|
||||
if ! [ -L svoboda/pechatalka/system/public/js/modules/pechatalka.mjs ]; then
|
||||
ln -s ../../../../../../pechatalka.mjs/pechatalka.mjs svoboda/pechatalka/system/public/js/modules/pechatalka.mjs;
|
||||
fi
|
||||
|
||||
Submodule pechatalka.mjs updated: b1400479c4...4496b0464b
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace svoboda\pechatalka\controllers;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\controllers\core;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content,
|
||||
mirzaev\minimal\http\enumerations\status;
|
||||
|
||||
/**
|
||||
* Index
|
||||
*
|
||||
* @package svoboda\pechatalka\constructor
|
||||
*
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method null index() Main page
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class constructor extends core
|
||||
{
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'system' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Pin
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function pin(): null
|
||||
{
|
||||
if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||
// Request for any response
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('/constructor/pin/page.html');
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
236
svoboda/pechatalka/system/controllers/generator.php
Executable file
236
svoboda/pechatalka/system/controllers/generator.php
Executable file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace svoboda\pechatalka\controllers;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\controllers\core,
|
||||
svoboda\pechatalka\models\paper,
|
||||
svoboda\pechatalka\models\product,
|
||||
/* svoboda\pechatalka\models\enumerations\paper\type as paper_type, */
|
||||
svoboda\pechatalka\models\enumerations\paper\format as paper_format,
|
||||
svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content,
|
||||
mirzaev\minimal\http\enumerations\status;
|
||||
|
||||
/**
|
||||
* Generator
|
||||
*
|
||||
* @package svoboda\pechatalka\controllers
|
||||
*
|
||||
* @param array $errors Registry of errors
|
||||
*
|
||||
* @method null index() Main page
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class generator extends core
|
||||
{
|
||||
/**
|
||||
* Errors
|
||||
*
|
||||
* @var array $errors Registry of errors
|
||||
*/
|
||||
protected array $errors = [
|
||||
'system' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* Pin
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function pin(): null
|
||||
{
|
||||
if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) {
|
||||
// Request for any response
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('/generator/pin/page.html');
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print
|
||||
*
|
||||
* Assemble and generate the blank from products and prepare to printing
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function print(
|
||||
string $type = '',
|
||||
string $products = '',
|
||||
?string $canvas = null
|
||||
): null {
|
||||
// Type
|
||||
!empty($type) and $type = product_type::{$type} ?? null;
|
||||
|
||||
// Amount
|
||||
/* preg_match('/[\d]+/', (string) $amount, $matches);
|
||||
$amount = (int) $matches[0] ?? 1;
|
||||
unset($matches); */
|
||||
|
||||
// Initializing products data
|
||||
$products = json_decode($products, true, 16);
|
||||
|
||||
// Initializing the canvas data
|
||||
$canvas = json_decode($canvas, true, 16);
|
||||
|
||||
if ($type instanceof product_type && count($products) > 0) {
|
||||
// Initialized all required arguments
|
||||
|
||||
// Declaring the images buffer
|
||||
$images = [];
|
||||
|
||||
foreach ($this->request->files as $name => $file) {
|
||||
// Iterating over received files
|
||||
|
||||
// Deserializing the file type
|
||||
!empty($file['type']) && is_string($file['type']) and $file['type'] = content::from($file['type']) ?? null;
|
||||
|
||||
if (match ($file['type']) {
|
||||
content::jpeg,
|
||||
content::png,
|
||||
content::webp => true,
|
||||
default => false
|
||||
}) {
|
||||
// Passed the file type filter
|
||||
|
||||
// Writing into the images buffer
|
||||
$images[$name] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($products as $product => &$layers) {
|
||||
// Iterating over products
|
||||
|
||||
// Creating the product
|
||||
$instance = new product()->create(
|
||||
account: 0,
|
||||
type: $type,
|
||||
layers: $layers,
|
||||
canvas: $canvas
|
||||
);
|
||||
|
||||
// Initializing the product storage path
|
||||
$storage = STORAGE
|
||||
. DIRECTORY_SEPARATOR
|
||||
. 'products'
|
||||
. DIRECTORY_SEPARATOR
|
||||
. $instance->identifier;
|
||||
|
||||
if (!file_exists(filename: $storage)) {
|
||||
// The product storage directory is not created
|
||||
|
||||
if (mkdir(directory: $storage, permissions: 0755, recursive: true)) {
|
||||
// Created the product storage directory
|
||||
} else {
|
||||
// Nor created the product storage directory
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_filter($images, fn(string $key) => preg_match('/^' . $product . '_\d{1,2}$/', $key), ARRAY_FILTER_USE_KEY) as $name => $image) {
|
||||
// Iterating over the product images
|
||||
|
||||
// Initializing the layer
|
||||
preg_match('/\d{1,2}$/', $name, $matches);
|
||||
$layer = $matches[0];
|
||||
|
||||
if (!empty($layer) || $layer == 0) {
|
||||
// Initialized the layer
|
||||
|
||||
// Initializing the product layer image file path
|
||||
$path = $storage . DIRECTORY_SEPARATOR . $layer . '.' . $image['type']->extension();
|
||||
|
||||
if (copy(from: $image['tmp_name'], to: $path)) {
|
||||
// Copied the product layer image into the product storage
|
||||
|
||||
// Matching the product image with the product
|
||||
$layers[$layer]['image'] = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($products as $product) {
|
||||
// Iterating over products
|
||||
|
||||
foreach ($product as $layer) {
|
||||
// Iterating over the product layer
|
||||
|
||||
if (empty($layer['image'])) {
|
||||
// Found the empty layer
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === product_type::pin_37) {
|
||||
// Pin 37mm
|
||||
|
||||
$biba = paper::generate(
|
||||
format: paper_format::a6,
|
||||
type: $type,
|
||||
products: $products,
|
||||
canvas: $canvas,
|
||||
dpi: 80,
|
||||
fit: true
|
||||
);
|
||||
}
|
||||
|
||||
if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||
// Request for any response
|
||||
|
||||
// Render page
|
||||
$page = $this->view->render('/generator/pin/page.html');
|
||||
|
||||
// Sending response
|
||||
$this->response
|
||||
->start()
|
||||
->clean()
|
||||
->sse()
|
||||
->write($page)
|
||||
->validate($this->request)
|
||||
?->body()
|
||||
->end();
|
||||
|
||||
// Deinitializing rendered page
|
||||
unset($page);
|
||||
|
||||
// Exit (success)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ final class index extends core
|
||||
*/
|
||||
public function index(): null
|
||||
{
|
||||
if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||
if (str_contains($this->request->headers['accept'] ?? '', content::any->value)) {
|
||||
// Request for any response
|
||||
|
||||
// Render page
|
||||
|
||||
0
svoboda/pechatalka/system/databases/.gitignore
vendored
Normal file → Executable file
0
svoboda/pechatalka/system/databases/.gitignore
vendored
Normal file → Executable file
@@ -14,7 +14,7 @@ return [
|
||||
// Main menu
|
||||
'menu_title' => 'Main menu',
|
||||
'menu_accounts' => 'Accounts',
|
||||
'menu_button_constructor_pin' => 'Pin',
|
||||
'menu_button_generator_pin' => 'Pin',
|
||||
'menu_not_syncronized' => 'The database does not synchronize with the blockchain network',
|
||||
|
||||
// Account
|
||||
|
||||
@@ -14,7 +14,7 @@ return [
|
||||
// Main menu
|
||||
'menu_title' => 'Главное меню',
|
||||
'menu_accounts' => 'Аккаунты',
|
||||
'menu_button_constructor_pin' => 'Значок',
|
||||
'menu_button_generator_pin' => 'Значок',
|
||||
'menu_not_syncronized' => 'База данных не синхронизируется с блокчейн сетью',
|
||||
|
||||
// Аккаунт
|
||||
|
||||
@@ -79,7 +79,7 @@ final class account extends core
|
||||
/**
|
||||
* Initialize
|
||||
*
|
||||
* Searches for the account record in the database, and if it does not find it, it creates it
|
||||
* Searches for the account record in the database, and if it does not find it, then creates it
|
||||
*
|
||||
* @param telegram $telegram The telegram account
|
||||
*
|
||||
|
||||
70
svoboda/pechatalka/system/models/enumerations/currency.php
Normal file
70
svoboda/pechatalka/system/models/enumerations/currency.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace svoboda\pechatalka\models\enumerations;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\models\enumerations\language;
|
||||
|
||||
/**
|
||||
* Types of currencies by ISO 4217 standart
|
||||
*
|
||||
* @package svoboda\pechatalka\models\enumerations
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
enum currency
|
||||
{
|
||||
case usd;
|
||||
case rub;
|
||||
|
||||
/**
|
||||
* Label
|
||||
*
|
||||
* Initialize label of the currency
|
||||
*
|
||||
* @param language|null $language Language into which to translate
|
||||
*
|
||||
* @return string Translated label of the currency
|
||||
*
|
||||
* @todo
|
||||
* 1. More currencies
|
||||
* 2. Cases???
|
||||
*/
|
||||
public function label(?language $language = language::en): string
|
||||
{
|
||||
// Exit (success)
|
||||
return match ($this) {
|
||||
currency::usd => match ($language) {
|
||||
language::en => 'Dollar',
|
||||
language::ru => 'Доллар'
|
||||
},
|
||||
currency::rub => match ($language) {
|
||||
language::en => 'Ruble',
|
||||
language::ru => 'Рубль'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Symbol
|
||||
*
|
||||
* Initialize symbol of the currency
|
||||
*
|
||||
* @return string Symbol of the currency
|
||||
*
|
||||
* @todo
|
||||
* 1. More currencies
|
||||
*/
|
||||
public function symbol(): string
|
||||
{
|
||||
// Exit (success)
|
||||
return match ($this) {
|
||||
currency::usd => '$',
|
||||
currency::rub => '₽'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace svoboda\pechatalka\models\enumerations\paper;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||
|
||||
/**
|
||||
* Types of products
|
||||
*
|
||||
* @package svoboda\pechatalka\models\enumerations\paper
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
enum format
|
||||
{
|
||||
case a6;
|
||||
|
||||
/**
|
||||
* Dimensions
|
||||
*
|
||||
* @return array The paper format dimensions in centimeters (cm) ['width', 'height']
|
||||
*/
|
||||
public function dimensions(): array
|
||||
{
|
||||
return match ($this) {
|
||||
format::a6 => [
|
||||
'width' => 10.5,
|
||||
'height' => 14.8
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Places
|
||||
*
|
||||
* @param product_type $type The product type
|
||||
* @param int|float $padding The paper padding (print borders)
|
||||
*
|
||||
* @return array The paper places by the product type in centimeters (cm)
|
||||
*/
|
||||
public function places(product_type $type, int|float $padding = 0): array|false
|
||||
{
|
||||
if ($this === format::a6) {
|
||||
// A6
|
||||
|
||||
// Initializing the paper dimensions
|
||||
$paper = $this->dimensions();
|
||||
|
||||
if ($type === product_type::pin_37) {
|
||||
// Pin 37mm
|
||||
|
||||
// Initializing the product dimensions
|
||||
$blank = $type->areas()['blank'];
|
||||
|
||||
// Calculating the general X coordinate
|
||||
$x = $paper['width'] / 2 - $blank['width'] / 2 - $padding;
|
||||
|
||||
// Initializing the amount of products
|
||||
$amount = 2;
|
||||
|
||||
// Calculating the summary products height
|
||||
$height = $blank['height'] * $amount;
|
||||
|
||||
// Calculating the free space on the page
|
||||
$free = $paper['height'] - $padding * 2 - $height;
|
||||
|
||||
// Calculating the offset between products and from products to the paper borders
|
||||
$offset = $free / ($amount + 1);
|
||||
|
||||
// Exit (success)
|
||||
return [
|
||||
[
|
||||
'x' => $x,
|
||||
'y' => $offset
|
||||
],
|
||||
[
|
||||
'x' => $x,
|
||||
'y' => $offset + $blank['height'] + $offset
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace svoboda\pechatalka\models\enumerations\product;
|
||||
|
||||
/**
|
||||
* Types of products
|
||||
*
|
||||
* @package svoboda\pechatalka\models\enumerations\product
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
enum type
|
||||
{
|
||||
case pin_37;
|
||||
|
||||
/**
|
||||
* Areas
|
||||
*
|
||||
* @return array The product type areas in centimeters (cm) ['visible' => ['width', 'height'], 'blank' => ['width', 'height']]
|
||||
*/
|
||||
public function areas(): array
|
||||
{
|
||||
return match ($this) {
|
||||
type::pin_37 => [
|
||||
'visible' => [
|
||||
'width' => 3.7,
|
||||
'height' => 3.7
|
||||
],
|
||||
'blank' => [
|
||||
'width' => 4.85,
|
||||
'height' => 4.85
|
||||
]
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
209
svoboda/pechatalka/system/models/paper.php
Normal file
209
svoboda/pechatalka/system/models/paper.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace svoboda\pechatalka\models;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\models\core,
|
||||
/* svoboda\pechatalka\models\enumerations\paper\type as paper_type, */
|
||||
svoboda\pechatalka\models\enumerations\paper\format as paper_format,
|
||||
svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||
|
||||
// Svoboda time
|
||||
use svoboda\time\statement as svoboda;
|
||||
|
||||
// Baza database
|
||||
use mirzaev\baza\database,
|
||||
mirzaev\baza\column,
|
||||
mirzaev\baza\record,
|
||||
mirzaev\baza\enumerations\encoding,
|
||||
mirzaev\baza\enumerations\type;
|
||||
|
||||
// Framework for Telegram
|
||||
use Zanzara\Telegram\Type\User as telegram;
|
||||
|
||||
// Built-in libraries
|
||||
use Imagick as imagick,
|
||||
ImagickKernel as imagick_kernel,
|
||||
ImagickPixel as imagick_pixel,
|
||||
ImagickDraw as imagick_draw,
|
||||
Exception as exception,
|
||||
RuntimeException as exception_runtime;
|
||||
|
||||
/**
|
||||
* Paper
|
||||
*
|
||||
* @package svoboda\pechatalka\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class paper extends core
|
||||
{
|
||||
/**
|
||||
* Generate
|
||||
*
|
||||
* @param paper_format $format
|
||||
* @param product_type $type
|
||||
* @param array $products
|
||||
* @param array $canvas
|
||||
* @param int|float $dpi Dots (pixels) per Inch (used with centimeters)
|
||||
* @param bool $fit Fit the maximum number of blanks on paper? (duplication)
|
||||
*
|
||||
* @return record The account record from the database
|
||||
*/
|
||||
public static function generate(paper_format $format, product_type $type, array $products, array $canvas = [], int|float $dpi = 300, bool $fit = true): mixed
|
||||
{
|
||||
// Initializing the paper dimensions
|
||||
$dimensions = $format->dimensions();
|
||||
$width = (int) round($dimensions['width'] * $dpi);
|
||||
$height = (int) round($dimensions['height'] * $dpi);
|
||||
unset($dimensions);
|
||||
|
||||
// Initializing the product areas
|
||||
$areas = $type->areas();
|
||||
|
||||
// Calculating the product blank dimensions by the print $dpi
|
||||
$blank = [
|
||||
'width' => (int) round($areas['blank']['width'] * $dpi),
|
||||
'height' => (int) round($areas['blank']['height'] * $dpi)
|
||||
];
|
||||
|
||||
// Calculating the $dpi ratio of the pechatalka interface to the blank dimensions
|
||||
$ratio = [
|
||||
'width' => $blank['width'] / $canvas['width'],
|
||||
'height' => $blank['height'] / $canvas['height']
|
||||
];
|
||||
|
||||
// Initializing the circle mask
|
||||
$mask = new imagick();
|
||||
$mask->setResolution($dpi, $dpi);
|
||||
$mask->newImage($blank['width'], $blank['height'], '#0000');
|
||||
$mask->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
$mask->setimageformat('png');
|
||||
$mask->setimagematte(true);
|
||||
$draw = new imagick_draw();
|
||||
$draw->setfillcolor('#fff');
|
||||
$draw->circle($blank['width'] / 2, $blank['height'] / 2, $blank['width'] / 2, $blank['height']);
|
||||
$mask->drawimage($draw);
|
||||
|
||||
// Initializing the paper places
|
||||
$places = $format->places(type: $type);
|
||||
|
||||
// Calculating the amount of products
|
||||
$have = count($products);
|
||||
|
||||
// Calculating the amount of places per paper
|
||||
$limit = count($places);
|
||||
|
||||
// Calculating the amount of missing products on the paper
|
||||
$need = $limit - $have;
|
||||
|
||||
if ($fit && $have < $limit) {
|
||||
// Received less products then can be fit on the page
|
||||
|
||||
for (; $need++ < $limit;) {
|
||||
// Iterating over free places
|
||||
|
||||
// Dublicating the product
|
||||
$products[] = $products[rand(0, $have - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
// Calculating the amount of papers
|
||||
$amount = $have % $limit;
|
||||
|
||||
for ($page = 0; $page < $amount; $page++) {
|
||||
// Iterating over pages
|
||||
|
||||
// Initializing the paper
|
||||
$paper = new imagick();
|
||||
$paper->setResolution($dpi, $dpi);
|
||||
$paper->newImage($width, $height, new imagick_pixel("#fff"));
|
||||
$paper->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
$paper->setImageFormat("jpg");
|
||||
$paper->setimagematte(true);
|
||||
|
||||
// Calculating the products pages offset
|
||||
$offset = $page === 0 ? 0 : $page * $amount;
|
||||
|
||||
foreach ($places as $index => $place) {
|
||||
// Iterating over places
|
||||
|
||||
// Recalculating the place coordinates with DPI
|
||||
$place['x'] *= $dpi;
|
||||
$place['y'] *= $dpi;
|
||||
|
||||
// Initializing the product
|
||||
$product = $products[$offset + $index];
|
||||
|
||||
foreach ($product as $layer) {
|
||||
// Iterating over the product layers
|
||||
|
||||
// Filtering and normalizing the layer parameters
|
||||
empty($layer['scale']) || $layer['scale'] == 0 and $layer['scale'] = 1;
|
||||
|
||||
// Initializing the layer image
|
||||
$image = new imagick();
|
||||
$image->setResolution($dpi, $dpi);
|
||||
$image->readImage($layer['image']);
|
||||
$image->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
$image->setimagematte(true);
|
||||
|
||||
// Resizing the layer image
|
||||
$image->adaptiveResizeImage((int) round($blank['width'] * $layer['scale']), 0);
|
||||
|
||||
if (!empty($layer['corners'])) {
|
||||
// Received the layer image corners
|
||||
|
||||
// Masking the layer image with corners
|
||||
$image->roundCornersImage($layer['corners'], $layer['corners']);
|
||||
}
|
||||
|
||||
// Calculating the layer image coordinates by the layer image mask
|
||||
$layer['x'] = (int) round(($layer['x'] - ($canvas['width'] - $layer['width']) / 2) * $ratio['width'] + ($blank['width'] - $image->getImageWidth()) / 2);
|
||||
$layer['y'] = (int) round(($layer['y'] - ($canvas['height'] - $layer['height']) / 2) * $ratio['height'] + ($blank['height'] - $image->getImageHeight()) / 2);
|
||||
|
||||
// Compositing the layer image mask with the layer image
|
||||
$image->compositeImage(
|
||||
$mask,
|
||||
imagick::COMPOSITE_DSTIN,
|
||||
(int) round(-$layer['x']),
|
||||
(int) round(-$layer['y'])
|
||||
);
|
||||
|
||||
// Drawing the cutting line
|
||||
$draw = new imagick_draw();
|
||||
$draw->setfillcolor($canvas['background'] ?? '#fff');
|
||||
$stroke = 2;
|
||||
$draw->setStrokeOpacity(1);
|
||||
$draw->setStrokeColor('#000');
|
||||
$draw->setStrokeWidth($stroke);
|
||||
$draw->circle(
|
||||
$place['x'] + $blank['width'] / 2,
|
||||
$place['y'] + $blank['height'] / 2,
|
||||
round($place['x'] + $blank['width'] / 2 + $stroke),
|
||||
round($place['y'] + $blank['height'] + $stroke)
|
||||
);
|
||||
$paper->drawimage($draw);
|
||||
|
||||
// Compositing the layer image with the paper
|
||||
$paper->compositeImage(
|
||||
$image,
|
||||
$image->getImageCompose(),
|
||||
(int) round($place['x'] + $layer['x']),
|
||||
(int) round($place['y'] + $layer['y'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Writing the paper file
|
||||
$paper->writeImage(INDEX . DIRECTORY_SEPARATOR . 'test.jpg');
|
||||
}
|
||||
|
||||
|
||||
// Exit (success)
|
||||
return [];
|
||||
}
|
||||
}
|
||||
132
svoboda/pechatalka/system/models/product.php
Normal file
132
svoboda/pechatalka/system/models/product.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace svoboda\pechatalka\models;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\models\core,
|
||||
svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||
|
||||
// Svoboda time
|
||||
use svoboda\time\statement as svoboda;
|
||||
|
||||
// Baza database
|
||||
use mirzaev\baza\database,
|
||||
mirzaev\baza\column,
|
||||
mirzaev\baza\record,
|
||||
mirzaev\baza\enumerations\encoding,
|
||||
mirzaev\baza\enumerations\type;
|
||||
|
||||
// Active Record pattern
|
||||
use mirzaev\record\interfaces\record as record_interface,
|
||||
mirzaev\record\traits\record as record_trait;
|
||||
|
||||
|
||||
// Framework for Telegram
|
||||
use Zanzara\Telegram\Type\User as telegram;
|
||||
|
||||
// Built-in libraries
|
||||
use Exception as exception,
|
||||
RuntimeException as exception_runtime;
|
||||
|
||||
/**
|
||||
* Product
|
||||
*
|
||||
* @package svoboda\pechatalka\models
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
final class product extends core implements record_interface
|
||||
{
|
||||
use record_trait;
|
||||
|
||||
/**
|
||||
* File
|
||||
*
|
||||
* @var string $database Path to the database file
|
||||
*/
|
||||
protected string $file = DATABASES . DIRECTORY_SEPARATOR . 'products.baza';
|
||||
|
||||
/**
|
||||
* Database
|
||||
*
|
||||
* @method record|null $record The record
|
||||
*
|
||||
* @var database $database The database
|
||||
*/
|
||||
public protected(set) database $database;
|
||||
|
||||
/**
|
||||
* Serialized
|
||||
*
|
||||
* @var bool $serialized Is the implementator object serialized?
|
||||
*/
|
||||
private bool $serialized = true;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(?record $record = null)
|
||||
{
|
||||
// Initializing the database
|
||||
$this->database = new database()
|
||||
->encoding(encoding::utf8)
|
||||
->columns(
|
||||
new column('identifier', type::integer_unsigned),
|
||||
new column('account', type::integer_unsigned),
|
||||
new column('type', type::string, ['length' => 32]),
|
||||
new column('layers', type::string, ['length' => 4096]),
|
||||
new column('canvas', type::string, ['length' => 4096]),
|
||||
new column('updated', type::integer_unsigned),
|
||||
new column('created', type::integer_unsigned)
|
||||
)
|
||||
->connect($this->file);
|
||||
|
||||
// Initializing the record
|
||||
$record instanceof record and $this->record = $record;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create
|
||||
*
|
||||
* @param int $account The account identifier
|
||||
* @param string|product_type $type The product type
|
||||
* @param string|array $layers The product layers (JSON)
|
||||
* @param string|array $canvas The product canvas (JSON)
|
||||
*
|
||||
* @return int|false The instance, if created
|
||||
*/
|
||||
public function create(
|
||||
int $account,
|
||||
string|product_type $type,
|
||||
string|array $layers,
|
||||
string|array|null $canvas = null
|
||||
): static|false
|
||||
{
|
||||
// Initializing the identifier
|
||||
$identifier = $this->database->count() + 1;
|
||||
|
||||
// Initializing the record
|
||||
$record = $this->database->record(
|
||||
$identifier,
|
||||
$account,
|
||||
$type instanceof product_type ? $type->name : $type,
|
||||
is_string($layers) && json_validate($layers, depth: 16) ? $layers : json_encode($layers, depth: 16),
|
||||
is_string($canvas) && json_validate($canvas, depth: 16) ? $canvas : json_encode($canvas, depth: 16),
|
||||
svoboda::timestamp(),
|
||||
svoboda::timestamp()
|
||||
);
|
||||
|
||||
// Writing the record into the database
|
||||
$created = $this->database->write($record);
|
||||
|
||||
// Exit (success)
|
||||
return $record ? new static(record: $record) : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +76,9 @@ final class commands extends core
|
||||
'inline_keyboard' => [
|
||||
[
|
||||
[
|
||||
'text' => '🔘 ' . $localization['menu_button_constructor_pin'],
|
||||
'text' => '🔘 ' . $localization['menu_button_generator_pin'],
|
||||
'web_app' => [
|
||||
'url' => 'https://pechatalka.svoboda.works/constructor/pin'
|
||||
'url' => 'https://pechatalka.svoboda.works/generator/pin'
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cascadia Code';
|
||||
src: url("/fonts/geologica/CascadiaCode-Regular.woff2");
|
||||
font-weight: 400;
|
||||
src: url("/fonts/cascadia_code/CascadiaCode.woff2");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ $core = new core(namespace: __NAMESPACE__);
|
||||
// Initializing routes
|
||||
$core->router
|
||||
->write('/', new route('index', 'index'), 'GET')
|
||||
->write('/constructor/pin', new route('constructor', 'pin'), 'GET')
|
||||
->write('/generator/print', new route('generator', 'print', options: ['controller_method_arguments' => 'strict']), 'POST')
|
||||
->write('/generator/pin', new route('generator', 'pin'), 'GET')
|
||||
;
|
||||
|
||||
// Handling request
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Core
|
||||
@@ -10,15 +10,26 @@
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
class core {
|
||||
/**
|
||||
* @name Global modules
|
||||
*
|
||||
* @type {object}
|
||||
*/
|
||||
static global = {};
|
||||
|
||||
/**
|
||||
* @name System modules
|
||||
*
|
||||
* @type {object}
|
||||
*/
|
||||
static system = {};
|
||||
|
||||
// Domain
|
||||
static domain = window.location.hostname;
|
||||
|
||||
// Language
|
||||
static language = "ru";
|
||||
|
||||
// Theme
|
||||
static theme = window.getComputedStyle(document.getElementById('theme'));
|
||||
|
||||
// Window
|
||||
static window;
|
||||
|
||||
@@ -78,6 +89,42 @@ class core {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Buffer
|
||||
*/
|
||||
static buffer = class buffer {
|
||||
/**
|
||||
* @name Write to buffers
|
||||
*
|
||||
* @description
|
||||
* Write to buffers (interface)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
*
|
||||
* @return {bool} Execution completed with an error?
|
||||
*/
|
||||
static write(name, value) {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Execute under damper
|
||||
this.write.damper(name, value);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Execute
|
||||
this.write.system(name, value);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Сгенерировать окно выбора действия
|
||||
*
|
||||
@@ -92,7 +139,7 @@ class core {
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
/* static choose = core.damper(
|
||||
/* static choose = core.global.damper(
|
||||
(
|
||||
title = "Выбор действия",
|
||||
text = "",
|
||||
@@ -266,32 +313,125 @@ Object.assign(
|
||||
/**
|
||||
* @name Connect modules
|
||||
*
|
||||
* @param {Array|string} modules Names of modules or name of the module
|
||||
* @param {(Array|string)} global Names of global modules without extension (`.mjs` only)
|
||||
* @param {(Array|string)} system Names of system modules without extenstion (`.mjs` only)
|
||||
*
|
||||
* @return {Prommise}
|
||||
* @return {Promise}
|
||||
*/
|
||||
async connect(modules) {
|
||||
// Normalisation required argiments
|
||||
if (typeof modules === "string") modules = [modules];
|
||||
async connect(global, system) {
|
||||
// Normalisation required arguments
|
||||
if (typeof global === "string") global = [global];
|
||||
if (typeof system === "string") system = [system];
|
||||
|
||||
if (modules instanceof Array) {
|
||||
// Received and validated required arguments
|
||||
// Initializing the registry of connected modules
|
||||
const connected = {
|
||||
system: [],
|
||||
global: []
|
||||
};
|
||||
|
||||
// Initializing the registry of loaded modules
|
||||
const loaded = [];
|
||||
if (global?.length > 0) {
|
||||
// Global
|
||||
|
||||
for (const module of modules) {
|
||||
// Iterating over modules
|
||||
for (const module of global) {
|
||||
// Iterating over global modules
|
||||
|
||||
// Downloading, importing and writing the module into a core property and into registry of loaded modules
|
||||
core[module] =
|
||||
loaded[module] =
|
||||
// Downloading, importing and writing the global module into a core property and into registry of connected modules
|
||||
core.global[module] =
|
||||
connected.global[module] =
|
||||
await (await import(`./modules/${module}.mjs`)).default;
|
||||
}
|
||||
}
|
||||
|
||||
if (system?.length > 0) {
|
||||
// System
|
||||
|
||||
for (const module of system) {
|
||||
// Iterating over system modules
|
||||
|
||||
// Downloading, importing and writing the system module into a core property and into registry of connected modules
|
||||
core.system[module] =
|
||||
connected.system[module] =
|
||||
await (await import(`./modules/system/${module}.mjs`)).default;
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (success)
|
||||
return loaded;
|
||||
return connected;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
core.buffer.write,
|
||||
{
|
||||
/**
|
||||
* @name Write to buffers
|
||||
*
|
||||
* @description
|
||||
* Write to buffers (damper)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
* @param {bool} force Ignore the damper? (false)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
damper: core.global.damper(
|
||||
(...variables) => core.buffer.write.system(...variables),
|
||||
300,
|
||||
2,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @name Write to buffers
|
||||
*
|
||||
* @description
|
||||
* Write to buffers (system)
|
||||
*
|
||||
* @param {string} name Name of the parameter
|
||||
* @param {string} value Value of the parameter (it can be JSON)
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
Object.assign(
|
||||
core.buffer.write,
|
||||
{
|
||||
system(
|
||||
name,
|
||||
value,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
core.modules.connect("session").then(() => {
|
||||
// Imported the session module
|
||||
|
||||
// Write to the session buffer
|
||||
core.session.buffer?.write(name, value);
|
||||
});
|
||||
|
||||
core.modules.connect("account").then(() => {
|
||||
// Imported the account module
|
||||
|
||||
// Write to the account buffer
|
||||
core.account.buffer?.write(name, value);
|
||||
});
|
||||
|
||||
// Exit (success)
|
||||
resolve();
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Dispatching event: "core.initialized"
|
||||
document.dispatchEvent(new CustomEvent("core.initialized"));
|
||||
|
||||
@@ -1 +1 @@
|
||||
../../../../../../damper.mjs/damper.min.mjs
|
||||
../../../../../../damper.mjs/damper.mjs
|
||||
247
svoboda/pechatalka/system/public/js/modules/generator.mjs
Normal file
247
svoboda/pechatalka/system/public/js/modules/generator.mjs
Normal file
@@ -0,0 +1,247 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @name Generator
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
export default class generator {
|
||||
/**
|
||||
* @name Print (interface)
|
||||
*
|
||||
* @description
|
||||
* Request to generate image and receive image for printing
|
||||
*
|
||||
* @param {string} type Type (37mm)
|
||||
* @param {number} [amount=1] Amount
|
||||
* @param {HTMLElement} canvas The canvas with images
|
||||
* @param {HTMLElement} button Button
|
||||
* @param {boolean} [force=false] Ignore the damper?
|
||||
*
|
||||
* @return {boolean} Did the processing complete without errors?
|
||||
*/
|
||||
static print(type, amount = 1, canvas, button, force = false) {
|
||||
core.modules.connect("damper").then(
|
||||
() => {
|
||||
// Imported the damper module
|
||||
|
||||
// Processing under damper
|
||||
generator.print.damper(type, amount, canvas, button, force);
|
||||
},
|
||||
() => {
|
||||
// Not imported the damper module
|
||||
|
||||
// Processing
|
||||
generator.print.system(type, amount, canvas, button);
|
||||
},
|
||||
);
|
||||
|
||||
// Exit (success)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
core.modules.connect("damper").then(() => {
|
||||
// Imported the damper module
|
||||
|
||||
Object.assign(
|
||||
generator.print,
|
||||
{
|
||||
/**
|
||||
* @name Print (damper)
|
||||
*
|
||||
* @description
|
||||
* Request to generate image and receive image for printing
|
||||
*
|
||||
* @memberof generator.print
|
||||
*
|
||||
* @param {string} type Type (37mm)
|
||||
* @param {number} [amount=1] Amount
|
||||
* @param {HTMLElement} canvas The canvas with images
|
||||
* @param {HTMLElement} button Button
|
||||
* @param {boolean} [force=false] Ignore the damper?
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
damper: core.global.damper(
|
||||
(...variables) => generator.print.system(...variables),
|
||||
300,
|
||||
4,
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Object.assign(
|
||||
generator.print,
|
||||
{
|
||||
/**
|
||||
* @name Print (system)
|
||||
*
|
||||
* @description
|
||||
* Request to generate image and receive image for printing
|
||||
*
|
||||
* @memberof generator.print
|
||||
*
|
||||
* @param {string} type Type (37mm)
|
||||
* @param {number} [amount=1] Amount
|
||||
* @param {HTMLElement} canvas The canvas with images
|
||||
* @param {HTMLElement} button Button
|
||||
* @param {function} resolve Resolve
|
||||
* @param {function} reject Reject
|
||||
*
|
||||
* @return {Promise} Request to the server
|
||||
*/
|
||||
async system(
|
||||
type,
|
||||
amount = 1,
|
||||
canvas,
|
||||
button,
|
||||
resolve = () => {},
|
||||
reject = () => {},
|
||||
) {
|
||||
try {
|
||||
if (
|
||||
typeof type === "string" &&
|
||||
type.length > 0 &&
|
||||
typeof amount === "number" &&
|
||||
canvas instanceof HTMLElement
|
||||
) {
|
||||
// Validated all required arguments
|
||||
|
||||
// Initializing the body buffer
|
||||
const body = new FormData();
|
||||
|
||||
// Writing parameters into the body buffer
|
||||
body.append("type", type);
|
||||
|
||||
// Initializing the canvas JSON buffer
|
||||
/* const canvas = {
|
||||
width:
|
||||
};
|
||||
*/
|
||||
// const background = ;
|
||||
|
||||
// Writing canvas into the body buffer
|
||||
body.append("canvas", JSON.stringify({
|
||||
width: canvas.clientWidth,
|
||||
height: canvas.clientHeight
|
||||
}));
|
||||
|
||||
//
|
||||
// Iterating over products
|
||||
|
||||
// Initializing the product JSON buffer
|
||||
const product = {};
|
||||
|
||||
for (const layer of canvas.querySelectorAll("div.layer")) {
|
||||
// Iterating over layers
|
||||
|
||||
// Initializing the layer index
|
||||
const index = +layer.getAttribute("id")?.split(
|
||||
"pechatalka_layer_",
|
||||
)[1];
|
||||
|
||||
if (typeof index === "number") {
|
||||
// Initialized the layer index
|
||||
|
||||
// Calculating the difference between the canvas and the layer
|
||||
const difference = {
|
||||
width: canvas.offsetWidth - layer.offsetWidth,
|
||||
height: canvas.offsetHeight - layer.offsetHeight
|
||||
};
|
||||
|
||||
// Initializing the layer
|
||||
product[index] = {
|
||||
x: parseInt(layer.style.getPropertyValue("left")) || (difference.width > 0 ? difference.width / 2 : 0),
|
||||
y: parseInt(layer.style.getPropertyValue("top")) || (difference.height > 0 ? difference.height / 2 : 0),
|
||||
width: parseInt(layer.offsetWidth),
|
||||
height: parseInt(layer.offsetHeight),
|
||||
scale: parseFloat(layer.style.getPropertyValue("scale") || 1)
|
||||
};
|
||||
|
||||
// Initializing the layer type
|
||||
const type = layer.getAttribute("data-layer-type");
|
||||
|
||||
if (type === "image") {
|
||||
// Image
|
||||
|
||||
// Initializing the layer image
|
||||
const image = layer.getElementsByTagName("img")[0];
|
||||
|
||||
if (image instanceof HTMLImageElement) {
|
||||
// Initialized the layer image
|
||||
|
||||
// Initializing the image `src` attribute value
|
||||
const src = image.getAttribute("src")?.split("?")[0];
|
||||
|
||||
if (src.length > 0) {
|
||||
// Initialized the image `src` attribute value
|
||||
|
||||
if (new URL(src).protocol === "blob:") {
|
||||
// Blob
|
||||
|
||||
await fetch(src).then((r) => r.blob()).then((value) => {
|
||||
// Converted "blob:..." string to the Blob object
|
||||
|
||||
// Writing into the body buffer (`{$product}_{$layer}`)
|
||||
body.append("0_" + index, value, index);
|
||||
});
|
||||
} else {
|
||||
// URL (expected)
|
||||
|
||||
// Writing the layer parameter
|
||||
product[index].image = src;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type === "film") {
|
||||
// Film (laminating)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writing products into the body buffer (the only one canvas)
|
||||
body.append("products", JSON.stringify({0: product}));
|
||||
|
||||
return await core.request(
|
||||
"/generator/print",
|
||||
body,
|
||||
"POST",
|
||||
{},
|
||||
null,
|
||||
).then(
|
||||
async (json) => {
|
||||
if (json) {
|
||||
// Received a JSON-response
|
||||
|
||||
if (
|
||||
json.errors !== null &&
|
||||
typeof json.errors === "object" &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
// Fail (received errors)
|
||||
|
||||
// Exit (fail)
|
||||
reject(json);
|
||||
} else {
|
||||
// Success (not received errors)
|
||||
|
||||
// Exit (success)
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
},
|
||||
() => reject(),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Exit (fail)
|
||||
reject(e);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -1 +1 @@
|
||||
../../../../../../pechatalka.mjs/pechatalka.min.mjs
|
||||
../../../../../../pechatalka.mjs/pechatalka.mjs
|
||||
@@ -1,13 +0,0 @@
|
||||
core.modules.connect(["pechatalka"])
|
||||
.then(() => {
|
||||
// Imported the pechatalka module
|
||||
|
||||
// Initializing the instance
|
||||
const instance = new core.pechatalka(
|
||||
document.getElementById("pechatalka"),
|
||||
document.getElementById("pechatalka")?.querySelector(".canvas"),
|
||||
document.getElementById("pechatalka")?.querySelector(".result"),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
112
svoboda/pechatalka/system/public/js/pages/generator/pin.js
Normal file
112
svoboda/pechatalka/system/public/js/pages/generator/pin.js
Normal file
@@ -0,0 +1,112 @@
|
||||
core.modules.connect(["generator", "pechatalka"])
|
||||
.then(() => {
|
||||
// Imported modules
|
||||
|
||||
// Initializing the instance
|
||||
const instance = core.global.pechatalka = new core.global.pechatalka(
|
||||
document.getElementById("pechatalka"),
|
||||
document.getElementById("pechatalka")?.querySelector(".canvas"),
|
||||
document.getElementById("pechatalka")?.querySelector(".result"),
|
||||
new Map([
|
||||
["interface", true],
|
||||
["round", true],
|
||||
]),
|
||||
);
|
||||
|
||||
// Activating the keyboard function
|
||||
instance.keyboard();
|
||||
|
||||
// Activating the drag and drop function
|
||||
instance.dragdrop();
|
||||
|
||||
// instence.layers.set('interface', true)
|
||||
|
||||
// Reinitializing the `layer create` event
|
||||
instance.events
|
||||
.get("layers")
|
||||
.set("create", (layer) => {
|
||||
for (const button of layer.buttons.values()) {
|
||||
// Iterating over the layer buttons
|
||||
|
||||
// Writing the class
|
||||
button.classList.add("rounded");
|
||||
}
|
||||
|
||||
// Initializing the interface changing function
|
||||
const interface_change = (value) => {
|
||||
if (value) {
|
||||
// Interface are enabled
|
||||
|
||||
for (const button of layer.buttons.values()) {
|
||||
// Iterating over the layer buttons
|
||||
|
||||
// Writing the class
|
||||
button.classList.remove("disabled");
|
||||
}
|
||||
} else {
|
||||
// Interface are disabled
|
||||
|
||||
for (const button of layer.buttons.values()) {
|
||||
// Iterating over the layer buttons
|
||||
|
||||
// Writing the class
|
||||
button.classList.add("disabled");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Reinitializing `interface` events functions
|
||||
layer.events.set(
|
||||
"interface",
|
||||
new Map([
|
||||
["toggle", interface_change],
|
||||
["set", interface_change],
|
||||
]),
|
||||
);
|
||||
|
||||
// Initial processing of the interface function
|
||||
interface_change(instance.preset.get("interface") ?? false);
|
||||
|
||||
// Initializing the round changing function
|
||||
const round_change = (value) => {
|
||||
if (value) {
|
||||
// Round are enabled
|
||||
|
||||
// Writing the class
|
||||
layer.content.classList.add("rounded");
|
||||
} else {
|
||||
// Round are disabled
|
||||
|
||||
// Writing the class
|
||||
layer.content.classList.remove("rounded");
|
||||
}
|
||||
};
|
||||
|
||||
// Reinitializing `round` events functions
|
||||
layer.events.set(
|
||||
"round",
|
||||
new Map([
|
||||
["toggle", round_change],
|
||||
["set", round_change],
|
||||
]),
|
||||
);
|
||||
|
||||
// Initial processing of the round function
|
||||
round_change(instance.preset.get("round") ?? false);
|
||||
|
||||
if (layer.type === "image") {
|
||||
// Image
|
||||
|
||||
// Writing the layer type
|
||||
layer.wrap.setAttribute("data-layer-type", "image");
|
||||
}
|
||||
});
|
||||
|
||||
// Reinitializing the `layer create` event
|
||||
instance.events
|
||||
.get("cost")
|
||||
.set("changed", (to, from) => {
|
||||
// Writing the total cost into the document
|
||||
instance.result.querySelector(".cost").innerText = to;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
i.icon.notification {
|
||||
position: relative;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
background:
|
||||
linear-gradient(to left, currentColor 10px, transparent 0) no-repeat right bottom/2px 8px,
|
||||
linear-gradient(to left, currentColor 10px, transparent 0) no-repeat left top/8px 2px;
|
||||
|
||||
&:after,
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:before {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-bottom: 2px solid;
|
||||
border-left: 2px solid;
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 4px;
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
&:is(:disabled, [disabled="true"], .disabled) {
|
||||
background:
|
||||
linear-gradient(to left, currentColor 10px, transparent 0) no-repeat right bottom/2px 14px,
|
||||
linear-gradient(to left, currentColor 10px, transparent 0) no-repeat left top/12px 2px;
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
i.icon.round {
|
||||
--border-radius-top-left: 0.7rem;
|
||||
width: var(--square, 10px);
|
||||
height: var(--square, 10px);
|
||||
border: var(--border-width, 2px) solid var(--border-color, #fff);
|
||||
border-radius:
|
||||
var(--border-radius-top-left, 0rem)
|
||||
var(--border-radius-top-right, 0rem)
|
||||
var(--border-radius-bottom-right, 0rem)
|
||||
var(--border-radius-bottom-left, 0rem);
|
||||
|
||||
&:is(:disabled, [disabled="true"], .disabled) {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
i.icon.share {
|
||||
position: relative;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
border-radius: 100px;
|
||||
background-color: currentColor;
|
||||
box-shadow:
|
||||
10px -6px 0,
|
||||
10px 6px 0;
|
||||
|
||||
&:after,
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
width: 10px;
|
||||
height: 2px;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
&:before {
|
||||
top: 0;
|
||||
transform: rotate(-35deg);
|
||||
}
|
||||
|
||||
&:after {
|
||||
bottom: 0;
|
||||
transform: rotate(35deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
div[data-type="select"] {
|
||||
--filter-width: max(14rem, 30vw);
|
||||
--filter-button-width: 2.5rem;
|
||||
--filter-height-element: var(--filters-height, 2rem);
|
||||
--filter-height-close: var(--filter-height-element);
|
||||
--filter-height-open: max-content;
|
||||
--filter-row-padding-x: 1rem;
|
||||
--filter-row-gap: 10px;
|
||||
position: relative;
|
||||
width: var(--filter-width);
|
||||
min-width: var(--width);
|
||||
height: var(--height-close);
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
border-radius: 0.75rem;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--select-background-color, var(--button-background-color, var(--tg-theme-button-color)));
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
div[data-type="select"] :is(button, :is(a, label)[type="button"]) {
|
||||
background-color: var(--select-background-color, var(--button-background-color, var(--tg-theme-button-color)));
|
||||
}
|
||||
|
||||
div[data-type="select"]>section {
|
||||
position: relative;
|
||||
height: var(--filter-height-close);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
border-radius: 0 0 0.75rem 0.75rem;
|
||||
background-color: var(--select-background-color, var(--button-background-color, var(--tg-theme-button-color)));
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
|
||||
div[data-type="select"]:has(>section:is([data-select="open"], :focus)) {
|
||||
>button:has(>i.icon.close) {
|
||||
border-radius: 0 0.75rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
div[data-type="select"]>button:has(>i.icon.close) {
|
||||
z-index: 100;
|
||||
/* position: fixed; */
|
||||
position: absolute;
|
||||
/* right: calc((var(--filter-button-width) + 0.4rem * 2) / 2); */
|
||||
right: 0;
|
||||
/* align-self: start; */
|
||||
width: var(--filter-button-width);
|
||||
height: var(--filter-height-close);
|
||||
padding: 0 0.4rem;
|
||||
border-radius: 0 0.75rem 0.75rem 0;
|
||||
}
|
||||
|
||||
|
||||
div[data-type="select"]:has(>section>input[id$="title"]:checked)>button:has(>i.icon.close),
|
||||
div[data-type="select"]:focus>button:has(>i.icon.close) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div[data-type="select"]:not(>section:focus, >section:has(>button>i.icon.close)):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(--select-text-color, var(--button-text-color, var(--tg-theme-button-text-color, black)));
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section>input {
|
||||
left: -99999px;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section>label {
|
||||
z-index: 10;
|
||||
order: 2;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
width: calc(100% + var(--filter-button-width));
|
||||
height: var(--filter-height-element);
|
||||
padding: 0 var(--filter-row-padding-x, 1rem);
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
color: var(--select-text-color, var(--button-text-color, var(--tg-theme-button-text-color)));
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section>a {
|
||||
height: var(--filter-height-element);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:not(:focus)>a {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:not(:checked)+label[for$='title'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:not(:checked)+label[for$='all']:not(:hover, :active, :focus) {
|
||||
filter: brightness(90%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section>input:not(:checked)+label {
|
||||
cursor: pointer;
|
||||
filter: brightness(80%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input+label:hover {
|
||||
filter: brightness(110%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:checked+label:hover {
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input+label:is(:active, :focus) {
|
||||
filter: brightness(60%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:checked+label:is(:active, :focus) {
|
||||
filter: brightness(70%);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section>input:checked+label {
|
||||
z-index: 20;
|
||||
order: 1;
|
||||
max-width: calc(var(--filter-width) - var(--filter-row-padding-x, 1rem) * 2 - var(--filter-row-gap, 10px));
|
||||
display: inline;
|
||||
line-height: var(--filter-height-element);
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>input:checked+label {
|
||||
max-width: initial;
|
||||
padding-right: initial;
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus) {
|
||||
height: var(--filter-height-open, max-content);
|
||||
}
|
||||
|
||||
div[data-type="select"]>section:is([data-select="open"], :focus)>label {
|
||||
position: relative;
|
||||
display: inline;
|
||||
line-height: var(--filter-height-element);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
div[data-type="select"]>section:only-child {
|
||||
--width: 100%
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
--localization-button-pins: "Pins";
|
||||
|
||||
--localization-cost: "Cost";
|
||||
--localization-currency: "$";
|
||||
--localization-buy: "Buy";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
--localization-button-pins: "Значки";
|
||||
|
||||
--localization-cost: "Стоимость";
|
||||
--localization-currency: "₽";
|
||||
--localization-buy: "Купить";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ body {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
overflow-x: clip;
|
||||
overflow: clip;
|
||||
background-color: var(--background-color, var(--tg-theme-bg-color));
|
||||
}
|
||||
|
||||
@@ -20,3 +20,46 @@ main {
|
||||
gap: var(--gap);
|
||||
transition: 0s;
|
||||
}
|
||||
|
||||
div#language {
|
||||
--filter-width: 2.3rem;
|
||||
--filter-height-element: calc(var(--filter-width) - var(--filter-width) / 10);
|
||||
--filter-height-close: var(--filter-height-element);
|
||||
--filter-button-width: 0rem;
|
||||
--filter-row-padding-x: 0rem;
|
||||
--filter-row-gap: 0px;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
border: 2px solid var(--section-background-color-inverted);
|
||||
filter: brightness(0.2);
|
||||
|
||||
&:hover {
|
||||
filter: unset;
|
||||
}
|
||||
|
||||
>section>label {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
div#currency {
|
||||
--filter-width: 2.3rem;
|
||||
--filter-height-element: calc(var(--filter-width) - var(--filter-width) / 10);
|
||||
--filter-height-close: var(--filter-height-element);
|
||||
--filter-button-width: 0rem;
|
||||
--filter-row-padding-x: 0rem;
|
||||
--filter-row-gap: 0px;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
border: 2px solid var(--section-background-color-inverted);
|
||||
filter: brightness(0.2);
|
||||
|
||||
&:hover {
|
||||
filter: unset;
|
||||
}
|
||||
|
||||
>section>label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
section#title {
|
||||
div#title {
|
||||
position: relative;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
@@ -23,7 +23,7 @@ section#title {
|
||||
}
|
||||
}
|
||||
|
||||
section#pechatalka {
|
||||
div#pechatalka {
|
||||
--width: 300px;
|
||||
width: var(--width, 300px);
|
||||
flex-grow: 1;
|
||||
@@ -33,7 +33,7 @@ section#pechatalka {
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
|
||||
>section.system {
|
||||
>div.system {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ section#pechatalka {
|
||||
overflow: clip;
|
||||
border: 2px solid var(--section-background-color-inverted);
|
||||
|
||||
>.button {
|
||||
>:is(button, .button) {
|
||||
--padding-x: 0.8rem;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
@@ -62,14 +62,17 @@ section#pechatalka {
|
||||
}
|
||||
|
||||
&:last-of-type:last-child:not(:only-of-type) {
|
||||
margin-left: auto;
|
||||
padding-right: calc(var(--padding-x, 0.6rem) + var(--menu-padding-x, 0.4rem)) !important;
|
||||
}
|
||||
|
||||
&:is([for="pechatalka_add_image"])+ :is(button, .button) {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
>div.color {
|
||||
--size: 1rem;
|
||||
width: var(--size, 1rem);
|
||||
height: var(--size, 1rem);
|
||||
--size: 14px;
|
||||
width: var(--size, 14px);
|
||||
height: var(--size, 14px);
|
||||
border-radius: 3px;
|
||||
background-color: var(--color, var(--paper, var(--white, #fff)));
|
||||
|
||||
@@ -83,21 +86,38 @@ section#pechatalka {
|
||||
}
|
||||
}
|
||||
|
||||
>section.canvas.pin {
|
||||
--diameter-cut: var(--width, 300px);
|
||||
>div.canvas.pin {
|
||||
--diameter-cut: 250px;
|
||||
--offset: calc(37 / 48 * 100);
|
||||
--diameter-display: calc(var(--diameter-cut) * var(--offset) / 100);
|
||||
position: relative;
|
||||
width: var(--diameter-cut, 300px);
|
||||
height: var(--diameter-cut, 300px);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: clip;
|
||||
border-radius: 100%;
|
||||
border: 2px solid var(--background-color-inverted);
|
||||
outline: 2px solid var(--background-color-inverted);
|
||||
background-color: var(--paper, var(--white, #fff));
|
||||
|
||||
&:is(.drag) {
|
||||
&:before {
|
||||
opacity: 1;
|
||||
background: radial-gradient(1px,
|
||||
transparent calc(var(--diameter-display, 77.08%) / 2),
|
||||
#53decbab);
|
||||
transition: opacity 0s, background 0s;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.drag) {
|
||||
&:before {
|
||||
transition: opacity 0.1s ease-out, background 0.1s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
z-index: 500;
|
||||
position: absolute;
|
||||
@@ -109,7 +129,6 @@ section#pechatalka {
|
||||
background: radial-gradient(1px,
|
||||
transparent calc(var(--diameter-display, 77.08%) / 2),
|
||||
#000a);
|
||||
transition: opacity 0.1s ease-out;
|
||||
}
|
||||
|
||||
&:has(div.layer) {
|
||||
@@ -147,10 +166,13 @@ section#pechatalka {
|
||||
>div.layer {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
min-width: 50%;
|
||||
max-width: 200%;
|
||||
/* min-width: 50%;
|
||||
max-width: 200%; */
|
||||
width: calc(var(--diameter-cut, var(--width, 100%)) + var(--width-zoom, 0px));
|
||||
/* height: calc(var(--diameter-cut, var(--width, 100%)) + var(--width-zoom, 0px)); */
|
||||
cursor: grab;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
@@ -164,29 +186,37 @@ section#pechatalka {
|
||||
animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
|
||||
}
|
||||
|
||||
>button.delete {
|
||||
>button {
|
||||
&:is(.delete) {
|
||||
--offset: 5px;
|
||||
--size: 40px;
|
||||
z-index: 1500;
|
||||
position: fixed;
|
||||
margin-top: var(--offset);
|
||||
margin-left: calc((var(--size, 40px) + var(--offset)) * -1);
|
||||
position: absolute;
|
||||
top: calc(var(--vertical-difference, 0px) / 2 + var(--offset, 5px));
|
||||
right: calc(var(--image-right, 0px) + var(--offset, 5px));
|
||||
width: var(--size, 40px);
|
||||
height: var(--size, 40px);
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
mix-blend-mode: overlay;
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
border: unset;
|
||||
background: unset;
|
||||
transition: pointer-events 0s, opacity 0.1s ease-in;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
mix-blend-mode: normal;
|
||||
&:is(:disabled, [disabled="true"], .disabled) {
|
||||
opacity: 0;
|
||||
pointer-events: none !important;
|
||||
transition: pointer-events 0s linear 0.1s, opacity 0.1s ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:has(> section.canvas.pin > div.layer) {
|
||||
>section.result {
|
||||
&:has(> div.canvas.pin > div.layer) {
|
||||
>div.result {
|
||||
>button.buy {
|
||||
cursor: pointer !important;
|
||||
filter: unset;
|
||||
@@ -194,7 +224,7 @@ section#pechatalka {
|
||||
}
|
||||
}
|
||||
|
||||
>section.result {
|
||||
>div.result {
|
||||
/* margin-top: auto; */
|
||||
margin-bottom: 1rem;
|
||||
width: 100%;
|
||||
@@ -218,7 +248,7 @@ section#pechatalka {
|
||||
} */
|
||||
|
||||
&:after {
|
||||
content: var(--localization-currency, "$");
|
||||
content: var(--currency, "?");
|
||||
margin-left: 0.1rem;
|
||||
}
|
||||
}
|
||||
2
svoboda/pechatalka/system/storage/products/.gitignore
vendored
Normal file
2
svoboda/pechatalka/system/storage/products/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -5,7 +5,8 @@ declare(strict_types=1);
|
||||
namespace svoboda\pechatalka\views;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\models\enumerations\language;
|
||||
use svoboda\pechatalka\models\enumerations\language,
|
||||
svoboda\pechatalka\models\enumerations\currency;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\controller;
|
||||
@@ -67,6 +68,9 @@ final class templater extends controller implements array_access
|
||||
$this->twig->addGlobal('server', $_SERVER);
|
||||
$this->twig->addGlobal('cookies', $_COOKIE);
|
||||
$this->twig->addGlobal('language', $language = $session?->buffer['language'] ?? language::en);
|
||||
$this->twig->addGlobal('languages', language::cases());
|
||||
$this->twig->addGlobal('currency', $currency = $session?->buffer['currency'] ?? currency::usd);
|
||||
$this->twig->addGlobal('currencies', currency::cases());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +87,6 @@ final class templater extends controller implements array_access
|
||||
{
|
||||
// Generation and exit (success)
|
||||
return $this->twig->render('themes' . DIRECTORY_SEPARATOR . $this->twig->getGlobals()['theme'] . DIRECTORY_SEPARATOR . $file, $variables + $this->variables);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,4 +211,3 @@ final class templater extends controller implements array_access
|
||||
return isset($this->variables[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<section id="pechatalka">
|
||||
<section class="system">
|
||||
<input id="pechatalka_add_image" type="file" name="images" accept="image/png, image/jpeg, image/webp"
|
||||
multiple="false" onchange="document.getElementById('pechatalka')?.pechatalka.image(this.files[0]);" />
|
||||
<input id="pechatalka_background" type="color" name="background" value="#ffffff"
|
||||
oninput="
|
||||
document.getElementById('pechatalka')?.querySelector('label[for=\'pechatalka_background\']>div.color')?.style.setProperty('--color', event.target.value); document.getElementById('pechatalka')?.pechatalka.canvas.style.setProperty('background-color', event.target.value);" />
|
||||
</section>
|
||||
|
||||
<nav class="tools rounded unselectable">
|
||||
<label class="button" for="pechatalka_add_image"><i class="icon plus"></i></label>
|
||||
<label class="button" for="pechatalka_background">
|
||||
<div class="color"></div>
|
||||
</label>
|
||||
</nav>
|
||||
|
||||
<section class="canvas pin unselectable">
|
||||
<label class="button add" for="pechatalka_add_image"><i class="icon plus"></i></label>
|
||||
|
||||
<div class="display"></div>
|
||||
</section>
|
||||
|
||||
<section class="result unselectable">
|
||||
<button class="print rounded"
|
||||
onclick="console.log('send parameters to the server, then return the generated page and print')"><i
|
||||
class="icon printer"></i></button>
|
||||
<span class="cost">0</span>
|
||||
<button class="buy rounded"></button>
|
||||
</section>
|
||||
</section>
|
||||
@@ -1,4 +0,0 @@
|
||||
<section id="title" class="unselectable">
|
||||
<h1>Pechatalka</h1>
|
||||
<small>Svoboda Work Union</small>
|
||||
</section>
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
{% block body %}
|
||||
<footer>
|
||||
<p>biba</p>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<div id="pechatalka">
|
||||
<div class="system">
|
||||
<input id="pechatalka_add_image" type="file" name="images" accept="image/png, image/jpeg, image/webp"
|
||||
multiple="false" onchange="core.global.pechatalka.image(this.files[0], 160);" />
|
||||
<input id="pechatalka_background" type="color" name="background" value="#ffffff"
|
||||
oninput="core.global.pechatalka?.wrap?.querySelector('label[for=\'pechatalka_background\']>div.color')?.style.setProperty('--color', event.target.value); core.global.pechatalka.canvas.style.setProperty('background-color', event.target.value);" />
|
||||
</div>
|
||||
|
||||
<nav class="tools rounded unselectable">
|
||||
<label class="button" for="pechatalka_add_image"><i class="icon plus"></i></label>
|
||||
<button id="pechatalka_interface" onclick="const icon = this.getElementsByTagName('i')[0]; icon.classList.toggle('disabled'); core.global.pechatalka.global('interface', !icon.classList.contains('disabled'), true);"><i class="icon notification"></i></button>
|
||||
<button id="pechatalka_round" onclick="const icon = this.getElementsByTagName('i')[0]; icon.classList.toggle('disabled'); core.global.pechatalka.global('round', !icon.classList.contains('disabled'), true);"><i class="icon round"></i></button>
|
||||
<label class="button" for="pechatalka_background"><div class="color"></div></label>
|
||||
</nav>
|
||||
|
||||
<div class="canvas pin unselectable">
|
||||
<label class="button add" for="pechatalka_add_image"><i class="icon plus"></i></label>
|
||||
|
||||
<div class="display"></div>
|
||||
</div>
|
||||
|
||||
<div class="result unselectable">
|
||||
<button class="print rounded"
|
||||
onclick="core.global.generator?.print('pin_37', 1, core.global.pechatalka.canvas, this)"><i
|
||||
class="icon printer"></i></button>
|
||||
<span class="cost" style="--currency: '{{ currency.symbol() ?? '?'}}'">0</span>
|
||||
<button class="buy rounded"></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
<div id="title" class="unselectable">
|
||||
<h1>{{ language.name == 'ru' ? 'Печаталка' : 'Pechatalka' }}</h1>
|
||||
<small>{{ language.name == 'ru' ? 'Рабочий Союз Свободы' : 'Svoboda Work Union' }}</small>
|
||||
</div>
|
||||
@@ -2,21 +2,24 @@
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/pages/constructor/pin.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/pages/generator/pin.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/trash.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/printer.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/plus.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/share.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/round.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/{{ theme }}/css/icons/notification.css" />
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% include "/themes/default/constructor/pin/elements/title.html" %}
|
||||
{% include "/themes/default/constructor/pin/elements/pechatalka.html" %}
|
||||
{% include "/themes/default/generator/pin/elements/title.html" %}
|
||||
{% include "/themes/default/generator/pin/elements/pechatalka.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script src="/js/modules/pechatalka.mjs.min" type="module"></script>
|
||||
<script src="/js/pages/constructor/pin.js"></script>
|
||||
<script src="/js/modules/pechatalka.mjs" type="module"></script>
|
||||
<script src="/js/modules/generator.mjs" type="module"></script>
|
||||
<script src="/js/pages/generator/pin.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
{% endfor %}
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/fonts.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/system.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/colorscheme.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/localizations/{{ language.label|lower }}.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/header.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/main.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/aside.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/footer.css" />
|
||||
<style id="theme">
|
||||
@import url('/themes/default/css/themes/default/colorsceme.css');
|
||||
@import url('/themes/default/css/colorscheme.css');
|
||||
</style>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="/themes/default/css/interface/select.css" />
|
||||
{% endblock %}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% include '/themes/default/interface/select/language.html' %}
|
||||
{% include '/themes/default/interface/select/currency.html' %}
|
||||
|
||||
{{ block('header') }}
|
||||
|
||||
{{ block('aside') }}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<div id="currency" class="row rounded" data-type="select">
|
||||
<section class="unselectable" tabindex="5">
|
||||
{% for _currency in currencies %}
|
||||
<input id="currency_{{ _currency.name }}" type="radio" name="currency" value="{{ _currency.name }}"
|
||||
oninput="core.account.buffer.write('currency', '{{ _currency.name }}')" {% if _currency.name==currency or (session.currency is empty and _currency.name == 'usd') %}checked{%
|
||||
endif %}>
|
||||
<label for="currency_{{ _currency.name }}" type="button">
|
||||
{{ _currency.symbol() }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<div id="language" class="row rounded" data-type="select">
|
||||
<section class="unselectable" tabindex="5">
|
||||
{% for _language in languages %}
|
||||
<input id="language_{{ _language.name }}" type="radio" name="language" value="{{ _language.name }}"
|
||||
oninput="core.account.buffer.write('language', '{{ _language.name }}')" {% if _language.name==language or (session.language is empty and _language.name == 'en') %}checked{%
|
||||
endif %}>
|
||||
<label for="language_{{ _language.name }}" type="button">
|
||||
{{ _language.flag() }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</section>
|
||||
</div>
|
||||
@@ -1,25 +1,14 @@
|
||||
{% block js %}
|
||||
{% for element in js %}
|
||||
<script {% if element.src %}src="{{ element.src }}"{% endif %} {% if element.type %}type="{{ element.type }}"{% endif %}>{{ element.innerText }}</script>
|
||||
{% endfor %}
|
||||
|
||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||
<!-- <script src="https://telegram.org/js/telegram-web-app.js"></script> -->
|
||||
<script src="/js/modules/damper.mjs" type="module"></script>
|
||||
<script src="/js/core.js"></script>
|
||||
<script src="/js/modules/loader.mjs" type="module"></script>
|
||||
<script src="/js/modules/telegram.mjs" type="module"></script>
|
||||
<script src="/js/modules/session.mjs" type="module"></script>
|
||||
<script src="/js/telegram.js"></script>
|
||||
|
||||
{% if javascript is not empty %}
|
||||
<script>
|
||||
core.modules.connect("damper").then(() => {
|
||||
let _window;
|
||||
|
||||
{% for code in javascript %}
|
||||
{{ code|raw }}
|
||||
{% endfor %}
|
||||
import("/js/core.js").then(() => {
|
||||
core.language = '{{ language.value }}';
|
||||
core.currency = '{ name: "{{ currency.name }}", symbol: "{{ currency.symbol() }}" }'
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user