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 (
previousMediaId: string | number | null,
newMediaId: string | number | null,
newMediaType?: number
) => {
console.log(newMediaId, newMediaType);
// Если переключаемся с/на 3D модель, очищаем весь кеш
if (newMediaType === 6 || previousMediaId) {
await clearAllGLTFCache();

View File

@@ -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,

View File

@@ -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;

View File

@@ -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 = "";

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 { 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) {

View File

@@ -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