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