feat: cache delete + empty snapshot + route page

This commit is contained in:
2026-04-28 03:50:29 +03:00
parent 248eea6f85
commit 60c6840db4
21 changed files with 770 additions and 361 deletions

View File

@@ -24,7 +24,7 @@ import {
} from "@widgets";
import { Plus, Save, Trash2, Unlink, X } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { toast } from "react-toastify";
import { MediaViewer } from "../../MediaViewer/index";
import {
@@ -55,6 +55,27 @@ export const RightWidgetTab = observer(
} = editSightStore;
const [previewMedia, setPreviewMedia] = useState<any | null>(null);
const shortNameRef = useRef<HTMLTextAreaElement | null>(null);
const insertNewline = () => {
const input = shortNameRef.current;
const currentValue = sight[language].name || "";
if (!input) {
updateSightInfo(language, { name: currentValue + "\n" });
return;
}
const start = input.selectionStart ?? currentValue.length;
const end = input.selectionEnd ?? start;
const newValue = currentValue.slice(0, start) + "\n" + currentValue.slice(end);
updateSightInfo(language, { name: newValue });
requestAnimationFrame(() => {
if (shortNameRef.current) {
shortNameRef.current.selectionStart = start + 1;
shortNameRef.current.selectionEnd = start + 1;
shortNameRef.current.focus();
}
});
};
useEffect(() => {
const fetchPreviewMedia = async () => {
@@ -88,13 +109,23 @@ export const RightWidgetTab = observer(
const [activeArticleIndex, setActiveArticleIndex] = useState<number | null>(
null
);
const [previewSection, setPreviewSection] = useState<number>(-1);
const [isSelectMediaModalOpen, setIsSelectMediaModalOpen] = useState(false);
const [isDeleteArticleModalOpen, setIsDeleteArticleModalOpen] =
useState(false);
const handleDeleteArticle = () => {
deleteRightArticle(sight[language].right[activeArticleIndex || 0].id);
setActiveArticleIndex(null);
const idx = activeArticleIndex || 0;
deleteRightArticle(sight[language].right[idx].id);
if (idx > 0) {
setActiveArticleIndex(idx - 1);
setPreviewSection(idx - 1);
setType("article");
} else {
setActiveArticleIndex(null);
setPreviewSection(-1);
setType("media");
}
};
const handleSelectArticle = (index: number) => {
@@ -111,6 +142,7 @@ export const RightWidgetTab = observer(
if (newIndex > -1) {
setActiveArticleIndex(newIndex);
setType("article");
setPreviewSection(newIndex);
}
} catch (error) {
console.error("Error creating new article:", error);
@@ -178,8 +210,15 @@ export const RightWidgetTab = observer(
<Box className="relative w-[20%] h-[70vh] flex flex-col rounded-2xl overflow-y-auto gap-3 border border-gray-300 p-3">
<Box className="flex flex-col gap-3 max-h-[60vh] overflow-y-auto">
<Box
onClick={() => setType("media")}
className="w-full bg-green-200 p-4 rounded-2xl cursor-pointer text-sm hover:bg-gray-300 transition-all duration-300"
onClick={() => {
setType("media");
setPreviewSection(-1);
}}
className={`w-full p-4 rounded-2xl cursor-pointer text-sm transition-all duration-300 ${
type === "media"
? "bg-blue-400 text-white"
: "bg-gray-200 hover:bg-gray-300"
}`}
>
<Typography>Предпросмотр медиа</Typography>
</Box>
@@ -204,14 +243,17 @@ export const RightWidgetTab = observer(
<Box
ref={provided.innerRef}
{...provided.draggableProps}
className={`w-full bg-gray-200 p-4 rounded-2xl text-sm cursor-pointer hover:bg-gray-300 ${
className={`w-full p-4 rounded-2xl text-sm cursor-pointer transition-all duration-300 ${
snapshot.isDragging
? "shadow-lg"
: ""
? "shadow-lg bg-gray-200"
: activeArticleIndex === index && type === "article"
? "bg-blue-400 text-white"
: "bg-gray-200 hover:bg-gray-300"
}`}
onClick={() => {
handleSelectArticle(index);
setType("article");
setPreviewSection(index);
}}
>
<Box {...provided.dragHandleProps}>
@@ -399,38 +441,62 @@ export const RightWidgetTab = observer(
</Box>
<Box sx={{ flexShrink: 0, width: "550px", display: "flex", flexDirection: "column", gap: 1 }}>
<Stack direction="row" spacing={2} alignItems="center">
{type === "media" && (
<Stack direction="row" spacing={2} alignItems="center">
<TextField
type="number"
label="Размер шрифта превью (px)"
size="small"
value={sight.common.preview_font_size ?? ""}
onChange={(e) => {
const raw = e.target.value;
if (raw === "") {
updateSightInfo(language, { preview_font_size: undefined }, true);
return;
}
const val = Math.max(1, Math.min(300, Math.round(Number(raw))));
if (Number.isFinite(val)) {
updateSightInfo(language, { preview_font_size: val }, true);
}
}}
slotProps={{ input: { min: 1, max: 300 } }}
sx={{ width: "200px" }}
/>
<Slider
value={sight.common.preview_font_size ?? 40}
min={1}
max={300}
step={1}
onChange={(_, newValue) => {
if (typeof newValue === "number") {
updateSightInfo(language, { preview_font_size: newValue }, true);
}
}}
sx={{ flexGrow: 1 }}
/>
</Stack>
)}
<Stack direction="row" spacing={1} alignItems="flex-start">
<TextField
type="number"
label="Размер шрифта превью (px)"
label="Полное название (поддерживает перенос ↵)"
multiline
minRows={1}
maxRows={4}
size="small"
value={sight.common.preview_font_size ?? ""}
onChange={(e) => {
const raw = e.target.value;
if (raw === "") {
updateSightInfo(language, { preview_font_size: undefined }, true);
return;
}
const val = Math.max(1, Math.min(300, Math.round(Number(raw))));
if (Number.isFinite(val)) {
updateSightInfo(language, { preview_font_size: val }, true);
}
}}
slotProps={{ input: { min: 1, max: 300 } }}
sx={{ width: "200px" }}
/>
<Slider
value={sight.common.preview_font_size ?? 40}
min={1}
max={300}
step={1}
onChange={(_, newValue) => {
if (typeof newValue === "number") {
updateSightInfo(language, { preview_font_size: newValue }, true);
}
}}
value={sight[language].name}
onChange={(e) => updateSightInfo(language, { name: e.target.value })}
inputRef={shortNameRef}
sx={{ flexGrow: 1 }}
/>
<Button
variant="outlined"
size="small"
onClick={insertNewline}
title="Вставить перенос строки"
sx={{ minWidth: 40, height: 40, fontSize: 18, p: 0, flexShrink: 0 }}
>
</Button>
</Stack>
<SightFramePreview
sightName={sight[language].name}
@@ -439,8 +505,19 @@ export const RightWidgetTab = observer(
onArticleSelect={(idx) => {
handleSelectArticle(idx);
setType("article");
setPreviewSection(idx);
}}
previewFontSize={sight.common.preview_font_size}
selectedSection={previewSection}
onSectionChange={(section) => {
setPreviewSection(section);
if (section === -1) {
setType("media");
} else {
handleSelectArticle(section);
setType("article");
}
}}
/>
</Box>
</Box>