This commit is contained in:
2026-01-07 22:27:52 +03:00
parent 481e532dce
commit ff1174671a
55 changed files with 11201 additions and 858 deletions
+32 -4
View File
@@ -53,7 +53,11 @@ export function usToRussianDay(usDay) {
export function formatDate(date) {
return `${
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) {
return (
@@ -128,9 +132,33 @@ export function convertEditorToApi(arr) {
}
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;
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();
}
}
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("")];
+18 -5
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import {
DndContext,
closestCenter,
@@ -155,6 +155,7 @@ function SortableRow({
onDuplicate,
setFocusEdit,
setHoverEdit,
pickmeMode,
}) {
const {
attributes,
@@ -175,7 +176,9 @@ function SortableRow({
<div
ref={setNodeRef}
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">
@@ -188,7 +191,11 @@ function SortableRow({
wrap="hard"
rows={1}
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}
onFocus={() => setFocusEdit(col.key)}
onBlur={() => setFocusEdit(null)}
@@ -244,6 +251,7 @@ export function TableEditor({
adminId,
date,
dengiCash,
pickmeMode = false,
}) {
const [items, setItems] = useState(initialData);
const [undoItems, setUndoItems] = useState([]);
@@ -553,7 +561,9 @@ export function TableEditor({
<span
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}
setFocusEdit={setFocusEdit}
setHoverEdit={setHoverEdit}
pickmeMode={pickmeMode}
/>
</motion.div>
))}
@@ -600,7 +611,9 @@ export function TableEditor({
opacity: 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>
+19 -73
View File
@@ -1,80 +1,26 @@
import { makeRequest } from "./req_mgr";
export async function EditorPrefillRequest(host, tab, adminid, date) {
try {
const req = await fetch(host + "?editor_data", {
method: "POST",
headers: {
"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];
}
return makeRequest(host + "?editor_data", {
adminId: adminid,
tab,
day: date,
});
}
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 (
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) {
return makeRequest(host + "?editor_publish", {
adminId: adminid,
tab,
day: date,
newData,
});
}
export async function EditorRemoveRequest(host, tab, adminid, date) {
try {
const req = await fetch(host + "?editor_fullitems_del", {
method: "POST",
headers: {
"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];
}
return makeRequest(host + "?editor_fullitems_del", {
adminId: adminid,
tab,
day: date,
});
}
+28 -21
View File
@@ -23,10 +23,7 @@ export function b64d(str) {
);
}
/** 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
* @returns {Promise<boolean>} - Returns true if successful, false otherwise
*/
@@ -37,34 +34,44 @@ export async function copyToClipboard(text) {
await navigator.clipboard.writeText(text);
return true;
} 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
try {
const textarea = document.createElement('textarea');
const textarea = document.createElement("textarea");
textarea.value = text;
// Make it invisible and non-intrusive
textarea.style.position = 'fixed';
textarea.style.left = '-999999px';
textarea.style.top = '-999999px';
textarea.style.opacity = '0';
textarea.setAttribute('readonly', '');
textarea.style.position = "fixed";
textarea.style.left = "-999999px";
textarea.style.top = "-999999px";
textarea.style.opacity = "0";
textarea.setAttribute("readonly", "");
document.body.appendChild(textarea);
// Select and copy
textarea.select();
textarea.setSelectionRange(0, text.length);
const success = document.execCommand('copy');
const success = document.execCommand("copy");
document.body.removeChild(textarea);
return success;
} catch (err) {
console.error('All copy methods failed:', err);
console.error("All copy methods failed:", err);
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("");
};
-167
View File
@@ -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
View File
@@ -1,127 +1,53 @@
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) {
try {
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];
}
return makeRequest(host + "?login", { classId: username });
}
export async function RaspGet(host, classId, day) {
try {
const req = await fetch(host + "?req_client_schedule", {
method: "POST",
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];
}
return makeRequest(host + "?req_client_schedule", {
classId,
day: apiFormatDate(day),
});
}
export async function AdminUserFRequest(host, adminid) {
try {
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 AdminReq(host, adminId) {
return makeRequest(host + "?admin_login", { adminId });
}
export async function classUserFRequest(host, classid) {
try {
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 AdminUserFRequest(host, adminId) {
return makeRequest(host + "?get_admin_user_f", { adminId });
}
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
View File
@@ -1,8 +1,15 @@
import React, { useEffect, useState } from "react";
import { BiCheck, BiLeftArrowAlt, BiRightArrowAlt, BiX } from "react-icons/bi";
import { useEffect, useState } from "react";
import {
BiCheck,
BiHide,
BiLeftArrowAlt,
BiRightArrowAlt,
BiShow,
BiX,
} from "react-icons/bi";
import { formatDate, getDateTags } from "./dengi.js";
import { AnimatePresence, motion, LayoutGroup } from "motion/react";
import trashcss from "./trash.module.css";
export function Switch({
checked,
defaultChecked = false,
@@ -67,7 +74,7 @@ export function Switch({
);
}
export function LoadingSpinner(props) {
export function LoadingSpinner({ size }) {
return (
<svg
aria-hidden="true"
@@ -75,8 +82,8 @@ export function LoadingSpinner(props) {
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
width={props.size}
height={props.size}
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"
@@ -90,10 +97,14 @@ export function LoadingSpinner(props) {
);
}
function DatePickerButton({ next = false, date, setDate }) {
function DatePickerButton({ next = false, date, setDate, pink = false }) {
return (
<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={() => {
const d = new Date(date);
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);
return (
<LayoutGroup>
<div className="flex items-center justify-between px-2 py-1 w-full bg-grain border-zinc-800/40 border rounded-2xl relative">
<DatePickerButton date={props.date} setDate={props.setDate} />
<div
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">
<motion.span
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={{
a: { opacity: 0, filter: "blur(1px)" },
b: { opacity: 1, filter: "blur(0px)" },
@@ -131,7 +154,9 @@ export const DatePicker = React.memo((props) => {
className={
"block min-w-2 min-h-2 rounded-full mx-1 transition-colors" +
(props.indicator
? " bg-zinc-300 animate-pulse"
? props.pink
? " bg-pink-500 animate-pulse"
: " bg-zinc-300 animate-pulse"
: " bg-transparent")
}
layoutId="datepicker_indicator"
@@ -151,7 +176,11 @@ export const DatePicker = React.memo((props) => {
layoutId="datepicker_date_div"
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.span
initial={{ x: 10, opacity: 0 }}
@@ -165,11 +194,16 @@ export const DatePicker = React.memo((props) => {
)}
</motion.span>
</AnimatePresence>
<DatePickerButton next date={props.date} setDate={props.setDate} />
<DatePickerButton
next
date={props.date}
setDate={props.setDate}
pink={props.pink}
/>
</div>
</LayoutGroup>
);
});
};
export function DengiButton({
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({
content = {
message: "?",
yesBtn: "da",
noBtn: "net",
bgDissmis: true,
yesIcon: <BiCheck />,
noIcon: <BiX />,
noBtnStyle: "",
yesBtnStyle: "",
},
content = defaultDialogContent,
show,
onAnswer,
}) {
return (
<>
{/* Background */}
<AnimatePresence>
{show && (
<motion.div
@@ -280,6 +315,7 @@ export function DengiYesOrNoDialog({
<DengiButton
className={content.yesBtnStyle + " w-full"}
onClick={() => onAnswer(true)}
disabled={content.yesBlocked}
>
{content.yesIcon}
{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>
);
}
+11
View File
@@ -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);
}
}