PRINT TO A6 PAPER

This commit is contained in:
2026-05-29 10:34:16 +00:00
parent eeca8fb535
commit 99daad5074
21 changed files with 672 additions and 120 deletions

View File

@@ -15,7 +15,7 @@
"name": "Arsen Mirzaev Tatyano-Muradovich",
"email": "arsen@mirzaev.sexy",
"homepage": "https://mirzaev.sexy",
"role": "Creator"
"role": "Developer"
}
],
"support": {
@@ -23,9 +23,10 @@
"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",
@@ -33,7 +34,8 @@
"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": {

View File

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

View File

@@ -6,7 +6,9 @@ namespace svoboda\pechatalka\controllers;
// Files of the project
use svoboda\pechatalka\controllers\core,
svoboda\pechatalka\models\enumerations\products\type;
svoboda\pechatalka\models\paper,
svoboda\pechatalka\models\product,
svoboda\pechatalka\models\enumerations\products\type as product_type;
// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
@@ -72,42 +74,127 @@ final class generator extends core
/**
* Print
*
* Assemble and generate the blank from layers and prepare to printing
* Assemble and generate the blank from products and prepare to printing
*
* @return null
*/
public function print(
string $type,
string|int $amount = 1,
?string $display
string $type = '',
string $products = '',
?string $canvas = null
): null {
// Type
$type = type::{$type} ?? null;
!empty($type) and $type = product_type::{$type} ?? null;
// Amount
preg_match('/[\d]+/', (string) $amount, $matches);
/* preg_match('/[\d]+/', (string) $amount, $matches);
$amount = (int) $matches[0] ?? 1;
unset($matches);
unset($matches); */
// Display
$display = json_decode($display, true, 10);
// Initializing products data
$products = json_decode($products, true, 16);
if ($type instanceof type && $amount > 0) {
// Initializing the canvas data
$canvas = json_decode($canvas, true, 16);
if ($type instanceof product_type && count($products) > 0) {
// Initialized all required arguments
// Universalizing received images
$files_images = array_filter($this->request->files, fn(string $key) => preg_match('/^layer_\d{1,2}_image/', $key), ARRAY_FILTER_USE_KEY);
// Declaring the images buffer
$images = [];
// Initializing the combined images buffer
$images_combined = [];
foreach ($this->request->files as $name => $file) {
// Iterating over received files
for ($i = 0; $i < count($files_images); ++$i) {
// Iterating over images
// Deserializing the file type
!empty($file['type']) && is_string($file['type']) and $file['type'] = content::from($file['type']) ?? null;
// Combining images with sorting
if (empty($display[$i]['image'])) $images_combined[] = $display[$i]['image'];
if (!empty($files_images["image_$i"])) $images_combined[] = $files_images["image_$i"];
if (empty($display[$i]['image'])) $display[$i]['image'] = $files_images[$i];
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::a5(type: $type, products: $products, canvas: $canvas);
}
if (str_contains($this->request->headers['accept'], content::any->value)) {

View File

@@ -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
View File

View File

@@ -15,5 +15,25 @@ namespace svoboda\pechatalka\models\enumerations\products;
enum type
{
case pin_37;
}
/**
* Areas
*
* @return array ['visible' => ['width', 'height'], 'blank' => ['width', 'height']]
*/
public function areas(): array
{
return match ($this) {
type::pin_37 => [
'visible' => [
'width' => 37,
'height' => 37
],
'blank' => [
'width' => 48.5,
'height' => 48.5
]
]
};
}
}

View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace svoboda\pechatalka\models;
// Files of the project
use svoboda\pechatalka\models\core,
svoboda\pechatalka\models\enumerations\products\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,
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
{
/**
* A5
*
*
*
* @return record The account record from the database
*/
public static function a5(product_type $type, array $products, array $canvas = []): mixed
{
// Initializing the print DPI
define('dpi', 80);
/* define('dpi', 300); */
// Initializing the A5 paper dimensions
$a5 = [
'width' => (int) round(10.5 * dpi),
'height' => (int) round(14.8 * dpi)
];
// Initializing the paper
$paper = new imagick();
$paper->setResolution(dpi, dpi);
$paper->newImage($a5['width'], $a5['height'], new imagick_pixel("white"));
$paper->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
$paper->setImageFormat("jpg");
foreach ($products as $product) {
// Iterating over products
foreach ($product as $layer) {
// Iterating over the product layers
// Filtering
empty($layer['scale']) || $layer['scale'] == 0 and $layer['scale'] = 1;
// Initializing the product areas
$areas = $type->areas();
// Calculating the product blank dimensions by the print DPI
$blank = [
'width' => (int) round($areas['blank']['width'] / 10 * dpi),
'height' => (int) round($areas['blank']['height'] / 10 * 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
$circle = new imagick();
$circle->setResolution(dpi, dpi);
$circle->newImage($blank['width'], $blank['height'], '#0008');
$circle->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
$circle->setimageformat('png');
$circle->setimagematte(true);
$draw = new imagick_draw();
$draw->setfillcolor('#fff');
$draw->circle($blank['width'] / 2, $blank['height'] / 2, $blank['width'] / 2, $blank['height']);
$circle->drawimage($draw);
// Initializing the layer image
$image = new imagick();
$image->setResolution(dpi, dpi);
$image->readImage($layer['image']);
$image->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
$image->setimagematte(true);
$width = (int) round($blank['width'] * $layer['scale']);
$image->adaptiveResizeImage($width, 0);
/* $image->roundCornersImage(50, 50); */
$offset = [
'x' => ($blank['width'] - $image->getImageWidth()) / 2,
'y' => ($blank['height'] - $image->getImageHeight()) / 2
];
$vertical = $blank['height'] - $image->getImageHeight();
// Добавить нормальные комментарии после глубокго тестирования
// Reinitializing the product layer offsets
$layer['x'] = (int) round($layer['x'] * $ratio['width']);
$layer['y'] = (int) round($layer['y'] * $ratio['height']);
$image->compositeImage(
$circle,
imagick::COMPOSITE_DSTIN,
(int) round(-$layer['x'] - $offset['x']),
/* (int) round(-$layer['y'] - $offset['y'] - $vertical / 2) */
(int) round(-$layer['y'] - $offset['y'])
);
// Compositing the layer with the paper
$paper->setImageVirtualPixelMethod(imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
$paper->setImageArtifact('compose:args', "1,0,-0.5,0.5");
$paper->compositeImage(
$image,
imagick::COMPOSITE_MATHEMATICS,
(int) round($a5['width'] / 2 - $blank['width'] / 2 + $layer['x'] + $offset['x']),
/* (int) round($a5['height'] / 2 - $blank['height'] / 2 + $layer['y'] + $offset['y'] + $vertical / 2) */
(int) round($a5['height'] / 2 - $blank['height'] / 2 + $layer['y'] + $offset['y'])
);
// Writing the paper file
$paper->writeImage(INDEX . DIRECTORY_SEPARATOR . 'test.jpg');
}
}
return true;
}
}

View 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\products\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;
}
}

View File

@@ -50,7 +50,7 @@ $core = new core(namespace: __NAMESPACE__);
// Initializing routes
$core->router
->write('/', new route('index', 'index'), 'GET')
->write('/generator/print', new route('generator', 'print'), 'POST')
->write('/generator/print', new route('generator', 'print', options: ['controller_method_arguments' => 'strict']), 'POST')
->write('/generator/pin', new route('generator', 'pin'), 'GET')
;

View File

@@ -10,17 +10,25 @@
* @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 = "english";
// Currency
static currency = { name: "usd", symbol: "$" };
// Theme
static theme = window.getComputedStyle(document.getElementById("theme"));
static language = "ru";
// Window
static window;
@@ -81,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;
}
};
/**
* Сгенерировать окно выбора действия
*
@@ -95,7 +139,7 @@ class core {
*
* @return {void}
*/
/* static choose = core.damper(
/* static choose = core.global.damper(
(
title = "Выбор действия",
text = "",
@@ -269,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 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)
return loaded;
resolve();
} catch (e) {
// Exit (fail)
reject(e);
}
},
},
);
// Dispatching event: "core.initialized"
document.dispatchEvent(new CustomEvent("core.initialized"));

View File

@@ -1 +1 @@
../../../../../../damper.mjs/damper.min.mjs
../../../../../../damper.mjs/damper.mjs

View File

@@ -66,7 +66,7 @@ core.modules.connect("damper").then(() => {
*
* @return {void}
*/
damper: core.damper(
damper: core.global.damper(
(...variables) => generator.print.system(...variables),
300,
4,
@@ -117,10 +117,25 @@ Object.assign(
// Writing parameters into the body buffer
body.append("type", type);
body.append("amount", amount);
// Initializing the display JSON buffer
const display = {};
// 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
@@ -134,15 +149,14 @@ Object.assign(
// Initialized the layer index
// Initializing the layer
display[index] ??= {};
product[index] ??= {};
// Writing the layer parameters
display[index].x =
product[index].x =
parseInt(layer.style.getPropertyValue("left")) || 0;
display[index].y =
product[index].y =
parseInt(layer.style.getPropertyValue("top")) || 0;
display[index].scale =
(parseInt(layer.style.getPropertyValue("scale")) || 1) * 100;
product[index].scale = parseFloat(layer.style.getPropertyValue("scale") || 1);
// Initializing the layer type
const type = layer.getAttribute("data-layer-type");
@@ -168,14 +182,14 @@ Object.assign(
await fetch(src).then((r) => r.blob()).then((value) => {
// Converted "blob:..." string to the Blob object
// Writing into the body buffer
body.append("layer_" + index + "_image", value, index);
// Writing into the body buffer (`{$product}_{$layer}`)
body.append("0_" + index, value, index);
});
} else {
// URL (expected)
// Writing the layer parameter
display[index].image = src;
product[index].image = src;
}
}
}
@@ -185,8 +199,8 @@ Object.assign(
}
}
// Writing into the body buffer
body.append("display", JSON.stringify(display));
// Writing products into the body buffer (the only one canvas)
body.append("products", JSON.stringify({0: product}));
return await core.request(
"/generator/print",
@@ -226,6 +240,3 @@ Object.assign(
},
},
);
// Connecting to the core
if (!core.generator) core.generator = generator;

View File

@@ -1 +1 @@
../../../../../../pechatalka.mjs/pechatalka.min.mjs
../../../../../../pechatalka.mjs/pechatalka.mjs

View File

@@ -1,9 +1,9 @@
core.modules.connect(["pechatalka"])
core.modules.connect(["generator", "pechatalka"])
.then(() => {
// Imported the pechatalka module
// Imported modules
// Initializing the instance
const instance = new core.pechatalka(
const instance = core.global.pechatalka = new core.global.pechatalka(
document.getElementById("pechatalka"),
document.getElementById("pechatalka")?.querySelector(".canvas"),
document.getElementById("pechatalka")?.querySelector(".result"),
@@ -11,12 +11,8 @@ core.modules.connect(["pechatalka"])
["interface", true],
["round", true],
]),
true,
);
// Reinitializing the core instance of pechatalka
core.pechatalka = instance;
// instence.layers.set('interface', true)
// Reinitializing the `layer create` event

View File

@@ -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;
}
@@ -86,7 +86,7 @@ section#pechatalka {
}
}
>section.canvas.pin {
>div.canvas.pin {
--diameter-cut: 250px;
--offset: calc(37 / 48 * 100);
--diameter-display: calc(var(--diameter-cut) * var(--offset) / 100);
@@ -150,10 +150,12 @@ 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;
align-content: center;
&:active {
cursor: grabbing;
@@ -195,8 +197,8 @@ section#pechatalka {
}
}
&:has(> section.canvas.pin > div.layer) {
>section.result {
&:has(> div.canvas.pin > div.layer) {
>div.result {
>button.buy {
cursor: pointer !important;
filter: unset;
@@ -204,7 +206,7 @@ section#pechatalka {
}
}
>section.result {
>div.result {
/* margin-top: auto; */
margin-bottom: 1rem;
width: 100%;

View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -1,29 +1,29 @@
<section id="pechatalka">
<section class="system">
<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="document.getElementById('pechatalka')?.pechatalka.image(this.files[0], 160);" />
multiple="false" onchange="core.global.pechatalka.image(this.files[0], 160);" />
<input id="pechatalka_background" type="color" name="background" value="#ffffff"
oninput="core.pechatalka?.wrap?.querySelector('label[for=\'pechatalka_background\']>div.color')?.style.setProperty('--color', event.target.value); core.pechatalka.canvas.style.setProperty('background-color', event.target.value);" />
</section>
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.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.pechatalka.global('round', !icon.classList.contains('disabled'), true);"><i class="icon round"></i></button>
<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>
<section class="canvas pin unselectable">
<div class="canvas pin unselectable">
<label class="button add" for="pechatalka_add_image"><i class="icon plus"></i></label>
<div class="display"></div>
</section>
</div>
<section class="result unselectable">
<div class="result unselectable">
<button class="print rounded"
onclick="core.generator?.print('pin_37', 1, document.getElementById('pechatalka')?.pechatalka.canvas, this)"><i
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>
</section>
</section>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<section id="title" class="unselectable">
<div id="title" class="unselectable">
<h1>{{ language.name == 'ru' ? 'Печаталка' : 'Pechatalka' }}</h1>
<small>{{ language.name == 'ru' ? 'Рабочий Союз Свободы' : 'Svoboda Work Union' }}</small>
</section>
</div>

View File

@@ -1,26 +1,10 @@
{% 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="/js/modules/damper.mjs" type="module"></script>
<script src="/js/core.js"></script>
<script src="/js/modules/telegram.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 %}
});
</script>
{% endif %}
<script>
import("/js/core.js").then(() => {
core.language = '{{ language.value }}';