feat: Update article modal and left widget naming

This commit is contained in:
2025-10-31 14:06:23 +03:00
parent 1235272161
commit 2afdb1e143
7 changed files with 107 additions and 76 deletions

View File

@@ -71,10 +71,8 @@ export const clearBlobAndGLTFCache = async (url: string) => {
*/ */
export const clearMediaTransitionCache = async ( export const clearMediaTransitionCache = async (
previousMediaId: string | number | null, previousMediaId: string | number | null,
newMediaId: string | number | null,
newMediaType?: number newMediaType?: number
) => { ) => {
console.log(newMediaId, newMediaType);
// Если переключаемся с/на 3D модель, очищаем весь кеш // Если переключаемся с/на 3D модель, очищаем весь кеш
if (newMediaType === 6 || previousMediaId) { if (newMediaType === 6 || previousMediaId) {
await clearAllGLTFCache(); await clearAllGLTFCache();

View File

@@ -523,42 +523,60 @@ export const ArticleSelectOrCreateDialog = observer(
article?.service_name?.toLowerCase().includes(searchQuery.toLowerCase()) article?.service_name?.toLowerCase().includes(searchQuery.toLowerCase())
); );
const [hoveredArticleId, setHoveredArticleId] = useState<string | null>( // Preview-by-click logic with request serialization to avoid concurrent requests
null const [isPreviewLoading, setIsPreviewLoading] = useState(false);
); const [queuedPreviewId, setQueuedPreviewId] = useState<number | null>(null);
const hoverTimerRef = (typeof window !== "undefined" const clickTimerRef = (typeof window !== "undefined"
? (window as any) ? (window as any)
: {}) as { : {}) as {
current?: any; current?: any;
} as React.MutableRefObject<NodeJS.Timeout | null>; } as React.MutableRefObject<NodeJS.Timeout | null>;
if (hoverTimerRef.current === undefined) { if (clickTimerRef.current === undefined) {
(hoverTimerRef as any).current = null; (clickTimerRef as any).current = null;
} }
useEffect(() => {
if ( const runPreviewFetch = async (articleId: number) => {
hoveredArticleId && if (isPreviewLoading) {
tabValue === 0 && setQueuedPreviewId(articleId);
!selectedArticleId && return;
!tempArticleId
) {
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
hoverTimerRef.current = setTimeout(() => {
getArticle(Number(hoveredArticleId), modalLanguage);
getArticleMedia(Number(hoveredArticleId));
}, 200);
} }
return () => { setIsPreviewLoading(true);
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current); try {
}; await Promise.all([
}, [ getArticle(articleId, modalLanguage),
hoveredArticleId, getArticleMedia(articleId),
tabValue, ]);
selectedArticleId, } finally {
tempArticleId, setIsPreviewLoading(false);
modalLanguage, if (queuedPreviewId && queuedPreviewId !== articleId) {
getArticle, const nextId = queuedPreviewId;
getArticleMedia, setQueuedPreviewId(null);
]); // Run the next queued preview
runPreviewFetch(nextId);
} else {
setQueuedPreviewId(null);
}
}
};
const handleListItemClick = (articleId: number) => {
// Delay to allow double-click to cancel preview
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
clickTimerRef.current = setTimeout(() => {
if (tabValue === 0 && !selectedArticleId && !tempArticleId) {
runPreviewFetch(articleId);
}
}, 200);
};
const handleListItemDoubleClick = (articleId: number) => {
// Cancel pending single-click preview and proceed to select
if (clickTimerRef.current) {
clearTimeout(clickTimerRef.current);
(clickTimerRef as any).current = null;
}
handleArticleSelect(articleId);
};
const previewData = { const previewData = {
heading: currentArticleData[modalLanguage].heading || "", heading: currentArticleData[modalLanguage].heading || "",
@@ -656,11 +674,10 @@ export const ArticleSelectOrCreateDialog = observer(
filteredArticles.map((article) => ( filteredArticles.map((article) => (
<ListItemButton <ListItemButton
key={article.id} key={article.id}
onClick={() => handleArticleSelect(article.id)} onClick={() => handleListItemClick(article.id)}
onMouseEnter={() => onDoubleClick={() =>
setHoveredArticleId(article.id.toString()) handleListItemDoubleClick(article.id)
} }
onMouseLeave={() => setHoveredArticleId(null)}
sx={{ sx={{
borderRadius: 1, borderRadius: 1,
mb: 0.5, mb: 0.5,

View File

@@ -340,55 +340,63 @@ class CreateSightStore {
createLeftArticle = async () => { createLeftArticle = async () => {
/* ... your existing logic to create a new left article (placeholder or DB) ... */ /* ... your existing logic to create a new left article (placeholder or DB) ... */
const ruName = (this.sight.ru.name || "").trim();
const enName = (this.sight.en.name || "").trim();
const zhName = (this.sight.zh.name || "").trim();
// If all names are empty, skip defaulting and use empty headings
const hasAnyName = !!(ruName || enName || zhName);
const response = await languageInstance("ru").post("/article", { const response = await languageInstance("ru").post("/article", {
heading: "Новая левая статья", heading: hasAnyName ? ruName : "",
body: "Заполните контентом", body: "",
}); });
const newLeftArticleId = response.data.id; const newLeftArticleId = response.data.id;
await languageInstance("en").patch(`/article/${newLeftArticleId}`, { await languageInstance("en").patch(`/article/${newLeftArticleId}`, {
heading: "New Left Article", heading: hasAnyName ? enName : "",
body: "Fill with content", body: "",
}); });
await languageInstance("zh").patch(`/article/${newLeftArticleId}`, { await languageInstance("zh").patch(`/article/${newLeftArticleId}`, {
heading: "新的左侧文章", heading: hasAnyName ? zhName : "",
body: "填写内容", body: "",
}); });
runInAction(() => { runInAction(() => {
this.sight.left_article = newLeftArticleId; // Store the actual ID this.sight.left_article = newLeftArticleId; // Store the actual ID
this.sight.ru.left = { this.sight.ru.left = {
heading: "Новая левая статья", heading: hasAnyName ? ruName : "",
body: "Заполните контентом", body: "",
media: [], media: [],
}; };
this.sight.en.left = { this.sight.en.left = {
heading: "New Left Article", heading: hasAnyName ? enName : "",
body: "Fill with content", body: "",
media: [], media: [],
}; };
this.sight.zh.left = { this.sight.zh.left = {
heading: "新的左侧文章", heading: hasAnyName ? zhName : "",
body: "填写内容", body: "",
media: [], media: [],
}; };
articlesStore.articles.ru.push({ articlesStore.articles.ru.push({
id: newLeftArticleId, id: newLeftArticleId,
heading: "Новая левая статья", heading: hasAnyName ? ruName : "",
body: "Заполните контентом", body: "",
service_name: "Новая левая статья", service_name: hasAnyName ? ruName : "",
}); });
articlesStore.articles.en.push({ articlesStore.articles.en.push({
id: newLeftArticleId, id: newLeftArticleId,
heading: "New Left Article", heading: hasAnyName ? enName : "",
body: "Fill with content", body: "",
service_name: "New Left Article", service_name: hasAnyName ? enName : "",
}); });
articlesStore.articles.zh.push({ articlesStore.articles.zh.push({
id: newLeftArticleId, id: newLeftArticleId,
heading: "新的左侧文章", heading: hasAnyName ? zhName : "",
body: "填写内容", body: "",
service_name: "新的左侧文章", service_name: hasAnyName ? zhName : "",
}); });
}); });
return newLeftArticleId; return newLeftArticleId;

View File

@@ -400,16 +400,36 @@ class EditSightStore {
}; };
createLeftArticle = async () => { createLeftArticle = async () => {
const ruName = (this.sight.ru.name || "").trim();
const enName = (this.sight.en.name || "").trim();
const zhName = (this.sight.zh.name || "").trim();
const hasAnyName = !!(ruName || enName || zhName);
const response = await languageInstance("ru").post(`/article`, { const response = await languageInstance("ru").post(`/article`, {
heading: "", heading: hasAnyName ? ruName : "",
body: "", body: "",
}); });
this.sight.common.left_article = response.data.id; this.sight.common.left_article = response.data.id;
this.sight.ru.left.heading = ""; await languageInstance("en").patch(
this.sight.en.left.heading = ""; `/article/${this.sight.common.left_article}`,
this.sight.zh.left.heading = ""; {
heading: hasAnyName ? enName : "",
body: "",
}
);
await languageInstance("zh").patch(
`/article/${this.sight.common.left_article}`,
{
heading: hasAnyName ? zhName : "",
body: "",
}
);
this.sight.ru.left.heading = hasAnyName ? ruName : "";
this.sight.en.left.heading = hasAnyName ? enName : "";
this.sight.zh.left.heading = hasAnyName ? zhName : "";
this.sight.ru.left.body = ""; this.sight.ru.left.body = "";
this.sight.en.left.body = ""; this.sight.en.left.body = "";
this.sight.zh.left.body = ""; this.sight.zh.left.body = "";

View File

@@ -1,4 +1,4 @@
import React, { useRef, useState, DragEvent, useEffect } from "react"; import React, { useRef, DragEvent } from "react";
import { Paper, Box, Typography, Button, Tooltip } from "@mui/material"; import { Paper, Box, Typography, Button, Tooltip } from "@mui/material";
import { X, Info, Plus } from "lucide-react"; // Assuming lucide-react for icons import { X, Info, Plus } from "lucide-react"; // Assuming lucide-react for icons
import { editSightStore } from "@shared"; import { editSightStore } from "@shared";
@@ -27,18 +27,9 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({
tooltipText, tooltipText,
}) => { }) => {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
const [isDragOver, setIsDragOver] = useState(false);
const { setFileToUpload } = editSightStore; const { setFileToUpload } = editSightStore;
useEffect(() => {
if (isDragOver) {
console.log("isDragOver");
}
}, [isDragOver]);
// --- Click to select file ---
const handleZoneClick = () => { const handleZoneClick = () => {
// Trigger the hidden file input click
fileInputRef.current?.click(); fileInputRef.current?.click();
}; };
@@ -68,19 +59,16 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({
const handleDragOver = (event: DragEvent<HTMLDivElement>) => { const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault(); // Crucial to allow a drop event.preventDefault(); // Crucial to allow a drop
event.stopPropagation(); event.stopPropagation();
setIsDragOver(true);
}; };
const handleDragLeave = (event: DragEvent<HTMLDivElement>) => { const handleDragLeave = (event: DragEvent<HTMLDivElement>) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setIsDragOver(false);
}; };
const handleDrop = async (event: DragEvent<HTMLDivElement>) => { const handleDrop = async (event: DragEvent<HTMLDivElement>) => {
event.preventDefault(); // Crucial to allow a drop event.preventDefault(); // Crucial to allow a drop
event.stopPropagation(); event.stopPropagation();
setIsDragOver(false);
const files = event.dataTransfer.files; const files = event.dataTransfer.files;
if (files && files.length > 0) { if (files && files.length > 0) {

View File

@@ -38,7 +38,7 @@ export function MediaViewer({
// Используем новый cache manager для очистки кеша // Используем новый cache manager для очистки кеша
clearMediaTransitionCache( clearMediaTransitionCache(
previousMediaId, previousMediaId,
media?.id || null,
media?.media_type media?.media_type
); );

File diff suppressed because one or more lines are too long