dx4 v2
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
api.lock
|
||||||
|
_api.lock
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
$password = 'dengidengi';
|
||||||
|
$directory = '../';
|
||||||
|
$subdirectories = false;
|
||||||
|
// $theme = 'phpliteadmin.css';
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,282 @@
|
|||||||
|
<?php
|
||||||
|
include 'k-api.php';
|
||||||
|
|
||||||
|
|
||||||
|
const ADMIN_PASS = "superdengipassword4";
|
||||||
|
const SECRET_KEY = "dengisecretkey_monay_ivan_pletnev";
|
||||||
|
const API_PATH = "./";
|
||||||
|
|
||||||
|
function db()
|
||||||
|
{
|
||||||
|
$pdo = new PDO('sqlite:' . API_PATH . 'data.db');
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
return $pdo;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function relativePathToUrl($rel)
|
||||||
|
{
|
||||||
|
$parts = explode('/', dirname($_SERVER['SCRIPT_NAME']) . '/' . $rel);
|
||||||
|
$norm = [];
|
||||||
|
foreach ($parts as $p) {
|
||||||
|
if ($p === '..' && $norm) array_pop($norm);
|
||||||
|
elseif ($p && $p !== '.') $norm[] = $p;
|
||||||
|
}
|
||||||
|
$proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||||
|
return $proto . '://' . $_SERVER['HTTP_HOST'] . '/' . implode('/', $norm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function FastDBReq(PDO $db, string $sql, array $params = []): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$stmt = $db->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Login($req)
|
||||||
|
{
|
||||||
|
return isset($req->data['pass']) && $req->data['pass'] == ADMIN_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateApi(function ($api) {
|
||||||
|
$api->on('/', 'GET', function ($req) {
|
||||||
|
return 'hi';
|
||||||
|
});
|
||||||
|
$api->on('/phpliteadmin', 'GET', function ($req) {
|
||||||
|
header('Location: ./adm/phpliteadmin.php');
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
$api->on('/login', 'POST', function ($req) {
|
||||||
|
if (!isset($req->data['pass'])) {
|
||||||
|
$req->res->code(401);
|
||||||
|
return 'bad data';
|
||||||
|
}
|
||||||
|
if ($req->data['pass'] == ADMIN_PASS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$api->on('/class', 'POST', function ($req) {
|
||||||
|
if (!Login($req)) {
|
||||||
|
$req->res->code(401);
|
||||||
|
return ['ok' => "auth_fail"];
|
||||||
|
}
|
||||||
|
$db = db();
|
||||||
|
if (isset($req->data['classId'])) {
|
||||||
|
$classId = $req->data['classId'];
|
||||||
|
if (isset($req->data['action'])) {
|
||||||
|
switch ($req->data['action']) {
|
||||||
|
// actions
|
||||||
|
case "update_name":
|
||||||
|
try {
|
||||||
|
if (!isset($req->data['value'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = trim($req->data['value']);
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
FastDBReq($db, "UPDATE classes SET userclassname = ? WHERE classid=?", [$value, $classId]);
|
||||||
|
return true;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log($e);
|
||||||
|
return "егор";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'create_admin':
|
||||||
|
if (!isset($req->data['value'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = trim($req->data['value']);
|
||||||
|
if (strlen($value) == 0 || !str_contains($value, ';')) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = explode(';', $value);
|
||||||
|
if (sizeof($value) != 3) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return 'bad data';
|
||||||
|
}
|
||||||
|
$n_adminId = $value[0];
|
||||||
|
$n_adminName = base64_decode($value[1]);
|
||||||
|
$n_adminLevel = $value[2];
|
||||||
|
|
||||||
|
try {
|
||||||
|
FastDBReq($db, "INSERT INTO admins (adminId,adminName,adminClass,adminPrems) VALUES (?,?,?,?)", [$n_adminId, $n_adminName, $classId, $n_adminLevel]);
|
||||||
|
return true;
|
||||||
|
} catch (PDOException) {
|
||||||
|
$req->res->code(500);
|
||||||
|
return "ошибка";
|
||||||
|
}
|
||||||
|
case "create":
|
||||||
|
if (!isset($req->data['value'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$classname = trim($req->data['value']);
|
||||||
|
if (strlen($classname) == 0) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
FastDBReq($db, "INSERT INTO classes (classid, userclassname) VALUES (?,?)", [$classId, $classname]);
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case "DELETE":
|
||||||
|
// DANGER ZONE::
|
||||||
|
FastDBReq($db, "DELETE FROM classes WHERE classid=?", [$classId]);
|
||||||
|
FastDBReq($db, "DELETE FROM admins WHERE adminClass=?", [$classId]);
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$db = null;
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// display class page
|
||||||
|
|
||||||
|
$results = FastDBReq($db, "SELECT * FROM classes WHERE classId=?", [$classId]);
|
||||||
|
if (count($results) == 0) {
|
||||||
|
$db = null;
|
||||||
|
$req->res->code(400);
|
||||||
|
return "idi naxodi";
|
||||||
|
}
|
||||||
|
$classData = $results[0];
|
||||||
|
$adminsData = FastDBReq($db, "SELECT * FROM admins WHERE adminClass=?", [$classId]);
|
||||||
|
$db = null;
|
||||||
|
|
||||||
|
return ["ok" => 'da', "class" => $classData, "admins" => $adminsData];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// diplay all classes
|
||||||
|
$result = FastDBReq($db, "SELECT * FROM classes");
|
||||||
|
return ['ok' => 'da', "data" => $result];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$api->on('/admin', "POST", function ($req) {
|
||||||
|
if (!Login($req)) {
|
||||||
|
$req->res->code(401);
|
||||||
|
return "auth_fail";
|
||||||
|
}
|
||||||
|
$db = db();
|
||||||
|
if (isset($req->data['adminId'])) {
|
||||||
|
if (isset($req->data['action'])) {
|
||||||
|
switch ($req->data['action']) {
|
||||||
|
case "update_name":
|
||||||
|
if (!$req->hasData(['value', 'adminId'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = trim($req->data['value']);
|
||||||
|
$adminId = $req->data['adminId'];
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
FastDBReq($db, 'UPDATE admins SET adminName = ? WHERE adminId = ?', [$value, $adminId]);
|
||||||
|
} catch (PDOException) {
|
||||||
|
$db = null;
|
||||||
|
return "egor";
|
||||||
|
}
|
||||||
|
$db = null;
|
||||||
|
return true;
|
||||||
|
case "DELETE":
|
||||||
|
//scary
|
||||||
|
if (!isset($req->data['adminId'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$adminId = $req->data['adminId'];
|
||||||
|
FastDBReq($db, "DELETE FROM admins WHERE adminId=?", [$adminId]);
|
||||||
|
return true;
|
||||||
|
case "update_level":
|
||||||
|
if (!$req->hasData(['value', 'adminId'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = trim($req->data['value']);
|
||||||
|
$adminId = $req->data['adminId'];
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
FastDBReq($db, 'UPDATE admins SET adminPrems = ? WHERE adminId = ?', [$value, $adminId]);
|
||||||
|
} catch (PDOException) {
|
||||||
|
$db = null;
|
||||||
|
return "egor";
|
||||||
|
}
|
||||||
|
$db = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$api->on('/server', "POST", function ($req) {
|
||||||
|
if (!Login($req)) {
|
||||||
|
$req->res->code(401);
|
||||||
|
return "auth_fail";
|
||||||
|
}
|
||||||
|
if (isset($req->data['action'])) {
|
||||||
|
switch ($req->data['action']) {
|
||||||
|
case 'enable':
|
||||||
|
// unblock
|
||||||
|
if (file_exists(API_PATH . 'api.lock')) {
|
||||||
|
rename(API_PATH . 'api.lock', API_PATH . '_api.lock');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'disable':
|
||||||
|
// block
|
||||||
|
if (file_exists(API_PATH . 'api.lock')) return true;
|
||||||
|
if (file_exists(API_PATH . '_api.lock')) {
|
||||||
|
rename(API_PATH . '_api.lock', API_PATH . 'api.lock');
|
||||||
|
} else {
|
||||||
|
$f = fopen(API_PATH . 'api.lock', 'w');
|
||||||
|
fclose($f);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'update_block_message':
|
||||||
|
if (!isset($req->data['value'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file_exists(API_PATH . 'api.lock')) {
|
||||||
|
// write to existing
|
||||||
|
$f = fopen(API_PATH . 'api.lock', 'w');
|
||||||
|
} else {
|
||||||
|
$f = fopen(API_PATH . '_api.lock', 'w');
|
||||||
|
}
|
||||||
|
fwrite($f, $req->data['value']);
|
||||||
|
fclose($f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$capi = relativePathToUrl(API_PATH . 'api.php');
|
||||||
|
$status = !file_exists(API_PATH . 'api.lock');
|
||||||
|
$apiblockmsg = "";
|
||||||
|
if ($status) {
|
||||||
|
// server enabled (lock disabled)
|
||||||
|
if (file_exists(API_PATH . '_api.lock')) {
|
||||||
|
$apiblockmsg = file_get_contents(API_PATH . '_api.lock');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$apiblockmsg = file_get_contents(API_PATH . 'api.lock');
|
||||||
|
}
|
||||||
|
return ["CLIENT_API" => $capi, "STATUS" => $status, "BLOCKMSG" => $apiblockmsg];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
+232
-6
@@ -10,6 +10,7 @@ function db()
|
|||||||
|
|
||||||
function findClosestDateSort($array, $targetDate)
|
function findClosestDateSort($array, $targetDate)
|
||||||
{
|
{
|
||||||
|
if (!preg_match('/\d{1,2}-\d{1,2}-(\d{2}|\d{4})$/', $targetDate)) return;
|
||||||
$targetTimestamp = DateTime::createFromFormat('d-m-y', $targetDate)->getTimestamp();
|
$targetTimestamp = DateTime::createFromFormat('d-m-y', $targetDate)->getTimestamp();
|
||||||
usort($array, function ($a, $b) use ($targetTimestamp) {
|
usort($array, function ($a, $b) use ($targetTimestamp) {
|
||||||
$aTimestamp = DateTime::createFromFormat('d-m-y', $a['fromDate'])->getTimestamp();
|
$aTimestamp = DateTime::createFromFormat('d-m-y', $a['fromDate'])->getTimestamp();
|
||||||
@@ -60,16 +61,31 @@ function joinArraysByKey(array $primaryArray, array $secondaryArray, bool $doNot
|
|||||||
|
|
||||||
function FastDBReq(PDO $db, string $sql, array $params = []): array
|
function FastDBReq(PDO $db, string $sql, array $params = []): array
|
||||||
{
|
{
|
||||||
// ivan
|
try {
|
||||||
// pickme love
|
$stmt = $db->prepare($sql);
|
||||||
// <3 <3 <3 <3
|
$stmt->execute($params);
|
||||||
$stmt = $db->prepare($sql);
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
$stmt->execute($params);
|
} catch (PDOException $e) {
|
||||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CreateApi(function ($api) {
|
CreateApi(function ($api) {
|
||||||
|
|
||||||
|
if (file_exists('./api.lock')) {
|
||||||
|
http_response_code(500);
|
||||||
|
$f = fopen('./api.lock', 'r') or die("server eгor");
|
||||||
|
$fs = filesize('./api.lock');
|
||||||
|
if ($fs == 0) {
|
||||||
|
$cnt = "";
|
||||||
|
} else {
|
||||||
|
$cnt = fread($f, $fs);
|
||||||
|
}
|
||||||
|
fclose($f);
|
||||||
|
die($cnt);
|
||||||
|
}
|
||||||
|
|
||||||
$api->on('/', 'GET', function () {
|
$api->on('/', 'GET', function () {
|
||||||
return 'dengi';
|
return 'dengi';
|
||||||
});
|
});
|
||||||
@@ -125,6 +141,23 @@ CreateApi(function ($api) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
$api->on('/get_admin_level', 'POST', function ($req) {
|
||||||
|
if (!isset($req->data['adminId'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
$adminId = $req->data['adminId'];
|
||||||
|
$db = db();
|
||||||
|
$results = FastDBReq($db, "SELECT adminPrems FROM admins where adminid = ?", [$adminId]);
|
||||||
|
$db = null;
|
||||||
|
|
||||||
|
if (count($results) != 0) {
|
||||||
|
return $results[0];
|
||||||
|
} else {
|
||||||
|
$req->res->code(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
$api->on('/admin_login', 'POST', function ($req) {
|
$api->on('/admin_login', 'POST', function ($req) {
|
||||||
if (!isset($req->data['adminId'])) {
|
if (!isset($req->data['adminId'])) {
|
||||||
$req->res->code(400);
|
$req->res->code(400);
|
||||||
@@ -152,6 +185,7 @@ CreateApi(function ($api) {
|
|||||||
}
|
}
|
||||||
$classId = substr(strtoupper($req->data['classId']), 0, 10);
|
$classId = substr(strtoupper($req->data['classId']), 0, 10);
|
||||||
$day = $req->data['day'];
|
$day = $req->data['day'];
|
||||||
|
if (!preg_match('/\d{1,2}-\d{1,2}-(\d{2}|\d{4})$/', $day)) return "wrong date format";
|
||||||
|
|
||||||
$db = db();
|
$db = db();
|
||||||
$results = FastDBReq($db, "SELECT * FROM changes WHERE classId = ? AND forDay = ?", [$classId, $day]);
|
$results = FastDBReq($db, "SELECT * FROM changes WHERE classId = ? AND forDay = ?", [$classId, $day]);
|
||||||
@@ -362,9 +396,201 @@ CreateApi(function ($api) {
|
|||||||
FastDBReq($db, 'DELETE FROM rings WHERE classId=? AND fromDate=?', [$classId, $day]);
|
FastDBReq($db, 'DELETE FROM rings WHERE classId=? AND fromDate=?', [$classId, $day]);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
$db = null;
|
||||||
return "you saved your live because you didn't know what tabs are here";
|
return "you saved your live because you didn't know what tabs are here";
|
||||||
}
|
}
|
||||||
$db = null;
|
$db = null;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
$api->on('/update_admin', "POST", function ($req) {
|
||||||
|
if (!$req->hasData(['adminId', 'action'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$adminId = $req->data['adminId'];
|
||||||
|
$withSelf = true;
|
||||||
|
$action = $req->data['action'];
|
||||||
|
if (isset($req->data['person'])) {
|
||||||
|
$withSelf = false;
|
||||||
|
}
|
||||||
|
$adminId = $req->data['adminId'];
|
||||||
|
$db = db();
|
||||||
|
$results = FastDBReq($db, "SELECT * FROM admins WHERE adminId=?", [$adminId]);
|
||||||
|
if (count($results) == 0) {
|
||||||
|
$db = null;
|
||||||
|
$req->res->code(400);
|
||||||
|
return "админ не найден";
|
||||||
|
}
|
||||||
|
if (!$withSelf && !in_array($results[0]['adminPrems'], [-1, 2])) {
|
||||||
|
$db = null;
|
||||||
|
$req->res->code(400);
|
||||||
|
return 'нету прав';
|
||||||
|
}
|
||||||
|
if ($withSelf) {
|
||||||
|
switch ($action) {
|
||||||
|
case "change_name":
|
||||||
|
if (!isset($req->data['value'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = trim($req->data['value']);
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
FastDBReq($db, 'UPDATE admins SET adminName = ? WHERE adminId = ?', [$value, $adminId]);
|
||||||
|
} catch (PDOException) {
|
||||||
|
$db = null;
|
||||||
|
return "egor";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$person = $req->data['person'];
|
||||||
|
switch ($action) {
|
||||||
|
case "change_name":
|
||||||
|
if (!isset($req->data['value'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = trim($req->data['value']);
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
FastDBReq($db, 'UPDATE admins SET adminName = ? WHERE adminId = ?', [$value, $person]);
|
||||||
|
} catch (PDOException) {
|
||||||
|
$db = null;
|
||||||
|
return "egor";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$db = null;
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$db = null;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
$api->on('/classprofile_view', "POST", function ($req) {
|
||||||
|
if (!$req->hasData(['adminId'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$adminId = $req->data['adminId'];
|
||||||
|
$db = db();
|
||||||
|
$results = FastDBReq($db, "SELECT * FROM admins WHERE adminId=?", [$adminId]);
|
||||||
|
if (count($results) == 0) {
|
||||||
|
$db = null;
|
||||||
|
$req->res->code(400);
|
||||||
|
return "админ не найден";
|
||||||
|
}
|
||||||
|
if (!in_array($results[0]['adminPrems'], [-1, 2])) {
|
||||||
|
$db = null;
|
||||||
|
$req->res->code(400);
|
||||||
|
return "недостаточно прав";
|
||||||
|
}
|
||||||
|
|
||||||
|
$classId = $results[0]['adminClass'];
|
||||||
|
|
||||||
|
$results = FastDBReq($db, "SELECT * FROM classes WHERE classId=?", [$classId]);
|
||||||
|
if (count($results) == 0) {
|
||||||
|
$db = null;
|
||||||
|
$req->res->code(400);
|
||||||
|
return "класса не существует";
|
||||||
|
}
|
||||||
|
$classData = $results[0];
|
||||||
|
$adminsData = FastDBReq($db, "SELECT * FROM admins WHERE adminClass=?", [$classId]);
|
||||||
|
$db = null;
|
||||||
|
|
||||||
|
return ["status" => 1, "class" => $classData, "admins" => $adminsData];
|
||||||
|
});
|
||||||
|
|
||||||
|
$api->on('/classprofile_edit', "POST", function ($req) {
|
||||||
|
if (!$req->hasData(['adminId', 'action'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$adminId = $req->data['adminId'];
|
||||||
|
$db = db();
|
||||||
|
$results = FastDBReq($db, "SELECT * FROM admins WHERE adminId=?", [$adminId]);
|
||||||
|
if (count($results) == 0) {
|
||||||
|
$db = null;
|
||||||
|
$req->res->code(400);
|
||||||
|
return "админ не найден";
|
||||||
|
}
|
||||||
|
if (!in_array($results[0]['adminPrems'], [-1, 2])) {
|
||||||
|
$db = null;
|
||||||
|
$req->res->code(400);
|
||||||
|
return "недостаточно прав";
|
||||||
|
}
|
||||||
|
|
||||||
|
$classId = $results[0]['adminClass'];
|
||||||
|
|
||||||
|
$action = $req->data['action'];
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'update_name':
|
||||||
|
try {
|
||||||
|
if (!isset($req->data['value'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = trim($req->data['value']);
|
||||||
|
if (strlen($value) == 0) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
FastDBReq($db, "UPDATE classes SET userclassname = ? WHERE classid=?", [$value, $classId]);
|
||||||
|
return true;
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log($e);
|
||||||
|
return "егор";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'create_admin':
|
||||||
|
if (!isset($req->data['value'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = trim($req->data['value']);
|
||||||
|
if (strlen($value) == 0 || !str_contains($value, ';')) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$value = explode(';', $value);
|
||||||
|
if (sizeof($value) != 2) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return 'bad data';
|
||||||
|
}
|
||||||
|
$n_adminId = $value[0];
|
||||||
|
$n_adminName = base64_decode($value[1]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
FastDBReq($db, "INSERT INTO admins (adminId,adminName,adminClass,adminPrems) VALUES (?,?,?,?)", [$n_adminId, $n_adminName, $classId, 1]);
|
||||||
|
return true;
|
||||||
|
} catch (PDOException) {
|
||||||
|
$req->res->code(500);
|
||||||
|
return "ошибка";
|
||||||
|
}
|
||||||
|
case "delete_admin":
|
||||||
|
if (!isset($req->data['value'])) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
$person = trim($req->data['value']);
|
||||||
|
if (strlen($person) == 0) {
|
||||||
|
$req->res->code(400);
|
||||||
|
return "bad data";
|
||||||
|
}
|
||||||
|
FastDBReq($db, "DELETE FROM admins WHERE adminId=?", [$person]);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
$db = null;
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+31
-6
@@ -1,38 +1,54 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/** @param callable(_K_Api): void $apiFun */
|
/** @param callable(_K_Api): void $apiFun */
|
||||||
function CreateApi(callable $apiFun)
|
function CreateApi(callable $apiFun, bool $devmode = false)
|
||||||
{
|
{
|
||||||
header("Access-Control-Allow-Origin: *");
|
header("Access-Control-Allow-Origin: *");
|
||||||
header("Access-Control-Allow-Headers: Content-Type");
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||||
|
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(200);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$api = new _K_Api();
|
$api = new _K_Api();
|
||||||
|
$api->dev_mode = $devmode;
|
||||||
$apiFun($api);
|
$apiFun($api);
|
||||||
$api->end();
|
$api->end();
|
||||||
}
|
}
|
||||||
class _K_Api
|
class _K_Api
|
||||||
{
|
{
|
||||||
private $has_exec = false;
|
private $has_exec = false;
|
||||||
|
public $dev_mode = false;
|
||||||
/** @param callable(_K_ApiRequest): string|array|int|float|bool $fun */
|
/** @param callable(_K_ApiRequest): string|array|int|float|bool $fun */
|
||||||
public function on(string $route, string $method, callable $fun)
|
public function on(string $route, string $method, callable $fun)
|
||||||
{
|
{
|
||||||
$request_url = (count($_GET) > 0 ? array_keys($_GET)[0] : '');
|
$request_url = (count($_GET) > 0 ? array_keys($_GET)[0] : '');
|
||||||
$r = str_starts_with($route, '/') ? substr($route, 1) : $route;
|
$r = str_starts_with($route, '/') ? substr($route, 1) : $route;
|
||||||
|
if ($this->has_exec) return;
|
||||||
if ($r != $request_url) {
|
if ($r != $request_url) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
$this->has_exec = true;
|
$this->has_exec = true;
|
||||||
if ($_SERVER['REQUEST_METHOD'] != $method) {
|
|
||||||
|
$req_method = $_SERVER['REQUEST_METHOD'];
|
||||||
|
if ($req_method != $method) {
|
||||||
http_response_code(405);
|
http_response_code(405);
|
||||||
die("ERROR: unsupported method ($method available)");
|
$_a = "";
|
||||||
|
if ($this->dev_mode)
|
||||||
|
$_a = " - using $req_method, available $method";
|
||||||
|
die("ERROR: Method Not Allowed" . $_a);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [];
|
$data = [];
|
||||||
if ($method == 'GET' && count($_GET) > 1) {
|
if ($method == 'GET' && count($_GET) > 1) {
|
||||||
$data = array_slice(array_keys($_GET), 1);
|
$data = array_slice(array_keys($_GET), 1);
|
||||||
} else if ($method == 'POST' && count($_POST) > 0) {
|
} else if ($method == 'POST' && count($_POST) > 0) {
|
||||||
$data = $_POST;
|
$data = $_POST;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fun = $fun(new _K_ApiRequest($data));
|
$fun = $fun(new _K_ApiRequest($data));
|
||||||
if (is_string($fun) || is_numeric($fun)) {
|
if (is_string($fun) || is_numeric($fun)) {
|
||||||
if (json_validate($fun) && !is_numeric($fun)) {
|
if (json_validate($fun) && !is_numeric($fun)) {
|
||||||
@@ -43,8 +59,17 @@ class _K_Api
|
|||||||
Header('Content-Type: application/json');
|
Header('Content-Type: application/json');
|
||||||
echo json_encode($fun);
|
echo json_encode($fun);
|
||||||
} else {
|
} else {
|
||||||
die("ERROR: unknown return type");
|
http_response_code(500);
|
||||||
|
$_a = "Internal Server Error";
|
||||||
|
if ($this->dev_mode) {
|
||||||
|
$_type = gettype($fun);
|
||||||
|
$_a = "unknown return type ($_type), (available bool, array, numeric, string)";
|
||||||
|
}
|
||||||
|
die("ERROR: " . $_a);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +77,7 @@ class _K_Api
|
|||||||
{
|
{
|
||||||
if (!$this->has_exec) {
|
if (!$this->has_exec) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo '404';
|
echo 'ERROR: Not Found';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<?php phpinfo();
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<?php
|
||||||
|
echo 'test';
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 0,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "dx4",
|
"name": "dx4",
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
"@vitejs/plugin-react-swc": "^4.2.1",
|
"@vitejs/plugin-react-swc": "^4.2.1",
|
||||||
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
"eslint": "^9.33.0",
|
"eslint": "^9.33.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
@@ -76,7 +78,7 @@
|
|||||||
|
|
||||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||||
|
|
||||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||||
|
|
||||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||||
|
|
||||||
@@ -220,7 +222,7 @@
|
|||||||
|
|
||||||
"@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
|
"@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
|
||||||
|
|
||||||
"@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||||
|
|
||||||
"@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="],
|
"@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="],
|
||||||
|
|
||||||
@@ -494,6 +496,8 @@
|
|||||||
|
|
||||||
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="],
|
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="],
|
||||||
|
|
||||||
|
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
|
||||||
|
|
||||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="],
|
||||||
@@ -1212,15 +1216,19 @@
|
|||||||
|
|
||||||
"@apideck/better-ajv-errors/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
"@apideck/better-ajv-errors/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
|
||||||
|
|
||||||
"@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
"@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
|
"@babel/core/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
|
"@babel/generator/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
"@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
"@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
"@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
"@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
"@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
"@babel/helper-remap-async-to-generator/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
"@babel/helper-remap-async-to-generator/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
@@ -1228,11 +1236,11 @@
|
|||||||
|
|
||||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/helper-wrap-function/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
"@babel/helper-wrap-function/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
"@babel/helper-wrap-function/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
"@babel/helpers/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
|
"@babel/parser/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
@@ -1244,14 +1252,18 @@
|
|||||||
|
|
||||||
"@babel/plugin-transform-function-name/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
"@babel/plugin-transform-function-name/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-modules-systemjs/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
"@babel/plugin-transform-modules-systemjs/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
"@babel/plugin-transform-object-rest-spread/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
"@babel/preset-env/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
"@babel/preset-env/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||||
|
|
||||||
|
"@babel/preset-modules/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
|
"@babel/template/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
|
"@babel/traverse/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
||||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||||
@@ -1286,6 +1298,14 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"@types/babel__core/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
|
"@types/babel__generator/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
|
"@types/babel__template/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
|
"@types/babel__traverse/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
|
||||||
|
|
||||||
"@vitejs/plugin-react-swc/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.46", "", {}, "sha512-xMNwJo/pHkEP/mhNVnW+zUiJDle6/hxrwO0mfSJuEVRbBfgrJFuUSRoZx/nYUw5pCjrysl9OkNXCkAdih8GCnA=="],
|
"@vitejs/plugin-react-swc/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.46", "", {}, "sha512-xMNwJo/pHkEP/mhNVnW+zUiJDle6/hxrwO0mfSJuEVRbBfgrJFuUSRoZx/nYUw5pCjrysl9OkNXCkAdih8GCnA=="],
|
||||||
|
|
||||||
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
||||||
@@ -1322,90 +1342,84 @@
|
|||||||
|
|
||||||
"@apideck/better-ajv-errors/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
"@apideck/better-ajv-errors/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
||||||
"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
"@babel/core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
|
"@babel/generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
"@babel/helper-module-imports/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
"@babel/helper-optimise-call-expression/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/helper-wrap-function/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/helper-wrap-function/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/helper-wrap-function/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/helper-wrap-function/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/helper-wrap-function/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
"@babel/helpers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
|
"@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-classes/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-function-name/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/plugin-transform-function-name/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-function-name/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/plugin-transform-function-name/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-function-name/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
"@babel/preset-modules/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
|
"@babel/template/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
|
"@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
"@rollup/plugin-node-resolve/@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"@rollup/plugin-node-resolve/@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
|
"@types/babel__core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
|
"@types/babel__generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
|
"@types/babel__template/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
|
"@types/babel__traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||||
|
|
||||||
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
|
|
||||||
"compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
@@ -1427,23 +1441,5 @@
|
|||||||
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
|
|
||||||
"workbox-build/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
"workbox-build/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/helper-replace-supers/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-classes/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-function-name/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# save as Count-Word.ps1
|
||||||
|
param(
|
||||||
|
[string]$Root = ".",
|
||||||
|
[string]$Word = "dengi"
|
||||||
|
)
|
||||||
|
|
||||||
|
$pattern = "\b" + [regex]::Escape($Word) + "\b"
|
||||||
|
$count = 0
|
||||||
|
|
||||||
|
Get-ChildItem -Path $Root -Recurse -File -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.FullName -notmatch "\\dist(\\|$)" } |
|
||||||
|
ForEach-Object {
|
||||||
|
try {
|
||||||
|
# ïðî÷èòàòü òåêñòîâûé ôàéë; åñëè íå òåêñòîâûé — ïðîïóñòèòü
|
||||||
|
$text = Get-Content -LiteralPath $_.FullName -ErrorAction Stop -Raw -Encoding UTF8
|
||||||
|
} catch {
|
||||||
|
try { $text = Get-Content -LiteralPath $_.FullName -ErrorAction Stop -Raw -Encoding Default } catch { return }
|
||||||
|
}
|
||||||
|
$matches = [regex]::Matches($text, $pattern, "IgnoreCase")
|
||||||
|
$count += $matches.Count
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output $count
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# React + TypeScript + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
## React Compiler
|
||||||
|
|
||||||
|
The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information.
|
||||||
|
|
||||||
|
Note: This will impact Vite dev & build performances.
|
||||||
|
|
||||||
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
// Other configs...
|
||||||
|
|
||||||
|
// Remove tseslint.configs.recommended and replace with this
|
||||||
|
tseslint.configs.recommendedTypeChecked,
|
||||||
|
// Alternatively, use this for stricter rules
|
||||||
|
tseslint.configs.strictTypeChecked,
|
||||||
|
// Optionally, add this for stylistic rules
|
||||||
|
tseslint.configs.stylisticTypeChecked,
|
||||||
|
|
||||||
|
// Other configs...
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
// other options...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// eslint.config.js
|
||||||
|
import reactX from 'eslint-plugin-react-x'
|
||||||
|
import reactDom from 'eslint-plugin-react-dom'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
extends: [
|
||||||
|
// Other configs...
|
||||||
|
// Enable lint rules for React
|
||||||
|
reactX.configs['recommended-typescript'],
|
||||||
|
// Enable lint rules for React DOM
|
||||||
|
reactDom.configs.recommended,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
// other options...
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
```
|
||||||
@@ -0,0 +1,569 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "dx4dashboard",
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"jotai": "^2.16.1",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
|
"tailwindcss": "^4.1.18",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@types/react": "^19.2.5",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
|
"globals": "^16.5.0",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"typescript-eslint": "^8.46.4",
|
||||||
|
"vite": "^7.2.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||||
|
|
||||||
|
"@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||||
|
|
||||||
|
"@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
|
||||||
|
|
||||||
|
"@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||||
|
|
||||||
|
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||||
|
|
||||||
|
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
||||||
|
|
||||||
|
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
|
||||||
|
|
||||||
|
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||||
|
|
||||||
|
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||||
|
|
||||||
|
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||||
|
|
||||||
|
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
|
||||||
|
|
||||||
|
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||||
|
|
||||||
|
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
|
||||||
|
|
||||||
|
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||||
|
|
||||||
|
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||||
|
|
||||||
|
"@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||||
|
|
||||||
|
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="],
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
|
||||||
|
|
||||||
|
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||||
|
|
||||||
|
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||||
|
|
||||||
|
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
|
||||||
|
|
||||||
|
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||||
|
|
||||||
|
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||||
|
|
||||||
|
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
|
||||||
|
|
||||||
|
"@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="],
|
||||||
|
|
||||||
|
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||||
|
|
||||||
|
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||||
|
|
||||||
|
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||||
|
|
||||||
|
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
|
||||||
|
|
||||||
|
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||||
|
|
||||||
|
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||||
|
|
||||||
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
|
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||||
|
|
||||||
|
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||||
|
|
||||||
|
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.1", "", { "os": "none", "cpu": "arm64" }, "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg=="],
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
|
||||||
|
|
||||||
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
|
||||||
|
|
||||||
|
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||||
|
|
||||||
|
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||||
|
|
||||||
|
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
||||||
|
|
||||||
|
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||||
|
|
||||||
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
|
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@24.10.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
|
||||||
|
|
||||||
|
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.52.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/type-utils": "8.52.0", "@typescript-eslint/utils": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.52.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.52.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.52.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.52.0", "@typescript-eslint/types": "^8.52.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.52.0", "", { "dependencies": { "@typescript-eslint/types": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0" } }, "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.52.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.52.0", "", { "dependencies": { "@typescript-eslint/types": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0", "@typescript-eslint/utils": "8.52.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.52.0", "", {}, "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.52.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.52.0", "@typescript-eslint/tsconfig-utils": "8.52.0", "@typescript-eslint/types": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.52.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.52.0", "", { "dependencies": { "@typescript-eslint/types": "8.52.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ=="],
|
||||||
|
|
||||||
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.2", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.53", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ=="],
|
||||||
|
|
||||||
|
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||||
|
|
||||||
|
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||||
|
|
||||||
|
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||||
|
|
||||||
|
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
|
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||||
|
|
||||||
|
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
|
||||||
|
|
||||||
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="],
|
||||||
|
|
||||||
|
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||||
|
|
||||||
|
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
|
||||||
|
|
||||||
|
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||||
|
|
||||||
|
"caniuse-lite": ["caniuse-lite@1.0.30001762", "", {}, "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw=="],
|
||||||
|
|
||||||
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
|
|
||||||
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
|
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||||
|
|
||||||
|
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||||
|
|
||||||
|
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||||
|
|
||||||
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
|
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||||
|
|
||||||
|
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||||
|
|
||||||
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
|
"electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="],
|
||||||
|
|
||||||
|
"enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
|
||||||
|
|
||||||
|
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
|
||||||
|
|
||||||
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
|
|
||||||
|
"eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="],
|
||||||
|
|
||||||
|
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
|
||||||
|
|
||||||
|
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.26", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ=="],
|
||||||
|
|
||||||
|
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||||
|
|
||||||
|
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||||
|
|
||||||
|
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||||
|
|
||||||
|
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
||||||
|
|
||||||
|
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||||
|
|
||||||
|
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||||
|
|
||||||
|
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||||
|
|
||||||
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||||
|
|
||||||
|
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||||
|
|
||||||
|
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||||
|
|
||||||
|
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||||
|
|
||||||
|
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||||
|
|
||||||
|
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||||
|
|
||||||
|
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||||
|
|
||||||
|
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||||
|
|
||||||
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
|
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||||
|
|
||||||
|
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||||
|
|
||||||
|
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
|
||||||
|
|
||||||
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
|
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||||
|
|
||||||
|
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||||
|
|
||||||
|
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||||
|
|
||||||
|
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||||
|
|
||||||
|
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||||
|
|
||||||
|
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||||
|
|
||||||
|
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||||
|
|
||||||
|
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||||
|
|
||||||
|
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
|
||||||
|
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||||
|
|
||||||
|
"jotai": ["jotai@2.16.1", "", { "peerDependencies": { "@babel/core": ">=7.0.0", "@babel/template": ">=7.0.0", "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@babel/core", "@babel/template", "@types/react", "react"] }, "sha512-vrHcAbo3P7Br37C8Bv6JshMtlKMPqqmx0DDREtTjT4nf3QChDrYdbH+4ik/9V0cXA57dK28RkJ5dctYvavcIlg=="],
|
||||||
|
|
||||||
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
||||||
|
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||||
|
|
||||||
|
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
|
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||||
|
|
||||||
|
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||||
|
|
||||||
|
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||||
|
|
||||||
|
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||||
|
|
||||||
|
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||||
|
|
||||||
|
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||||
|
|
||||||
|
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||||
|
|
||||||
|
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
|
||||||
|
|
||||||
|
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
|
||||||
|
|
||||||
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
|
|
||||||
|
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||||
|
|
||||||
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
|
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||||
|
|
||||||
|
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||||
|
|
||||||
|
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||||
|
|
||||||
|
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||||
|
|
||||||
|
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||||
|
|
||||||
|
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||||
|
|
||||||
|
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||||
|
|
||||||
|
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||||
|
|
||||||
|
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||||
|
|
||||||
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
|
|
||||||
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
|
|
||||||
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||||
|
|
||||||
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
|
"react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
|
||||||
|
|
||||||
|
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
|
||||||
|
|
||||||
|
"react-icons": ["react-icons@5.5.0", "", { "peerDependencies": { "react": "*" } }, "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw=="],
|
||||||
|
|
||||||
|
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
||||||
|
|
||||||
|
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||||
|
|
||||||
|
"rollup": ["rollup@4.55.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.1", "@rollup/rollup-android-arm64": "4.55.1", "@rollup/rollup-darwin-arm64": "4.55.1", "@rollup/rollup-darwin-x64": "4.55.1", "@rollup/rollup-freebsd-arm64": "4.55.1", "@rollup/rollup-freebsd-x64": "4.55.1", "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", "@rollup/rollup-linux-arm-musleabihf": "4.55.1", "@rollup/rollup-linux-arm64-gnu": "4.55.1", "@rollup/rollup-linux-arm64-musl": "4.55.1", "@rollup/rollup-linux-loong64-gnu": "4.55.1", "@rollup/rollup-linux-loong64-musl": "4.55.1", "@rollup/rollup-linux-ppc64-gnu": "4.55.1", "@rollup/rollup-linux-ppc64-musl": "4.55.1", "@rollup/rollup-linux-riscv64-gnu": "4.55.1", "@rollup/rollup-linux-riscv64-musl": "4.55.1", "@rollup/rollup-linux-s390x-gnu": "4.55.1", "@rollup/rollup-linux-x64-gnu": "4.55.1", "@rollup/rollup-linux-x64-musl": "4.55.1", "@rollup/rollup-openbsd-x64": "4.55.1", "@rollup/rollup-openharmony-arm64": "4.55.1", "@rollup/rollup-win32-arm64-msvc": "4.55.1", "@rollup/rollup-win32-ia32-msvc": "4.55.1", "@rollup/rollup-win32-x64-gnu": "4.55.1", "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A=="],
|
||||||
|
|
||||||
|
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||||
|
|
||||||
|
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||||
|
|
||||||
|
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||||
|
|
||||||
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||||
|
|
||||||
|
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||||
|
|
||||||
|
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
|
||||||
|
|
||||||
|
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||||
|
|
||||||
|
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||||
|
|
||||||
|
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
|
||||||
|
|
||||||
|
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||||
|
|
||||||
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
|
"typescript-eslint": ["typescript-eslint@8.52.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.52.0", "@typescript-eslint/parser": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0", "@typescript-eslint/utils": "8.52.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
|
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||||
|
|
||||||
|
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||||
|
|
||||||
|
"vite": ["vite@7.3.0", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg=="],
|
||||||
|
|
||||||
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
|
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||||
|
|
||||||
|
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
|
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
|
"zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
|
||||||
|
|
||||||
|
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||||
|
|
||||||
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
||||||
|
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import js from "@eslint/js";
|
||||||
|
import globals from "globals";
|
||||||
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(["dist"]),
|
||||||
|
{
|
||||||
|
files: ["**/*.{ts,tsx}"],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"no-unused-vars": ["warn", { varsIgnorePattern: "^[A-Z_]|^motion$" }],
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"warn",
|
||||||
|
{ varsIgnorePattern: "^[A-Z_]|^motion$" },
|
||||||
|
],
|
||||||
|
"react-hooks/exhaustive-deps": ["off"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>dx4dashboard</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "dx4dashboard",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"jotai": "^2.16.1",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
|
"tailwindcss": "^4.1.18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@types/react": "^19.2.5",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
|
"eslint": "^9.39.1",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
|
"globals": "^16.5.0",
|
||||||
|
"typescript": "~5.9.3",
|
||||||
|
"typescript-eslint": "^8.46.4",
|
||||||
|
"vite": "^7.2.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,104 @@
|
|||||||
|
// CHANGE TO YOUR API DESTINATION
|
||||||
|
export const API_BASE_URL = "http://localhost:8321/admin_api.php";
|
||||||
|
// export const API_BASE_URL = "http://192.168.3.10:8321/admin_api.php";
|
||||||
|
// export const API_BASE_URL = "http://x90620bx.beget.tech/dx4back/admin_api.php"
|
||||||
|
|
||||||
|
// TYPE: url/dx4/
|
||||||
|
export const CLIENT_FRONTEND_URL = "http://localhost:5173/dx4/";
|
||||||
|
// export const CLIENT_FRONTEND_URL = "http://x90620bx.beget.tech/dx4/";
|
||||||
|
|
||||||
|
import { atom, useAtom } from "jotai";
|
||||||
|
import { atomWithStorage } from "jotai/utils";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import LoginPage from "./pages/LoginPage";
|
||||||
|
import DashPage from "./pages/DashPage";
|
||||||
|
import ClassProfilePage from "./pages/ClassProfile";
|
||||||
|
const passAtom = atomWithStorage("DX4DASH_pass", "");
|
||||||
|
|
||||||
|
// KATAMAZ DENGI ROUTER 4 PRO MAX ULTIMATE (from dx4, yeah, oh now in typescript)
|
||||||
|
// 💀💀
|
||||||
|
// PROS - vrode raboatat,
|
||||||
|
// too simple, lightweight, stupidly fast
|
||||||
|
// (beacause you basicly download entire site, lol)
|
||||||
|
// CONS - shit
|
||||||
|
function firstPathSegment(path: string) {
|
||||||
|
const m = path.match(/^\/?([^/]+)/);
|
||||||
|
return m ? m[1] : "";
|
||||||
|
}
|
||||||
|
const secondPathSegment = (p: string) =>
|
||||||
|
p.replace(/^\/?[^/]+/, "").replace(/^\/+/, "");
|
||||||
|
|
||||||
|
const routeAtom = atom("/");
|
||||||
|
const basePageProps = {
|
||||||
|
// atoms
|
||||||
|
passAtom,
|
||||||
|
routeAtom,
|
||||||
|
|
||||||
|
// fucntions
|
||||||
|
setsetRoute,
|
||||||
|
secondPathSegment,
|
||||||
|
|
||||||
|
// constants
|
||||||
|
API_BASE_URL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const pages = {
|
||||||
|
"/": <LoginPage {...basePageProps} />,
|
||||||
|
"/dash": <DashPage {...basePageProps} />,
|
||||||
|
"/class": <ClassProfilePage {...basePageProps} />,
|
||||||
|
};
|
||||||
|
|
||||||
|
let changeAnchor = true;
|
||||||
|
const defaultAnchor = document.location.hash.substring(1);
|
||||||
|
|
||||||
|
// lol 💀
|
||||||
|
function setsetRoute(setRoute: (s: string) => void, path: string) {
|
||||||
|
changeAnchor = true;
|
||||||
|
setRoute(path);
|
||||||
|
document.location.hash = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DX4() {
|
||||||
|
const [route, _setRoute] = useAtom(routeAtom);
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
document.location.hash &&
|
||||||
|
document.location.hash.substring(1) !== route
|
||||||
|
) {
|
||||||
|
_setRoute(document.location.hash.substring(1));
|
||||||
|
}
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
if (defaultAnchor == "" || defaultAnchor == "/") {
|
||||||
|
document.location.hash = defaultAnchor;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [route, _setRoute]);
|
||||||
|
window.addEventListener("hashchange", () => {
|
||||||
|
if (document.location.hash == "") {
|
||||||
|
document.location.hash = "/";
|
||||||
|
}
|
||||||
|
if (changeAnchor) {
|
||||||
|
changeAnchor = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_setRoute(document.location.hash.substring(1));
|
||||||
|
});
|
||||||
|
const page = Object.entries(pages).find(
|
||||||
|
([key]) => firstPathSegment(key) == firstPathSegment(route)
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
(page ? page[1] : false) || <ERROR404 /> || <div>some shit happened</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ERROR404() {
|
||||||
|
const route = useAtom(routeAtom)[0];
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center min-h-screen flex-col gap-10">
|
||||||
|
<div className="text-6xl font-black bg-linear-to-r from-red-400 to-pink-300 bg-clip-text text-transparent">
|
||||||
|
404((((
|
||||||
|
</div>
|
||||||
|
<div>{route}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_2026_10)">
|
||||||
|
<g filter="url(#filter0_d_2026_10)">
|
||||||
|
<mask id="mask0_2026_10" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="89" y="110" width="325" height="281">
|
||||||
|
<path d="M229.595 373.25L321.333 295.083L229.595 214.853V373.25Z" fill="white"/>
|
||||||
|
<path d="M321.333 295.083L229.595 373.25M321.333 295.083L229.595 214.853M321.333 295.083L402.292 214.853M321.333 295.083L402.292 378.833M229.595 373.25V214.853M229.595 373.25H104.393C101.851 373.25 100.461 370.288 102.085 368.333L229.595 214.853M229.595 214.853V122" stroke="white" stroke-width="23" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_2026_10)">
|
||||||
|
<rect width="350" height="287" transform="matrix(1 0 0 -1 76 395)" fill="url(#paint0_linear_2026_10)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_2026_10" x="34.365" y="55" width="434.927" height="390.833" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset/>
|
||||||
|
<feGaussianBlur stdDeviation="27.75"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.34 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2026_10"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2026_10" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear_2026_10" x1="-73.3091" y1="366.596" x2="487.371" y2="-46.6548" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="white"/>
|
||||||
|
<stop offset="0.528846" stop-color="#a19597ff"/>
|
||||||
|
<stop offset="1" stop-color="#6b2c3cff"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip0_2026_10">
|
||||||
|
<rect width="500" height="500" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,31 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
font-family: "Jost","Segoe UI", sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: dark;
|
||||||
|
color: #ffffffde;
|
||||||
|
background-color: #141414;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ensure selectable elements still selectable */
|
||||||
|
.selectable,
|
||||||
|
.selectable::after,
|
||||||
|
.selectable::before,
|
||||||
|
.selectable *,
|
||||||
|
.selectable *::before,
|
||||||
|
.selectable *::after,
|
||||||
|
input {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-grain {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='noise' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.45'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App.tsx'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
@@ -0,0 +1,556 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import type { ssroutetype } from "../types";
|
||||||
|
import { CoolBg, CopyClassLink, Header } from "./DashPage";
|
||||||
|
import {
|
||||||
|
AvailableAdminSymbols,
|
||||||
|
copyDXLink,
|
||||||
|
randomString,
|
||||||
|
secondPathSegment,
|
||||||
|
} from "../stilltrash/trash";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
DengiButton,
|
||||||
|
DengiEditable,
|
||||||
|
DengiYesOrNoDialog,
|
||||||
|
LoadingSpinner,
|
||||||
|
TextPreloader,
|
||||||
|
} from "../stilltrash/dengi_components";
|
||||||
|
import {
|
||||||
|
AdminCreateReq,
|
||||||
|
AdminRemoveReq,
|
||||||
|
AdminUpdateReq,
|
||||||
|
ClassReq,
|
||||||
|
ClassUpdateNameReq,
|
||||||
|
DELETEClassRequest,
|
||||||
|
} from "../stilltrash/req_mgr";
|
||||||
|
import {
|
||||||
|
BiCheckCircle,
|
||||||
|
BiLink,
|
||||||
|
BiPlus,
|
||||||
|
BiPlusCircle,
|
||||||
|
BiTrash,
|
||||||
|
BiX,
|
||||||
|
} from "react-icons/bi";
|
||||||
|
|
||||||
|
export default function ClassProfilePage({
|
||||||
|
passAtom,
|
||||||
|
routeAtom,
|
||||||
|
setsetRoute,
|
||||||
|
}: {
|
||||||
|
passAtom: any;
|
||||||
|
routeAtom: any;
|
||||||
|
setsetRoute: ssroutetype;
|
||||||
|
}) {
|
||||||
|
const [route, setRoute] = useAtom(routeAtom);
|
||||||
|
const [pass, _setPass] = useAtom(passAtom);
|
||||||
|
const classId = secondPathSegment(route as string);
|
||||||
|
if (classId == "") setRoute("/dash/");
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
|
||||||
|
<div className="w-screen h-screen flex flex-col sm:max-w-125 sm:max-h-[90vh] gap-3">
|
||||||
|
<Header {...{ setsetRoute, setRoute }} back />
|
||||||
|
<div className="bg-grain bg-zinc-800/50 p-5 items-center rounded-2xl not-md:w-[90%] flex flex-col gap-3">
|
||||||
|
<ClassProfileEditor
|
||||||
|
{...{ classId }}
|
||||||
|
pass={pass as string}
|
||||||
|
setRoute={(r) => setsetRoute(setRoute, r)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CoolBg />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ClassProfileEditor({
|
||||||
|
pass,
|
||||||
|
classId,
|
||||||
|
setRoute,
|
||||||
|
}: {
|
||||||
|
pass: string;
|
||||||
|
classId: string;
|
||||||
|
setRoute: (r: string) => void;
|
||||||
|
}) {
|
||||||
|
const [classData, setClassData] = useState<Record<string, string> | false>(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const [adminsData, setAdminsData] = useState<
|
||||||
|
Record<string, string>[] | false
|
||||||
|
>(false);
|
||||||
|
const [dataI, setDataI] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
ClassReq(pass, classId).then((i) => {
|
||||||
|
if (i[0]) {
|
||||||
|
setClassData(i[1]["class"]);
|
||||||
|
setAdminsData(i[1]["admins"]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [classId, dataI]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1 className="flex gap-3 text-2xl items-center">
|
||||||
|
управление классом <b>{classId}</b>{" "}
|
||||||
|
{classData ? <b>({classData.userclassname})</b> : <TextPreloader />}
|
||||||
|
</h1>
|
||||||
|
<table className="max-w-80">
|
||||||
|
<tbody className="*:*:min-w-20 *:*:h-10">
|
||||||
|
<tr>
|
||||||
|
<td className="">classId</td>
|
||||||
|
<td className="font-mono text-center select-all selectable">
|
||||||
|
{classId}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="">название</td>
|
||||||
|
<td className="w-40">
|
||||||
|
<DengiEditable
|
||||||
|
value={classData ? classData.userclassname : false}
|
||||||
|
onSubmit={async (v, idle) => {
|
||||||
|
idle(true);
|
||||||
|
const r = await ClassUpdateNameReq(
|
||||||
|
pass,
|
||||||
|
classId,
|
||||||
|
v as string
|
||||||
|
);
|
||||||
|
if (r[1] === true) {
|
||||||
|
setClassData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
userclassname: v as string,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
idle(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="w-50 mt-5 flex gap-2 items-center justify-center">
|
||||||
|
<CopyClassLink {...{ classId }} />
|
||||||
|
{classData && (
|
||||||
|
<RemoveClassButton
|
||||||
|
{...{ classId, pass, setRoute, className: classData.userclassname }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="h-px w-[80%] bg-zinc-700 block my-5 "></span>
|
||||||
|
<span className="text-xl font-bold block">админы</span>
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr className="*:px-2">
|
||||||
|
<th>имя</th>
|
||||||
|
<th>id</th>
|
||||||
|
<th>уров.</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{adminsData &&
|
||||||
|
adminsData.map((i) => (
|
||||||
|
<AdminProfileRow
|
||||||
|
key={i.adminId}
|
||||||
|
{...{
|
||||||
|
adminId: i.adminId,
|
||||||
|
pass: pass as string,
|
||||||
|
adminName: i.adminName,
|
||||||
|
adminLevel: i.adminPrems.toString(),
|
||||||
|
classId,
|
||||||
|
update: () => {
|
||||||
|
setDataI(dataI + 1);
|
||||||
|
},
|
||||||
|
setAdminFrId: (v: string) => {
|
||||||
|
setAdminsData(
|
||||||
|
(prev) =>
|
||||||
|
prev &&
|
||||||
|
prev.map((a: any) =>
|
||||||
|
a.adminId === i.adminId ? { ...a, adminName: v } : a
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{!adminsData && (
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span className="flex w-full justify-center">
|
||||||
|
<TextPreloader />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span className="flex w-full justify-center">
|
||||||
|
<TextPreloader />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span className="flex w-full justify-center">
|
||||||
|
<TextPreloader />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<LoadingSpinner size={20} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{adminsData && (
|
||||||
|
<CreateAdmin
|
||||||
|
{...{
|
||||||
|
update: () => {
|
||||||
|
setDataI(dataI + 1);
|
||||||
|
},
|
||||||
|
classId,
|
||||||
|
pass,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AdminProfileRow({
|
||||||
|
adminId,
|
||||||
|
pass,
|
||||||
|
adminName,
|
||||||
|
classId,
|
||||||
|
setAdminFrId,
|
||||||
|
adminLevel,
|
||||||
|
update,
|
||||||
|
}: {
|
||||||
|
adminId: string;
|
||||||
|
pass: string;
|
||||||
|
adminName: string;
|
||||||
|
classId: string;
|
||||||
|
setAdminFrId: (v: string) => void;
|
||||||
|
adminLevel: string;
|
||||||
|
update: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<tr className="h-12">
|
||||||
|
<td className="px-2">
|
||||||
|
<DengiEditable
|
||||||
|
value={adminName}
|
||||||
|
onSubmit={async (v, i) => {
|
||||||
|
i(true);
|
||||||
|
const r = await AdminUpdateReq(pass, adminId, "name", v as string);
|
||||||
|
if (r[1] === true) {
|
||||||
|
setAdminFrId(v as string);
|
||||||
|
}
|
||||||
|
i(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td className="px-2">
|
||||||
|
<DengiEditable value={adminId} password={true} disabled />
|
||||||
|
</td>
|
||||||
|
<td className="text-center">
|
||||||
|
<AdminLevelPicker
|
||||||
|
{...{
|
||||||
|
value: adminLevel,
|
||||||
|
pass,
|
||||||
|
adminId,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td className="inline-flex gap-1 items-center justify-center">
|
||||||
|
<CopyAdminLink {...{ classId, adminId }} />
|
||||||
|
<RemoveAdminButton {...{ adminId, pass, adminName, update }} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AdminLevelPicker = ({
|
||||||
|
adminId,
|
||||||
|
pass,
|
||||||
|
value,
|
||||||
|
}: {
|
||||||
|
adminId: string;
|
||||||
|
pass: string;
|
||||||
|
value: string;
|
||||||
|
}) => {
|
||||||
|
const [val, setVal] = useState(value);
|
||||||
|
const [idle, setIdle] = useState(false);
|
||||||
|
return (
|
||||||
|
<form className="flex items-center justify-center">
|
||||||
|
{!idle ? (
|
||||||
|
<select
|
||||||
|
className="bg-zinc-800 p-1.5 rounded-xl text-center"
|
||||||
|
value={val}
|
||||||
|
onChange={async (e) => {
|
||||||
|
setIdle(true);
|
||||||
|
const v = (e.target as HTMLSelectElement).value;
|
||||||
|
const r = await AdminUpdateReq(pass, adminId, "level", v);
|
||||||
|
if (r[1] === true) {
|
||||||
|
setVal(v);
|
||||||
|
}
|
||||||
|
setIdle(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="-1">-1</option>
|
||||||
|
<option value="1">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
<LoadingSpinner size={20} />
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const RemoveAdminButton = ({
|
||||||
|
adminId,
|
||||||
|
pass,
|
||||||
|
adminName,
|
||||||
|
update,
|
||||||
|
}: {
|
||||||
|
adminId: string;
|
||||||
|
pass: string;
|
||||||
|
adminName: string;
|
||||||
|
update: () => void;
|
||||||
|
}) => {
|
||||||
|
const [showDiag, setShowDiag] = useState(false);
|
||||||
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DengiYesOrNoDialog
|
||||||
|
show={showDiag}
|
||||||
|
content={{
|
||||||
|
//@ts-ignore
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
вы точно хотите удалить админа <b>{adminName}</b>?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
yesBtn: "да",
|
||||||
|
noBtn: "нет",
|
||||||
|
yesIcon: <BiTrash />,
|
||||||
|
noIcon: <BiX size={20} />,
|
||||||
|
bgDissmis: true,
|
||||||
|
yesBtnStyle: "bg-red-500! text-white! md:hover:bg-red-600!",
|
||||||
|
}}
|
||||||
|
onAnswer={async (answ: boolean) => {
|
||||||
|
setShowDiag(false);
|
||||||
|
if (answ) {
|
||||||
|
setDeleting(true);
|
||||||
|
const r = await AdminRemoveReq(pass, adminId);
|
||||||
|
if (r[1] === true) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DengiButton
|
||||||
|
onClick={() => {
|
||||||
|
if (deleting) return;
|
||||||
|
setShowDiag(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{deleting ? <LoadingSpinner size={20} /> : <BiTrash />}
|
||||||
|
</DengiButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const RemoveClassButton = ({
|
||||||
|
pass,
|
||||||
|
classId,
|
||||||
|
setRoute,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
pass: string;
|
||||||
|
classId: string;
|
||||||
|
setRoute: (r: string) => void;
|
||||||
|
className: string;
|
||||||
|
}) => {
|
||||||
|
const [showDiag, setShowDiag] = useState(false);
|
||||||
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DengiYesOrNoDialog
|
||||||
|
show={showDiag}
|
||||||
|
content={{
|
||||||
|
//@ts-ignore
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
вы точно хотите удалить класс{" "}
|
||||||
|
<b>
|
||||||
|
{classId} ({className})
|
||||||
|
</b>
|
||||||
|
?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
yesBtn: "да",
|
||||||
|
noBtn: "нет",
|
||||||
|
yesIcon: <BiTrash />,
|
||||||
|
noIcon: <BiX size={20} />,
|
||||||
|
bgDissmis: true,
|
||||||
|
yesBtnStyle: "bg-red-500! text-white! md:hover:bg-red-600!",
|
||||||
|
}}
|
||||||
|
onAnswer={async (answ: boolean) => {
|
||||||
|
setShowDiag(false);
|
||||||
|
if (answ) {
|
||||||
|
setDeleting(true);
|
||||||
|
const r = await DELETEClassRequest(pass, classId);
|
||||||
|
if (r[1] === true) {
|
||||||
|
setRoute("/dash/class");
|
||||||
|
}
|
||||||
|
setDeleting(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DengiButton
|
||||||
|
onClick={() => {
|
||||||
|
if (deleting) return;
|
||||||
|
setShowDiag(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{deleting ? <LoadingSpinner size={20} /> : <BiTrash />}
|
||||||
|
</DengiButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CopyAdminLink = ({
|
||||||
|
classId,
|
||||||
|
adminId,
|
||||||
|
}: {
|
||||||
|
classId: string;
|
||||||
|
adminId: string;
|
||||||
|
}) => {
|
||||||
|
const [linkCopied, setLinkCopied] = useState(false);
|
||||||
|
const copy = () => {
|
||||||
|
copyDXLink(classId, adminId).then((a) => {
|
||||||
|
if (a) {
|
||||||
|
setLinkCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLinkCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DengiButton onClick={() => copy()}>
|
||||||
|
{linkCopied ? <BiCheckCircle /> : <BiLink />}
|
||||||
|
</DengiButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function CreateAdmin({
|
||||||
|
pass,
|
||||||
|
classId,
|
||||||
|
update,
|
||||||
|
}: {
|
||||||
|
pass: string;
|
||||||
|
update: () => void;
|
||||||
|
classId: string;
|
||||||
|
}) {
|
||||||
|
const [creating, setCreating] = useState(false);
|
||||||
|
const [adminId, setAdminId] = useState("");
|
||||||
|
const [adminName, setAdminName] = useState("");
|
||||||
|
const [levelVal, setLevelVal] = useState("1");
|
||||||
|
const [locked, setLocked] = useState(false);
|
||||||
|
const form = useRef(null);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex w-full gap-1">
|
||||||
|
<span className="w-full" />
|
||||||
|
{creating && (
|
||||||
|
<form
|
||||||
|
className="flex gap-2 max-w-100"
|
||||||
|
ref={form}
|
||||||
|
onSubmit={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (locked || adminId.length < 5) return;
|
||||||
|
setLocked(true);
|
||||||
|
const c = await AdminCreateReq(
|
||||||
|
pass,
|
||||||
|
classId,
|
||||||
|
adminId,
|
||||||
|
adminName,
|
||||||
|
levelVal
|
||||||
|
);
|
||||||
|
if (c[1] == true) {
|
||||||
|
update();
|
||||||
|
setCreating(false);
|
||||||
|
}
|
||||||
|
setLocked(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2 max-w-35"
|
||||||
|
placeholder="имя"
|
||||||
|
required
|
||||||
|
value={adminName}
|
||||||
|
disabled={locked}
|
||||||
|
maxLength={50}
|
||||||
|
onInput={(e) =>
|
||||||
|
setAdminName((e.target as HTMLInputElement).value.trimStart())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2 max-w-35"
|
||||||
|
placeholder="adminId"
|
||||||
|
value={adminId}
|
||||||
|
required
|
||||||
|
disabled={locked}
|
||||||
|
maxLength={20}
|
||||||
|
minLength={5}
|
||||||
|
min={5}
|
||||||
|
onInput={(e) => {
|
||||||
|
let val = (e.target as HTMLInputElement).value;
|
||||||
|
let nval = "";
|
||||||
|
val.split("").forEach((i) => {
|
||||||
|
if (AvailableAdminSymbols.includes(i)) {
|
||||||
|
nval = nval + i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
(e.target as HTMLInputElement).value = nval;
|
||||||
|
setAdminId(nval);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
className="bg-zinc-800 p-1.5 rounded-xl text-center"
|
||||||
|
value={levelVal}
|
||||||
|
required
|
||||||
|
onChange={async (e) => {
|
||||||
|
const v = (e.target as HTMLSelectElement).value;
|
||||||
|
setLevelVal(v);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="-1">-1</option>
|
||||||
|
<option value="1">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
</select>
|
||||||
|
<button>
|
||||||
|
<DengiButton>
|
||||||
|
{locked ? <LoadingSpinner size={20} /> : <BiPlus />}
|
||||||
|
</DengiButton>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
<DengiButton
|
||||||
|
disabled={locked}
|
||||||
|
onClick={() => {
|
||||||
|
setCreating(!creating);
|
||||||
|
setAdminName("");
|
||||||
|
setAdminId(randomString(8, AvailableAdminSymbols));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className={`${creating ? "rotate-45" : ""} transition`}>
|
||||||
|
<BiPlusCircle />
|
||||||
|
</span>
|
||||||
|
</DengiButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,548 @@
|
|||||||
|
import { useAtom } from "jotai";
|
||||||
|
import {
|
||||||
|
BiCheckCircle,
|
||||||
|
BiEdit,
|
||||||
|
BiExit,
|
||||||
|
BiLeftArrow,
|
||||||
|
BiLink,
|
||||||
|
BiPlus,
|
||||||
|
BiPlusCircle,
|
||||||
|
} from "react-icons/bi";
|
||||||
|
import Logo from "../assets/logo.svg";
|
||||||
|
import type { ssroutetype } from "../types";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
AvailableSymbols,
|
||||||
|
copyDXLink,
|
||||||
|
randomString,
|
||||||
|
secondPathSegment,
|
||||||
|
} from "../stilltrash/trash";
|
||||||
|
import { API_BASE_URL, CLIENT_FRONTEND_URL } from "../App";
|
||||||
|
import {
|
||||||
|
DengiButton,
|
||||||
|
DengiEditable,
|
||||||
|
Link,
|
||||||
|
LoadingSpinner,
|
||||||
|
Switch,
|
||||||
|
TextPreloader,
|
||||||
|
} from "../stilltrash/dengi_components";
|
||||||
|
import {
|
||||||
|
ClassesReq,
|
||||||
|
CreateClassReq,
|
||||||
|
ServerBlockReq,
|
||||||
|
ServerMsgBlockReq,
|
||||||
|
ServerReq,
|
||||||
|
} from "../stilltrash/req_mgr";
|
||||||
|
|
||||||
|
export default function DashPage({
|
||||||
|
passAtom,
|
||||||
|
routeAtom,
|
||||||
|
setsetRoute,
|
||||||
|
}: {
|
||||||
|
passAtom: any;
|
||||||
|
routeAtom: any;
|
||||||
|
setsetRoute: ssroutetype;
|
||||||
|
}) {
|
||||||
|
const [route, setRoute] = useAtom(routeAtom);
|
||||||
|
const [pass, _setPass] = useAtom(passAtom);
|
||||||
|
|
||||||
|
const tabs: Record<string, string> = {
|
||||||
|
class: "классы",
|
||||||
|
admin: "админы",
|
||||||
|
server: "сервер",
|
||||||
|
};
|
||||||
|
|
||||||
|
let tab = secondPathSegment(route as string);
|
||||||
|
if (!tab || tab == "") tab = Object.keys(tabs)[0];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
|
||||||
|
<div className="w-screen h-screen flex items-center flex-col sm:max-w-125 sm:max-h-[90vh] gap-3">
|
||||||
|
<Header {...{ setsetRoute, setRoute }} />
|
||||||
|
<div className="bg-zinc-800/50 items-center justify-center rounded-2xl flex gap-2 w-full">
|
||||||
|
{Object.keys(tabs).map((i, idx) => (
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
"p-2 px-3 select-none hover:bg-zinc-200/50 rounded-2xl my-2 cursor-pointer border transition-colors " +
|
||||||
|
(tab == i ? "border-zinc-100/50" : "border-transparent")
|
||||||
|
}
|
||||||
|
key={idx}
|
||||||
|
onClick={() => setsetRoute(setRoute, "/dash/" + i)}
|
||||||
|
>
|
||||||
|
{tabs[i]}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* {tab} */}
|
||||||
|
<div className="bg-grain w-full bg-zinc-800/50 p-5 rounded-2xl not-md:w-[90%] flex flex-col gap-3">
|
||||||
|
{(() => {
|
||||||
|
switch (tab) {
|
||||||
|
case "class":
|
||||||
|
return (
|
||||||
|
<ClassesMainEditor
|
||||||
|
pass={pass as string}
|
||||||
|
setRoute={(p: string) => setsetRoute(setRoute, p)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "admin":
|
||||||
|
return <AdminsMainEditor pass={pass as string} />;
|
||||||
|
case "server":
|
||||||
|
return <ServerEditor pass={pass as string} />;
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CoolBg />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ClassesMainEditor({
|
||||||
|
pass,
|
||||||
|
setRoute,
|
||||||
|
}: {
|
||||||
|
pass: string;
|
||||||
|
setRoute: (s: string) => void;
|
||||||
|
}) {
|
||||||
|
const [classesData, setClassesData] = useState<
|
||||||
|
Record<string, string>[] | false
|
||||||
|
>(false);
|
||||||
|
const [updateId, setUpdateId] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
ClassesReq(pass).then((i) => {
|
||||||
|
if (i[1]["ok"] == "da") {
|
||||||
|
setClassesData(i[1]["data"]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [updateId]);
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col w-full">
|
||||||
|
{classesData ? (
|
||||||
|
<>
|
||||||
|
{classesData.map((i, idx) => (
|
||||||
|
<div
|
||||||
|
className="flex w-full items-center hover:bg-zinc-300/30 transition-colors rounded-xl py-3 px-2"
|
||||||
|
key={idx}
|
||||||
|
>
|
||||||
|
<span className="w-full block text-center">{i.classid}</span>
|
||||||
|
<span className="w-full block text-center">
|
||||||
|
{i.userclassname}
|
||||||
|
</span>
|
||||||
|
<span className="flex gap-2">
|
||||||
|
<CopyClassLink classId={i.classid} compact />
|
||||||
|
<DengiButton onClick={() => setRoute("/class/" + i.classid)}>
|
||||||
|
<BiEdit />
|
||||||
|
</DengiButton>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="mt-5">
|
||||||
|
<CreateClass pass={pass} update={() => setUpdateId(updateId + 1)} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<LoadingSpinner size={20} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export const CopyClassLink = ({
|
||||||
|
classId,
|
||||||
|
compact = false,
|
||||||
|
}: {
|
||||||
|
classId: string;
|
||||||
|
compact?: boolean;
|
||||||
|
}) => {
|
||||||
|
const [linkCopied, setLinkCopied] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DengiButton
|
||||||
|
onClick={() => {
|
||||||
|
copyDXLink(classId).then((a) => {
|
||||||
|
if (a) {
|
||||||
|
setLinkCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLinkCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{compact ? (
|
||||||
|
<>{linkCopied ? <BiCheckCircle /> : <BiLink />}</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<BiLink />
|
||||||
|
<span>{linkCopied ? "скопировано" : "коп ссылку"}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DengiButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
function AdminsMainEditor({ pass }: { pass: string }) {
|
||||||
|
return (
|
||||||
|
<div className="block">
|
||||||
|
<img src="https://od.lk/s/MTBfMzQ0MTQ0Njg5Xw/admin1.webp" alt="" className="w-30 h-auto" draggable={false} onContextMenu={e=>e.preventDefault()} />
|
||||||
|
{/* <img src="https://od.lk/s/MTBfMzQ0MTQ0NjkyXw/admin2.webp" alt="" className="w-30 h-auto" draggable={false} onContextMenu={e=>e.preventDefault()}/> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function ServerEditor({ pass }: { pass: string }) {
|
||||||
|
const [serverData, setServerData] = useState<Record<string, string> | false>(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const [idle, setIdle] = useState(false);
|
||||||
|
const [showL, setShowL] = useState(false);
|
||||||
|
const [blockChecked, setBlockChecked] = useState(false);
|
||||||
|
let update = 0;
|
||||||
|
useEffect(() => {
|
||||||
|
ServerReq(pass).then(async (i) => {
|
||||||
|
if (i[0]) {
|
||||||
|
setServerData(i[1]);
|
||||||
|
setBlockChecked(!i[1]["STATUS"] as boolean);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [update]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1 className="text-2xl py-5 block">настройки api сервера</h1>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th className="w-35">admin_api_path</th>
|
||||||
|
<td>
|
||||||
|
<Link className="underline" target="_blank">
|
||||||
|
{API_BASE_URL}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th className="w-35">client_api_path</th>
|
||||||
|
<td>
|
||||||
|
{serverData ? (
|
||||||
|
<Link className="underline" target="_blank">
|
||||||
|
{serverData["CLIENT_API"]}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<TextPreloader />
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th className="w-35">client_frontend</th>
|
||||||
|
<td>
|
||||||
|
<Link className="underline" target="_blank">
|
||||||
|
{CLIENT_FRONTEND_URL}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={API_BASE_URL + "?phpliteadmin"}
|
||||||
|
target="_blank"
|
||||||
|
className="flex p-2 w-50 items-center justify-center rounded hover:bg-zinc-700/50 transition-colors border border-zinc-500"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://bitbucket.org/phpliteadmin/public/raw/3fa9fedad19dc407a1786f97ba2b18a3c7b803bc/icon/phpLiteAdmin_small.png"
|
||||||
|
alt=""
|
||||||
|
width={32}
|
||||||
|
className="invert"
|
||||||
|
/>
|
||||||
|
open phpliteadmin
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-3 justify-center">
|
||||||
|
<h4>блокировка api</h4>
|
||||||
|
<div className="flex gap-2 items-center">
|
||||||
|
{serverData && (
|
||||||
|
<>
|
||||||
|
<DengiEditable
|
||||||
|
value={serverData ? serverData["BLOCKMSG"] : false}
|
||||||
|
disabled={idle}
|
||||||
|
placeholder="error block message"
|
||||||
|
onSubmit={async (v, s) => {
|
||||||
|
console.log(v);
|
||||||
|
s(true);
|
||||||
|
setIdle(true);
|
||||||
|
const r = await ServerMsgBlockReq(pass, v as string);
|
||||||
|
if (r[1] !== true) {
|
||||||
|
alert(r[1]);
|
||||||
|
} else {
|
||||||
|
setServerData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
BLOCKMSG: v as string,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
s(false);
|
||||||
|
setIdle(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Switch
|
||||||
|
disabled={idle}
|
||||||
|
checked={blockChecked}
|
||||||
|
onChange={async (e) => {
|
||||||
|
setBlockChecked(e);
|
||||||
|
setIdle(true);
|
||||||
|
setShowL(true);
|
||||||
|
const r = await ServerBlockReq(pass, e);
|
||||||
|
if (r[1] !== true) {
|
||||||
|
alert(r[1]);
|
||||||
|
setBlockChecked(!e);
|
||||||
|
}
|
||||||
|
setIdle(false);
|
||||||
|
setShowL(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{(showL || !serverData) && <LoadingSpinner size={20} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreateClass({ pass, update }: { pass: string; update: () => void }) {
|
||||||
|
const [creating, setCreating] = useState(false);
|
||||||
|
const [classId, setClassId] = useState("");
|
||||||
|
const [className, setClassName] = useState("");
|
||||||
|
const [locked, setLocked] = useState(false);
|
||||||
|
const form = useRef(null);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex w-full gap-1">
|
||||||
|
<span className="w-full" />
|
||||||
|
{creating && (
|
||||||
|
<form
|
||||||
|
className="flex gap-2 max-w-100"
|
||||||
|
ref={form}
|
||||||
|
onSubmit={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (locked || classId.length < 5) return;
|
||||||
|
setLocked(true);
|
||||||
|
const c = await CreateClassReq(pass, classId, className);
|
||||||
|
if (c[1] == true) {
|
||||||
|
update();
|
||||||
|
setCreating(false);
|
||||||
|
}
|
||||||
|
setLocked(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2 max-w-35"
|
||||||
|
placeholder="название"
|
||||||
|
required
|
||||||
|
value={className}
|
||||||
|
disabled={locked}
|
||||||
|
maxLength={50}
|
||||||
|
onInput={(e) =>
|
||||||
|
setClassName((e.target as HTMLInputElement).value.trimStart())
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2 max-w-35"
|
||||||
|
placeholder="classId"
|
||||||
|
value={classId}
|
||||||
|
required
|
||||||
|
disabled={locked}
|
||||||
|
maxLength={10}
|
||||||
|
minLength={5}
|
||||||
|
min={5}
|
||||||
|
onInput={(e) => {
|
||||||
|
let val = (e.target as HTMLInputElement).value.toUpperCase();
|
||||||
|
let nval = "";
|
||||||
|
val.split("").forEach((i) => {
|
||||||
|
if (AvailableSymbols.includes(i)) {
|
||||||
|
nval = nval + i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
(e.target as HTMLInputElement).value = nval;
|
||||||
|
setClassId(nval);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button>
|
||||||
|
<DengiButton>
|
||||||
|
{locked ? <LoadingSpinner size={20} /> : <BiPlus />}
|
||||||
|
</DengiButton>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
<DengiButton
|
||||||
|
disabled={locked}
|
||||||
|
onClick={() => {
|
||||||
|
setCreating(!creating);
|
||||||
|
setClassName("");
|
||||||
|
setClassId(randomString(8, AvailableSymbols).toUpperCase());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className={`${creating ? "rotate-45" : ""} transition`}>
|
||||||
|
<BiPlusCircle />
|
||||||
|
</span>
|
||||||
|
</DengiButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function AdminProfileEditor({
|
||||||
|
// adminFrId,
|
||||||
|
// setAdminFrId,
|
||||||
|
// adminId,
|
||||||
|
// classId,
|
||||||
|
// pickmeMode = false,
|
||||||
|
// }) {
|
||||||
|
// return (
|
||||||
|
// <div className=" bg-grain bg-zinc-800/50 p-5 rounded-2xl not-md:w-[90%]">
|
||||||
|
// <table className="">
|
||||||
|
// <tbody className="*:*:w-10 *:*:h-10">
|
||||||
|
// <tr>
|
||||||
|
// <td className="">имя</td>
|
||||||
|
// <td className="w-50!">
|
||||||
|
// <DengiEditable
|
||||||
|
// value={adminFrId}
|
||||||
|
// pink={pickmeMode}
|
||||||
|
// onSubmit={async (e) => {
|
||||||
|
// e.setIdle(true);
|
||||||
|
// const r = await AdminUpdateRequest(API_BASE_URL, adminId, {
|
||||||
|
// action: "change_name",
|
||||||
|
// value: e.value.trim(),
|
||||||
|
// });
|
||||||
|
// if (r[1] == true) setAdminFrId(e.value);
|
||||||
|
// e.setIdle(false);
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </td>
|
||||||
|
// </tr>
|
||||||
|
// <tr>
|
||||||
|
// <td>id</td>
|
||||||
|
// <td>
|
||||||
|
// <DengiEditable
|
||||||
|
// value={adminId}
|
||||||
|
// password={true}
|
||||||
|
// pink={pickmeMode}
|
||||||
|
// disabled
|
||||||
|
// />
|
||||||
|
// </td>
|
||||||
|
// </tr>
|
||||||
|
// <tr>
|
||||||
|
// <td>class</td>
|
||||||
|
// <td className="font-mono text-center select-all selectable">
|
||||||
|
// {classId ? classId : <TextPreloader />}
|
||||||
|
// </td>
|
||||||
|
// </tr>
|
||||||
|
// </tbody>
|
||||||
|
// </table>
|
||||||
|
// <CopyLink {...{ adminId, classId }} />
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const CopyLink = ({ classId, adminId }) => {
|
||||||
|
// const [showDiag, setShowDiag] = useState(false);
|
||||||
|
// const [seenDiag, setSeenDiag] = useState(false);
|
||||||
|
// const [linkCopied, setLinkCopied] = useState(false);
|
||||||
|
// const copy = () => {
|
||||||
|
// copyDXLink(classId, adminId).then((a) => {
|
||||||
|
// if (a) {
|
||||||
|
// setLinkCopied(true);
|
||||||
|
// setTimeout(() => {
|
||||||
|
// setLinkCopied(false);
|
||||||
|
// }, 2000);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
// return (
|
||||||
|
// <div className="flex justify-end mt-2">
|
||||||
|
// <DengiYesOrNoDialog
|
||||||
|
// show={showDiag}
|
||||||
|
// content={{
|
||||||
|
// message: (
|
||||||
|
// <p>
|
||||||
|
// <b>любой</b>, кто получит ссылку,{" "}
|
||||||
|
// <b>будет иметь доступ к админке</b>.<br /> вы желаете продолжить?
|
||||||
|
// </p>
|
||||||
|
// ),
|
||||||
|
// yesBtn: "да",
|
||||||
|
// noBtn: "нет",
|
||||||
|
// yesIcon: <BiCopy />,
|
||||||
|
// noIcon: <BiX size={20} />,
|
||||||
|
// bgDissmis: true,
|
||||||
|
// yesBtnStyle: "bg-orange-500! text-white! md:hover:bg-orange-600!",
|
||||||
|
// }}
|
||||||
|
// onAnswer={(answ) => {
|
||||||
|
// if (answ) {
|
||||||
|
// setSeenDiag(true);
|
||||||
|
// copy();
|
||||||
|
// }
|
||||||
|
// setShowDiag(false);
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// <DengiButton onClick={() => (seenDiag ? copy() : setShowDiag(true))}>
|
||||||
|
// <BiLink /> <BiError color={"orange"} />
|
||||||
|
// <span>{linkCopied ? "скопировано" : "коп ссылку на админку"}</span>
|
||||||
|
// </DengiButton>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const CoolBg = ({}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-linear-to-tl to-black to-50% bg-fixed
|
||||||
|
from-red-600/20`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Header = ({
|
||||||
|
setsetRoute,
|
||||||
|
setRoute,
|
||||||
|
back = false,
|
||||||
|
}: {
|
||||||
|
setsetRoute: ssroutetype;
|
||||||
|
setRoute: any;
|
||||||
|
back?: boolean;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={"flex items-center w-full justify-between px-5"}>
|
||||||
|
<span className="flex gap-2 items-center">
|
||||||
|
{back && (
|
||||||
|
<BiLeftArrow
|
||||||
|
size={35}
|
||||||
|
onClick={() => {
|
||||||
|
setsetRoute(setRoute, "/dash/");
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
"rounded-full " +
|
||||||
|
"lg:hover:bg-white lg:hover:fill-black" +
|
||||||
|
" lg:hover:scale-105 transition cursor-pointer"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<img src={Logo} alt="logo" width={100} draggable={false} />
|
||||||
|
</span>
|
||||||
|
<BiExit
|
||||||
|
size={35}
|
||||||
|
onClick={() => {
|
||||||
|
setsetRoute(setRoute, "/");
|
||||||
|
}}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
"rounded-full " +
|
||||||
|
"lg:hover:bg-white lg:hover:fill-black" +
|
||||||
|
" lg:hover:scale-105 transition cursor-pointer"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import Logo from "../assets/logo.svg";
|
||||||
|
|
||||||
|
import { BiHide, BiShow } from "react-icons/bi";
|
||||||
|
|
||||||
|
import { LoginReq } from "../stilltrash/req_mgr";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
import type { ssroutetype } from "../types";
|
||||||
|
export default function LoginPage({
|
||||||
|
passAtom,
|
||||||
|
routeAtom,
|
||||||
|
setsetRoute,
|
||||||
|
}: {
|
||||||
|
passAtom: any;
|
||||||
|
routeAtom: any;
|
||||||
|
setsetRoute: ssroutetype;
|
||||||
|
}) {
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
const [atomPass, setAtomPass] = useAtom(passAtom);
|
||||||
|
const [_route, setRoute] = useAtom(routeAtom);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [pass, setPass] = useState("");
|
||||||
|
return (
|
||||||
|
<div className="bg-linear-to-br from-red-500 to-zinc-800 w-screen min-h-screen flex items-center justify-center">
|
||||||
|
<div className="flex flex-col rounded-2xl w-[90vw] items-center justify-center py-3 max-w-150 focus-within:border-white/10 border border-transparent transition-colors relative bg-zinc-900/30 text-zinc-100">
|
||||||
|
{atomPass != "" && (
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<a
|
||||||
|
href="#/dash"
|
||||||
|
className="bg-linear-to-r from-neutral-900 to-zinc-900 p-2 rounded-xl not-disabled:cursor-pointer transition-colors duration-200 hover:from-white hover:to-zinc-400 not-disabled:hover:text-black"
|
||||||
|
>
|
||||||
|
go to dash
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={() => setAtomPass("")}
|
||||||
|
className="bg-linear-to-l from-neutral-900 to-zinc-900 p-2 rounded-xl not-disabled:cursor-pointer transition-colors duration-200 hover:from-white hover:to-zinc-400 not-disabled:hover:text-black"
|
||||||
|
>
|
||||||
|
logoff
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<img src={Logo} alt="logo dengi" width={200} />
|
||||||
|
<h1 className="text-zinc-200">dashboard</h1>
|
||||||
|
<h2 className="text-3xl font-light">вход</h2>
|
||||||
|
<form
|
||||||
|
action="#"
|
||||||
|
className="mt-2 flex flex-col gap-3 *:outline-none *:border *:border-transparent *:focus:border-white"
|
||||||
|
onSubmit={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
const r = await LoginReq(pass);
|
||||||
|
if (r[1]) {
|
||||||
|
setAtomPass(pass);
|
||||||
|
setsetRoute(setRoute, "/dash");
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="flex items-center">
|
||||||
|
<input
|
||||||
|
type={show ? "text" : "password"}
|
||||||
|
autoComplete="off"
|
||||||
|
className="bg-zinc-800/50 p-2 rounded-l-2xl w-full min-w-51.5"
|
||||||
|
placeholder="password"
|
||||||
|
minLength={3}
|
||||||
|
maxLength={20}
|
||||||
|
required
|
||||||
|
value={pass}
|
||||||
|
onInput={(e) => setPass((e.target as HTMLInputElement).value)}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={"p-2 rounded-r-2xl border-l border-l-zinc-500 transition-colors" + (show ? " bg-white" : " bg-zinc-800/50")}
|
||||||
|
onClick={() => {
|
||||||
|
setShow(!show);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!show ? (
|
||||||
|
<BiShow color="#fff" size={23} />
|
||||||
|
) : (
|
||||||
|
<BiHide color="#000" size={23} />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<CoolLoginButton disabled={loading} />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CoolLoginButton({ disabled }: { disabled: boolean }) {
|
||||||
|
const logbuttonRef = useRef(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
ref={logbuttonRef}
|
||||||
|
{...{ disabled }}
|
||||||
|
onMouseMove={(e) => {
|
||||||
|
const rect = (e.target as HTMLElement).getBoundingClientRect();
|
||||||
|
(e.target as HTMLElement).style.setProperty(
|
||||||
|
"--tw-gradient-via-position",
|
||||||
|
`${((e.clientX - rect.left) / rect.width) * 100}%`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="bg-linear-to-r from-neutral-900 to-zinc-900 p-2 rounded-xl not-disabled:cursor-pointer transition-colors duration-200 not-disabled:hover:via-white via-30% not-disabled:hover:text-black disabled:via-white disabled:via-0%! disabled:animate-pulse"
|
||||||
|
>
|
||||||
|
войти
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
import { useEffect, useState, type AnchorHTMLAttributes } from "react";
|
||||||
|
import { BiCheck, BiHide, BiShow, BiX } from "react-icons/bi";
|
||||||
|
|
||||||
|
export function Link(props: AnchorHTMLAttributes<HTMLAnchorElement>) {
|
||||||
|
let link = props.href;
|
||||||
|
if (!link) {
|
||||||
|
link = props.children as string;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<a {...props} href={link}>
|
||||||
|
{link}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Switch({
|
||||||
|
checked,
|
||||||
|
defaultChecked = false,
|
||||||
|
onChange,
|
||||||
|
disabled = false,
|
||||||
|
ariaLabel,
|
||||||
|
className = "",
|
||||||
|
size = "md", // "sm" | "md" | "lg"
|
||||||
|
}: {
|
||||||
|
checked?: boolean;
|
||||||
|
defaultChecked?: boolean;
|
||||||
|
onChange?: (newState: boolean) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
ariaLabel?: string;
|
||||||
|
className?: string;
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
}) {
|
||||||
|
const isControlled = checked !== undefined;
|
||||||
|
const [internalChecked, setInternalChecked] = useState(defaultChecked);
|
||||||
|
const current = isControlled ? checked : internalChecked;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isControlled) return;
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
|
setInternalChecked(checked);
|
||||||
|
}, [checked, isControlled]);
|
||||||
|
|
||||||
|
const sizes = {
|
||||||
|
sm: { track: "w-9 h-5", thumb: "w-3 h-3", translate: "translate-x-4" },
|
||||||
|
md: { track: "w-11 h-6", thumb: "w-4 h-4", translate: "translate-x-5" },
|
||||||
|
lg: { track: "w-14 h-8", thumb: "w-6 h-6", translate: "translate-x-6" },
|
||||||
|
};
|
||||||
|
const s = sizes[size] || sizes.md;
|
||||||
|
|
||||||
|
const toggle = (next?: boolean) => {
|
||||||
|
if (disabled) return;
|
||||||
|
const newState = typeof next === "boolean" ? next : !current;
|
||||||
|
if (!isControlled) setInternalChecked(newState);
|
||||||
|
onChange?.(newState);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="switch"
|
||||||
|
aria-checked={current}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
disabled={disabled}
|
||||||
|
tabIndex={disabled ? -1 : 0}
|
||||||
|
onClick={() => toggle()}
|
||||||
|
className={[
|
||||||
|
"inline-flex items-center p-0 focus:outline-none",
|
||||||
|
// focus-visible: shows ring only when focused by keyboard (better UX)
|
||||||
|
"focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-0.5 focus-visible:ring-offset-transparent",
|
||||||
|
disabled ? "opacity-60" : "cursor-pointer",
|
||||||
|
current ? "bg-zinc-500" : "bg-gray-300/10",
|
||||||
|
"rounded-full transition-colors duration-150",
|
||||||
|
s.track,
|
||||||
|
className,
|
||||||
|
].join(" ")}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={[
|
||||||
|
"relative block rounded-full bg-white shadow transform transition-transform ease-out duration-150",
|
||||||
|
s.thumb,
|
||||||
|
current ? s.translate : "",
|
||||||
|
size === "sm" ? "m-1" : "m-1",
|
||||||
|
].join(" ")}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LoadingSpinner({ size }: { size: number }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="animate-spin ease-out text-zinc-600 fill-white"
|
||||||
|
viewBox="0 0 100 101"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||||
|
fill="currentFill"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function DengiEditable({
|
||||||
|
value,
|
||||||
|
password = false,
|
||||||
|
disWHidden = true,
|
||||||
|
disabled = false,
|
||||||
|
showLoading = true,
|
||||||
|
minLenght = 1,
|
||||||
|
placeholder,
|
||||||
|
onSubmit = () => {},
|
||||||
|
onInput = () => {},
|
||||||
|
}: {
|
||||||
|
value?: string | false;
|
||||||
|
password?: boolean;
|
||||||
|
disWHidden?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
showLoading?: boolean;
|
||||||
|
minLenght?: number;
|
||||||
|
onSubmit?: (
|
||||||
|
value: string | false | undefined,
|
||||||
|
setIdle: (v: boolean) => void
|
||||||
|
) => void;
|
||||||
|
onInput?: (
|
||||||
|
value: string | false | undefined,
|
||||||
|
setIdle: (v: boolean) => void
|
||||||
|
) => void;
|
||||||
|
}) {
|
||||||
|
const [val, setVal] = useState(value);
|
||||||
|
const [hid, setHid] = useState(true);
|
||||||
|
const [idle, setIdle] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
setVal(value);
|
||||||
|
}, [value]);
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
action="#"
|
||||||
|
className="flex rounded-xl overflow-hidden max-w-50 h-8 relative"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
if (idle) return;
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit(val, setIdle);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value !== false || !showLoading ? (
|
||||||
|
<input
|
||||||
|
type={hid && password ? "password" : "text"}
|
||||||
|
value={val as any}
|
||||||
|
placeholder={placeholder}
|
||||||
|
minLength={minLenght}
|
||||||
|
disabled={(disWHidden && password && hid) || disabled || idle}
|
||||||
|
className={`bg-zinc-800 ${
|
||||||
|
val === value && !password ? "rounded-r-xl" : ""
|
||||||
|
} pl-2 rounded-l-xl w-full py-1 transition`}
|
||||||
|
onInput={(e) => {
|
||||||
|
const v = (e.target as HTMLInputElement).value;
|
||||||
|
setVal(v);
|
||||||
|
onInput(v, setIdle);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className={`w-full bg-zinc-800 py-1 flex items-center justify-center`}
|
||||||
|
>
|
||||||
|
<LoadingSpinner size={25} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{password && (
|
||||||
|
<button
|
||||||
|
className={`bg-zinc-600 hover:bg-zinc-700 cursor-pointer transition-colors ${
|
||||||
|
val === value ? "rounded-r-xl" : ""
|
||||||
|
} p-1 text-2xl `}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if ((disWHidden && password && hid) || disabled || idle) return;
|
||||||
|
setVal(value);
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setHid(!hid);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hid ? <BiShow /> : <BiHide />}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{val !== value && (
|
||||||
|
<button
|
||||||
|
disabled={idle}
|
||||||
|
className={`bg-zinc-600 disabled:bg-zinc-900 hover:bg-zinc-700
|
||||||
|
p-1 text-2xl rounded-r-xl transition-colors not-disabled:cursor-pointer`}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setVal(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{idle ? <LoadingSpinner size={24} /> : <BiCheck />}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
import trashcss from "./trash.module.css";
|
||||||
|
export function TextPreloader({ width = "5rem" }) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={"h-4 block bg-zinc-900 overflow-hidden rounded-full w-(--w)"}
|
||||||
|
style={{ "--w": width } as Record<string, string>}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
"block h-10 -translate-y-3 w-3 bg-zinc-300 relative blur-[7px] " +
|
||||||
|
trashcss.anim_preloader
|
||||||
|
}
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DengiButton({
|
||||||
|
onClick,
|
||||||
|
children,
|
||||||
|
disabled = false,
|
||||||
|
className = "",
|
||||||
|
}: {
|
||||||
|
onClick?: () => void;
|
||||||
|
children?: any;
|
||||||
|
disabled?: boolean;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
role="button"
|
||||||
|
className={
|
||||||
|
"flex bg-black items-center justify-center border p-1 gap-2 px-2 border-white " +
|
||||||
|
(disabled
|
||||||
|
? "cursor-not-allowed text-zinc-700 border-zinc-900 md:hover:border-red-950/50 "
|
||||||
|
: "not-disabled:cursor-pointer md:not-hover:border-zinc-700 md:not-hover:text-zinc-400 text-white ") +
|
||||||
|
"rounded-xl transition-colors h-10 min-w-10 not-md:p-2 " +
|
||||||
|
(className ? className : "")
|
||||||
|
}
|
||||||
|
aria-disabled={disabled}
|
||||||
|
onClick={disabled ? undefined : onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultDialogContent = {
|
||||||
|
message: "?",
|
||||||
|
yesBtn: "da",
|
||||||
|
noBtn: "net",
|
||||||
|
bgDissmis: true,
|
||||||
|
yesBlocked: false,
|
||||||
|
yesIcon: <BiCheck />,
|
||||||
|
noIcon: <BiX />,
|
||||||
|
noBtnStyle: "",
|
||||||
|
yesBtnStyle: "",
|
||||||
|
};
|
||||||
|
export function DengiYesOrNoDialog({
|
||||||
|
content = defaultDialogContent,
|
||||||
|
show = false,
|
||||||
|
//@ts-ignore
|
||||||
|
onAnswer,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{show && (
|
||||||
|
<div
|
||||||
|
className="fixed z-99 bg-black/50 backdrop-blur-xl top-0 left-0 w-full h-full overflow-hidden flex items-center justify-center"
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target === e.currentTarget && content.bgDissmis)
|
||||||
|
onAnswer(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="flex flex-col justify-center gap-4 p-4 border bg-black border-zinc-400 rounded-2xl"
|
||||||
|
>
|
||||||
|
<span>{content.message}</span>
|
||||||
|
<div className="flex gap-2 justify-stretch">
|
||||||
|
<DengiButton
|
||||||
|
className={content.yesBtnStyle + " w-full"}
|
||||||
|
onClick={() => onAnswer(true)}
|
||||||
|
disabled={content.yesBlocked}
|
||||||
|
>
|
||||||
|
{content.yesIcon}
|
||||||
|
{content.yesBtn}
|
||||||
|
</DengiButton>
|
||||||
|
<DengiButton
|
||||||
|
className={content.noBtnStyle + " w-full"}
|
||||||
|
onClick={() => onAnswer(false)}
|
||||||
|
>
|
||||||
|
{content.noIcon}
|
||||||
|
{content.noBtn}
|
||||||
|
</DengiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import { API_BASE_URL } from "../App";
|
||||||
|
import { b64e } from "./trash";
|
||||||
|
|
||||||
|
export async function makeRequest(
|
||||||
|
url: string,
|
||||||
|
params?:
|
||||||
|
| string
|
||||||
|
| Record<string, string>
|
||||||
|
| string[][]
|
||||||
|
| URLSearchParams
|
||||||
|
| undefined
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const req = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
|
body: new URLSearchParams(params).toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return req.ok &&
|
||||||
|
req.headers.get("Content-Type")?.includes("application/json")
|
||||||
|
? [1, await req.json()]
|
||||||
|
: [req.status, await req.text()];
|
||||||
|
} catch (err) {
|
||||||
|
return [-1, err];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LoginReq(pass: string) {
|
||||||
|
return makeRequest(API_BASE_URL + "?login", { pass });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ServerReq(pass: string) {
|
||||||
|
return makeRequest(API_BASE_URL + "?server", { pass });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ServerBlockReq(pass: string, block: boolean) {
|
||||||
|
return makeRequest(API_BASE_URL + "?server", {
|
||||||
|
pass,
|
||||||
|
action: block ? "disable" : "enable",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function ServerMsgBlockReq(pass: string, value: string) {
|
||||||
|
return makeRequest(API_BASE_URL + "?server", {
|
||||||
|
pass,
|
||||||
|
action: "update_block_message",
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClassesReq(pass: string) {
|
||||||
|
return makeRequest(API_BASE_URL + "?class", { pass });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClassReq(pass: string, classId: string) {
|
||||||
|
return makeRequest(API_BASE_URL + "?class", { pass, classId });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClassUpdateNameReq(
|
||||||
|
pass: string,
|
||||||
|
classId: string,
|
||||||
|
value: string
|
||||||
|
) {
|
||||||
|
return makeRequest(API_BASE_URL + "?class", {
|
||||||
|
pass,
|
||||||
|
classId,
|
||||||
|
action: "update_name",
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CreateClassReq(
|
||||||
|
pass: string,
|
||||||
|
classId: string,
|
||||||
|
className: string
|
||||||
|
) {
|
||||||
|
return makeRequest(API_BASE_URL + "?class", {
|
||||||
|
pass,
|
||||||
|
classId,
|
||||||
|
action: "create",
|
||||||
|
value: className,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DELETEClassRequest(pass: string, classId: string) {
|
||||||
|
return makeRequest(API_BASE_URL + "?class", {
|
||||||
|
pass,
|
||||||
|
classId,
|
||||||
|
action: "DELETE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminUpdateReq(
|
||||||
|
pass: string,
|
||||||
|
adminId: string,
|
||||||
|
action: "level" | "name",
|
||||||
|
value: string
|
||||||
|
) {
|
||||||
|
return makeRequest(API_BASE_URL + "?admin", {
|
||||||
|
pass,
|
||||||
|
adminId,
|
||||||
|
action: "update_" + action,
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminRemoveReq(pass: string, adminId: string) {
|
||||||
|
return makeRequest(API_BASE_URL + "?admin", {
|
||||||
|
pass,
|
||||||
|
adminId,
|
||||||
|
action: "DELETE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminCreateReq(
|
||||||
|
pass: string,
|
||||||
|
classId: string,
|
||||||
|
adminId: string,
|
||||||
|
adminName: string,
|
||||||
|
adminPrems: string
|
||||||
|
) {
|
||||||
|
return makeRequest(API_BASE_URL + "?class", {
|
||||||
|
pass,
|
||||||
|
classId,
|
||||||
|
action: "create_admin",
|
||||||
|
value: `${adminId};${b64e(adminName)};${adminPrems}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
.anim_preloader {
|
||||||
|
animation: anim-preloader 1s infinite linear;
|
||||||
|
}
|
||||||
|
@keyframes anim-preloader {
|
||||||
|
from {
|
||||||
|
transform: translateX(-50px) rotate(20deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(calc((var(--w, 50px)) + (50px))) rotate(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { CLIENT_FRONTEND_URL } from "../App";
|
||||||
|
|
||||||
|
export const secondPathSegment = (p: string) =>
|
||||||
|
p.replace(/^\/?[^/]+/, "").replace(/^\/+/, "");
|
||||||
|
|
||||||
|
export function b64e(str: string) {
|
||||||
|
return btoa(
|
||||||
|
encodeURIComponent(str).replace(
|
||||||
|
/%([0-9A-F]{2})/g,
|
||||||
|
function toSolidBytes(_match, p1) {
|
||||||
|
// @ts-ignore
|
||||||
|
return String.fromCharCode("0x" + p1);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function b64d(str: string) {
|
||||||
|
return decodeURIComponent(
|
||||||
|
atob(str)
|
||||||
|
.split("")
|
||||||
|
.map(function (c) {
|
||||||
|
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
||||||
|
})
|
||||||
|
.join("")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** by claude
|
||||||
|
* @param {string} text - The text to copy to clipboard
|
||||||
|
* @returns {Promise<boolean>} - Returns true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
export async function copyToClipboard(text: string) {
|
||||||
|
// Method 1: Modern Clipboard API (preferred)
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Clipboard API failed, trying fallback:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Method 2: Fallback for older browsers or non-secure contexts
|
||||||
|
try {
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.value = text;
|
||||||
|
textarea.style.position = "fixed";
|
||||||
|
textarea.style.left = "-999999px";
|
||||||
|
textarea.style.top = "-999999px";
|
||||||
|
textarea.style.opacity = "0";
|
||||||
|
textarea.setAttribute("readonly", "");
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
textarea.setSelectionRange(0, text.length);
|
||||||
|
const success = document.execCommand("copy");
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
return success;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("All copy methods failed:", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dx4 specific
|
||||||
|
export async function copyDXLink(
|
||||||
|
classId: string,
|
||||||
|
adminId: string | undefined = undefined
|
||||||
|
) {
|
||||||
|
return copyToClipboard(
|
||||||
|
CLIENT_FRONTEND_URL + "#//" + classId + (adminId ? ";" + b64e(adminId) : "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const randomString = (length: number, symbols: string[]) => {
|
||||||
|
const len = symbols.length;
|
||||||
|
return Array.from(
|
||||||
|
{ length },
|
||||||
|
() => symbols[Math.floor(Math.random() * len)]
|
||||||
|
).join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const AvailableSymbols = (() => {
|
||||||
|
const a = "abcdefghijklmnopqrstuvwxyz"; // alphabet
|
||||||
|
const A = a.toUpperCase(); // ALPHABET
|
||||||
|
const n = "1234567890"; // numbers
|
||||||
|
const s = "_-"; // specials
|
||||||
|
return (a + n + A + s).split("");
|
||||||
|
})();
|
||||||
|
|
||||||
|
export const AvailableAdminSymbols = [...AvailableSymbols, ..."$()*^%@!+=.".split("")];
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export type ssroutetype = (setRoute: (e: string) => void, r: string) => void;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "ES2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
react({
|
||||||
|
babel: {
|
||||||
|
plugins: [["babel-plugin-react-compiler"]],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
tailwindcss(),
|
||||||
|
],
|
||||||
|
base: "/dx4dashboard",
|
||||||
|
});
|
||||||
+15
-15
@@ -23,6 +23,21 @@
|
|||||||
content="/icons/windows11/Square150x150Logo.scale-100.png"
|
content="/icons/windows11/Square150x150Logo.scale-100.png"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- Google tag (gtag.js) -->
|
||||||
|
<script
|
||||||
|
async
|
||||||
|
src="https://www.googletagmanager.com/gtag/js?id=G-8ND46F82BL"
|
||||||
|
></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag() {
|
||||||
|
dataLayer.push(arguments);
|
||||||
|
}
|
||||||
|
gtag("js", new Date());
|
||||||
|
|
||||||
|
gtag("config", "G-8ND46F82BL");
|
||||||
|
</script>
|
||||||
|
|
||||||
<title>dx 4</title>
|
<title>dx 4</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -64,15 +79,6 @@
|
|||||||
div[data-ktkz-loading] img {
|
div[data-ktkz-loading] img {
|
||||||
animation: ktkz-preloader-pulse 3s var(--animate-ease) infinite;
|
animation: ktkz-preloader-pulse 3s var(--animate-ease) infinite;
|
||||||
}
|
}
|
||||||
div[data-ktkz-loading] p {
|
|
||||||
font-family: "Segoe UI", sans-serif;
|
|
||||||
color: #eef;
|
|
||||||
opacity: 0;
|
|
||||||
animation: ktkz-preloader-show 2s linear 2s forwards;
|
|
||||||
}
|
|
||||||
div[data-ktkz-loading] p a {
|
|
||||||
color: #aaf;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes ktkz-preloader-spin {
|
@keyframes ktkz-preloader-spin {
|
||||||
to {
|
to {
|
||||||
@@ -104,12 +110,6 @@
|
|||||||
<div class="cont">
|
<div class="cont">
|
||||||
<img src="./src/assets/logo.svg" width="150" alt="" />
|
<img src="./src/assets/logo.svg" width="150" alt="" />
|
||||||
<div class="spinner" aria-hidden="true"></div>
|
<div class="spinner" aria-hidden="true"></div>
|
||||||
<p>
|
|
||||||
если не работает попробуйте
|
|
||||||
<a href="#" onclick="event.preventDefault();location.reload()"
|
|
||||||
>перезагрузить</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
"@vitejs/plugin-react-swc": "^4.2.1",
|
"@vitejs/plugin-react-swc": "^4.2.1",
|
||||||
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
"eslint": "^9.33.0",
|
"eslint": "^9.33.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
|
|||||||
+6
-2
@@ -1,7 +1,7 @@
|
|||||||
// CHANGE TO YOUR API DESTINATION
|
// CHANGE TO YOUR API DESTINATION
|
||||||
|
export const API_BASE_URL = "http://localhost:8321/api.php";
|
||||||
// export const API_BASE_URL = "http://192.168.3.10:8321/api.php";
|
// export const API_BASE_URL = "http://192.168.3.10:8321/api.php";
|
||||||
// export const API_BASE_URL = "http://localhost:8321/api.php"
|
// export const API_BASE_URL = "http://x90620bx.beget.tech/dx4back/api.php"
|
||||||
export const API_BASE_URL = "http://x90620bx.beget.tech/dx4back/dengidengi.php"
|
|
||||||
|
|
||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { atomWithStorage } from "jotai/utils";
|
import { atomWithStorage } from "jotai/utils";
|
||||||
@@ -19,6 +19,8 @@ const pickmeModeAtom = atomWithStorage("pickmeMode", false);
|
|||||||
|
|
||||||
const EditorPage = lazy(() => import("./pages/editor"));
|
const EditorPage = lazy(() => import("./pages/editor"));
|
||||||
const TestPage = lazy(() => import("./pages/test"));
|
const TestPage = lazy(() => import("./pages/test"));
|
||||||
|
const AdminProfilePage = lazy(() => import("./pages/AdminProfile"));
|
||||||
|
const ClassProfilePage = lazy(() => import("./pages/ClassProfile"));
|
||||||
|
|
||||||
const dengi_cash = new DengiCaching();
|
const dengi_cash = new DengiCaching();
|
||||||
|
|
||||||
@@ -61,6 +63,8 @@ const pages = {
|
|||||||
"/exit": <ExitPage {...basePageProps} />,
|
"/exit": <ExitPage {...basePageProps} />,
|
||||||
"/editor": <EditorPage {...basePageProps} />,
|
"/editor": <EditorPage {...basePageProps} />,
|
||||||
"/test": <TestPage />,
|
"/test": <TestPage />,
|
||||||
|
"/adminprofile": <AdminProfilePage {...basePageProps} />,
|
||||||
|
"/classprofile": <ClassProfilePage {...basePageProps} />,
|
||||||
};
|
};
|
||||||
|
|
||||||
let changeAnchor = true;
|
let changeAnchor = true;
|
||||||
|
|||||||
@@ -33,3 +33,7 @@ input {
|
|||||||
.bg-grain {
|
.bg-grain {
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='noise' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.45'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='noise' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.45'/%3E%3C/svg%3E");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body > *:not(#root):not(vite-error-overlay) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { motion } from "motion/react";
|
||||||
|
import { BiCopy, BiError, BiLeftArrowAlt, BiLink, BiX } from "react-icons/bi";
|
||||||
|
import Logo from "../assets/logo.svg";
|
||||||
|
import { AdminReq, AdminUpdateRequest } from "../trash/req_mgr";
|
||||||
|
import { API_BASE_URL } from "../App";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
DengiButton,
|
||||||
|
DengiEditable,
|
||||||
|
DengiYesOrNoDialog,
|
||||||
|
TextPreloader,
|
||||||
|
} from "../trash/trash-components";
|
||||||
|
import { copyDXLink } from "../trash/helpers";
|
||||||
|
|
||||||
|
export default function AdminProfilePage({
|
||||||
|
adminAtom,
|
||||||
|
setsetRoute,
|
||||||
|
routeAtom,
|
||||||
|
pickmeModeAtom,
|
||||||
|
dengi_cash,
|
||||||
|
classIDAtom,
|
||||||
|
}) {
|
||||||
|
const [pickmeMode, _] = useAtom(pickmeModeAtom);
|
||||||
|
const [_route, setRoute] = useAtom(routeAtom);
|
||||||
|
const [adminId, _setAdminId] = useAtom(adminAtom);
|
||||||
|
const [classId, _setClassId] = useAtom(classIDAtom);
|
||||||
|
const [adminFrId, setAdminFrId] = useState(false);
|
||||||
|
const [adminData, setAdminData] = useState(false);
|
||||||
|
if (classId === "") {
|
||||||
|
setsetRoute(setRoute, "/");
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const a = (await AdminReq(API_BASE_URL, adminId)) || false;
|
||||||
|
if (!cancelled && a[0] === 1) {
|
||||||
|
setAdminFrId(a[1].adminName);
|
||||||
|
setAdminData({ class: a[1].adminClass, adminPerms: a[1].adminPerms });
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [adminId]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<motion.div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
|
||||||
|
<motion.div
|
||||||
|
className="w-screen h-screen flex flex-col itemsц-center sm:max-w-[500px] sm:max-h-[90vh] gap-3"
|
||||||
|
initial={{ scale: 0.99, opacity: 0, y: 5 }}
|
||||||
|
animate={{ scale: 1, opacity: 1, y: 0 }}
|
||||||
|
>
|
||||||
|
<Header {...{ setsetRoute, setRoute, pickmeMode }} />
|
||||||
|
<span className="text-xl font-bold">профиль админа</span>
|
||||||
|
<AdminProfileEditor
|
||||||
|
{...{
|
||||||
|
adminId,
|
||||||
|
classId: adminData.class ? adminData.class : false,
|
||||||
|
adminFrId,
|
||||||
|
pickmeMode,
|
||||||
|
setAdminFrId: (v) => {
|
||||||
|
setAdminFrId(v);
|
||||||
|
dengi_cash.delete(`adminfr-${adminId}`);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
<CoolBg {...{ pickmeMode }} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AdminProfileEditor({
|
||||||
|
adminFrId,
|
||||||
|
setAdminFrId,
|
||||||
|
adminId,
|
||||||
|
classId,
|
||||||
|
pickmeMode = false,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className=" bg-grain bg-zinc-800/50 p-5 rounded-2xl not-md:w-[90%]">
|
||||||
|
<table className="">
|
||||||
|
<tbody className="*:*:w-10 *:*:h-10">
|
||||||
|
<tr>
|
||||||
|
<td className="">имя</td>
|
||||||
|
<td className="w-50!">
|
||||||
|
<DengiEditable
|
||||||
|
value={adminFrId}
|
||||||
|
pink={pickmeMode}
|
||||||
|
onSubmit={async (e) => {
|
||||||
|
e.setIdle(true);
|
||||||
|
const r = await AdminUpdateRequest(API_BASE_URL, adminId, {
|
||||||
|
action: "change_name",
|
||||||
|
value: e.value.trim(),
|
||||||
|
});
|
||||||
|
if (r[1] == true) setAdminFrId(e.value);
|
||||||
|
e.setIdle(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>id</td>
|
||||||
|
<td>
|
||||||
|
<DengiEditable
|
||||||
|
value={adminId}
|
||||||
|
password={true}
|
||||||
|
pink={pickmeMode}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>class</td>
|
||||||
|
<td className="font-mono text-center select-all selectable">
|
||||||
|
{classId ? classId : <TextPreloader />}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<CopyLink {...{ adminId, classId }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CopyLink = ({ classId, adminId }) => {
|
||||||
|
const [showDiag, setShowDiag] = useState(false);
|
||||||
|
const [seenDiag, setSeenDiag] = useState(false);
|
||||||
|
const [linkCopied, setLinkCopied] = useState(false);
|
||||||
|
const copy = () => {
|
||||||
|
copyDXLink(classId, adminId).then((a) => {
|
||||||
|
if (a) {
|
||||||
|
setLinkCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLinkCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="flex justify-end mt-2">
|
||||||
|
<DengiYesOrNoDialog
|
||||||
|
show={showDiag}
|
||||||
|
content={{
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
<b>любой</b>, кто получит ссылку,{" "}
|
||||||
|
<b>будет иметь доступ к админке</b>.<br /> вы желаете продолжить?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
yesBtn: "да",
|
||||||
|
noBtn: "нет",
|
||||||
|
yesIcon: <BiCopy />,
|
||||||
|
noIcon: <BiX size={20} />,
|
||||||
|
bgDissmis: true,
|
||||||
|
yesBtnStyle: "bg-orange-500! text-white! md:hover:bg-orange-600!",
|
||||||
|
}}
|
||||||
|
onAnswer={(answ) => {
|
||||||
|
if (answ) {
|
||||||
|
setSeenDiag(true);
|
||||||
|
copy();
|
||||||
|
}
|
||||||
|
setShowDiag(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DengiButton onClick={() => (seenDiag ? copy() : setShowDiag(true))}>
|
||||||
|
<BiLink /> <BiError color={"orange"} />
|
||||||
|
<span>{linkCopied ? "скопировано" : "коп ссылку на админку"}</span>
|
||||||
|
</DengiButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CoolBg = ({ pickmeMode }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-gradient-to-tl to-black to-50% bg-fixed
|
||||||
|
${pickmeMode ? "from-pink-400/10" : "from-white/2"}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = ({ setsetRoute, setRoute, pickmeMode }) => {
|
||||||
|
return (
|
||||||
|
<div className={"flex items-center w-full justify-between px-5"}>
|
||||||
|
<span className="flex gap-2 items-center">
|
||||||
|
<BiLeftArrowAlt
|
||||||
|
size={35}
|
||||||
|
onClick={() => {
|
||||||
|
setsetRoute(setRoute, "/app");
|
||||||
|
}}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
"rounded-full " +
|
||||||
|
(pickmeMode
|
||||||
|
? "lg:hover:bg-pink-400"
|
||||||
|
: "lg:hover:bg-white lg:hover:fill-black") +
|
||||||
|
" lg:hover:scale-105 transition cursor-pointer"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<img src={Logo} alt="logo" width={100} draggable={false} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,521 @@
|
|||||||
|
import { useAtom } from "jotai";
|
||||||
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
|
import {
|
||||||
|
BiCheck,
|
||||||
|
BiCheckCircle,
|
||||||
|
BiCopy,
|
||||||
|
BiLeftArrowAlt,
|
||||||
|
BiLink,
|
||||||
|
BiPlus,
|
||||||
|
BiPlusCircle,
|
||||||
|
BiTrash,
|
||||||
|
BiX,
|
||||||
|
BiXCircle,
|
||||||
|
} from "react-icons/bi";
|
||||||
|
import Logo from "../assets/logo.svg";
|
||||||
|
import {
|
||||||
|
AdminReq,
|
||||||
|
AdminUpdateRequest,
|
||||||
|
ClassUpdateRequest,
|
||||||
|
getClassProfile,
|
||||||
|
} from "../trash/req_mgr";
|
||||||
|
import { API_BASE_URL } from "../App";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
DengiButton,
|
||||||
|
DengiEditable,
|
||||||
|
DengiYesOrNoDialog,
|
||||||
|
LoadingSpinner,
|
||||||
|
TextPreloader,
|
||||||
|
} from "../trash/trash-components";
|
||||||
|
import { b64e, copyDXLink, randomString } from "../trash/helpers";
|
||||||
|
import { AvailableAdminSymbols } from "../trash/dengi";
|
||||||
|
|
||||||
|
export default function AdminProfilePage({
|
||||||
|
classIDAtom,
|
||||||
|
adminAtom,
|
||||||
|
setsetRoute,
|
||||||
|
routeAtom,
|
||||||
|
pickmeModeAtom,
|
||||||
|
}) {
|
||||||
|
const [pickmeMode, _] = useAtom(pickmeModeAtom);
|
||||||
|
const [_route, setRoute] = useAtom(routeAtom);
|
||||||
|
const [adminId, _setAdminId] = useAtom(adminAtom);
|
||||||
|
const [classId, _setClassId] = useAtom(classIDAtom);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [classData, setClassData] = useState(false);
|
||||||
|
const [adminsData, setAdminsData] = useState(false);
|
||||||
|
const [dataId, setDataId] = useState(0);
|
||||||
|
if (classId === "") {
|
||||||
|
setsetRoute(setRoute, "/");
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const a = (await getClassProfile(API_BASE_URL, adminId)) || false;
|
||||||
|
if (!cancelled && a[0] === 1) {
|
||||||
|
if (a[1]["class"]) {
|
||||||
|
setClassData(a[1]["class"]);
|
||||||
|
setAdminsData(a[1]["admins"]);
|
||||||
|
} else {
|
||||||
|
setError(a[1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError(a[1]);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [adminId, dataId]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<motion.div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
|
||||||
|
<motion.div
|
||||||
|
className="w-screen h-screen flex flex-col items-center sm:max-w-[500px] sm:max-h-[90vh] gap-3"
|
||||||
|
initial={{ scale: 0.99, opacity: 0, y: 5 }}
|
||||||
|
animate={{ scale: 1, opacity: 1, y: 0 }}
|
||||||
|
>
|
||||||
|
<Header {...{ setsetRoute, setRoute, pickmeMode }} />
|
||||||
|
<span className="text-xl font-bold text-center">
|
||||||
|
управление классом
|
||||||
|
</span>
|
||||||
|
{error && typeof error == "string" && (
|
||||||
|
<span className="text-red-500 text-2xl">{error}</span>
|
||||||
|
)}
|
||||||
|
<ClassProfileEditor
|
||||||
|
{...{
|
||||||
|
classId,
|
||||||
|
loaded: classData !== false,
|
||||||
|
classFr: classData.userclassname,
|
||||||
|
pickmeMode,
|
||||||
|
adminId,
|
||||||
|
adminsData,
|
||||||
|
update: () => {
|
||||||
|
setDataId(dataId + 1);
|
||||||
|
},
|
||||||
|
setAdminsData,
|
||||||
|
setClassFr: (v) => {
|
||||||
|
setClassData((prev) => ({ ...prev, userclassname: v }));
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
<CoolBg {...{ pickmeMode }} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClassProfileEditor({
|
||||||
|
classId,
|
||||||
|
classFr,
|
||||||
|
pickmeMode,
|
||||||
|
loaded,
|
||||||
|
adminId,
|
||||||
|
adminsData,
|
||||||
|
setClassFr,
|
||||||
|
update,
|
||||||
|
setAdminsData,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center bg-grain gap-2 bg-zinc-800/50 p-5 rounded-2xl not-md:w-[90%] relative md:min-w-[520px]">
|
||||||
|
<table className="">
|
||||||
|
<tbody className="*:*:min-w-20 *:*:h-10">
|
||||||
|
<tr>
|
||||||
|
<td className="">classId</td>
|
||||||
|
<td className="font-mono text-center select-all selectable">
|
||||||
|
{classId}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="">название</td>
|
||||||
|
<td className="w-40">
|
||||||
|
<DengiEditable
|
||||||
|
value={loaded ? classFr : false}
|
||||||
|
pink={pickmeMode}
|
||||||
|
onSubmit={async (e) => {
|
||||||
|
e.setIdle(true);
|
||||||
|
let r = await ClassUpdateRequest(API_BASE_URL, adminId, {
|
||||||
|
action: "update_name",
|
||||||
|
value: e.value.trim(),
|
||||||
|
});
|
||||||
|
if (r[1] == true) setClassFr(e.value.trim());
|
||||||
|
e.setIdle(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="w-50 mt-5">
|
||||||
|
<CopyClassLink {...{ classId }} />
|
||||||
|
</div>
|
||||||
|
<span className="h-[1px] w-[80%] bg-zinc-700 block my-5 "></span>
|
||||||
|
<span className="text-xl font-bold block">админы</span>
|
||||||
|
<table className="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr className="*:px-2">
|
||||||
|
<th>имя</th>
|
||||||
|
<th>id</th>
|
||||||
|
<th>уров.</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{adminsData &&
|
||||||
|
adminsData.map((i) => (
|
||||||
|
<AdminProfileRow
|
||||||
|
key={i.adminId}
|
||||||
|
{...{
|
||||||
|
adminId: i.adminId,
|
||||||
|
editorAdminId: adminId,
|
||||||
|
adminName: i.adminName,
|
||||||
|
adminLevel: i.adminPrems,
|
||||||
|
classId,
|
||||||
|
update,
|
||||||
|
pickmeMode,
|
||||||
|
setAdminFrId: (v) => {
|
||||||
|
setAdminsData((prev) =>
|
||||||
|
prev.map((a) =>
|
||||||
|
a.adminId === i.adminId ? { ...a, adminName: v } : a
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{!loaded && (
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span className="flex w-full justify-center">
|
||||||
|
<TextPreloader />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span className="flex w-full justify-center">
|
||||||
|
<TextPreloader />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span className="flex w-full justify-center">
|
||||||
|
<TextPreloader />
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<LoadingSpinner size={20} />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{loaded && <CreateAdmin {...{ editorAdminId: adminId, update }} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreateAdmin({ editorAdminId, update }) {
|
||||||
|
const [creating, setCreating] = useState(false);
|
||||||
|
const [adminId, setAdminId] = useState("");
|
||||||
|
const [adminName, setAdminName] = useState("");
|
||||||
|
const [locked, setLocked] = useState(false);
|
||||||
|
const form = useRef(null);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex w-full gap-1">
|
||||||
|
<span className="w-full" />
|
||||||
|
<AnimatePresence>
|
||||||
|
{creating && (
|
||||||
|
<motion.form
|
||||||
|
className="flex gap-2"
|
||||||
|
variants={{
|
||||||
|
off: { opacity: 0, x: 20 },
|
||||||
|
on: { opacity: 1, x: 0 },
|
||||||
|
}}
|
||||||
|
initial="off"
|
||||||
|
animate="on"
|
||||||
|
exit="off"
|
||||||
|
ref={form}
|
||||||
|
onSubmit={async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (locked || adminId.length < 5) return;
|
||||||
|
setLocked(true);
|
||||||
|
const c = await ClassUpdateRequest(
|
||||||
|
API_BASE_URL,
|
||||||
|
editorAdminId,
|
||||||
|
{
|
||||||
|
action: "create_admin",
|
||||||
|
value: `${adminId};${b64e(adminName)}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (c[1] == true) {
|
||||||
|
update();
|
||||||
|
setCreating(false);
|
||||||
|
}
|
||||||
|
setLocked(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2"
|
||||||
|
placeholder="имя"
|
||||||
|
required
|
||||||
|
value={adminName}
|
||||||
|
disabled={locked}
|
||||||
|
maxLength={50}
|
||||||
|
onInput={(e) => setAdminName(e.target.value.trimStart())}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2"
|
||||||
|
placeholder="adminId"
|
||||||
|
value={adminId}
|
||||||
|
required
|
||||||
|
disabled={locked}
|
||||||
|
maxLength={20}
|
||||||
|
minLength={5}
|
||||||
|
min={5}
|
||||||
|
onInput={(e) => {
|
||||||
|
let val = e.target.value;
|
||||||
|
let nval = "";
|
||||||
|
val.split("").forEach((i) => {
|
||||||
|
if (AvailableAdminSymbols.includes(i)) {
|
||||||
|
nval = nval + i;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
e.target.value = nval;
|
||||||
|
setAdminId(nval);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button>
|
||||||
|
<DengiButton>
|
||||||
|
{locked ? <LoadingSpinner /> : <BiPlus />}
|
||||||
|
</DengiButton>
|
||||||
|
</button>
|
||||||
|
</motion.form>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
<DengiButton
|
||||||
|
disabled={locked}
|
||||||
|
onClick={() => {
|
||||||
|
setCreating(!creating);
|
||||||
|
setAdminName("");
|
||||||
|
setAdminId(randomString(8, AvailableAdminSymbols));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className={`${creating ? "rotate-45" : ""} transition`}>
|
||||||
|
<BiPlusCircle />
|
||||||
|
</span>
|
||||||
|
</DengiButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function AdminProfileRow({
|
||||||
|
adminId,
|
||||||
|
editorAdminId,
|
||||||
|
adminName,
|
||||||
|
classId,
|
||||||
|
pickmeMode,
|
||||||
|
setAdminFrId,
|
||||||
|
adminLevel,
|
||||||
|
update,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<tr className="h-12">
|
||||||
|
<td className="px-2">
|
||||||
|
<DengiEditable
|
||||||
|
value={adminName}
|
||||||
|
pink={pickmeMode}
|
||||||
|
onSubmit={async (e) => {
|
||||||
|
e.setIdle(true);
|
||||||
|
const r = await AdminUpdateRequest(API_BASE_URL, editorAdminId, {
|
||||||
|
action: "change_name",
|
||||||
|
value: e.value.trim(),
|
||||||
|
person: adminId,
|
||||||
|
});
|
||||||
|
if (r[1] == true) setAdminFrId(e.value);
|
||||||
|
e.setIdle(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td className="px-2">
|
||||||
|
<DengiEditable
|
||||||
|
value={adminId}
|
||||||
|
password={true}
|
||||||
|
pink={pickmeMode}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td className="text-center">{adminLevel}</td>
|
||||||
|
<td className="inline-flex gap-1 items-center justify-center">
|
||||||
|
<CopyAdminLink {...{ classId, adminId }} />
|
||||||
|
<RemoveAdminButton
|
||||||
|
{...{ adminId, editorAdminId, adminName, update }}
|
||||||
|
disabled={[-1, 2].includes(adminLevel)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RemoveAdminButton = ({
|
||||||
|
adminId,
|
||||||
|
editorAdminId,
|
||||||
|
adminName,
|
||||||
|
update,
|
||||||
|
disabled,
|
||||||
|
}) => {
|
||||||
|
const [showDiag, setShowDiag] = useState(false);
|
||||||
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DengiYesOrNoDialog
|
||||||
|
show={showDiag}
|
||||||
|
content={{
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
вы точно хотите удалить админа <b>{adminName}</b>?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
yesBtn: "да",
|
||||||
|
noBtn: "нет",
|
||||||
|
yesIcon: <BiTrash />,
|
||||||
|
noIcon: <BiX size={20} />,
|
||||||
|
bgDissmis: true,
|
||||||
|
yesBtnStyle: "bg-red-500! text-white! md:hover:bg-red-600!",
|
||||||
|
}}
|
||||||
|
onAnswer={async (answ) => {
|
||||||
|
setShowDiag(false);
|
||||||
|
if (answ) {
|
||||||
|
setDeleting(true);
|
||||||
|
const c = await ClassUpdateRequest(API_BASE_URL, editorAdminId, {
|
||||||
|
action: "delete_admin",
|
||||||
|
value: adminId,
|
||||||
|
});
|
||||||
|
if (c[1] == true) {
|
||||||
|
update();
|
||||||
|
setDeleting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DengiButton
|
||||||
|
disabled={adminId === editorAdminId || disabled}
|
||||||
|
onClick={() => {
|
||||||
|
if (deleting) return;
|
||||||
|
setShowDiag(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{deleting ? <LoadingSpinner size={20} /> : <BiTrash />}
|
||||||
|
</DengiButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CopyClassLink = ({ classId }) => {
|
||||||
|
const [linkCopied, setLinkCopied] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DengiButton
|
||||||
|
onClick={() => {
|
||||||
|
copyDXLink(classId).then((a) => {
|
||||||
|
if (a) {
|
||||||
|
setLinkCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLinkCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BiLink />
|
||||||
|
<span>{linkCopied ? "скопировано" : "коп ссылку"}</span>
|
||||||
|
</DengiButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const CopyAdminLink = ({ classId, adminId }) => {
|
||||||
|
const [showDiag, setShowDiag] = useState(false);
|
||||||
|
const [seenDiag, setSeenDiag] = useState(false);
|
||||||
|
const [linkCopied, setLinkCopied] = useState(false);
|
||||||
|
const copy = () => {
|
||||||
|
copyDXLink(classId, adminId).then((a) => {
|
||||||
|
if (a) {
|
||||||
|
setLinkCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLinkCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DengiYesOrNoDialog
|
||||||
|
show={showDiag}
|
||||||
|
content={{
|
||||||
|
message: (
|
||||||
|
<p>
|
||||||
|
<b>любой</b>, кто получит ссылку,{" "}
|
||||||
|
<b>будет иметь доступ к админке</b>.<br /> вы желаете продолжить?
|
||||||
|
</p>
|
||||||
|
),
|
||||||
|
yesBtn: "да",
|
||||||
|
noBtn: "нет",
|
||||||
|
yesIcon: <BiCopy />,
|
||||||
|
noIcon: <BiX size={20} />,
|
||||||
|
bgDissmis: true,
|
||||||
|
yesBtnStyle: "bg-orange-500! text-white! md:hover:bg-orange-600!",
|
||||||
|
}}
|
||||||
|
onAnswer={(answ) => {
|
||||||
|
if (answ) {
|
||||||
|
setSeenDiag(true);
|
||||||
|
copy();
|
||||||
|
}
|
||||||
|
setShowDiag(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DengiButton onClick={() => (seenDiag ? copy() : setShowDiag(true))}>
|
||||||
|
{linkCopied ? <BiCheckCircle /> : <BiLink />}
|
||||||
|
</DengiButton>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CoolBg = ({ pickmeMode }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-gradient-to-tl to-black to-50% bg-fixed ${
|
||||||
|
pickmeMode ? "from-pink-400/10" : "from-white/2"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = ({ setsetRoute, setRoute, pickmeMode }) => {
|
||||||
|
return (
|
||||||
|
<div className={"flex items-center w-full justify-between px-5"}>
|
||||||
|
<span className="flex gap-2 items-center">
|
||||||
|
<BiLeftArrowAlt
|
||||||
|
size={35}
|
||||||
|
onClick={() => {
|
||||||
|
setsetRoute(setRoute, "/app");
|
||||||
|
}}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
"rounded-full " +
|
||||||
|
(pickmeMode
|
||||||
|
? "lg:hover:bg-pink-400"
|
||||||
|
: "lg:hover:bg-white lg:hover:fill-black") +
|
||||||
|
" lg:hover:scale-105 transition cursor-pointer"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<img src={Logo} alt="logo" width={100} draggable={false} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export default function Panel() {
|
|
||||||
return <>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>;
|
|
||||||
}
|
|
||||||
+174
-96
@@ -1,20 +1,22 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import { Fragment, memo, useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
BiDotsHorizontalRounded,
|
BiDotsHorizontalRounded,
|
||||||
BiExit,
|
BiExit,
|
||||||
BiSolidUser,
|
BiSolidUser,
|
||||||
BiStar,
|
|
||||||
BiPencil,
|
BiPencil,
|
||||||
BiLink,
|
BiLink,
|
||||||
|
BiWrench,
|
||||||
} from "react-icons/bi";
|
} from "react-icons/bi";
|
||||||
import Logo from "../assets/logo.svg";
|
import Logo from "../assets/logo.svg";
|
||||||
import {
|
import {
|
||||||
Switch,
|
Switch,
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
|
TextPreloader,
|
||||||
// CoolMouseBg,
|
// CoolMouseBg,
|
||||||
} from "../trash/trash-components";
|
} from "../trash/trash-components";
|
||||||
import {
|
import {
|
||||||
|
AdminReq,
|
||||||
AdminUserFRequest,
|
AdminUserFRequest,
|
||||||
classUserFRequest,
|
classUserFRequest,
|
||||||
RaspGet,
|
RaspGet,
|
||||||
@@ -24,7 +26,7 @@ import { AnimatePresence, LayoutGroup, motion } from "motion/react";
|
|||||||
import { apiFormatDate, isTimeBeforeNow } from "../trash/dengi";
|
import { apiFormatDate, isTimeBeforeNow } from "../trash/dengi";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { API_BASE_URL } from "../App";
|
import { API_BASE_URL } from "../App";
|
||||||
import { copyToClipboard, ifstyle } from "../trash/helpers";
|
import { copyDXLink, ifstyle } from "../trash/helpers";
|
||||||
|
|
||||||
export function DX4App({
|
export function DX4App({
|
||||||
classIDAtom,
|
classIDAtom,
|
||||||
@@ -41,6 +43,7 @@ export function DX4App({
|
|||||||
const [pickmeMode, setPickmeMode] = useAtom(pickmeModeAtom);
|
const [pickmeMode, setPickmeMode] = useAtom(pickmeModeAtom);
|
||||||
const [rasp, setRasp] = useState([false, {}, 0]);
|
const [rasp, setRasp] = useState([false, {}, 0]);
|
||||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||||
|
const [managing, setManaging] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@@ -49,14 +52,14 @@ export function DX4App({
|
|||||||
if (!dengi_cash.wasCached(cacheName)) {
|
if (!dengi_cash.wasCached(cacheName)) {
|
||||||
RaspGet(API_BASE_URL, classId, selectedDate).then(
|
RaspGet(API_BASE_URL, classId, selectedDate).then(
|
||||||
(d) => {
|
(d) => {
|
||||||
if (cancelled) return;
|
|
||||||
if (d[0] == 1) {
|
if (d[0] == 1) {
|
||||||
try {
|
try {
|
||||||
setRasp([
|
if (!cancelled)
|
||||||
true,
|
setRasp([
|
||||||
d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322,
|
true,
|
||||||
d[1]["type"] ? d[1]["type"] : -1,
|
d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322,
|
||||||
]);
|
d[1]["type"] ? d[1]["type"] : -1,
|
||||||
|
]);
|
||||||
//console.log(d, d[1] ? d[1] : false);
|
//console.log(d, d[1] ? d[1] : false);
|
||||||
dengi_cash.cache(cacheName, [
|
dengi_cash.cache(cacheName, [
|
||||||
d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322,
|
d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322,
|
||||||
@@ -64,18 +67,20 @@ export function DX4App({
|
|||||||
]);
|
]);
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
//console.error(exception);
|
//console.error(exception);
|
||||||
setRasp([true, exception.toString()]);
|
if (!cancelled) setRasp([true, exception.toString()]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setRasp([true, "ошибка: " + d[1].toString()]);
|
if (!cancelled) setRasp([true, "ошибка: " + d[1].toString()]);
|
||||||
//console.log(d[1]);
|
//console.log(d[1]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
|
if (cancelled) return;
|
||||||
setRasp([true, "ошибка: " + err]);
|
setRasp([true, "ошибка: " + err]);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
if (cancelled) return;
|
||||||
setRasp([true, ...dengi_cash.get(cacheName)]);
|
setRasp([true, ...dengi_cash.get(cacheName)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,8 +138,8 @@ export function DX4App({
|
|||||||
else promises.push(Promise.resolve([0]));
|
else promises.push(Promise.resolve([0]));
|
||||||
if (adminId !== "")
|
if (adminId !== "")
|
||||||
promises.push(
|
promises.push(
|
||||||
dengi_cash.withCacheAsync(`adminfr-${adminId}`, async () =>
|
dengi_cash.withCacheAsync(`admindata-${adminId}`, async () =>
|
||||||
AdminUserFRequest(API_BASE_URL, adminId)
|
AdminReq(API_BASE_URL, adminId)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
else promises.push(Promise.resolve([0]));
|
else promises.push(Promise.resolve([0]));
|
||||||
@@ -143,6 +148,8 @@ export function DX4App({
|
|||||||
const classF = classRes[0] === 1 ? classRes[1].userclassname : false;
|
const classF = classRes[0] === 1 ? classRes[1].userclassname : false;
|
||||||
const adminF = adminRes[0] === 1 ? adminRes[1].adminName : false;
|
const adminF = adminRes[0] === 1 ? adminRes[1].adminName : false;
|
||||||
setUserFrNames({ admin: adminF, class: classF });
|
setUserFrNames({ admin: adminF, class: classF });
|
||||||
|
if (adminRes[0] === 1)
|
||||||
|
setManaging([-1, 2].includes(adminRes[1].adminPrems));
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
@@ -259,28 +266,65 @@ export function DX4App({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
{adminId != "" && managing && (
|
||||||
|
<motion.div
|
||||||
|
className="flex w-full justify-end"
|
||||||
|
layout="position"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
// onClick={() => {
|
||||||
|
// setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`);
|
||||||
|
// }}
|
||||||
|
href={`#/classprofile`}
|
||||||
|
className={`flex items-center justify-center border p-1 gap-2 px-2 cursor-pointer rounded-xl transition-colors ${
|
||||||
|
pickmeMode
|
||||||
|
? "border-pink-400/30 text-pink-300/80 lg:hover:text-white lg:hover:border-pink-500"
|
||||||
|
: "border-zinc-700 text-zinc-400 lg:hover:text-white lg:hover:border-white"
|
||||||
|
} `}
|
||||||
|
>
|
||||||
|
<BiWrench /> управление
|
||||||
|
</a>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
<motion.div
|
<motion.div
|
||||||
layout="position"
|
layout="position"
|
||||||
className="flex flex-col gap-5 w-full not-md:px-4"
|
className="flex flex-col gap-5 w-full not-md:px-4"
|
||||||
>
|
>
|
||||||
{adminId != "" && (
|
<div className="flex gap-2 items-center justify-center">
|
||||||
<motion.div className="flex w-full justify-end">
|
<DatePicker
|
||||||
|
date={selectedDate}
|
||||||
|
setDate={setSelectedDate}
|
||||||
|
indicator={
|
||||||
|
typeof rasp[2] == "number" && [1, 2].includes(rasp[2])
|
||||||
|
}
|
||||||
|
pink={pickmeMode}
|
||||||
|
/>
|
||||||
|
{adminId != "" && (
|
||||||
<a
|
<a
|
||||||
// onClick={() => {
|
// onClick={() => {
|
||||||
// setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`);
|
// setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`);
|
||||||
// }}
|
// }}
|
||||||
href={`#/editor/${apiFormatDate(selectedDate)}`}
|
href={
|
||||||
className="flex items-center justify-center border p-1 gap-2 px-2 lg:hover:border-white cursor-pointer rounded-xl transition-colors border-zinc-700 text-zinc-400 lg:hover:text-white"
|
userFrNames.admin
|
||||||
|
? `#/editor/${apiFormatDate(selectedDate)}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
className={`flex items-center justify-center p-1.5 cursor-pointer border transition *:transition-colors rounded-full ${
|
||||||
|
pickmeMode
|
||||||
|
? "border-pink-400/30 lg:hover:text-white hover:lg:bg-pink-500"
|
||||||
|
: "border-zinc-700 text-white lg:hover:text-black lg:hover:bg-white"
|
||||||
|
} `}
|
||||||
>
|
>
|
||||||
<BiPencil /> открыть в редакторе
|
{userFrNames.admin ? (
|
||||||
|
<BiPencil size={28} />
|
||||||
|
) : (
|
||||||
|
<LoadingSpinner size={28} />
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</motion.div>
|
)}
|
||||||
)}
|
</div>
|
||||||
<DatePicker
|
|
||||||
date={selectedDate}
|
|
||||||
setDate={setSelectedDate}
|
|
||||||
indicator={typeof rasp[2] == "number" && [1, 2].includes(rasp[2])}
|
|
||||||
/>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<LayoutGroup>
|
<LayoutGroup>
|
||||||
<RaspView {...{ rasp, pickmeMode, selectedDate }} />
|
<RaspView {...{ rasp, pickmeMode, selectedDate }} />
|
||||||
@@ -306,7 +350,7 @@ export function DX4App({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const CoolMainBg = React.memo(({ pickmeMode }) => {
|
const CoolMainBg = memo(({ pickmeMode }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-gradient-to-br to-black to-50% bg-fixed
|
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-gradient-to-br to-black to-50% bg-fixed
|
||||||
@@ -315,7 +359,7 @@ const CoolMainBg = React.memo(({ pickmeMode }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
|
export const RaspView = memo(({ rasp, pickmeMode, selectedDate }) => {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence mode="popLayout">
|
<AnimatePresence mode="popLayout">
|
||||||
{rasp[0] && typeof rasp[1] == "object" && (
|
{rasp[0] && typeof rasp[1] == "object" && (
|
||||||
@@ -324,10 +368,14 @@ export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
|
|||||||
//layout="position"
|
//layout="position"
|
||||||
key={selectedDate}
|
key={selectedDate}
|
||||||
className={
|
className={
|
||||||
"w-full border-collapse border-spacing-x-0 border-spacing-y-[10px] bg-gradient-to-br backdrop-blur-xl mb-10 " +
|
"w-full border-collapse border-spacing-x-0 border-spacing-y-[10px] bg-gradient-to-br mb-10 " +
|
||||||
(pickmeMode ? "from-pink-400/30" : "from-zinc-800/40") +
|
(pickmeMode ? "from-pink-400/30" : "from-zinc-800/40") +
|
||||||
" to-transparent rounded-2xl selectable"
|
" to-transparent rounded-2xl selectable"
|
||||||
}
|
}
|
||||||
|
onContextMenu={(ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
itemRedo.action();
|
||||||
|
}}
|
||||||
variants={{
|
variants={{
|
||||||
hid: { opacity: 0 },
|
hid: { opacity: 0 },
|
||||||
show: { opacity: 1 },
|
show: { opacity: 1 },
|
||||||
@@ -372,61 +420,7 @@ export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="*:*:text-center *:*:py-2 *:not-last:border-b *:not-last:border-zinc-800 text-white">
|
<tbody className="*:*:text-center *:*:py-2 *:not-last:border-b *:not-last:border-zinc-800 text-white">
|
||||||
{rasp[1].map((d, i) => (
|
{rasp[1].map((d, i) => (
|
||||||
<tr
|
<TableItem {...{ d }} key={`table-header-${i}`} />
|
||||||
//layout="position"
|
|
||||||
key={`table-header-${i}`}
|
|
||||||
className="transition-colors *:transition-colors lg:hover:bg-zinc-300/15"
|
|
||||||
//rasp-key={"rasp_key_" + i + Array(d).join()}
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
//layout="position"
|
|
||||||
//layoutId={`table-num-${i}`}
|
|
||||||
>
|
|
||||||
{d["num"]}
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
//layout="position"
|
|
||||||
//layoutId={`table-subj-${i}`}
|
|
||||||
>
|
|
||||||
{d.subject
|
|
||||||
.toString()
|
|
||||||
.split("\n")
|
|
||||||
.map((line, i, array) => (
|
|
||||||
<React.Fragment key={i}>
|
|
||||||
{line}
|
|
||||||
{i < array.length - 1 && <br />}
|
|
||||||
</React.Fragment>
|
|
||||||
)) || d.subject}
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
//layout="position"
|
|
||||||
//layoutId={`table-cab-${i}`}
|
|
||||||
>
|
|
||||||
{d.cabinet
|
|
||||||
.toString()
|
|
||||||
.split("\n")
|
|
||||||
.map((line, i, array) => (
|
|
||||||
<React.Fragment key={i}>
|
|
||||||
{line}
|
|
||||||
{i < array.length - 1 && <br />}
|
|
||||||
</React.Fragment>
|
|
||||||
)) || d.cabinet}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div
|
|
||||||
//layout="position"
|
|
||||||
//layoutId={`table-time-${i}`}
|
|
||||||
className="flex items-center justify-center min-h-9"
|
|
||||||
>
|
|
||||||
<div className="flex gap-0.5 flex-col *:block w-[70%] *:font-light">
|
|
||||||
<span className="text-left">
|
|
||||||
{d["t-begin"]} -
|
|
||||||
</span>
|
|
||||||
<span className="text-right">{d["t-end"]}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</motion.table>
|
</motion.table>
|
||||||
@@ -457,7 +451,7 @@ export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const UserSettings = React.memo(
|
const UserSettings = memo(
|
||||||
({
|
({
|
||||||
openUserSettings,
|
openUserSettings,
|
||||||
userFrNames,
|
userFrNames,
|
||||||
@@ -476,8 +470,8 @@ const UserSettings = React.memo(
|
|||||||
<motion.div
|
<motion.div
|
||||||
className="flex flex-col bg-grain gap-2 bg-zinc-700/20 p-2 rounded-2xl"
|
className="flex flex-col bg-grain gap-2 bg-zinc-700/20 p-2 rounded-2xl"
|
||||||
variants={{
|
variants={{
|
||||||
hidden: { opacity: 0, y: 10, filter: "blur(2px)" },
|
hidden: { opacity: 0, y: 10 },
|
||||||
show: { opacity: 1, y: 0, filter: "blur(0)" },
|
show: { opacity: 1, y: 0 },
|
||||||
}}
|
}}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="show"
|
animate="show"
|
||||||
@@ -507,21 +501,20 @@ const UserSettings = React.memo(
|
|||||||
// ],
|
// ],
|
||||||
[
|
[
|
||||||
<BiSolidUser />,
|
<BiSolidUser />,
|
||||||
`админ ${
|
<>
|
||||||
userFrNames["admin"] ? userFrNames["admin"] : adminId
|
{`админ ${userFrNames["admin"] ? userFrNames["admin"] : ""}`}
|
||||||
}`,
|
{!userFrNames["admin"] && <TextPreloader width="30px" />}
|
||||||
() => {},
|
</>,
|
||||||
|
() => {
|
||||||
|
setsetRoute(setRoute, "/adminprofile");
|
||||||
|
},
|
||||||
adminId != "",
|
adminId != "",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
<BiLink />,
|
<BiLink />,
|
||||||
classLinkCopied ? "скопированно" : "коп. ссылку",
|
classLinkCopied ? "скопировано" : "коп. ссылку",
|
||||||
() => {
|
() => {
|
||||||
copyToClipboard(
|
copyDXLink(classId).then((a) => {
|
||||||
location.href.substring(0, location.href.search("#")) +
|
|
||||||
"#//" +
|
|
||||||
classId
|
|
||||||
).then((a) => {
|
|
||||||
if (a) {
|
if (a) {
|
||||||
setClassLinkCopied(true);
|
setClassLinkCopied(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -556,3 +549,88 @@ const UserSettings = React.memo(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
class GlobalListener {
|
||||||
|
constructor() {
|
||||||
|
this.listeners = [];
|
||||||
|
}
|
||||||
|
listen(fn, once = false) {
|
||||||
|
const wrapper = (...args) => {
|
||||||
|
try {
|
||||||
|
fn(...args);
|
||||||
|
} finally {
|
||||||
|
if (once) this.listeners = this.listeners.filter((l) => l !== wrapper);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.listeners.push(wrapper);
|
||||||
|
return () => {
|
||||||
|
this.listeners = this.listeners.filter((l) => l !== wrapper);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
action(...args) {
|
||||||
|
this.listeners.slice().forEach((fn) => {
|
||||||
|
try {
|
||||||
|
fn(...args);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemRedo = new GlobalListener();
|
||||||
|
|
||||||
|
const TableItem = memo(({ d }) => {
|
||||||
|
const [sel, setSel] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
itemRedo.listen(() => {
|
||||||
|
setSel(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
//layout="position"
|
||||||
|
className={
|
||||||
|
"transition-colors *:transition-colors " +
|
||||||
|
(sel
|
||||||
|
? "bg-zinc-300/15 lg:hover:bg-zinc-300/20"
|
||||||
|
: " lg:hover:bg-zinc-300/15")
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
setSel(!sel);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td>{d["num"]}</td>
|
||||||
|
<td>
|
||||||
|
{d.subject
|
||||||
|
.toString()
|
||||||
|
.split("\n")
|
||||||
|
.map((line, i, array) => (
|
||||||
|
<Fragment key={i}>
|
||||||
|
{line}
|
||||||
|
{i < array.length - 1 && <br />}
|
||||||
|
</Fragment>
|
||||||
|
)) || d.subject}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{d.cabinet
|
||||||
|
.toString()
|
||||||
|
.split("\n")
|
||||||
|
.map((line, i, array) => (
|
||||||
|
<Fragment key={i}>
|
||||||
|
{line}
|
||||||
|
{i < array.length - 1 && <br />}
|
||||||
|
</Fragment>
|
||||||
|
)) || d.cabinet}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="flex items-center justify-center min-h-9">
|
||||||
|
<div className="flex gap-0.5 flex-col *:block w-[70%] *:font-light">
|
||||||
|
<span className="text-left">{d["t-begin"]} -</span>
|
||||||
|
<span className="text-right">{d["t-end"]}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
+20
-29
@@ -7,15 +7,7 @@ import { BiHide, BiShow, BiSolidErrorCircle, BiBlock } from "react-icons/bi";
|
|||||||
import { LoginReq, AdminReq } from "../trash/req_mgr";
|
import { LoginReq, AdminReq } from "../trash/req_mgr";
|
||||||
import { API_BASE_URL } from "../App";
|
import { API_BASE_URL } from "../App";
|
||||||
import { b64d } from "../trash/helpers";
|
import { b64d } from "../trash/helpers";
|
||||||
const AvailableSymbols = (() => {
|
import { AvailableAdminSymbols, AvailableSymbols } from "../trash/dengi";
|
||||||
const a = "abcdefghijklmnopqrstuvwxyz"; // alphabet
|
|
||||||
const A = a.toUpperCase(); // ALPHABET
|
|
||||||
const n = "1234567890"; // numbers
|
|
||||||
const s = "_-"; // specials
|
|
||||||
return (a + n + A + s).split("");
|
|
||||||
})();
|
|
||||||
|
|
||||||
const AvailableAdminSymbols = [...AvailableSymbols, ..."$()*^%@!+=.".split("")];
|
|
||||||
|
|
||||||
export function LoginPage({
|
export function LoginPage({
|
||||||
classIDAtom,
|
classIDAtom,
|
||||||
@@ -51,7 +43,7 @@ export function LoginPage({
|
|||||||
setsetRoute(setRoute, "/app");
|
setsetRoute(setRoute, "/app");
|
||||||
} else {
|
} else {
|
||||||
const al = await AdminReq(API_BASE_URL, aid);
|
const al = await AdminReq(API_BASE_URL, aid);
|
||||||
console.log(al);
|
// console.log(al);
|
||||||
if (al[0] === 1) {
|
if (al[0] === 1) {
|
||||||
// yes, I know that check must go on backend, but im stupid lazy piece of shit 💀💀💀
|
// yes, I know that check must go on backend, but im stupid lazy piece of shit 💀💀💀
|
||||||
// TODO: FIX LATER
|
// TODO: FIX LATER
|
||||||
@@ -61,8 +53,8 @@ export function LoginPage({
|
|||||||
setsetRoute(setRoute, "/app");
|
setsetRoute(setRoute, "/app");
|
||||||
} else {
|
} else {
|
||||||
// you are not from here
|
// you are not from here
|
||||||
console.log(al);
|
// console.log(al);
|
||||||
setErrorText("админ не пренадлежит этому классу");
|
setErrorText("админ не найден");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setErrorText(al[1]);
|
setErrorText(al[1]);
|
||||||
@@ -108,9 +100,9 @@ export function LoginPage({
|
|||||||
<AnimatePresence mode="popLayout">
|
<AnimatePresence mode="popLayout">
|
||||||
{errorText.length > 0 && (
|
{errorText.length > 0 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ y: 50, opacity: 0, filter: "blur(5px)" }}
|
initial={{ y: 50, opacity: 0 }}
|
||||||
animate={{ y: 0, opacity: 1, filter: "blur(0px)" }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
exit={{ y: 50, opacity: 0, filter: "blur(5px)" }}
|
exit={{ y: 50, opacity: 0 }}
|
||||||
layout="position"
|
layout="position"
|
||||||
className="p-2 bg-gradient-to-r from-zinc-900 to-white/40 text-white rounded-xl w-[90vw] max-w-[600px] flex gap-2 items-center justify-between"
|
className="p-2 bg-gradient-to-r from-zinc-900 to-white/40 text-white rounded-xl w-[90vw] max-w-[600px] flex gap-2 items-center justify-between"
|
||||||
>
|
>
|
||||||
@@ -128,24 +120,23 @@ export function LoginPage({
|
|||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex lg:backdrop-blur-xl flex-col rounded-2xl w-[90vw] items-center justify-center py-3 max-w-[600px] bg-grain focus-within:border-white/10 border border-transparent transition-colors"
|
className="flex flex-col rounded-2xl w-[90vw] items-center justify-center py-3 max-w-[600px] bg-grain focus-within:border-white/10 border border-transparent transition-colors relative"
|
||||||
initial={{
|
initial={{
|
||||||
y: 30,
|
y: 30,
|
||||||
filter: "blur(5px)",
|
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
}}
|
}}
|
||||||
layout="position"
|
layout="position"
|
||||||
animate={{ y: 0, filter: "blur(0)", opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
>
|
>
|
||||||
<motion.img src={Logo} alt="logo dengi" width={200} />
|
<img src={Logo} alt="logo dengi" width={200} />
|
||||||
<motion.h1 className="text-zinc-600">добро пожаловать.</motion.h1>
|
<h1 className="text-zinc-600">добро пожаловать.</h1>
|
||||||
<motion.h2 className="text-3xl font-light">вход</motion.h2>
|
<h2 className="text-3xl font-light">вход</h2>
|
||||||
<form
|
<form
|
||||||
action="#"
|
action="#"
|
||||||
className="mt-2 flex flex-col gap-3 *:outline-none *:border *:border-transparent *:focus:border-white"
|
className="mt-2 flex flex-col gap-3 *:outline-none *:border *:border-transparent *:focus:border-white"
|
||||||
onSubmit={formSubmit}
|
onSubmit={formSubmit}
|
||||||
>
|
>
|
||||||
<motion.input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="d"
|
name="d"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@@ -170,7 +161,7 @@ export function LoginPage({
|
|||||||
disabled={isLogining}
|
disabled={isLogining}
|
||||||
minLength={3}
|
minLength={3}
|
||||||
/>
|
/>
|
||||||
<motion.span className="w-full flex gap-2 items-center justify-center ">
|
<span className="w-full flex gap-2 items-center justify-center ">
|
||||||
<Switch
|
<Switch
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
onChange={(e) => setIsAdmin(e)}
|
onChange={(e) => setIsAdmin(e)}
|
||||||
@@ -178,7 +169,7 @@ export function LoginPage({
|
|||||||
name=""
|
name=""
|
||||||
/>
|
/>
|
||||||
админка
|
админка
|
||||||
</motion.span>
|
</span>
|
||||||
<CoolInputWithHide
|
<CoolInputWithHide
|
||||||
{...{ enabled: isAdmin, setEnteredAId, isLogining, adminEnteredId }}
|
{...{ enabled: isAdmin, setEnteredAId, isLogining, adminEnteredId }}
|
||||||
/>
|
/>
|
||||||
@@ -200,8 +191,8 @@ export function LoginPage({
|
|||||||
<AnimatePresence mode="popLayout">
|
<AnimatePresence mode="popLayout">
|
||||||
{isLogining && (
|
{isLogining && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ y: -40, opacity: 0, filter: "blur(5px)" }}
|
initial={{ y: -40, opacity: 0 }}
|
||||||
animate={{ y: 0, opacity: 1, filter: "blur(0px)" }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
className="p-2 w-[90vw] max-w-[600px] border-2 rounded-xl border-zinc-500 flex gap-2 items-center justify-center hover:border-white transition-colors bg-grain"
|
className="p-2 w-[90vw] max-w-[600px] border-2 rounded-xl border-zinc-500 flex gap-2 items-center justify-center hover:border-white transition-colors bg-grain"
|
||||||
layout="position"
|
layout="position"
|
||||||
>
|
>
|
||||||
@@ -224,7 +215,7 @@ function CoolLoginButton({ display, isLogining }) {
|
|||||||
initial={{ y: 10, opacity: 0 }}
|
initial={{ y: 10, opacity: 0 }}
|
||||||
animate={{ y: 0, opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
ref={logbuttonRef}
|
ref={logbuttonRef}
|
||||||
exit={{ y: 40, opacity: 0, rotate: 20, filter: "blur(3px)" }}
|
exit={{ y: 40, opacity: 0, rotate: 20 }}
|
||||||
onMouseMove={(e) => {
|
onMouseMove={(e) => {
|
||||||
const rect = e.target.getBoundingClientRect();
|
const rect = e.target.getBoundingClientRect();
|
||||||
e.target.style.setProperty(
|
e.target.style.setProperty(
|
||||||
@@ -257,10 +248,10 @@ function CoolInputWithHide({
|
|||||||
exit={{ y: 10, opacity: 0 }}
|
exit={{ y: 10, opacity: 0 }}
|
||||||
className="flex *:outline-none *:border *:border-transparent *:focus:border-white"
|
className="flex *:outline-none *:border *:border-transparent *:focus:border-white"
|
||||||
>
|
>
|
||||||
<motion.input
|
<input
|
||||||
type={!showAId ? "password" : "text"}
|
type={!showAId ? "password" : "text"}
|
||||||
name="ad"
|
name="ad"
|
||||||
className="bg-zinc-800/50 p-2 rounded-l-2xl max-w-[19b0px]"
|
className="bg-zinc-800/50 p-2 rounded-l-2xl"
|
||||||
placeholder="id админки"
|
placeholder="id админки"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
disabled={isLogining}
|
disabled={isLogining}
|
||||||
|
|||||||
+42
-19
@@ -1,4 +1,4 @@
|
|||||||
import { motion, AnimatePresence, LayoutGroup } from "motion/react";
|
import { motion, LayoutGroup } from "motion/react";
|
||||||
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
@@ -7,20 +7,16 @@ import { AdminUserFRequest, classUserFRequest } from "../trash/req_mgr";
|
|||||||
import {
|
import {
|
||||||
BiCalendar,
|
BiCalendar,
|
||||||
BiCalendarEdit,
|
BiCalendarEdit,
|
||||||
BiCheck,
|
|
||||||
BiExit,
|
BiExit,
|
||||||
BiPencil,
|
BiPencil,
|
||||||
BiPlus,
|
|
||||||
BiRedo,
|
|
||||||
BiTime,
|
BiTime,
|
||||||
BiTrash,
|
BiTrash,
|
||||||
BiUndo,
|
|
||||||
BiX,
|
BiX,
|
||||||
} from "react-icons/bi";
|
} from "react-icons/bi";
|
||||||
import { formatDate, parseApiDate } from "../trash/dengi";
|
import { formatDate, parseApiDate } from "../trash/dengi";
|
||||||
import {
|
import {
|
||||||
DengiButton,
|
|
||||||
DengiYesOrNoDialog,
|
DengiYesOrNoDialog,
|
||||||
|
TextPreloader,
|
||||||
// CoolMouseBg
|
// CoolMouseBg
|
||||||
} from "../trash/trash-components";
|
} from "../trash/trash-components";
|
||||||
import { TableEditor } from "../trash/editorTable";
|
import { TableEditor } from "../trash/editorTable";
|
||||||
@@ -30,6 +26,7 @@ import { API_BASE_URL } from "../App";
|
|||||||
export default function EditorPage(props) {
|
export default function EditorPage(props) {
|
||||||
const [classId, _setUserId] = useAtom(props.classIDAtom);
|
const [classId, _setUserId] = useAtom(props.classIDAtom);
|
||||||
const [adminId, _setAdminId] = useAtom(props.adminAtom);
|
const [adminId, _setAdminId] = useAtom(props.adminAtom);
|
||||||
|
const [pickmeMode, _setPickmeMode] = useAtom(props.pickmeModeAtom);
|
||||||
const [route, setRoute] = useAtom(props.routeAtom);
|
const [route, setRoute] = useAtom(props.routeAtom);
|
||||||
const dateRaw = props.secondPathSegment(route);
|
const dateRaw = props.secondPathSegment(route);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -78,7 +75,7 @@ export default function EditorPage(props) {
|
|||||||
}, [classId, adminId]);
|
}, [classId, adminId]);
|
||||||
// on rerenderr
|
// on rerenderr
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("editor cache clean");
|
// console.log("editor cache clean");
|
||||||
props.dengi_cash.delete(
|
props.dengi_cash.delete(
|
||||||
"editor_schedule",
|
"editor_schedule",
|
||||||
"editor_changes",
|
"editor_changes",
|
||||||
@@ -163,9 +160,17 @@ export default function EditorPage(props) {
|
|||||||
<span>
|
<span>
|
||||||
{userFrNames["class"] ? userFrNames["class"] : classId}
|
{userFrNames["class"] ? userFrNames["class"] : classId}
|
||||||
</span>
|
</span>
|
||||||
<span className="block h-[1px] w-[20px] -rotate-45 bg-zinc-600" />
|
<span
|
||||||
|
className={`block h-[1px] w-[20px] -rotate-45 ${
|
||||||
|
pickmeMode ? "bg-pink-600" : "bg-zinc-600"
|
||||||
|
} `}
|
||||||
|
/>
|
||||||
<span>
|
<span>
|
||||||
{userFrNames["admin"] ? userFrNames["admin"] : adminId}
|
{userFrNames["admin"] ? (
|
||||||
|
userFrNames["admin"]
|
||||||
|
) : (
|
||||||
|
<TextPreloader />
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</motion.span>
|
</motion.span>
|
||||||
<motion.span
|
<motion.span
|
||||||
@@ -188,13 +193,20 @@ export default function EditorPage(props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<BiPencil size={25} />
|
<BiPencil
|
||||||
|
color={pickmeMode ? "#FF0078" : undefined}
|
||||||
|
size={25}
|
||||||
|
/>
|
||||||
{formatDate(date)}
|
{formatDate(date)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
href="#/app"
|
href="#/app"
|
||||||
className="flex items-center justify-center border p-1 gap-2 px-2 lg:hover:border-white cursor-pointer rounded-xl transition-colors border-zinc-700 text-zinc-400 lg:hover:text-white"
|
className={`flex items-center justify-center border p-1 gap-2 px-2 cursor-pointer rounded-xl transition-colors ${
|
||||||
|
pickmeMode
|
||||||
|
? "border-pink-400/30 text-pink-300/80 lg:hover:text-white lg:hover:border-pink-500"
|
||||||
|
: "border-zinc-700 text-zinc-400 lg:hover:text-white lg:hover:border-white"
|
||||||
|
} `}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (changesWereMade) {
|
if (changesWereMade) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -207,12 +219,20 @@ export default function EditorPage(props) {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-5 select-none bg-grain bg-zinc-800/35 p-2 rounded-xl">
|
<div
|
||||||
|
className={`flex items-center justify-center gap-5 select-none bg-grain ${
|
||||||
|
pickmeMode ? "bg-pink-900/35" : "bg-zinc-800/35"
|
||||||
|
} p-2 rounded-xl`}
|
||||||
|
>
|
||||||
<LayoutGroup>
|
<LayoutGroup>
|
||||||
{tabs.map((item, idx) => (
|
{tabs.map((item, idx) => (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
"flex items-center justify-center gap-1 p-2 rounded-2xl md:hover:bg-white/10 transition-colors relative md:hover:text-white cursor-pointer " +
|
`flex items-center justify-center gap-1 p-2 rounded-2xl ${
|
||||||
|
pickmeMode
|
||||||
|
? "md:hover:bg-pink-400/10"
|
||||||
|
: "md:hover:bg-white/10"
|
||||||
|
} transition-colors relative md:hover:text-white cursor-pointer ` +
|
||||||
(idx == selectedTab ? "text-white" : "text-zinc-500")
|
(idx == selectedTab ? "text-white" : "text-zinc-500")
|
||||||
}
|
}
|
||||||
key={idx}
|
key={idx}
|
||||||
@@ -230,7 +250,9 @@ export default function EditorPage(props) {
|
|||||||
{idx == selectedTab && (
|
{idx == selectedTab && (
|
||||||
<motion.div
|
<motion.div
|
||||||
layoutId="editor_tabs_underline"
|
layoutId="editor_tabs_underline"
|
||||||
className="absolute -bottom-[4px] left-0 h-[3px] bg-white/70 rounded-full w-full"
|
className={`absolute -bottom-[4px] left-0 h-[3px] ${
|
||||||
|
pickmeMode ? "bg-pink-500/70" : "bg-white/70"
|
||||||
|
} rounded-full w-full`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -246,6 +268,7 @@ export default function EditorPage(props) {
|
|||||||
setChangesWereMade,
|
setChangesWereMade,
|
||||||
date,
|
date,
|
||||||
adminId,
|
adminId,
|
||||||
|
pickmeMode,
|
||||||
}}
|
}}
|
||||||
dengiCash={props.dengi_cash}
|
dengiCash={props.dengi_cash}
|
||||||
/>
|
/>
|
||||||
@@ -255,16 +278,16 @@ export default function EditorPage(props) {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
{/* <CoolMouseBg /> */}
|
{/* <CoolMouseBg /> */}
|
||||||
</div>
|
</div>
|
||||||
<DefaultEditorBG />
|
<DefaultEditorBG pickmeMode={pickmeMode} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function DefaultEditorBG() {
|
function DefaultEditorBG({ pickmeMode }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={`w-full h-screen bg-gradient-to-br fixed top-0 left-0 via-black via-50% gap-5 overflow-hidden ${
|
||||||
"w-full h-screen bg-gradient-to-br fixed top-0 left-0 via-black via-50% gap-5 overflow-hidden from-white/2 to-blue-500/10 -z-5 bg-fixed"
|
!pickmeMode ? "from-white/2" : "from-pink-400/10"
|
||||||
}
|
} to-blue-500/10 -z-5 bg-fixed`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-207
@@ -1,212 +1,11 @@
|
|||||||
import React, { useState } from 'react';
|
import { TextPreloader } from "../trash/trash-components";
|
||||||
import { DndContext, closestCenter, PointerSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
|
|
||||||
import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
|
||||||
|
|
||||||
// Configuration
|
export default function TestPage() {
|
||||||
const COLUMNS = [
|
|
||||||
{ key: 'position', label: 'Pos', width: 'w-16', placeholder: '#' },
|
|
||||||
{ key: 'subject', label: 'Subject', width: 'flex-1', placeholder: 'Enter subject' },
|
|
||||||
{ key: 'time', label: 'Time', width: 'w-24', placeholder: '00:00' },
|
|
||||||
{ key: 'room', label: 'Room', width: 'w-20', placeholder: 'A-1' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const INITIAL_DATA = [
|
|
||||||
{ id: '1', position: '1', subject: 'Mathematics', time: '09:00', room: 'A-1' },
|
|
||||||
{ id: '2', position: '2', subject: 'Physics', time: '10:00', room: 'B-2' },
|
|
||||||
{ id: '3', position: '3', subject: 'Chemistry', time: '11:00', room: 'A-3' },
|
|
||||||
{ id: '4', position: '4', subject: 'Biology', time: '13:00', room: 'C-1' },
|
|
||||||
];
|
|
||||||
|
|
||||||
function SortableRow({ item, columns, onUpdate, onDelete }) {
|
|
||||||
const {
|
|
||||||
attributes,
|
|
||||||
listeners,
|
|
||||||
setNodeRef,
|
|
||||||
transform,
|
|
||||||
transition,
|
|
||||||
isDragging,
|
|
||||||
} = useSortable({ id: item.id });
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
transform: CSS.Transform.toString(transform),
|
|
||||||
transition,
|
|
||||||
opacity: isDragging ? 0.5 : 1,
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
ref={setNodeRef}
|
<div className="bg-black w-screen h-screen flex items-center justify-center">
|
||||||
style={style}
|
<TextPreloader />
|
||||||
className="bg-gray-800 rounded-lg overflow-hidden mb-2"
|
|
||||||
>
|
|
||||||
<div className="flex items-stretch">
|
|
||||||
{columns.map((col) => (
|
|
||||||
<input
|
|
||||||
key={col.key}
|
|
||||||
type="text"
|
|
||||||
value={item[col.key] || ''}
|
|
||||||
onChange={(e) => onUpdate(item.id, col.key, e.target.value)}
|
|
||||||
className={`${col.width} px-3 py-3 bg-transparent text-white border-r border-gray-700 last:border-r-0 focus:outline-none focus:bg-gray-700/50 placeholder-gray-500`}
|
|
||||||
placeholder={col.placeholder}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<div
|
|
||||||
{...attributes}
|
|
||||||
{...listeners}
|
|
||||||
className="w-12 bg-gray-900 flex items-center justify-center cursor-move touch-none select-none text-gray-400 hover:text-white"
|
|
||||||
>
|
|
||||||
⋮
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => onDelete(item.id)}
|
|
||||||
className="w-12 bg-red-600/20 text-red-400 hover:bg-red-600/40 hover:text-red-300 active:bg-red-600/60"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ScheduleEditor({
|
|
||||||
initialData = INITIAL_DATA,
|
|
||||||
columns = COLUMNS,
|
|
||||||
onChange,
|
|
||||||
}) {
|
|
||||||
const [items, setItems] = useState(initialData);
|
|
||||||
|
|
||||||
const sensors = useSensors(
|
|
||||||
useSensor(PointerSensor, {
|
|
||||||
activationConstraint: {
|
|
||||||
distance: 8,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
useSensor(TouchSensor, {
|
|
||||||
activationConstraint: {
|
|
||||||
delay: 200,
|
|
||||||
tolerance: 8,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateItems = (newItems) => {
|
|
||||||
setItems(newItems);
|
|
||||||
onChange?.(newItems);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDragEnd = (event) => {
|
|
||||||
const { active, over } = event;
|
|
||||||
|
|
||||||
if (over && active.id !== over.id) {
|
|
||||||
const oldIndex = items.findIndex((item) => item.id === active.id);
|
|
||||||
const newIndex = items.findIndex((item) => item.id === over.id);
|
|
||||||
|
|
||||||
const newItems = arrayMove(items, oldIndex, newIndex);
|
|
||||||
|
|
||||||
// Update positions based on new order
|
|
||||||
const positions = newItems.map(item => item.position);
|
|
||||||
const reorderedItems = newItems.map((item, idx) => ({
|
|
||||||
...item,
|
|
||||||
position: positions[idx]
|
|
||||||
}));
|
|
||||||
|
|
||||||
updateItems(reorderedItems);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUpdate = (id, field, value) => {
|
|
||||||
const newItems = items.map(item =>
|
|
||||||
item.id === id ? { ...item, [field]: value } : item
|
|
||||||
);
|
|
||||||
updateItems(newItems);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = (id) => {
|
|
||||||
const newItems = items.filter(item => item.id !== id);
|
|
||||||
updateItems(newItems);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddRow = () => {
|
|
||||||
const newId = String(Date.now());
|
|
||||||
const lastPosition = items.length > 0
|
|
||||||
? String(parseInt(items[items.length - 1].position || '0') + 1)
|
|
||||||
: '1';
|
|
||||||
|
|
||||||
const newItem = { id: newId, position: lastPosition };
|
|
||||||
columns.forEach(col => {
|
|
||||||
if (col.key !== 'position') {
|
|
||||||
newItem[col.key] = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
updateItems([...items, newItem]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClear = () => {
|
|
||||||
updateItems([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gray-900 p-4 pb-24">
|
|
||||||
<div className="max-w-3xl mx-auto">
|
|
||||||
<h1 className="text-2xl font-bold mb-6 text-white">
|
|
||||||
Schedule Editor
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<DndContext
|
|
||||||
sensors={sensors}
|
|
||||||
collisionDetection={closestCenter}
|
|
||||||
onDragEnd={handleDragEnd}
|
|
||||||
>
|
|
||||||
<SortableContext
|
|
||||||
items={items.map(item => item.id)}
|
|
||||||
strategy={verticalListSortingStrategy}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{items.map((item) => (
|
|
||||||
<SortableRow
|
|
||||||
key={item.id}
|
|
||||||
item={item}
|
|
||||||
columns={columns}
|
|
||||||
onUpdate={handleUpdate}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</SortableContext>
|
|
||||||
</DndContext>
|
|
||||||
|
|
||||||
{items.length === 0 && (
|
|
||||||
<div className="border-2 border-dashed border-gray-700 rounded-lg p-12 text-center text-gray-500">
|
|
||||||
No items. Add a row to get started.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-6 flex gap-3">
|
|
||||||
<button
|
|
||||||
onClick={handleAddRow}
|
|
||||||
className="flex-1 bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 active:bg-blue-800"
|
|
||||||
>
|
|
||||||
+ Add Row
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={handleClear}
|
|
||||||
className="px-6 py-3 rounded-lg border-2 border-gray-700 text-gray-300 font-semibold hover:bg-gray-800 active:bg-gray-700"
|
|
||||||
>
|
|
||||||
Clear All
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 p-4 rounded-lg bg-gray-800 text-sm text-gray-300">
|
|
||||||
<p className="font-bold mb-2 text-white">Instructions:</p>
|
|
||||||
<ul className="space-y-1">
|
|
||||||
<li>• Drag rows by the handle (⋮) on the right to reorder</li>
|
|
||||||
<li>• Positions move with their rows when dragged</li>
|
|
||||||
<li>• Click any field to edit</li>
|
|
||||||
<li>• Click × to delete a row</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { DndContext, closestCenter, PointerSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||||
|
import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||||
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const COLUMNS = [
|
||||||
|
{ key: 'position', label: 'Pos', width: 'w-16', placeholder: '#' },
|
||||||
|
{ key: 'subject', label: 'Subject', width: 'flex-1', placeholder: 'Enter subject' },
|
||||||
|
{ key: 'time', label: 'Time', width: 'w-24', placeholder: '00:00' },
|
||||||
|
{ key: 'room', label: 'Room', width: 'w-20', placeholder: 'A-1' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const INITIAL_DATA = [
|
||||||
|
{ id: '1', position: '1', subject: 'Mathematics', time: '09:00', room: 'A-1' },
|
||||||
|
{ id: '2', position: '2', subject: 'Physics', time: '10:00', room: 'B-2' },
|
||||||
|
{ id: '3', position: '3', subject: 'Chemistry', time: '11:00', room: 'A-3' },
|
||||||
|
{ id: '4', position: '4', subject: 'Biology', time: '13:00', room: 'C-1' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function SortableRow({ item, columns, onUpdate, onDelete }) {
|
||||||
|
const {
|
||||||
|
attributes,
|
||||||
|
listeners,
|
||||||
|
setNodeRef,
|
||||||
|
transform,
|
||||||
|
transition,
|
||||||
|
isDragging,
|
||||||
|
} = useSortable({ id: item.id });
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition,
|
||||||
|
opacity: isDragging ? 0.5 : 1,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={setNodeRef}
|
||||||
|
style={style}
|
||||||
|
className="bg-gray-800 rounded-lg overflow-hidden mb-2"
|
||||||
|
>
|
||||||
|
<div className="flex items-stretch">
|
||||||
|
{columns.map((col) => (
|
||||||
|
<input
|
||||||
|
key={col.key}
|
||||||
|
type="text"
|
||||||
|
value={item[col.key] || ''}
|
||||||
|
onChange={(e) => onUpdate(item.id, col.key, e.target.value)}
|
||||||
|
className={`${col.width} px-3 py-3 bg-transparent text-white border-r border-gray-700 last:border-r-0 focus:outline-none focus:bg-gray-700/50 placeholder-gray-500`}
|
||||||
|
placeholder={col.placeholder}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<div
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
className="w-12 bg-gray-900 flex items-center justify-center cursor-move touch-none select-none text-gray-400 hover:text-white"
|
||||||
|
>
|
||||||
|
⋮
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => onDelete(item.id)}
|
||||||
|
className="w-12 bg-red-600/20 text-red-400 hover:bg-red-600/40 hover:text-red-300 active:bg-red-600/60"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ScheduleEditor({
|
||||||
|
initialData = INITIAL_DATA,
|
||||||
|
columns = COLUMNS,
|
||||||
|
onChange,
|
||||||
|
}) {
|
||||||
|
const [items, setItems] = useState(initialData);
|
||||||
|
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(PointerSensor, {
|
||||||
|
activationConstraint: {
|
||||||
|
distance: 8,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
useSensor(TouchSensor, {
|
||||||
|
activationConstraint: {
|
||||||
|
delay: 200,
|
||||||
|
tolerance: 8,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateItems = (newItems) => {
|
||||||
|
setItems(newItems);
|
||||||
|
onChange?.(newItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnd = (event) => {
|
||||||
|
const { active, over } = event;
|
||||||
|
|
||||||
|
if (over && active.id !== over.id) {
|
||||||
|
const oldIndex = items.findIndex((item) => item.id === active.id);
|
||||||
|
const newIndex = items.findIndex((item) => item.id === over.id);
|
||||||
|
|
||||||
|
const newItems = arrayMove(items, oldIndex, newIndex);
|
||||||
|
|
||||||
|
// Update positions based on new order
|
||||||
|
const positions = newItems.map(item => item.position);
|
||||||
|
const reorderedItems = newItems.map((item, idx) => ({
|
||||||
|
...item,
|
||||||
|
position: positions[idx]
|
||||||
|
}));
|
||||||
|
|
||||||
|
updateItems(reorderedItems);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = (id, field, value) => {
|
||||||
|
const newItems = items.map(item =>
|
||||||
|
item.id === id ? { ...item, [field]: value } : item
|
||||||
|
);
|
||||||
|
updateItems(newItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (id) => {
|
||||||
|
const newItems = items.filter(item => item.id !== id);
|
||||||
|
updateItems(newItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddRow = () => {
|
||||||
|
const newId = String(Date.now());
|
||||||
|
const lastPosition = items.length > 0
|
||||||
|
? String(parseInt(items[items.length - 1].position || '0') + 1)
|
||||||
|
: '1';
|
||||||
|
|
||||||
|
const newItem = { id: newId, position: lastPosition };
|
||||||
|
columns.forEach(col => {
|
||||||
|
if (col.key !== 'position') {
|
||||||
|
newItem[col.key] = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateItems([...items, newItem]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
updateItems([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-900 p-4 pb-24">
|
||||||
|
<div className="max-w-3xl mx-auto">
|
||||||
|
<h1 className="text-2xl font-bold mb-6 text-white">
|
||||||
|
Schedule Editor
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<DndContext
|
||||||
|
sensors={sensors}
|
||||||
|
collisionDetection={closestCenter}
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
>
|
||||||
|
<SortableContext
|
||||||
|
items={items.map(item => item.id)}
|
||||||
|
strategy={verticalListSortingStrategy}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{items.map((item) => (
|
||||||
|
<SortableRow
|
||||||
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
columns={columns}
|
||||||
|
onUpdate={handleUpdate}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
|
|
||||||
|
{items.length === 0 && (
|
||||||
|
<div className="border-2 border-dashed border-gray-700 rounded-lg p-12 text-center text-gray-500">
|
||||||
|
No items. Add a row to get started.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-6 flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={handleAddRow}
|
||||||
|
className="flex-1 bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 active:bg-blue-800"
|
||||||
|
>
|
||||||
|
+ Add Row
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleClear}
|
||||||
|
className="px-6 py-3 rounded-lg border-2 border-gray-700 text-gray-300 font-semibold hover:bg-gray-800 active:bg-gray-700"
|
||||||
|
>
|
||||||
|
Clear All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 p-4 rounded-lg bg-gray-800 text-sm text-gray-300">
|
||||||
|
<p className="font-bold mb-2 text-white">Instructions:</p>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
<li>• Drag rows by the handle (⋮) on the right to reorder</li>
|
||||||
|
<li>• Positions move with their rows when dragged</li>
|
||||||
|
<li>• Click any field to edit</li>
|
||||||
|
<li>• Click × to delete a row</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+32
-4
@@ -53,7 +53,11 @@ export function usToRussianDay(usDay) {
|
|||||||
export function formatDate(date) {
|
export function formatDate(date) {
|
||||||
return `${
|
return `${
|
||||||
dayOfWeeksNames[usToRussianDay(date.getDay())]
|
dayOfWeeksNames[usToRussianDay(date.getDay())]
|
||||||
}, ${date.getDate()} ${monthNames[date.getMonth()]}`;
|
}, ${date.getDate()} ${monthNames[date.getMonth()]}${
|
||||||
|
date.getFullYear() != new Date().getFullYear()
|
||||||
|
? " " + date.getFullYear()
|
||||||
|
: ""
|
||||||
|
}`;
|
||||||
}
|
}
|
||||||
export function apiFormatDate(date) {
|
export function apiFormatDate(date) {
|
||||||
return (
|
return (
|
||||||
@@ -128,9 +132,33 @@ export function convertEditorToApi(arr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isTimeBeforeNow(timeStr) {
|
export function isTimeBeforeNow(timeStr) {
|
||||||
const [h, m] = timeStr.split(':').map(Number);
|
const [h, m] = timeStr.split(":").map(Number);
|
||||||
if (!Number.isFinite(h) || !Number.isFinite(m)) return false;
|
if (!Number.isFinite(h) || !Number.isFinite(m)) return false;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const t = new Date(now.getFullYear(), now.getMonth(), now.getDate(), h, m, 0, 0);
|
const t = new Date(
|
||||||
|
now.getFullYear(),
|
||||||
|
now.getMonth(),
|
||||||
|
now.getDate(),
|
||||||
|
h,
|
||||||
|
m,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
return t.getTime() < now.getTime();
|
return t.getTime() < now.getTime();
|
||||||
}
|
}
|
||||||
|
export const premsMeanings = {
|
||||||
|
"-1": "неограниченый",
|
||||||
|
// 0: "reserved"
|
||||||
|
1: "обычный",
|
||||||
|
2: "управляющий",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AvailableSymbols = (() => {
|
||||||
|
const a = "abcdefghijklmnopqrstuvwxyz"; // alphabet
|
||||||
|
const A = a.toUpperCase(); // ALPHABET
|
||||||
|
const n = "1234567890"; // numbers
|
||||||
|
const s = "_-"; // specials
|
||||||
|
return (a + n + A + s).split("");
|
||||||
|
})();
|
||||||
|
|
||||||
|
export const AvailableAdminSymbols = [...AvailableSymbols, ..."$()*^%@!+=.".split("")];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
closestCenter,
|
closestCenter,
|
||||||
@@ -155,6 +155,7 @@ function SortableRow({
|
|||||||
onDuplicate,
|
onDuplicate,
|
||||||
setFocusEdit,
|
setFocusEdit,
|
||||||
setHoverEdit,
|
setHoverEdit,
|
||||||
|
pickmeMode,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
attributes,
|
attributes,
|
||||||
@@ -175,7 +176,9 @@ function SortableRow({
|
|||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
className="bg-zinc-900 bg-grain rounded-lg mb-2"
|
className={`${
|
||||||
|
pickmeMode ? "bg-pink-500/35" : "bg-zinc-900"
|
||||||
|
} bg-grain rounded-lg mb-2`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
@@ -188,7 +191,11 @@ function SortableRow({
|
|||||||
wrap="hard"
|
wrap="hard"
|
||||||
rows={1}
|
rows={1}
|
||||||
onChange={(e) => onUpdate(item.id, col.key, e.target.value)}
|
onChange={(e) => onUpdate(item.id, col.key, e.target.value)}
|
||||||
className={`px-1 py-3 text-center bg-transparent text-white border-r border-zinc-700 last:border-r-0 last:rounded-tr-lg focus:outline-none focus:bg-gray-700/50 placeholder-gray-500 transition-colors hover:bg-gray-700/20 first:rounded-tl-lg max-h-min resize-none block min-w-0 grow ${col.width}`}
|
className={`px-1 py-3 text-center bg-transparent text-white border-r ${
|
||||||
|
pickmeMode ? "border-pink-400/50" : "border-zinc-700"
|
||||||
|
} last:border-r-0 last:rounded-tr-lg focus:outline-none focus:bg-gray-700/50 placeholder-gray-500 transition-colors hover:bg-gray-700/20 first:rounded-tl-lg max-h-min resize-none block min-w-0 grow ${
|
||||||
|
col.width
|
||||||
|
}`}
|
||||||
placeholder={col.placeholder}
|
placeholder={col.placeholder}
|
||||||
onFocus={() => setFocusEdit(col.key)}
|
onFocus={() => setFocusEdit(col.key)}
|
||||||
onBlur={() => setFocusEdit(null)}
|
onBlur={() => setFocusEdit(null)}
|
||||||
@@ -244,6 +251,7 @@ export function TableEditor({
|
|||||||
adminId,
|
adminId,
|
||||||
date,
|
date,
|
||||||
dengiCash,
|
dengiCash,
|
||||||
|
pickmeMode = false,
|
||||||
}) {
|
}) {
|
||||||
const [items, setItems] = useState(initialData);
|
const [items, setItems] = useState(initialData);
|
||||||
const [undoItems, setUndoItems] = useState([]);
|
const [undoItems, setUndoItems] = useState([]);
|
||||||
@@ -553,7 +561,9 @@ export function TableEditor({
|
|||||||
|
|
||||||
<span
|
<span
|
||||||
key={col.key + i + "divider"}
|
key={col.key + i + "divider"}
|
||||||
className="w-4 h-[1px] bg-zinc-600 -rotate-45 last:hidden"
|
className={`w-4 h-[1px] ${
|
||||||
|
pickmeMode ? "bg-pink-600" : "bg-zinc-600"
|
||||||
|
} -rotate-45 last:hidden`}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
@@ -585,6 +595,7 @@ export function TableEditor({
|
|||||||
onDuplicate={handleDuplicate}
|
onDuplicate={handleDuplicate}
|
||||||
setFocusEdit={setFocusEdit}
|
setFocusEdit={setFocusEdit}
|
||||||
setHoverEdit={setHoverEdit}
|
setHoverEdit={setHoverEdit}
|
||||||
|
pickmeMode={pickmeMode}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
@@ -600,7 +611,9 @@ export function TableEditor({
|
|||||||
opacity: 1,
|
opacity: 1,
|
||||||
scale: 1,
|
scale: 1,
|
||||||
}}
|
}}
|
||||||
className="border-2 border-dashed border-gray-700 rounded-lg p-12 text-center text-gray-500"
|
className={`border-2 border-dashed ${
|
||||||
|
pickmeMode ? "border-pink-700" : "border-gray-700"
|
||||||
|
} rounded-lg p-12 text-center text-gray-500`}
|
||||||
>
|
>
|
||||||
пусто
|
пусто
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
+19
-73
@@ -1,80 +1,26 @@
|
|||||||
|
import { makeRequest } from "./req_mgr";
|
||||||
|
|
||||||
export async function EditorPrefillRequest(host, tab, adminid, date) {
|
export async function EditorPrefillRequest(host, tab, adminid, date) {
|
||||||
try {
|
return makeRequest(host + "?editor_data", {
|
||||||
const req = await fetch(host + "?editor_data", {
|
adminId: adminid,
|
||||||
method: "POST",
|
tab,
|
||||||
headers: {
|
day: date,
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
});
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
adminId: adminid,
|
|
||||||
tab: tab,
|
|
||||||
day: date,
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
req.ok &&
|
|
||||||
req.headers.get("Content-Type")?.includes("application/json")
|
|
||||||
) {
|
|
||||||
return [1, await req.json()];
|
|
||||||
} else {
|
|
||||||
return [req.status, await req.text()];
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return [-1, err];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
export async function EditorPublishRequest(host, tab, adminid, date, newData) {
|
|
||||||
try {
|
|
||||||
const req = await fetch(host + "?editor_publish", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
adminId: adminid,
|
|
||||||
tab: tab,
|
|
||||||
day: date,
|
|
||||||
newData: newData,
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
export async function EditorPublishRequest(host, tab, adminid, date, newData) {
|
||||||
req.ok &&
|
return makeRequest(host + "?editor_publish", {
|
||||||
req.headers.get("Content-Type")?.includes("application/json")
|
adminId: adminid,
|
||||||
) {
|
tab,
|
||||||
return [1, await req.json()];
|
day: date,
|
||||||
} else {
|
newData,
|
||||||
return [req.status, await req.text()];
|
});
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return [-1, err];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function EditorRemoveRequest(host, tab, adminid, date) {
|
export async function EditorRemoveRequest(host, tab, adminid, date) {
|
||||||
try {
|
return makeRequest(host + "?editor_fullitems_del", {
|
||||||
const req = await fetch(host + "?editor_fullitems_del", {
|
adminId: adminid,
|
||||||
method: "POST",
|
tab,
|
||||||
headers: {
|
day: date,
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
});
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
adminId: adminid,
|
|
||||||
tab: tab,
|
|
||||||
day: date,
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
req.ok &&
|
|
||||||
req.headers.get("Content-Type")?.includes("application/json")
|
|
||||||
) {
|
|
||||||
return [1, await req.json()];
|
|
||||||
} else {
|
|
||||||
return [req.status, await req.text()];
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return [-1, err];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-21
@@ -23,10 +23,7 @@ export function b64d(str) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** by claude
|
/** by claude
|
||||||
* Universal copy-to-clipboard function
|
|
||||||
* Works with modern Clipboard API and falls back to legacy methods
|
|
||||||
* @param {string} text - The text to copy to clipboard
|
* @param {string} text - The text to copy to clipboard
|
||||||
* @returns {Promise<boolean>} - Returns true if successful, false otherwise
|
* @returns {Promise<boolean>} - Returns true if successful, false otherwise
|
||||||
*/
|
*/
|
||||||
@@ -37,34 +34,44 @@ export async function copyToClipboard(text) {
|
|||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Clipboard API failed, trying fallback:', err);
|
console.warn("Clipboard API failed, trying fallback:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method 2: Fallback for older browsers or non-secure contexts
|
// Method 2: Fallback for older browsers or non-secure contexts
|
||||||
try {
|
try {
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement("textarea");
|
||||||
textarea.value = text;
|
textarea.value = text;
|
||||||
|
textarea.style.position = "fixed";
|
||||||
// Make it invisible and non-intrusive
|
textarea.style.left = "-999999px";
|
||||||
textarea.style.position = 'fixed';
|
textarea.style.top = "-999999px";
|
||||||
textarea.style.left = '-999999px';
|
textarea.style.opacity = "0";
|
||||||
textarea.style.top = '-999999px';
|
textarea.setAttribute("readonly", "");
|
||||||
textarea.style.opacity = '0';
|
|
||||||
textarea.setAttribute('readonly', '');
|
|
||||||
|
|
||||||
document.body.appendChild(textarea);
|
document.body.appendChild(textarea);
|
||||||
|
|
||||||
// Select and copy
|
|
||||||
textarea.select();
|
textarea.select();
|
||||||
textarea.setSelectionRange(0, text.length);
|
textarea.setSelectionRange(0, text.length);
|
||||||
|
const success = document.execCommand("copy");
|
||||||
const success = document.execCommand('copy');
|
|
||||||
document.body.removeChild(textarea);
|
document.body.removeChild(textarea);
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('All copy methods failed:', err);
|
console.error("All copy methods failed:", err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dx4 specific
|
||||||
|
export async function copyDXLink(classId, adminId = undefined) {
|
||||||
|
return copyToClipboard(
|
||||||
|
location.href.substring(0, location.href.search("#")) +
|
||||||
|
"#//" +
|
||||||
|
classId +
|
||||||
|
(adminId ? ";" + b64e(adminId) : "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const randomString = (length, symbols) => {
|
||||||
|
const len = symbols.length;
|
||||||
|
return Array.from(
|
||||||
|
{ length },
|
||||||
|
() => symbols[Math.floor(Math.random() * len)]
|
||||||
|
).join("");
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
/**
|
|
||||||
* MD5 hash implementation
|
|
||||||
* Based on: http://www.myersdaily.org/joseph/javascript/md5-text.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
const HEX_CHARS = '0123456789abcdef';
|
|
||||||
|
|
||||||
// Pre-compute add32 function based on environment
|
|
||||||
const add32 = (() => {
|
|
||||||
const test = (a, b) => (a + b) & 0xFFFFFFFF;
|
|
||||||
// Test if simple addition works correctly
|
|
||||||
if (test(0x80000000, 0x80000000) === 0) {
|
|
||||||
return test;
|
|
||||||
}
|
|
||||||
// Fallback for environments with non-standard 32-bit arithmetic
|
|
||||||
return (x, y) => {
|
|
||||||
const lsw = (x & 0xFFFF) + (y & 0xFFFF);
|
|
||||||
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
|
||||||
return (msw << 16) | (lsw & 0xFFFF);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
const cmn = (q, a, b, x, s, t) => {
|
|
||||||
a = add32(add32(a, q), add32(x, t));
|
|
||||||
return add32((a << s) | (a >>> (32 - s)), b);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ff = (a, b, c, d, x, s, t) => cmn((b & c) | ((~b) & d), a, b, x, s, t);
|
|
||||||
const gg = (a, b, c, d, x, s, t) => cmn((b & d) | (c & (~d)), a, b, x, s, t);
|
|
||||||
const hh = (a, b, c, d, x, s, t) => cmn(b ^ c ^ d, a, b, x, s, t);
|
|
||||||
const ii = (a, b, c, d, x, s, t) => cmn(c ^ (b | (~d)), a, b, x, s, t);
|
|
||||||
|
|
||||||
const md5blk = (s) => {
|
|
||||||
const blks = [];
|
|
||||||
for (let i = 0; i < 64; i += 4) {
|
|
||||||
blks[i >> 2] = s.charCodeAt(i) +
|
|
||||||
(s.charCodeAt(i + 1) << 8) +
|
|
||||||
(s.charCodeAt(i + 2) << 16) +
|
|
||||||
(s.charCodeAt(i + 3) << 24);
|
|
||||||
}
|
|
||||||
return blks;
|
|
||||||
};
|
|
||||||
|
|
||||||
const md5cycle = (x, k) => {
|
|
||||||
let a = x[0], b = x[1], c = x[2], d = x[3];
|
|
||||||
|
|
||||||
a = ff(a, b, c, d, k[0], 7, -680876936);
|
|
||||||
d = ff(d, a, b, c, k[1], 12, -389564586);
|
|
||||||
c = ff(c, d, a, b, k[2], 17, 606105819);
|
|
||||||
b = ff(b, c, d, a, k[3], 22, -1044525330);
|
|
||||||
a = ff(a, b, c, d, k[4], 7, -176418897);
|
|
||||||
d = ff(d, a, b, c, k[5], 12, 1200080426);
|
|
||||||
c = ff(c, d, a, b, k[6], 17, -1473231341);
|
|
||||||
b = ff(b, c, d, a, k[7], 22, -45705983);
|
|
||||||
a = ff(a, b, c, d, k[8], 7, 1770035416);
|
|
||||||
d = ff(d, a, b, c, k[9], 12, -1958414417);
|
|
||||||
c = ff(c, d, a, b, k[10], 17, -42063);
|
|
||||||
b = ff(b, c, d, a, k[11], 22, -1990404162);
|
|
||||||
a = ff(a, b, c, d, k[12], 7, 1804603682);
|
|
||||||
d = ff(d, a, b, c, k[13], 12, -40341101);
|
|
||||||
c = ff(c, d, a, b, k[14], 17, -1502002290);
|
|
||||||
b = ff(b, c, d, a, k[15], 22, 1236535329);
|
|
||||||
|
|
||||||
a = gg(a, b, c, d, k[1], 5, -165796510);
|
|
||||||
d = gg(d, a, b, c, k[6], 9, -1069501632);
|
|
||||||
c = gg(c, d, a, b, k[11], 14, 643717713);
|
|
||||||
b = gg(b, c, d, a, k[0], 20, -373897302);
|
|
||||||
a = gg(a, b, c, d, k[5], 5, -701558691);
|
|
||||||
d = gg(d, a, b, c, k[10], 9, 38016083);
|
|
||||||
c = gg(c, d, a, b, k[15], 14, -660478335);
|
|
||||||
b = gg(b, c, d, a, k[4], 20, -405537848);
|
|
||||||
a = gg(a, b, c, d, k[9], 5, 568446438);
|
|
||||||
d = gg(d, a, b, c, k[14], 9, -1019803690);
|
|
||||||
c = gg(c, d, a, b, k[3], 14, -187363961);
|
|
||||||
b = gg(b, c, d, a, k[8], 20, 1163531501);
|
|
||||||
a = gg(a, b, c, d, k[13], 5, -1444681467);
|
|
||||||
d = gg(d, a, b, c, k[2], 9, -51403784);
|
|
||||||
c = gg(c, d, a, b, k[7], 14, 1735328473);
|
|
||||||
b = gg(b, c, d, a, k[12], 20, -1926607734);
|
|
||||||
|
|
||||||
a = hh(a, b, c, d, k[5], 4, -378558);
|
|
||||||
d = hh(d, a, b, c, k[8], 11, -2022574463);
|
|
||||||
c = hh(c, d, a, b, k[11], 16, 1839030562);
|
|
||||||
b = hh(b, c, d, a, k[14], 23, -35309556);
|
|
||||||
a = hh(a, b, c, d, k[1], 4, -1530992060);
|
|
||||||
d = hh(d, a, b, c, k[4], 11, 1272893353);
|
|
||||||
c = hh(c, d, a, b, k[7], 16, -155497632);
|
|
||||||
b = hh(b, c, d, a, k[10], 23, -1094730640);
|
|
||||||
a = hh(a, b, c, d, k[13], 4, 681279174);
|
|
||||||
d = hh(d, a, b, c, k[0], 11, -358537222);
|
|
||||||
c = hh(c, d, a, b, k[3], 16, -722521979);
|
|
||||||
b = hh(b, c, d, a, k[6], 23, 76029189);
|
|
||||||
a = hh(a, b, c, d, k[9], 4, -640364487);
|
|
||||||
d = hh(d, a, b, c, k[12], 11, -421815835);
|
|
||||||
c = hh(c, d, a, b, k[15], 16, 530742520);
|
|
||||||
b = hh(b, c, d, a, k[2], 23, -995338651);
|
|
||||||
|
|
||||||
a = ii(a, b, c, d, k[0], 6, -198630844);
|
|
||||||
d = ii(d, a, b, c, k[7], 10, 1126891415);
|
|
||||||
c = ii(c, d, a, b, k[14], 15, -1416354905);
|
|
||||||
b = ii(b, c, d, a, k[5], 21, -57434055);
|
|
||||||
a = ii(a, b, c, d, k[12], 6, 1700485571);
|
|
||||||
d = ii(d, a, b, c, k[3], 10, -1894986606);
|
|
||||||
c = ii(c, d, a, b, k[10], 15, -1051523);
|
|
||||||
b = ii(b, c, d, a, k[1], 21, -2054922799);
|
|
||||||
a = ii(a, b, c, d, k[8], 6, 1873313359);
|
|
||||||
d = ii(d, a, b, c, k[15], 10, -30611744);
|
|
||||||
c = ii(c, d, a, b, k[6], 15, -1560198380);
|
|
||||||
b = ii(b, c, d, a, k[13], 21, 1309151649);
|
|
||||||
a = ii(a, b, c, d, k[4], 6, -145523070);
|
|
||||||
d = ii(d, a, b, c, k[11], 10, -1120210379);
|
|
||||||
c = ii(c, d, a, b, k[2], 15, 718787259);
|
|
||||||
b = ii(b, c, d, a, k[9], 21, -343485551);
|
|
||||||
|
|
||||||
x[0] = add32(a, x[0]);
|
|
||||||
x[1] = add32(b, x[1]);
|
|
||||||
x[2] = add32(c, x[2]);
|
|
||||||
x[3] = add32(d, x[3]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const rhex = (n) => {
|
|
||||||
let s = '';
|
|
||||||
for (let j = 0; j < 4; j++) {
|
|
||||||
s += HEX_CHARS[(n >> (j * 8 + 4)) & 0x0F] + HEX_CHARS[(n >> (j * 8)) & 0x0F];
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
};
|
|
||||||
|
|
||||||
const hex = (x) => x.map(rhex).join('');
|
|
||||||
|
|
||||||
const md51 = (s) => {
|
|
||||||
const n = s.length;
|
|
||||||
const state = [1732584193, -271733879, -1732584194, 271733878];
|
|
||||||
|
|
||||||
let i;
|
|
||||||
for (i = 64; i <= n; i += 64) {
|
|
||||||
md5cycle(state, md5blk(s.substring(i - 64, i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
s = s.substring(i - 64);
|
|
||||||
const tail = new Array(16).fill(0);
|
|
||||||
|
|
||||||
for (i = 0; i < s.length; i++) {
|
|
||||||
tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
|
|
||||||
|
|
||||||
if (i > 55) {
|
|
||||||
md5cycle(state, tail);
|
|
||||||
tail.fill(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
tail[14] = n * 8;
|
|
||||||
md5cycle(state, tail);
|
|
||||||
|
|
||||||
return state;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate MD5 hash of a string
|
|
||||||
* @param {string} s - Input string
|
|
||||||
* @returns {string} MD5 hash in hexadecimal format
|
|
||||||
*/
|
|
||||||
export function md5(s) {
|
|
||||||
return hex(md51(s));
|
|
||||||
}
|
|
||||||
+41
-115
@@ -1,127 +1,53 @@
|
|||||||
import { apiFormatDate } from "./dengi";
|
import { apiFormatDate } from "./dengi";
|
||||||
|
|
||||||
|
export async function makeRequest(url, params) {
|
||||||
|
try {
|
||||||
|
const req = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
|
body: new URLSearchParams(params).toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return req.ok &&
|
||||||
|
req.headers.get("Content-Type")?.includes("application/json")
|
||||||
|
? [1, await req.json()]
|
||||||
|
: [req.status, await req.text()];
|
||||||
|
} catch (err) {
|
||||||
|
return [-1, err];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function LoginReq(host, username) {
|
export async function LoginReq(host, username) {
|
||||||
try {
|
return makeRequest(host + "?login", { classId: username });
|
||||||
const req = await fetch(host + "?login", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded", // 💀 важный заголовок
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
classId: username,
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
req.ok &&
|
|
||||||
req.headers.get("Content-Type")?.includes("application/json")
|
|
||||||
) {
|
|
||||||
return [1, await req.json()];
|
|
||||||
} else {
|
|
||||||
return [req.status, await req.text()];
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return [-1, err];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function RaspGet(host, classId, day) {
|
export async function RaspGet(host, classId, day) {
|
||||||
try {
|
return makeRequest(host + "?req_client_schedule", {
|
||||||
const req = await fetch(host + "?req_client_schedule", {
|
classId,
|
||||||
method: "POST",
|
day: apiFormatDate(day),
|
||||||
headers: {
|
});
|
||||||
"Content-Type": "application/x-www-form-urlencoded", // 💀 важный заголовок
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
classId: classId,
|
|
||||||
day: apiFormatDate(day),
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
req.ok &&
|
|
||||||
req.headers.get("Content-Type")?.includes("application/json")
|
|
||||||
) {
|
|
||||||
return [1, await req.json()];
|
|
||||||
} else {
|
|
||||||
return [req.status, await req.text()];
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return [-1, err];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export async function AdminReq(host, adminid) {
|
|
||||||
try {
|
|
||||||
const req = await fetch(host + "?admin_login", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded", // 💀 dengi
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
adminId: adminid,
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
req.ok &&
|
|
||||||
req.headers.get("Content-Type")?.includes("application/json")
|
|
||||||
) {
|
|
||||||
return [1, await req.json()];
|
|
||||||
} else {
|
|
||||||
return [req.status, await req.text()];
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return [-1, err];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function AdminUserFRequest(host, adminid) {
|
export async function AdminReq(host, adminId) {
|
||||||
try {
|
return makeRequest(host + "?admin_login", { adminId });
|
||||||
const req = await fetch(host + "?get_admin_user_f", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
adminId: adminid,
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
req.ok &&
|
|
||||||
req.headers.get("Content-Type")?.includes("application/json")
|
|
||||||
) {
|
|
||||||
return [1, await req.json()];
|
|
||||||
} else {
|
|
||||||
return [req.status, await req.text()];
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return [-1, err];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function classUserFRequest(host, classid) {
|
export async function AdminUserFRequest(host, adminId) {
|
||||||
try {
|
return makeRequest(host + "?get_admin_user_f", { adminId });
|
||||||
const req = await fetch(host + "?get_class_user_f", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
classId: classid,
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
req.ok &&
|
|
||||||
req.headers.get("Content-Type")?.includes("application/json")
|
|
||||||
) {
|
|
||||||
return [1, await req.json()];
|
|
||||||
} else {
|
|
||||||
return [req.status, await req.text()];
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return [-1, err];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function classUserFRequest(host, classId) {
|
||||||
|
return makeRequest(host + "?get_class_user_f", { classId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@param {{action:string,person?:string,value?}} data */
|
||||||
|
export async function AdminUpdateRequest(host, adminId, data) {
|
||||||
|
return makeRequest(host + "?update_admin", { adminId, ...data });
|
||||||
|
}
|
||||||
|
/**@param {{action:string,value?}} data */
|
||||||
|
export async function ClassUpdateRequest(host, adminId, data) {
|
||||||
|
return makeRequest(host + "?classprofile_edit", { adminId, ...data });
|
||||||
|
}
|
||||||
|
export async function getClassProfile(host, adminId) {
|
||||||
|
return makeRequest(host + "?classprofile_view", { adminId });
|
||||||
|
}
|
||||||
|
|||||||
+179
-27
@@ -1,8 +1,15 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { BiCheck, BiLeftArrowAlt, BiRightArrowAlt, BiX } from "react-icons/bi";
|
import {
|
||||||
|
BiCheck,
|
||||||
|
BiHide,
|
||||||
|
BiLeftArrowAlt,
|
||||||
|
BiRightArrowAlt,
|
||||||
|
BiShow,
|
||||||
|
BiX,
|
||||||
|
} from "react-icons/bi";
|
||||||
import { formatDate, getDateTags } from "./dengi.js";
|
import { formatDate, getDateTags } from "./dengi.js";
|
||||||
import { AnimatePresence, motion, LayoutGroup } from "motion/react";
|
import { AnimatePresence, motion, LayoutGroup } from "motion/react";
|
||||||
|
import trashcss from "./trash.module.css";
|
||||||
export function Switch({
|
export function Switch({
|
||||||
checked,
|
checked,
|
||||||
defaultChecked = false,
|
defaultChecked = false,
|
||||||
@@ -67,7 +74,7 @@ export function Switch({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LoadingSpinner(props) {
|
export function LoadingSpinner({ size }) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -75,8 +82,8 @@ export function LoadingSpinner(props) {
|
|||||||
viewBox="0 0 100 101"
|
viewBox="0 0 100 101"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width={props.size}
|
width={size}
|
||||||
height={props.size}
|
height={size}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||||
@@ -90,10 +97,14 @@ export function LoadingSpinner(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DatePickerButton({ next = false, date, setDate }) {
|
function DatePickerButton({ next = false, date, setDate, pink = false }) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className="cursor-pointer lg:hover:*:fill-black lg:hover:bg-white transition *:transition-colors rounded-full"
|
className={`cursor-pointer ${
|
||||||
|
!pink
|
||||||
|
? "lg:hover:*:fill-black lg:hover:bg-white"
|
||||||
|
: "lg:hover:*:fill-white lg:hover:bg-pink-500"
|
||||||
|
} transition *:transition-colors rounded-full`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
d.setDate(d.getDate() + (next ? 1 : -1));
|
d.setDate(d.getDate() + (next ? 1 : -1));
|
||||||
@@ -105,17 +116,29 @@ function DatePickerButton({ next = false, date, setDate }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DatePicker = React.memo((props) => {
|
export const DatePicker = (props) => {
|
||||||
const dateTag = getDateTags(props.date);
|
const dateTag = getDateTags(props.date);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutGroup>
|
<LayoutGroup>
|
||||||
<div className="flex items-center justify-between px-2 py-1 w-full bg-grain border-zinc-800/40 border rounded-2xl relative">
|
<div
|
||||||
<DatePickerButton date={props.date} setDate={props.setDate} />
|
className={`flex items-center justify-between px-2 py-1 w-full bg-grain ${
|
||||||
|
!props.pink ? "border-zinc-800/40" : "border-pink-500/20"
|
||||||
|
} border rounded-2xl relative`}
|
||||||
|
>
|
||||||
|
<DatePickerButton
|
||||||
|
date={props.date}
|
||||||
|
setDate={props.setDate}
|
||||||
|
pink={props.pink}
|
||||||
|
/>
|
||||||
<AnimatePresence mode="popLayout">
|
<AnimatePresence mode="popLayout">
|
||||||
<motion.span
|
<motion.span
|
||||||
key={formatDate(props.date)}
|
key={formatDate(props.date)}
|
||||||
className="cursor-pointer flex items-center gap-1 justify-center w-[80%] text-zinc-300 lg:hover:text-black *:transition-colors transition-colors p-1 rounded-3xl lg:hover:bg-white px-3 overflow-hidden lg:hover:*:text-black"
|
className={`cursor-pointer flex items-center gap-1 justify-center w-[80%] lg:hover:text-black *:transition-colors transition-colors p-1 rounded-3xl text-zinc-300 ${
|
||||||
|
!props.pink
|
||||||
|
? "lg:hover:bg-white lg:hover:*:text-black"
|
||||||
|
: "lg:hover:bg-pink-500 lg:hover:*:text-white"
|
||||||
|
} px-3 overflow-hidden `}
|
||||||
variants={{
|
variants={{
|
||||||
a: { opacity: 0, filter: "blur(1px)" },
|
a: { opacity: 0, filter: "blur(1px)" },
|
||||||
b: { opacity: 1, filter: "blur(0px)" },
|
b: { opacity: 1, filter: "blur(0px)" },
|
||||||
@@ -131,7 +154,9 @@ export const DatePicker = React.memo((props) => {
|
|||||||
className={
|
className={
|
||||||
"block min-w-2 min-h-2 rounded-full mx-1 transition-colors" +
|
"block min-w-2 min-h-2 rounded-full mx-1 transition-colors" +
|
||||||
(props.indicator
|
(props.indicator
|
||||||
? " bg-zinc-300 animate-pulse"
|
? props.pink
|
||||||
|
? " bg-pink-500 animate-pulse"
|
||||||
|
: " bg-zinc-300 animate-pulse"
|
||||||
: " bg-transparent")
|
: " bg-transparent")
|
||||||
}
|
}
|
||||||
layoutId="datepicker_indicator"
|
layoutId="datepicker_indicator"
|
||||||
@@ -151,7 +176,11 @@ export const DatePicker = React.memo((props) => {
|
|||||||
layoutId="datepicker_date_div"
|
layoutId="datepicker_date_div"
|
||||||
layout="position"
|
layout="position"
|
||||||
>
|
>
|
||||||
<span className="h-[1px] -rotate-50 bg-zinc-800 w-[20px] " />
|
<span
|
||||||
|
className={`h-[1px] -rotate-50 ${
|
||||||
|
props.pink ? "bg-pink-400/50" : "bg-zinc-800"
|
||||||
|
} w-[20px] `}
|
||||||
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.span
|
<motion.span
|
||||||
initial={{ x: 10, opacity: 0 }}
|
initial={{ x: 10, opacity: 0 }}
|
||||||
@@ -165,11 +194,16 @@ export const DatePicker = React.memo((props) => {
|
|||||||
)}
|
)}
|
||||||
</motion.span>
|
</motion.span>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<DatePickerButton next date={props.date} setDate={props.setDate} />
|
<DatePickerButton
|
||||||
|
next
|
||||||
|
date={props.date}
|
||||||
|
setDate={props.setDate}
|
||||||
|
pink={props.pink}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</LayoutGroup>
|
</LayoutGroup>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
export function DengiButton({
|
export function DengiButton({
|
||||||
onClick,
|
onClick,
|
||||||
@@ -235,23 +269,24 @@ export function DengiButton({
|
|||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
const defaultDialogContent = {
|
||||||
|
message: "?",
|
||||||
|
yesBtn: "da",
|
||||||
|
noBtn: "net",
|
||||||
|
bgDissmis: true,
|
||||||
|
yesBlocked: false,
|
||||||
|
yesIcon: <BiCheck />,
|
||||||
|
noIcon: <BiX />,
|
||||||
|
noBtnStyle: "",
|
||||||
|
yesBtnStyle: "",
|
||||||
|
};
|
||||||
export function DengiYesOrNoDialog({
|
export function DengiYesOrNoDialog({
|
||||||
content = {
|
content = defaultDialogContent,
|
||||||
message: "?",
|
|
||||||
yesBtn: "da",
|
|
||||||
noBtn: "net",
|
|
||||||
bgDissmis: true,
|
|
||||||
yesIcon: <BiCheck />,
|
|
||||||
noIcon: <BiX />,
|
|
||||||
noBtnStyle: "",
|
|
||||||
yesBtnStyle: "",
|
|
||||||
},
|
|
||||||
show,
|
show,
|
||||||
onAnswer,
|
onAnswer,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Background */}
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{show && (
|
{show && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -280,6 +315,7 @@ export function DengiYesOrNoDialog({
|
|||||||
<DengiButton
|
<DengiButton
|
||||||
className={content.yesBtnStyle + " w-full"}
|
className={content.yesBtnStyle + " w-full"}
|
||||||
onClick={() => onAnswer(true)}
|
onClick={() => onAnswer(true)}
|
||||||
|
disabled={content.yesBlocked}
|
||||||
>
|
>
|
||||||
{content.yesIcon}
|
{content.yesIcon}
|
||||||
{content.yesBtn}
|
{content.yesBtn}
|
||||||
@@ -299,3 +335,119 @@ export function DengiYesOrNoDialog({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function DengiEditable({
|
||||||
|
value,
|
||||||
|
password = false,
|
||||||
|
disWHidden = true,
|
||||||
|
disabled = false,
|
||||||
|
showLoading = true,
|
||||||
|
minLenght = 1,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
onSubmit = ({ value, setIdle }) => {},
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
onInput = ({ value, setIdle }) => {},
|
||||||
|
pink = false,
|
||||||
|
}) {
|
||||||
|
const [val, setVal] = useState(value);
|
||||||
|
const [hid, setHid] = useState(true);
|
||||||
|
const [idle, setIdle] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
setVal(value);
|
||||||
|
}, [value]);
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
action="#"
|
||||||
|
className="flex rounded-xl overflow-hidden max-w-50 h-8 relative"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
if (idle) return;
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit({ value: val, setIdle });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value !== false || !showLoading ? (
|
||||||
|
<input
|
||||||
|
type={hid && password ? "password" : "text"}
|
||||||
|
value={val}
|
||||||
|
minLength={minLenght}
|
||||||
|
disabled={(disWHidden && password && hid) || disabled || idle}
|
||||||
|
className={`${pink ? "bg-pink-700/20" : "bg-zinc-800"} ${
|
||||||
|
val === value && !password ? "rounded-r-xl" : ""
|
||||||
|
} pl-2 rounded-l-xl w-full py-1 transition`}
|
||||||
|
onInput={(e) => {
|
||||||
|
setVal(e.target.value);
|
||||||
|
onInput({ value: e.target.value, setIdle });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className={`w-full ${
|
||||||
|
pink ? "bg-pink-700/20" : "bg-zinc-800"
|
||||||
|
} py-1 w-full flex items-center justify-center`}
|
||||||
|
>
|
||||||
|
<LoadingSpinner size={25} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{password && (
|
||||||
|
<button
|
||||||
|
className={`${
|
||||||
|
pink
|
||||||
|
? "bg-pink-400 hover:bg-pink-600"
|
||||||
|
: "bg-zinc-600 hover:bg-zinc-700 "
|
||||||
|
} cursor-pointer transition-colors ${
|
||||||
|
val === value ? "rounded-r-xl" : ""
|
||||||
|
} p-1 text-2xl `}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if ((disWHidden && password && hid) || disabled || idle) return;
|
||||||
|
setVal(value);
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setHid(!hid);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hid ? <BiShow /> : <BiHide />}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<AnimatePresence mode="popLayout">
|
||||||
|
{val !== value && (
|
||||||
|
<motion.button
|
||||||
|
initial={{ opacity: 0, translateX: 10 }}
|
||||||
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
disabled={idle}
|
||||||
|
className={`${
|
||||||
|
pink
|
||||||
|
? "bg-pink-400 disabled:bg-pink-900/50 hover:bg-pink-600"
|
||||||
|
: "bg-zinc-600 disabled:bg-zinc-900 hover:bg-zinc-700"
|
||||||
|
} p-1 text-2xl rounded-r-xl transition-colors not-disabled:cursor-pointer`}
|
||||||
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setVal(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{idle ? <LoadingSpinner size={24} /> : <BiCheck />}
|
||||||
|
</motion.button>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TextPreloader({ width = "5rem" }) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={"h-4 block bg-zinc-900 overflow-hidden rounded-full w-(--w)"}
|
||||||
|
style={{ "--w": width }}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={
|
||||||
|
"block h-10 -translate-y-3 w-3 bg-zinc-300 relative blur-[7px] " +
|
||||||
|
trashcss.anim_preloader
|
||||||
|
}
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
.anim_preloader {
|
||||||
|
animation: anim-preloader 1s infinite linear;
|
||||||
|
}
|
||||||
|
@keyframes anim-preloader {
|
||||||
|
from {
|
||||||
|
transform: translateX(-50px) rotate(20deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(calc((var(--w, 50px)) + (50px))) rotate(20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-2
@@ -1,5 +1,5 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import react from "@vitejs/plugin-react-swc";
|
import react from "@vitejs/plugin-react";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import { VitePWA } from "vite-plugin-pwa";
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
|
|
||||||
@@ -7,7 +7,11 @@ import { VitePWA } from "vite-plugin-pwa";
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: "/dx4/",
|
base: "/dx4/",
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react({
|
||||||
|
babel: {
|
||||||
|
plugins: [["babel-plugin-react-compiler"]],
|
||||||
|
},
|
||||||
|
}),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: "autoUpdate",
|
registerType: "autoUpdate",
|
||||||
|
|||||||
Reference in New Issue
Block a user