bug fixes

This commit is contained in:
unknown
2025-11-29 20:53:09 +03:00
parent 641a12bb0f
commit 61674fbc6f
6 changed files with 272 additions and 225 deletions
+3
View File
@@ -15,6 +15,7 @@
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"tailwindcss": "^4.1.12", "tailwindcss": "^4.1.12",
"use-debounce": "^10.0.6",
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.33.0", "@eslint/js": "^9.33.0",
@@ -1135,6 +1136,8 @@
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"use-debounce": ["use-debounce@10.0.6", "", { "peerDependencies": { "react": "*" } }, "sha512-C5OtPyhAZgVoteO9heXMTdW7v/IbFI+8bSVKYCJrSmiWWCLsbUxiBSp4t9v0hNBTGY97bT72ydDIDyGSFWfwXg=="],
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
+17
View File
@@ -64,6 +64,15 @@
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 {
@@ -74,9 +83,11 @@
from, from,
to { to {
opacity: 1; opacity: 1;
transform: scale(100%);
} }
50% { 50% {
opacity: 0.3; opacity: 0.3;
transform: scale(98%);
} }
} }
@keyframes ktkz-preloader-show { @keyframes ktkz-preloader-show {
@@ -93,6 +104,12 @@
<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>
+2 -1
View File
@@ -20,7 +20,8 @@
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"tailwindcss": "^4.1.12" "tailwindcss": "^4.1.12",
"use-debounce": "^10.0.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.33.0", "@eslint/js": "^9.33.0",
+186 -162
View File
@@ -19,7 +19,7 @@ import {
classUserFRequest, classUserFRequest,
RaspGet, RaspGet,
} from "../trash/req_mgr"; } from "../trash/req_mgr";
import { AnimatePresence, motion } from "motion/react"; 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";
@@ -90,7 +90,6 @@ export function DX4App({
class: false, class: false,
}); });
const [classLinkCopied, setClassLinkCopied] = useState(false);
const lessonsEndedTriggered = useRef(false); const lessonsEndedTriggered = useRef(false);
//TODO: Auto change day v2 //TODO: Auto change day v2
@@ -151,6 +150,37 @@ export function DX4App({
})(); })();
}, [classId, adminId]); }, [classId, adminId]);
const handleKeyPress = (event) => {
switch (event.key) {
case "a":
case "ArrowLeft":
setSelectedDate((prevDate) => {
const newDate = new Date(prevDate);
newDate.setDate(newDate.getDate() - 1);
return newDate;
});
break;
case "d":
case "ArrowRight":
setSelectedDate((prevDate) => {
const newDate = new Date(prevDate);
newDate.setDate(newDate.getDate() + 1);
return newDate;
});
break;
default:
break;
}
};
useEffect(() => {
window.addEventListener("keydown", handleKeyPress);
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, []);
if (classId === "") { if (classId === "") {
setsetRoute(setRoute, "/"); setsetRoute(setRoute, "/");
} }
@@ -167,7 +197,6 @@ export function DX4App({
"flex items-center w-full justify-between px-5" + "flex items-center w-full justify-between px-5" +
ifstyle(openUserSettings, " pt-5") ifstyle(openUserSettings, " pt-5")
} }
layout="position"
> >
<motion.img <motion.img
src={Logo} src={Logo}
@@ -214,125 +243,53 @@ export function DX4App({
/> />
</motion.span> </motion.span>
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{openUserSettings && ( <UserSettings
<motion.div {...{
className="flex flex-col bg-grain gap-2 bg-zinc-700/20 p-2 rounded-2xl" openUserSettings,
variants={{ userFrNames,
hidden: { opacity: 0, y: 10, filter: "blur(2px)" }, setUserId,
show: { opacity: 1, y: 0, filter: "blur(0)" }, setsetRoute,
}} setRoute,
initial="hidden" adminId,
animate="show" classId,
exit="hidden" pickmeMode,
> }}
{[ />
// icon
// text
// onclick
// condition
[
<BiExit />,
"выход",
() => {
setUserId("");
setsetRoute(setRoute, "/");
},
true,
],
// [
// <BiStar />,
// "пикми режим",
// () => {
// setPickmeMode(!pickmeMode);
// },
// true,
// ],
[
<BiSolidUser />,
`админ ${
userFrNames["admin"] ? userFrNames["admin"] : adminId
}`,
() => {},
adminId != "",
],
[
<BiLink />,
classLinkCopied ? "скопированно" : "коп. ссылку",
() => {
copyToClipboard(
location.href.substring(
0,
location.href.search("#")
) +
"#//" +
classId
).then((a) => {
if (a) {
setClassLinkCopied(true);
setTimeout(() => {
setClassLinkCopied(false);
}, 2000);
}
});
},
classId != "",
],
].map(
(item, i) =>
item[3] && (
<div
key={i}
className={
"flex gap-2 items-center justify-center transition-colors " +
(pickmeMode
? "lg:hover:bg-pink-400"
: "lg:hover:bg-white lg:hover:text-black") +
" p-1 rounded-xl cursor-pointer"
}
onClick={item[2]}
>
{item[0]} {item[1]}
</div>
)
)}
</motion.div>
)}
</AnimatePresence> </AnimatePresence>
</motion.div> </motion.div>
</motion.div> </motion.div>
{adminId != "" && (
<motion.div
layout="position"
className="flex w-full justify-end px-5"
>
<a
// onClick={() => {
// setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`);
// }}
href={`#/editor/${apiFormatDate(selectedDate)}`}
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"
>
<BiPencil /> открыть в редакторе
</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 != "" && (
<motion.div className="flex w-full justify-end">
<a
// onClick={() => {
// setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`);
// }}
href={`#/editor/${apiFormatDate(selectedDate)}`}
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"
>
<BiPencil /> открыть в редакторе
</a>
</motion.div>
)}
<DatePicker <DatePicker
date={selectedDate} date={selectedDate}
setDate={setSelectedDate} setDate={setSelectedDate}
indicator={typeof rasp[2] == "number" && [1, 2].includes(rasp[2])} indicator={typeof rasp[2] == "number" && [1, 2].includes(rasp[2])}
/> />
<RaspView {...{ rasp, pickmeMode, selectedDate }} /> <div className="relative">
<AnimatePresence mode="popLayout"> <LayoutGroup>
<RaspView {...{ rasp, pickmeMode, selectedDate }} />
</LayoutGroup>
{!rasp[0] && ( {!rasp[0] && (
<motion.div <motion.div
className="w-full flex justify-center" className="w-full flex justify-center"
initial="hid" initial="hid"
animate="show" animate="show"
exit="hid"
variants={{ variants={{
show: { opacity: 1, scale: 1 }, show: { opacity: 1, scale: 1 },
hid: { opacity: 0, scale: 0 }, hid: { opacity: 0, scale: 0 },
@@ -341,27 +298,24 @@ export function DX4App({
<LoadingSpinner size={25} /> <LoadingSpinner size={25} />
</motion.div> </motion.div>
)} )}
</AnimatePresence> </div>
</motion.div> </motion.div>
</motion.div> </motion.div>
{/* <CoolMouseBg /> */}
</motion.div> </motion.div>
<CoolMainBg {...{ pickmeMode }} /> <CoolMainBg {...{ pickmeMode }} />
</> </>
); );
} }
function CoolMainBg({ pickmeMode }) { const CoolMainBg = React.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
${pickmeMode ? "from-pink-400/10" : "from-white/2"}`} ${pickmeMode ? "from-pink-400/10" : "from-white/2"}`}
/> />
); );
} });
export function RaspView({ rasp, pickmeMode, selectedDate }) {
const [tableColHover, setTableColHover] = useState(-1);
export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
return ( return (
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{rasp[0] && typeof rasp[1] == "object" && ( {rasp[0] && typeof rasp[1] == "object" && (
@@ -388,48 +342,24 @@ export function RaspView({ rasp, pickmeMode, selectedDate }) {
className="max-w-10" className="max-w-10"
//layout="position" //layout="position"
//layoutId="table-num-h" //layoutId="table-num-h"
onMouseEnter={() => {
setTableColHover(0);
}}
onMouseLeave={() => {
setTableColHover(-1);
}}
> >
n урока n урока
</th> </th>
<th <th
//layout="position" //layout="position"
//layoutId="table-subj-h" //layoutId="table-subj-h"
onMouseEnter={() => {
setTableColHover(1);
}}
onMouseLeave={() => {
setTableColHover(-1);
}}
> >
предмет предмет
</th> </th>
<th <th
//layout="position" //layout="position"
//layoutId="table-cab-h" //layoutId="table-cab-h"
onMouseEnter={() => {
setTableColHover(2);
}}
onMouseLeave={() => {
setTableColHover(-1);
}}
> >
каб каб
</th> </th>
<th <th
//layout="position" //layout="position"
//layoutId="table-t-h" //layoutId="table-t-h"
onMouseEnter={() => {
setTableColHover(3);
}}
onMouseLeave={() => {
setTableColHover(-1);
}}
> >
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
{rasp[2] == 2 && ( {rasp[2] == 2 && (
@@ -449,19 +379,14 @@ export function RaspView({ rasp, pickmeMode, selectedDate }) {
//rasp-key={"rasp_key_" + i + Array(d).join()} //rasp-key={"rasp_key_" + i + Array(d).join()}
> >
<td <td
className={ //layout="position"
(tableColHover == 0 ? "lg:bg-zinc-300/15" : "") + //layoutId={`table-num-${i}`}
" font-bold"
}
//layout="position"
//layoutId={`table-num-${i}`}
> >
{d["num"]} {d["num"]}
</td> </td>
<td <td
className={tableColHover == 1 ? "lg:bg-zinc-300/15" : ""} //layout="position"
//layout="position" //layoutId={`table-subj-${i}`}
//layoutId={`table-subj-${i}`}
> >
{d.subject {d.subject
.toString() .toString()
@@ -474,9 +399,8 @@ export function RaspView({ rasp, pickmeMode, selectedDate }) {
)) || d.subject} )) || d.subject}
</td> </td>
<td <td
//layout="position" //layout="position"
//layoutId={`table-cab-${i}`} //layoutId={`table-cab-${i}`}
className={tableColHover == 2 ? "lg:bg-zinc-300/15" : ""}
> >
{d.cabinet {d.cabinet
.toString() .toString()
@@ -488,7 +412,7 @@ export function RaspView({ rasp, pickmeMode, selectedDate }) {
</React.Fragment> </React.Fragment>
)) || d.cabinet} )) || d.cabinet}
</td> </td>
<td className={tableColHover == 3 ? "lg:bg-zinc-300/15" : ""}> <td>
<div <div
//layout="position" //layout="position"
//layoutId={`table-time-${i}`} //layoutId={`table-time-${i}`}
@@ -510,18 +434,18 @@ export function RaspView({ rasp, pickmeMode, selectedDate }) {
{typeof rasp[1] == "number" && rasp[1] === -322 && ( {typeof rasp[1] == "number" && rasp[1] === -322 && (
<motion.div <motion.div
className="w-full flex justify-center" className="w-full flex justify-center"
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1 }}
exit={{ opacity: 0, x: 10 }} exit={{ opacity: 0 }}
> >
<span className="text-2xl font-bold">нет уроков</span> <span className="text-2xl font-bold">нет уроков</span>
</motion.div> </motion.div>
)} )}
{typeof rasp[1] == "string" && rasp[1].startsWith("ошибка") && ( {typeof rasp[1] == "string" && rasp[1].startsWith("ошибка") && (
<motion.div <motion.div
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1 }}
exit={{ opacity: 0, x: 10 }} exit={{ opacity: 0 }}
className="w-full flex justify-center" className="w-full flex justify-center"
> >
<span className="text-xl text-center text-transparent bg-clip-text bg-gradient-to-r from-red-300 to-red-500"> <span className="text-xl text-center text-transparent bg-clip-text bg-gradient-to-r from-red-300 to-red-500">
@@ -531,4 +455,104 @@ export function RaspView({ rasp, pickmeMode, selectedDate }) {
)} )}
</AnimatePresence> </AnimatePresence>
); );
} });
const UserSettings = React.memo(
({
openUserSettings,
userFrNames,
setUserId,
setsetRoute,
setRoute,
adminId,
classId,
pickmeMode,
}) => {
const [classLinkCopied, setClassLinkCopied] = useState(false);
return (
<>
{openUserSettings && (
<motion.div
className="flex flex-col bg-grain gap-2 bg-zinc-700/20 p-2 rounded-2xl"
variants={{
hidden: { opacity: 0, y: 10, filter: "blur(2px)" },
show: { opacity: 1, y: 0, filter: "blur(0)" },
}}
initial="hidden"
animate="show"
exit="hidden"
>
{[
// icon
// text
// onclick
// condition
[
<BiExit />,
"выход",
() => {
setUserId("");
setsetRoute(setRoute, "/");
},
true,
],
// [
// <BiStar />,
// "пикми режим",
// () => {
// setPickmeMode(!pickmeMode);
// },
// true,
// ],
[
<BiSolidUser />,
`админ ${
userFrNames["admin"] ? userFrNames["admin"] : adminId
}`,
() => {},
adminId != "",
],
[
<BiLink />,
classLinkCopied ? "скопированно" : "коп. ссылку",
() => {
copyToClipboard(
location.href.substring(0, location.href.search("#")) +
"#//" +
classId
).then((a) => {
if (a) {
setClassLinkCopied(true);
setTimeout(() => {
setClassLinkCopied(false);
}, 2000);
}
});
},
classId != "",
],
].map(
(item, i) =>
item[3] && (
<div
key={i}
className={
"flex gap-2 items-center justify-center transition-colors " +
(pickmeMode
? "lg:hover:bg-pink-400"
: "lg:hover:bg-white lg:hover:text-black") +
" p-1 rounded-xl cursor-pointer"
}
onClick={item[2]}
>
{item[0]} {item[1]}
</div>
)
)}
</motion.div>
)}
</>
);
}
);
+60 -58
View File
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { BiCheck, BiLeftArrowAlt, BiRightArrowAlt, BiX } from "react-icons/bi"; import { BiCheck, BiLeftArrowAlt, BiRightArrowAlt, BiX } from "react-icons/bi";
import { formatDate, getDateTags } from "./dengi.js"; import { formatDate, getDateTags } from "./dengi.js";
import { AnimatePresence, motion } from "motion/react"; import { AnimatePresence, motion, LayoutGroup } from "motion/react";
export function Switch({ export function Switch({
checked, checked,
@@ -105,69 +105,71 @@ function DatePickerButton({ next = false, date, setDate }) {
); );
} }
export function DatePicker(props) { export const DatePicker = React.memo((props) => {
const dateTag = getDateTags(props.date); const dateTag = getDateTags(props.date);
return ( return (
<div className="flex items-center justify-between px-2 py-1 w-full bg-grain border-zinc-800/40 border rounded-2xl"> <LayoutGroup>
<DatePickerButton date={props.date} setDate={props.setDate} /> <div className="flex items-center justify-between px-2 py-1 w-full bg-grain border-zinc-800/40 border rounded-2xl relative">
<AnimatePresence mode="popLayout"> <DatePickerButton date={props.date} setDate={props.setDate} />
<motion.span <AnimatePresence mode="popLayout">
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"
variants={{
a: { opacity: 0, filter: "blur(1px)" },
b: { opacity: 1, filter: "blur(0px)" },
}}
initial="a"
animate="b"
exit="a"
onClick={() => {
props.setDate(new Date());
}}
>
<motion.span <motion.span
className={ key={formatDate(props.date)}
"block min-w-2 min-h-2 rounded-full mx-1 transition-colors" + 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"
(props.indicator variants={{
? " bg-zinc-300 animate-pulse" a: { opacity: 0, filter: "blur(1px)" },
: " bg-transparent") b: { opacity: 1, filter: "blur(0px)" },
} }}
layoutId="datepicker_indicator" initial="a"
layout="position" animate="b"
/> exit="a"
<motion.span onClick={() => {
layoutId="datepicker_date" props.setDate(new Date());
layout="position" }}
className="font-semibold"
> >
{formatDate(props.date)} <motion.span
className={
"block min-w-2 min-h-2 rounded-full mx-1 transition-colors" +
(props.indicator
? " bg-zinc-300 animate-pulse"
: " bg-transparent")
}
layoutId="datepicker_indicator"
layout="position"
/>
<motion.span
layoutId="datepicker_date"
layout="position"
className="font-semibold"
>
{formatDate(props.date)}
</motion.span>
{dateTag.length > 0 && (
<>
<motion.div
className="flex"
layoutId="datepicker_date_div"
layout="position"
>
<span className="h-[1px] -rotate-50 bg-zinc-800 w-[20px] " />
</motion.div>
<motion.span
initial={{ x: 10, opacity: 0 }}
transition={{ delay: 0.1 }}
animate={{ x: 0, opacity: 1 }}
className="text-zinc-600 italic"
>
{dateTag}
</motion.span>
</>
)}
</motion.span> </motion.span>
{dateTag.length > 0 && ( </AnimatePresence>
<> <DatePickerButton next date={props.date} setDate={props.setDate} />
<motion.div </div>
className="flex" </LayoutGroup>
layoutId="datepicker_date_div"
layout="position"
>
<span className="h-[1px] -rotate-50 bg-zinc-800 w-[20px] " />
</motion.div>
<motion.span
initial={{ x: 10, opacity: 0 }}
transition={{ delay: 0.1 }}
animate={{ x: 0, opacity: 1 }}
className="text-zinc-600 italic"
>
{dateTag}
</motion.span>
</>
)}
</motion.span>
</AnimatePresence>
<DatePickerButton next date={props.date} setDate={props.setDate} />
</div>
); );
} });
export function DengiButton({ export function DengiButton({
onClick, onClick,
+4 -4
View File
@@ -5,17 +5,17 @@ import { VitePWA } from "vite-plugin-pwa";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
base:"/dx4/", base: "/dx4/",
plugins: [ plugins: [
react(), react(),
tailwindcss(), tailwindcss(),
VitePWA({ VitePWA({
registerType: "autoUpdate", registerType: "autoUpdate",
manifest: { manifest: {
name: "dnevnik-x 4", name: "dx 4",
short_name: "dx4", short_name: "dx4",
description: "", description: "dnevnik-x app",
theme_color: "#000", theme_color: "#eee",
background_color: "#000", background_color: "#000",
display: "standalone", display: "standalone",
scope: "/dx4/", scope: "/dx4/",