something old

This commit is contained in:
2026-05-20 21:41:48 +05:00
parent 77f62d53af
commit eeca8fb535
37 changed files with 1043 additions and 190 deletions

View File

@@ -30,6 +30,7 @@
"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"

View File

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

View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace svoboda\pechatalka\controllers;
// Files of the project
use svoboda\pechatalka\controllers\core,
svoboda\pechatalka\models\enumerations\products\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 layers and prepare to printing
*
* @return null
*/
public function print(
string $type,
string|int $amount = 1,
?string $display
): null {
// Type
$type = type::{$type} ?? null;
// Amount
preg_match('/[\d]+/', (string) $amount, $matches);
$amount = (int) $matches[0] ?? 1;
unset($matches);
// Display
$display = json_decode($display, true, 10);
if ($type instanceof type && $amount > 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);
// Initializing the combined images buffer
$images_combined = [];
for ($i = 0; $i < count($files_images); ++$i) {
// Iterating over images
// 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 (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;
}
}

View 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

View File

@@ -14,7 +14,7 @@ return [
// Main menu
'menu_title' => 'Главное меню',
'menu_accounts' => 'Аккаунты',
'menu_button_constructor_pin' => 'Значок',
'menu_button_generator_pin' => 'Значок',
'menu_not_syncronized' => 'База данных не синхронизируется с блокчейн сетью',
// Аккаунт

View File

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

View 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 => '₽'
};
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace svoboda\pechatalka\models\enumerations\products;
/**
* Types of products
*
* @package svoboda\pechatalka\models\enumerations\products
*
* @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;
}

View File

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

View File

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

View File

@@ -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'), 'POST')
->write('/generator/pin', new route('generator', 'pin'), 'GET')
;
// Handling request

View File

@@ -1,4 +1,4 @@
'use strict';
"use strict";
/**
* @name Core
@@ -14,10 +14,13 @@ class core {
static domain = window.location.hostname;
// Language
static language = "ru";
static language = "english";
// Currency
static currency = { name: "usd", symbol: "$" };
// Theme
static theme = window.getComputedStyle(document.getElementById('theme'));
static theme = window.getComputedStyle(document.getElementById("theme"));
// Window
static window;

View File

@@ -0,0 +1,231 @@
"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.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);
body.append("amount", amount);
// Initializing the display JSON buffer
const display = {};
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
// Initializing the layer
display[index] ??= {};
// Writing the layer parameters
display[index].x =
parseInt(layer.style.getPropertyValue("left")) || 0;
display[index].y =
parseInt(layer.style.getPropertyValue("top")) || 0;
display[index].scale =
(parseInt(layer.style.getPropertyValue("scale")) || 1) * 100;
// 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
body.append("layer_" + index + "_image", value, index);
});
} else {
// URL (expected)
// Writing the layer parameter
display[index].image = src;
}
}
}
} else if (type === "film") {
// Film (laminating)
}
}
}
// Writing into the body buffer
body.append("display", JSON.stringify(display));
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);
}
},
},
);
// Connecting to the core
if (!core.generator) core.generator = generator;

View File

@@ -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,
);
});

View File

@@ -0,0 +1,110 @@
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"),
new Map([
["interface", true],
["round", true],
]),
true,
);
// Reinitializing the core instance of pechatalka
core.pechatalka = instance;
// 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;
});
});

View File

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

View File

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

View File

@@ -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);
}
}

View File

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

View File

@@ -8,7 +8,6 @@
--localization-button-pins: "Pins";
--localization-cost: "Cost";
--localization-currency: "$";
--localization-buy: "Buy";
}
}

View File

@@ -8,7 +8,6 @@
--localization-button-pins: "Значки";
--localization-cost: "Стоимость";
--localization-currency: "₽";
--localization-buy: "Купить";
}
}

View File

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

View File

@@ -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)));
@@ -84,7 +87,7 @@ section#pechatalka {
}
>section.canvas.pin {
--diameter-cut: var(--width, 300px);
--diameter-cut: 250px;
--offset: calc(37 / 48 * 100);
--diameter-display: calc(var(--diameter-cut) * var(--offset) / 100);
position: relative;
@@ -164,22 +167,29 @@ section#pechatalka {
animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
}
>button.delete {
--offset: 5px;
--size: 40px;
z-index: 1500;
position: fixed;
margin-top: var(--offset);
margin-left: calc((var(--size, 40px) + var(--offset)) * -1);
width: var(--size, 40px);
height: var(--size, 40px);
display: inline-flex;
justify-content: center;
align-items: center;
mix-blend-mode: overlay;
>button {
&:is(:disabled, [disabled="true"], .disabled) {
display: none !important;
pointer-events: none !important;
}
&:hover {
mix-blend-mode: normal;
&:is(.delete) {
--offset: 5px;
--size: 40px;
z-index: 1500;
position: fixed;
margin-top: var(--offset);
margin-left: calc((var(--size, 40px) + var(--offset)) * -1);
width: var(--size, 40px);
height: var(--size, 40px);
display: inline-flex;
justify-content: center;
align-items: center;
mix-blend-mode: overlay;
&:hover {
mix-blend-mode: normal;
}
}
}
}
@@ -218,7 +228,7 @@ section#pechatalka {
} */
&:after {
content: var(--localization-currency, "$");
content: var(--currency, "?");
margin-left: 0.1rem;
}
}

View File

@@ -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);
}
/**
@@ -113,7 +116,7 @@ final class templater extends controller implements array_access
*/
public function __get(string $name): mixed
{
// Read the variable and exit (success)
// Read the variable and exit (success)
return $this->variables[$name];
}
@@ -128,7 +131,7 @@ final class templater extends controller implements array_access
*/
public function __unset(string $name): void
{
// Delete the variable and exit (success)
// Delete the variable and exit (success)
unset($this->variables[$name]);
}
@@ -174,7 +177,7 @@ final class templater extends controller implements array_access
*/
public function offsetGet(mixed $name): mixed
{
// Read the variable and exit (success)
// Read the variable and exit (success)
return $this->variables[$name];
}
@@ -189,7 +192,7 @@ final class templater extends controller implements array_access
*/
public function offsetUnset(mixed $name): void
{
// Delete the variable and exit (success)
// Delete the variable and exit (success)
unset($this->variables[$name]);
}
@@ -208,4 +211,3 @@ final class templater extends controller implements array_access
return isset($this->variables[$name]);
}
}

View File

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

View File

@@ -1,4 +0,0 @@
<section id="title" class="unselectable">
<h1>Pechatalka</h1>
<small>Svoboda Work Union</small>
</section>

View File

@@ -3,6 +3,7 @@
{% block body %}
<footer>
<p>biba</p>
</footer>
{% endblock %}

View File

@@ -0,0 +1,29 @@
<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], 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>
<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>
<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="core.generator?.print('pin_37', 1, document.getElementById('pechatalka')?.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>

View File

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

View File

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

View File

@@ -1,28 +1,29 @@
{% block title %}
<title>{% if head.title != empty %}{{ head.title }}{% else %}pechatalka by mirzaev{% endif %}</title>
<title>{% if head.title != empty %}{{ head.title }}{% else %}pechatalka by mirzaev{% endif %}</title>
{% endblock %}
{% block meta %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% for meta in head.metas %}
<meta {% for name, value in meta.attributes %}{{ name }}="{{ value }}" {% endfor %}>
{% endfor %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% for meta in head.metas %}
<meta {% for name, value in meta.attributes %}{{ name }}="{{ value }}" {% endfor %}>
{% endfor %}
{% endblock %}
{% block css %}
{% for element in css %}
<link type="text/css" rel="stylesheet" {% if element.href %} href="{{ element.href }}" {% endif %} />
{% 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');
</style>
{% for element in css %}
<link type="text/css" rel="stylesheet" {% if element.href %} href="{{ element.href }}" {% endif %} />
{% 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/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/colorscheme.css');
</style>
<link type="text/css" rel="stylesheet" href="/themes/default/css/interface/select.css" />
{% endblock %}

View File

@@ -2,8 +2,8 @@
{% endblock %}
{% block body %}
<header>
</header>
<header>
</header>
{% endblock %}
{% block js %}

View File

@@ -11,13 +11,16 @@
{% endblock %}
{% block body %}
{% include '/themes/default/interface/select/language.html' %}
{% include '/themes/default/interface/select/currency.html' %}
{{ block('header') }}
{{ block('aside') }}
<main>
{% block main %}
{{ main|raw }}
{{ main|raw }}
{% endblock %}
</main>

View File

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

View File

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

View File

@@ -6,9 +6,7 @@
<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 %}
@@ -22,4 +20,11 @@
});
</script>
{% endif %}
<script>
import("/js/core.js").then(() => {
core.language = '{{ language.value }}';
core.currency = '{ name: "{{ currency.name }}", symbol: "{{ currency.symbol() }}" }'
});
</script>
{% endblock %}