feat: cache delete + empty snapshot + route page
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user