feat: Update article modal and left widget naming
				
					
				
			This commit is contained in:
		| @@ -71,10 +71,8 @@ export const clearBlobAndGLTFCache = async (url: string) => { | ||||
|  */ | ||||
| export const clearMediaTransitionCache = async ( | ||||
|   previousMediaId: string | number | null, | ||||
|   newMediaId: string | number | null, | ||||
|   newMediaType?: number | ||||
| ) => { | ||||
|   console.log(newMediaId, newMediaType); | ||||
|   // Если переключаемся с/на 3D модель, очищаем весь кеш | ||||
|   if (newMediaType === 6 || previousMediaId) { | ||||
|     await clearAllGLTFCache(); | ||||
|   | ||||
| @@ -523,42 +523,60 @@ export const ArticleSelectOrCreateDialog = observer( | ||||
|       article?.service_name?.toLowerCase().includes(searchQuery.toLowerCase()) | ||||
|     ); | ||||
|  | ||||
|     const [hoveredArticleId, setHoveredArticleId] = useState<string | null>( | ||||
|       null | ||||
|     ); | ||||
|     const hoverTimerRef = (typeof window !== "undefined" | ||||
|     // Preview-by-click logic with request serialization to avoid concurrent requests | ||||
|     const [isPreviewLoading, setIsPreviewLoading] = useState(false); | ||||
|     const [queuedPreviewId, setQueuedPreviewId] = useState<number | null>(null); | ||||
|     const clickTimerRef = (typeof window !== "undefined" | ||||
|       ? (window as any) | ||||
|       : {}) as { | ||||
|       current?: any; | ||||
|     } as React.MutableRefObject<NodeJS.Timeout | null>; | ||||
|     if (hoverTimerRef.current === undefined) { | ||||
|       (hoverTimerRef as any).current = null; | ||||
|     if (clickTimerRef.current === undefined) { | ||||
|       (clickTimerRef as any).current = null; | ||||
|     } | ||||
|     useEffect(() => { | ||||
|       if ( | ||||
|         hoveredArticleId && | ||||
|         tabValue === 0 && | ||||
|         !selectedArticleId && | ||||
|         !tempArticleId | ||||
|       ) { | ||||
|         if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current); | ||||
|         hoverTimerRef.current = setTimeout(() => { | ||||
|           getArticle(Number(hoveredArticleId), modalLanguage); | ||||
|           getArticleMedia(Number(hoveredArticleId)); | ||||
|         }, 200); | ||||
|  | ||||
|     const runPreviewFetch = async (articleId: number) => { | ||||
|       if (isPreviewLoading) { | ||||
|         setQueuedPreviewId(articleId); | ||||
|         return; | ||||
|       } | ||||
|       return () => { | ||||
|         if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current); | ||||
|       }; | ||||
|     }, [ | ||||
|       hoveredArticleId, | ||||
|       tabValue, | ||||
|       selectedArticleId, | ||||
|       tempArticleId, | ||||
|       modalLanguage, | ||||
|       getArticle, | ||||
|       getArticleMedia, | ||||
|     ]); | ||||
|       setIsPreviewLoading(true); | ||||
|       try { | ||||
|         await Promise.all([ | ||||
|           getArticle(articleId, modalLanguage), | ||||
|           getArticleMedia(articleId), | ||||
|         ]); | ||||
|       } finally { | ||||
|         setIsPreviewLoading(false); | ||||
|         if (queuedPreviewId && queuedPreviewId !== articleId) { | ||||
|           const nextId = queuedPreviewId; | ||||
|           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 = { | ||||
|       heading: currentArticleData[modalLanguage].heading || "", | ||||
| @@ -656,11 +674,10 @@ export const ArticleSelectOrCreateDialog = observer( | ||||
|                         filteredArticles.map((article) => ( | ||||
|                           <ListItemButton | ||||
|                             key={article.id} | ||||
|                             onClick={() => handleArticleSelect(article.id)} | ||||
|                             onMouseEnter={() => | ||||
|                               setHoveredArticleId(article.id.toString()) | ||||
|                             onClick={() => handleListItemClick(article.id)} | ||||
|                             onDoubleClick={() => | ||||
|                               handleListItemDoubleClick(article.id) | ||||
|                             } | ||||
|                             onMouseLeave={() => setHoveredArticleId(null)} | ||||
|                             sx={{ | ||||
|                               borderRadius: 1, | ||||
|                               mb: 0.5, | ||||
|   | ||||
| @@ -340,55 +340,63 @@ class CreateSightStore { | ||||
|  | ||||
|   createLeftArticle = async () => { | ||||
|     /* ... 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", { | ||||
|       heading: "Новая левая статья", | ||||
|       body: "Заполните контентом", | ||||
|       heading: hasAnyName ? ruName : "", | ||||
|       body: "", | ||||
|     }); | ||||
|     const newLeftArticleId = response.data.id; | ||||
|  | ||||
|     await languageInstance("en").patch(`/article/${newLeftArticleId}`, { | ||||
|       heading: "New Left Article", | ||||
|       body: "Fill with content", | ||||
|       heading: hasAnyName ? enName : "", | ||||
|       body: "", | ||||
|     }); | ||||
|     await languageInstance("zh").patch(`/article/${newLeftArticleId}`, { | ||||
|       heading: "新的左侧文章", | ||||
|       body: "填写内容", | ||||
|       heading: hasAnyName ? zhName : "", | ||||
|       body: "", | ||||
|     }); | ||||
|  | ||||
|     runInAction(() => { | ||||
|       this.sight.left_article = newLeftArticleId; // Store the actual ID | ||||
|       this.sight.ru.left = { | ||||
|         heading: "Новая левая статья", | ||||
|         body: "Заполните контентом", | ||||
|         heading: hasAnyName ? ruName : "", | ||||
|         body: "", | ||||
|         media: [], | ||||
|       }; | ||||
|       this.sight.en.left = { | ||||
|         heading: "New Left Article", | ||||
|         body: "Fill with content", | ||||
|         heading: hasAnyName ? enName : "", | ||||
|         body: "", | ||||
|         media: [], | ||||
|       }; | ||||
|       this.sight.zh.left = { | ||||
|         heading: "新的左侧文章", | ||||
|         body: "填写内容", | ||||
|         heading: hasAnyName ? zhName : "", | ||||
|         body: "", | ||||
|         media: [], | ||||
|       }; | ||||
|  | ||||
|       articlesStore.articles.ru.push({ | ||||
|         id: newLeftArticleId, | ||||
|         heading: "Новая левая статья", | ||||
|         body: "Заполните контентом", | ||||
|         service_name: "Новая левая статья", | ||||
|         heading: hasAnyName ? ruName : "", | ||||
|         body: "", | ||||
|         service_name: hasAnyName ? ruName : "", | ||||
|       }); | ||||
|       articlesStore.articles.en.push({ | ||||
|         id: newLeftArticleId, | ||||
|         heading: "New Left Article", | ||||
|         body: "Fill with content", | ||||
|         service_name: "New Left Article", | ||||
|         heading: hasAnyName ? enName : "", | ||||
|         body: "", | ||||
|         service_name: hasAnyName ? enName : "", | ||||
|       }); | ||||
|       articlesStore.articles.zh.push({ | ||||
|         id: newLeftArticleId, | ||||
|         heading: "新的左侧文章", | ||||
|         body: "填写内容", | ||||
|         service_name: "新的左侧文章", | ||||
|         heading: hasAnyName ? zhName : "", | ||||
|         body: "", | ||||
|         service_name: hasAnyName ? zhName : "", | ||||
|       }); | ||||
|     }); | ||||
|     return newLeftArticleId; | ||||
|   | ||||
| @@ -400,16 +400,36 @@ class EditSightStore { | ||||
|   }; | ||||
|  | ||||
|   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`, { | ||||
|       heading: "", | ||||
|       heading: hasAnyName ? ruName : "", | ||||
|       body: "", | ||||
|     }); | ||||
|  | ||||
|     this.sight.common.left_article = response.data.id; | ||||
|  | ||||
|     this.sight.ru.left.heading = ""; | ||||
|     this.sight.en.left.heading = ""; | ||||
|     this.sight.zh.left.heading = ""; | ||||
|     await languageInstance("en").patch( | ||||
|       `/article/${this.sight.common.left_article}`, | ||||
|       { | ||||
|         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.en.left.body = ""; | ||||
|     this.sight.zh.left.body = ""; | ||||
|   | ||||
| @@ -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 { X, Info, Plus } from "lucide-react"; // Assuming lucide-react for icons | ||||
| import { editSightStore } from "@shared"; | ||||
| @@ -27,18 +27,9 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({ | ||||
|   tooltipText, | ||||
| }) => { | ||||
|   const fileInputRef = useRef<HTMLInputElement>(null); | ||||
|   const [isDragOver, setIsDragOver] = useState(false); | ||||
|   const { setFileToUpload } = editSightStore; | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (isDragOver) { | ||||
|       console.log("isDragOver"); | ||||
|     } | ||||
|   }, [isDragOver]); | ||||
|  | ||||
|   // --- Click to select file --- | ||||
|   const handleZoneClick = () => { | ||||
|     // Trigger the hidden file input click | ||||
|     fileInputRef.current?.click(); | ||||
|   }; | ||||
|  | ||||
| @@ -68,19 +59,16 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({ | ||||
|   const handleDragOver = (event: DragEvent<HTMLDivElement>) => { | ||||
|     event.preventDefault(); // Crucial to allow a drop | ||||
|     event.stopPropagation(); | ||||
|     setIsDragOver(true); | ||||
|   }; | ||||
|  | ||||
|   const handleDragLeave = (event: DragEvent<HTMLDivElement>) => { | ||||
|     event.preventDefault(); | ||||
|     event.stopPropagation(); | ||||
|     setIsDragOver(false); | ||||
|   }; | ||||
|  | ||||
|   const handleDrop = async (event: DragEvent<HTMLDivElement>) => { | ||||
|     event.preventDefault(); // Crucial to allow a drop | ||||
|     event.stopPropagation(); | ||||
|     setIsDragOver(false); | ||||
|  | ||||
|     const files = event.dataTransfer.files; | ||||
|     if (files && files.length > 0) { | ||||
|   | ||||
| @@ -38,7 +38,7 @@ export function MediaViewer({ | ||||
|       // Используем новый cache manager для очистки кеша | ||||
|       clearMediaTransitionCache( | ||||
|         previousMediaId, | ||||
|         media?.id || null, | ||||
|  | ||||
|         media?.media_type | ||||
|       ); | ||||
|  | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user