dx v2.1
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"tailwindcss": "^4.1.12",
|
"tailwindcss": "^4.1.12",
|
||||||
"use-debounce": "^10.0.6",
|
"use-debounce": "^10.0.6",
|
||||||
|
"web-haptics": "^0.0.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
@@ -1152,6 +1153,8 @@
|
|||||||
|
|
||||||
"vite-plugin-ssr-ssg": ["vite-plugin-ssr-ssg@1.4.1", "", { "dependencies": { "compression": "^1.7.4", "consola": "^2.15.3", "express": "^4.17.1", "fs-extra": "^9.1.0", "recursive-readdir": "^2.2.2", "yargs": "^16.2.0" }, "bin": { "vite-ssrg": "dist/bin.js" } }, "sha512-lXw6VyzIrHNN5aOdtJmaf3rVb8g/stRu/xSJ653PDbO2P6J/4i2PxXoCStwj4Exry7JE3YpxujOJ4KOtgwCT3A=="],
|
"vite-plugin-ssr-ssg": ["vite-plugin-ssr-ssg@1.4.1", "", { "dependencies": { "compression": "^1.7.4", "consola": "^2.15.3", "express": "^4.17.1", "fs-extra": "^9.1.0", "recursive-readdir": "^2.2.2", "yargs": "^16.2.0" }, "bin": { "vite-ssrg": "dist/bin.js" } }, "sha512-lXw6VyzIrHNN5aOdtJmaf3rVb8g/stRu/xSJ653PDbO2P6J/4i2PxXoCStwj4Exry7JE3YpxujOJ4KOtgwCT3A=="],
|
||||||
|
|
||||||
|
"web-haptics": ["web-haptics@0.0.6", "", { "peerDependencies": { "react": ">=18", "react-dom": ">=18", "svelte": ">=4", "vue": ">=3" }, "optionalPeers": ["react", "react-dom", "svelte", "vue"] }, "sha512-eCzcf1LDi20+Fr0x9V3OkX92k0gxEQXaHajmhXHitsnk6SxPeshv8TBtBRqxyst8HI1uf2FyFVE7QS3jo1gkrw=="],
|
||||||
|
|
||||||
"webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="],
|
"webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="],
|
||||||
|
|
||||||
"whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="],
|
"whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="],
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<title>dx 4</title>
|
<title>dx 4</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="root">
|
<div id="root">
|
||||||
<style>
|
<style>
|
||||||
div[data-ktkz-loading] {
|
div[data-ktkz-loading] {
|
||||||
@@ -113,6 +114,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div k-version>
|
||||||
|
dx4 v2.1
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+2
-1
@@ -21,7 +21,8 @@
|
|||||||
"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"
|
"use-debounce": "^10.0.6",
|
||||||
|
"web-haptics": "^0.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
|
|||||||
+10
-1
@@ -34,6 +34,15 @@ input {
|
|||||||
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) {
|
/* free ad blocker*/
|
||||||
|
body:not(.dev) > *:not(#root):not(vite-error-overlay):not([k-version]) {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[k-version] {
|
||||||
|
position: fixed;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #eee5;
|
||||||
|
}
|
||||||
|
|||||||
+5
-1
@@ -22,8 +22,12 @@ import "@fontsource/jost/700.css";
|
|||||||
import "@fontsource/jost/800.css";
|
import "@fontsource/jost/800.css";
|
||||||
//import "@fontsource/jost/900.css";
|
//import "@fontsource/jost/900.css";
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
document.body.classList.add("dev");
|
||||||
|
}
|
||||||
|
|
||||||
createRoot(document.getElementById("root")).render(
|
createRoot(document.getElementById("root")).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</StrictMode>
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
|
|||||||
+49
-24
@@ -10,24 +10,20 @@ import {
|
|||||||
import Logo from "../assets/logo.svg";
|
import Logo from "../assets/logo.svg";
|
||||||
import LogoAlternative from "../assets/fully_alternative_logo.svg";
|
import LogoAlternative from "../assets/fully_alternative_logo.svg";
|
||||||
import {
|
import {
|
||||||
Switch,
|
// AnimatedCanvas,
|
||||||
LoadingSpinner,
|
LoadingSpinner,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
TextPreloader,
|
TextPreloader,
|
||||||
// CoolMouseBg,
|
// CoolMouseBg,
|
||||||
} from "../trash/trash-components";
|
} from "../trash/trash-components";
|
||||||
import {
|
import { AdminReq, classUserFRequest, RaspGet } from "../trash/req_mgr";
|
||||||
AdminReq,
|
|
||||||
AdminUserFRequest,
|
|
||||||
classUserFRequest,
|
|
||||||
RaspGet,
|
|
||||||
} from "../trash/req_mgr";
|
|
||||||
import { AnimatePresence, LayoutGroup, 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";
|
||||||
import { API_BASE_URL } from "../App";
|
import { API_BASE_URL } from "../App";
|
||||||
import { copyDXLink, ifstyle } from "../trash/helpers";
|
import { copyDXLink, ifstyle } from "../trash/helpers";
|
||||||
|
import { useWebHaptics } from "web-haptics/react";
|
||||||
|
|
||||||
export function DX4App({
|
export function DX4App({
|
||||||
classIDAtom,
|
classIDAtom,
|
||||||
@@ -78,7 +74,7 @@ export function DX4App({
|
|||||||
(err) => {
|
(err) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setRasp([true, "ошибка: " + err]);
|
setRasp([true, "ошибка: " + err]);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
@@ -133,15 +129,15 @@ export function DX4App({
|
|||||||
if (classId !== "")
|
if (classId !== "")
|
||||||
promises.push(
|
promises.push(
|
||||||
dengi_cash.withCacheAsync(`userfr-${classId}`, async () =>
|
dengi_cash.withCacheAsync(`userfr-${classId}`, async () =>
|
||||||
classUserFRequest(API_BASE_URL, classId)
|
classUserFRequest(API_BASE_URL, classId),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
else promises.push(Promise.resolve([0]));
|
else promises.push(Promise.resolve([0]));
|
||||||
if (adminId !== "")
|
if (adminId !== "")
|
||||||
promises.push(
|
promises.push(
|
||||||
dengi_cash.withCacheAsync(`admindata-${adminId}`, async () =>
|
dengi_cash.withCacheAsync(`admindata-${adminId}`, async () =>
|
||||||
AdminReq(API_BASE_URL, adminId)
|
AdminReq(API_BASE_URL, adminId),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
else promises.push(Promise.resolve([0]));
|
else promises.push(Promise.resolve([0]));
|
||||||
Promise.all(promises).then(([classRes, adminRes]) => {
|
Promise.all(promises).then(([classRes, adminRes]) => {
|
||||||
@@ -192,6 +188,9 @@ export function DX4App({
|
|||||||
if (classId === "") {
|
if (classId === "") {
|
||||||
setsetRoute(setRoute, "/");
|
setsetRoute(setRoute, "/");
|
||||||
}
|
}
|
||||||
|
const { trigger } = useWebHaptics();
|
||||||
|
|
||||||
|
const raspDragStartX = useRef(0);
|
||||||
return (
|
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-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
|
||||||
@@ -236,7 +235,12 @@ export function DX4App({
|
|||||||
>
|
>
|
||||||
<BiDotsHorizontalRounded
|
<BiDotsHorizontalRounded
|
||||||
size={35}
|
size={35}
|
||||||
onClick={() => setOpenUserSettings(!openUserSettings)}
|
onClick={() => {
|
||||||
|
setOpenUserSettings(!openUserSettings);
|
||||||
|
trigger([{ duration: 10 }, { delay: 100, duration: 10 }], {
|
||||||
|
intensity: 1,
|
||||||
|
});
|
||||||
|
}}
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setPickmeMode(!pickmeMode);
|
setPickmeMode(!pickmeMode);
|
||||||
@@ -269,7 +273,7 @@ export function DX4App({
|
|||||||
|
|
||||||
{adminId != "" && managing && (
|
{adminId != "" && managing && (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex w-full justify-end"
|
className="flex w-full justify-end not-md:px-4"
|
||||||
layout="position"
|
layout="position"
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
@@ -326,7 +330,28 @@ export function DX4App({
|
|||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<motion.div
|
||||||
|
className="relative min-h-[50vh]"
|
||||||
|
drag="x"
|
||||||
|
dragConstraints={{ top: 0, right: 0, bottom: 0, left: 0 }}
|
||||||
|
dragElastic={0.2}
|
||||||
|
onDragStart={(ev) => {
|
||||||
|
raspDragStartX.current = ev.clientX;
|
||||||
|
}}
|
||||||
|
onDragEnd={(ev) => {
|
||||||
|
if (raspDragStartX.current > ev.clientX + 150) {
|
||||||
|
const d = new Date(selectedDate);
|
||||||
|
d.setDate(d.getDate() + 1);
|
||||||
|
setSelectedDate(d);
|
||||||
|
trigger([{ duration: 10 }], { intensity: 1 });
|
||||||
|
} else if (raspDragStartX.current < ev.clientX - 150) {
|
||||||
|
const d = new Date(selectedDate);
|
||||||
|
d.setDate(d.getDate() - 1);
|
||||||
|
setSelectedDate(d);
|
||||||
|
trigger([{ duration: 10 }], { intensity: 1 });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<LayoutGroup>
|
<LayoutGroup>
|
||||||
<RaspView {...{ rasp, pickmeMode, selectedDate }} />
|
<RaspView {...{ rasp, pickmeMode, selectedDate }} />
|
||||||
</LayoutGroup>
|
</LayoutGroup>
|
||||||
@@ -343,7 +368,7 @@ export function DX4App({
|
|||||||
<LoadingSpinner size={25} />
|
<LoadingSpinner size={25} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@@ -370,8 +395,8 @@ export const RaspView = memo(({ rasp, pickmeMode, selectedDate }) => {
|
|||||||
key={selectedDate}
|
key={selectedDate}
|
||||||
className={
|
className={
|
||||||
"w-full border-collapse border-spacing-x-0 border-spacing-y-[10px] bg-gradient-to-br 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-[#101011ee]") +
|
||||||
" to-transparent rounded-2xl selectable"
|
" to-[#030303fa] rounded-2xl"
|
||||||
}
|
}
|
||||||
onContextMenu={(ev) => {
|
onContextMenu={(ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@@ -419,7 +444,7 @@ export const RaspView = memo(({ rasp, pickmeMode, selectedDate }) => {
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</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) => (
|
||||||
<TableItem {...{ d }} key={`table-header-${i}`} />
|
<TableItem {...{ d }} key={`table-header-${i}`} />
|
||||||
))}
|
))}
|
||||||
@@ -542,13 +567,13 @@ const UserSettings = memo(
|
|||||||
>
|
>
|
||||||
{item[0]} {item[1]}
|
{item[0]} {item[1]}
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
class GlobalListener {
|
class GlobalListener {
|
||||||
@@ -592,10 +617,10 @@ const TableItem = memo(({ d }) => {
|
|||||||
<tr
|
<tr
|
||||||
//layout="position"
|
//layout="position"
|
||||||
className={
|
className={
|
||||||
"transition-colors *:transition-colors " +
|
"transition-colors *:transition-colors last:*:first:rounded-bl-2xl last:*:last:rounded-br-2xl " +
|
||||||
(sel
|
(sel
|
||||||
? "bg-zinc-300/15 lg:hover:bg-zinc-300/20"
|
? "*:bg-zinc-300/15 lg:hover:*:bg-zinc-300/20"
|
||||||
: " lg:hover:bg-zinc-300/15")
|
: " lg:hover:*:bg-zinc-300/15")
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSel(!sel);
|
setSel(!sel);
|
||||||
|
|||||||
+12
-6
@@ -22,6 +22,7 @@ import {
|
|||||||
import { TableEditor } from "../trash/editorTable";
|
import { TableEditor } from "../trash/editorTable";
|
||||||
import { ifstyle } from "../trash/helpers";
|
import { ifstyle } from "../trash/helpers";
|
||||||
import { API_BASE_URL } from "../App";
|
import { API_BASE_URL } from "../App";
|
||||||
|
import { useWebHaptics } from "web-haptics/react";
|
||||||
|
|
||||||
export default function EditorPage(props) {
|
export default function EditorPage(props) {
|
||||||
const [classId, _setUserId] = useAtom(props.classIDAtom);
|
const [classId, _setUserId] = useAtom(props.classIDAtom);
|
||||||
@@ -49,17 +50,18 @@ export default function EditorPage(props) {
|
|||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const promises = [];
|
const promises = [];
|
||||||
if (classId !== "")
|
if (classId !== "")
|
||||||
|
// TODO: rewrite this bullshit into one pretty request
|
||||||
promises.push(
|
promises.push(
|
||||||
props.dengi_cash.withCacheAsync(`userfr-${classId}`, async () =>
|
props.dengi_cash.withCacheAsync(`userfr-${classId}`, async () =>
|
||||||
classUserFRequest(API_BASE_URL, classId)
|
classUserFRequest(API_BASE_URL, classId),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
else promises.push(Promise.resolve([0]));
|
else promises.push(Promise.resolve([0]));
|
||||||
if (adminId !== "")
|
if (adminId !== "")
|
||||||
promises.push(
|
promises.push(
|
||||||
props.dengi_cash.withCacheAsync(`adminfr-${adminId}`, async () =>
|
props.dengi_cash.withCacheAsync(`adminfr-${adminId}`, async () =>
|
||||||
AdminUserFRequest(API_BASE_URL, adminId)
|
AdminUserFRequest(API_BASE_URL, adminId),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
else promises.push(Promise.resolve([0]));
|
else promises.push(Promise.resolve([0]));
|
||||||
Promise.all(promises).then(([classRes, adminRes]) => {
|
Promise.all(promises).then(([classRes, adminRes]) => {
|
||||||
@@ -79,9 +81,12 @@ export default function EditorPage(props) {
|
|||||||
props.dengi_cash.delete(
|
props.dengi_cash.delete(
|
||||||
"editor_schedule",
|
"editor_schedule",
|
||||||
"editor_changes",
|
"editor_changes",
|
||||||
"editor_rings"
|
"editor_rings",
|
||||||
);
|
);
|
||||||
}, []); // clean on full rerender
|
}, []); // clean on full rerender
|
||||||
|
|
||||||
|
const { trigger } = useWebHaptics();
|
||||||
|
|
||||||
// tabs
|
// tabs
|
||||||
const tabs = [
|
const tabs = [
|
||||||
[
|
[
|
||||||
@@ -189,7 +194,7 @@ export default function EditorPage(props) {
|
|||||||
"flex items-center gap-2 transition-colors " +
|
"flex items-center gap-2 transition-colors " +
|
||||||
ifstyle(
|
ifstyle(
|
||||||
selectedTab == 2,
|
selectedTab == 2,
|
||||||
"text-transparent pointer-events-none select-none"
|
"text-transparent pointer-events-none select-none",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -238,6 +243,7 @@ export default function EditorPage(props) {
|
|||||||
key={idx}
|
key={idx}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedTab == idx) return;
|
if (selectedTab == idx) return;
|
||||||
|
trigger([{ duration: 35 }], { intensity: 1 });
|
||||||
if (!changesWereMade) {
|
if (!changesWereMade) {
|
||||||
selectTab(idx);
|
selectTab(idx);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+28
-11
@@ -42,6 +42,7 @@ import {
|
|||||||
EditorRemoveRequest,
|
EditorRemoveRequest,
|
||||||
} from "./editor_req_mgr";
|
} from "./editor_req_mgr";
|
||||||
import { API_BASE_URL } from "../App";
|
import { API_BASE_URL } from "../App";
|
||||||
|
import { useWebHaptics } from "web-haptics/react";
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
@@ -171,7 +172,7 @@ function SortableRow({
|
|||||||
transition,
|
transition,
|
||||||
opacity: isDragging ? 0.5 : 1,
|
opacity: isDragging ? 0.5 : 1,
|
||||||
};
|
};
|
||||||
|
const { trigger } = useWebHaptics();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
@@ -207,7 +208,10 @@ function SortableRow({
|
|||||||
<span className="w-[98%] mx-auto h-[1px] bg-zinc-500" />
|
<span className="w-[98%] mx-auto h-[1px] bg-zinc-500" />
|
||||||
<div className="flex justify-end w-full">
|
<div className="flex justify-end w-full">
|
||||||
<button
|
<button
|
||||||
onClick={() => onDuplicate(item.id)}
|
onClick={() => {
|
||||||
|
onDuplicate(item.id);
|
||||||
|
trigger([{ duration: 25 }], { intensity: 0.7 });
|
||||||
|
}}
|
||||||
className="min-w-12 min-h-12 transition-colors bg-zinc-600/20 text-zinc-400 hover:bg-zinc-600/40 hover:text-zinc-300 flex items-center justify-center rounded-l-md"
|
className="min-w-12 min-h-12 transition-colors bg-zinc-600/20 text-zinc-400 hover:bg-zinc-600/40 hover:text-zinc-300 flex items-center justify-center rounded-l-md"
|
||||||
>
|
>
|
||||||
<BiCopy size={20} />
|
<BiCopy size={20} />
|
||||||
@@ -220,7 +224,14 @@ function SortableRow({
|
|||||||
<BiDotsVertical size={24} />
|
<BiDotsVertical size={24} />
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => onDelete(item.id)}
|
onClick={() => {
|
||||||
|
onDelete(item.id);
|
||||||
|
trigger([
|
||||||
|
{ duration: 40, intensity: 0.7 },
|
||||||
|
{ delay: 40, duration: 40, intensity: 0.7 },
|
||||||
|
{ delay: 40, duration: 40, intensity: 0.9 },
|
||||||
|
]);
|
||||||
|
}}
|
||||||
className="min-w-12 min-h-12 transition-colors bg-red-600/20 text-red-400 hover:bg-red-600/40 hover:text-red-300 active:bg-red-600/60 flex items-center justify-center rounded-r-md"
|
className="min-w-12 min-h-12 transition-colors bg-red-600/20 text-red-400 hover:bg-red-600/40 hover:text-red-300 active:bg-red-600/60 flex items-center justify-center rounded-r-md"
|
||||||
>
|
>
|
||||||
<BiX size={24} />
|
<BiX size={24} />
|
||||||
@@ -235,7 +246,7 @@ async function getPrefilledRasp(tab, date, adminId) {
|
|||||||
API_BASE_URL,
|
API_BASE_URL,
|
||||||
tab,
|
tab,
|
||||||
adminId,
|
adminId,
|
||||||
apiFormatDate(date)
|
apiFormatDate(date),
|
||||||
);
|
);
|
||||||
if (a[0] == 1 && a[1]) {
|
if (a[0] == 1 && a[1]) {
|
||||||
return convertApiToEditor(a[1]);
|
return convertApiToEditor(a[1]);
|
||||||
@@ -291,7 +302,7 @@ export function TableEditor({
|
|||||||
delay: 200,
|
delay: 200,
|
||||||
tolerance: 8,
|
tolerance: 8,
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateItems = (newItems) => {
|
const updateItems = (newItems) => {
|
||||||
@@ -330,7 +341,7 @@ export function TableEditor({
|
|||||||
clearTimeout(updateTimeoutRef.current[key]);
|
clearTimeout(updateTimeoutRef.current[key]);
|
||||||
}
|
}
|
||||||
const newItems = items.map((item) =>
|
const newItems = items.map((item) =>
|
||||||
item.id === id ? { ...item, [field]: value } : item
|
item.id === id ? { ...item, [field]: value } : item,
|
||||||
);
|
);
|
||||||
updateItems(newItems);
|
updateItems(newItems);
|
||||||
updateTimeoutRef.current[key] = setTimeout(() => {
|
updateTimeoutRef.current[key] = setTimeout(() => {
|
||||||
@@ -338,7 +349,7 @@ export function TableEditor({
|
|||||||
const itemsWithOriginalValue = items.map((item) =>
|
const itemsWithOriginalValue = items.map((item) =>
|
||||||
item.id === id
|
item.id === id
|
||||||
? { ...item, [field]: previousValueRef.current[key] }
|
? { ...item, [field]: previousValueRef.current[key] }
|
||||||
: item
|
: item,
|
||||||
);
|
);
|
||||||
setUndoItems([itemsWithOriginalValue, ...undoItems]);
|
setUndoItems([itemsWithOriginalValue, ...undoItems]);
|
||||||
|
|
||||||
@@ -406,7 +417,7 @@ export function TableEditor({
|
|||||||
const lastPosition =
|
const lastPosition =
|
||||||
items.length > 0
|
items.length > 0
|
||||||
? String(
|
? String(
|
||||||
parseInt(sortedItems[sortedItems.length - 1].position || "0") + 1
|
parseInt(sortedItems[sortedItems.length - 1].position || "0") + 1,
|
||||||
)
|
)
|
||||||
: "1";
|
: "1";
|
||||||
|
|
||||||
@@ -425,7 +436,7 @@ export function TableEditor({
|
|||||||
left: window.scrollX, // preserve current X
|
left: window.scrollX, // preserve current X
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
}),
|
}),
|
||||||
100
|
100,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -461,7 +472,7 @@ export function TableEditor({
|
|||||||
selectedTab,
|
selectedTab,
|
||||||
adminId,
|
adminId,
|
||||||
apiFormatDate(date),
|
apiFormatDate(date),
|
||||||
JSON.stringify(convertEditorToApi(items))
|
JSON.stringify(convertEditorToApi(items)),
|
||||||
).then(() => {
|
).then(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
@@ -471,6 +482,11 @@ export function TableEditor({
|
|||||||
setDeleteDiag(true);
|
setDeleteDiag(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { trigger } = useWebHaptics();
|
||||||
|
const handleDragOver = (ev) => {
|
||||||
|
trigger([{ duration: 25 }], { intensity: 0.7 });
|
||||||
|
};
|
||||||
|
|
||||||
let deleteDialogBusy = false; // NOTE: we don't need a state
|
let deleteDialogBusy = false; // NOTE: we don't need a state
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -496,7 +512,7 @@ export function TableEditor({
|
|||||||
API_BASE_URL,
|
API_BASE_URL,
|
||||||
selectedTab,
|
selectedTab,
|
||||||
adminId,
|
adminId,
|
||||||
apiFormatDate(date)
|
apiFormatDate(date),
|
||||||
).then(() => {
|
).then(() => {
|
||||||
deleteDialogBusy = false; // for 100% sure
|
deleteDialogBusy = false; // for 100% sure
|
||||||
setDeleteDiag(false);
|
setDeleteDiag(false);
|
||||||
@@ -573,6 +589,7 @@ export function TableEditor({
|
|||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
>
|
>
|
||||||
<SortableContext
|
<SortableContext
|
||||||
items={items.map((item) => item.id)}
|
items={items.map((item) => item.id)}
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import {
|
|||||||
BiShow,
|
BiShow,
|
||||||
BiX,
|
BiX,
|
||||||
} from "react-icons/bi";
|
} from "react-icons/bi";
|
||||||
import { formatDate, getDateTags } from "./dengi.js";
|
import { apiFormatDate, 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";
|
import trashcss from "./trash.module.css";
|
||||||
|
import { useWebHaptics } from "web-haptics/react";
|
||||||
|
|
||||||
export function Switch({
|
export function Switch({
|
||||||
checked,
|
checked,
|
||||||
defaultChecked = false,
|
defaultChecked = false,
|
||||||
@@ -98,6 +100,7 @@ export function LoadingSpinner({ size }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DatePickerButton({ next = false, date, setDate, pink = false }) {
|
function DatePickerButton({ next = false, date, setDate, pink = false }) {
|
||||||
|
const { trigger } = useWebHaptics();
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`cursor-pointer ${
|
className={`cursor-pointer ${
|
||||||
@@ -109,6 +112,7 @@ function DatePickerButton({ next = false, date, setDate, pink = false }) {
|
|||||||
const d = new Date(date);
|
const d = new Date(date);
|
||||||
d.setDate(d.getDate() + (next ? 1 : -1));
|
d.setDate(d.getDate() + (next ? 1 : -1));
|
||||||
setDate(d);
|
setDate(d);
|
||||||
|
trigger([{ duration: 15 }], { intensity: 1 });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!next ? <BiLeftArrowAlt size={34} /> : <BiRightArrowAlt size={34} />}
|
{!next ? <BiLeftArrowAlt size={34} /> : <BiRightArrowAlt size={34} />}
|
||||||
@@ -118,7 +122,7 @@ function DatePickerButton({ next = false, date, setDate, pink = false }) {
|
|||||||
|
|
||||||
export const DatePicker = (props) => {
|
export const DatePicker = (props) => {
|
||||||
const dateTag = getDateTags(props.date);
|
const dateTag = getDateTags(props.date);
|
||||||
|
const { trigger } = useWebHaptics();
|
||||||
return (
|
return (
|
||||||
<LayoutGroup>
|
<LayoutGroup>
|
||||||
<div
|
<div
|
||||||
@@ -133,20 +137,27 @@ export const DatePicker = (props) => {
|
|||||||
/>
|
/>
|
||||||
<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%] lg:hover:text-black *:transition-colors transition-colors p-1 rounded-3xl text-zinc-300 ${
|
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
|
!props.pink
|
||||||
? "lg:hover:bg-white lg:hover:*:text-black"
|
? "lg:hover:bg-white lg:hover:*:text-black"
|
||||||
: "lg:hover:bg-pink-500 lg:hover:*:text-white"
|
: "lg:hover:bg-pink-500 lg:hover:*:text-white"
|
||||||
} px-3 overflow-hidden `}
|
} 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)" },
|
||||||
}}
|
// }}
|
||||||
initial="a"
|
// initial="a"
|
||||||
animate="b"
|
// animate="b"
|
||||||
exit="a"
|
// exit="a"
|
||||||
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (apiFormatDate(props.date) != apiFormatDate(new Date()))
|
||||||
|
trigger([
|
||||||
|
{ duration: 10, intensity: 1 },
|
||||||
|
{ delay: 40, duration: 20, intensity: 0.26 },
|
||||||
|
{ delay: 90, duration: 20, intensity: 0.34 },
|
||||||
|
]);
|
||||||
props.setDate(new Date());
|
props.setDate(new Date());
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -160,7 +171,7 @@ export const DatePicker = (props) => {
|
|||||||
: " bg-transparent")
|
: " bg-transparent")
|
||||||
}
|
}
|
||||||
layoutId="datepicker_indicator"
|
layoutId="datepicker_indicator"
|
||||||
layout="position"
|
// layout="position"
|
||||||
/>
|
/>
|
||||||
<motion.span
|
<motion.span
|
||||||
layoutId="datepicker_date"
|
layoutId="datepicker_date"
|
||||||
@@ -173,7 +184,7 @@ export const DatePicker = (props) => {
|
|||||||
<>
|
<>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="flex"
|
className="flex"
|
||||||
layoutId="datepicker_date_div"
|
// layoutId="datepicker_date_div"
|
||||||
layout="position"
|
layout="position"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -183,9 +194,9 @@ export const DatePicker = (props) => {
|
|||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.span
|
<motion.span
|
||||||
initial={{ x: 10, opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
transition={{ delay: 0.1 }}
|
transition={{ delay: 0.1 }}
|
||||||
animate={{ x: 0, opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
className="text-zinc-600 italic"
|
className="text-zinc-600 italic"
|
||||||
>
|
>
|
||||||
{dateTag}
|
{dateTag}
|
||||||
@@ -211,6 +222,8 @@ export function DengiButton({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
className = "",
|
className = "",
|
||||||
}) {
|
}) {
|
||||||
|
const { trigger } = useWebHaptics();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
role="button"
|
role="button"
|
||||||
@@ -223,7 +236,19 @@ export function DengiButton({
|
|||||||
(className ? className : "")
|
(className ? className : "")
|
||||||
}
|
}
|
||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
onClick={disabled ? null : onClick}
|
onClick={
|
||||||
|
disabled
|
||||||
|
? null
|
||||||
|
: () => {
|
||||||
|
onClick();
|
||||||
|
trigger([
|
||||||
|
{
|
||||||
|
duration: 40,
|
||||||
|
intensity: 0.8,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
@@ -285,6 +310,22 @@ export function DengiYesOrNoDialog({
|
|||||||
show,
|
show,
|
||||||
onAnswer,
|
onAnswer,
|
||||||
}) {
|
}) {
|
||||||
|
const { trigger } = useWebHaptics();
|
||||||
|
useEffect(() => {
|
||||||
|
if (show) {
|
||||||
|
trigger([
|
||||||
|
{
|
||||||
|
duration: 40,
|
||||||
|
intensity: 0.8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
delay: 15,
|
||||||
|
duration: 170,
|
||||||
|
intensity: 0.9,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, [show]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -296,8 +337,10 @@ export function DengiYesOrNoDialog({
|
|||||||
exit="off"
|
exit="off"
|
||||||
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"
|
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) => {
|
onClick={(e) => {
|
||||||
if (e.target === e.currentTarget && content.bgDissmis)
|
if (e.target === e.currentTarget && content.bgDissmis) {
|
||||||
onAnswer(false);
|
onAnswer(false);
|
||||||
|
trigger([{ duration: 40, intensity: 1 }]);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
Reference in New Issue
Block a user