Compare commits
2 Commits
a8e4404b56
...
f1d155529a
| Author | SHA1 | Date | |
|---|---|---|---|
| f1d155529a | |||
| 2fc93a0dc3 |
@ -12,18 +12,45 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: opacity ease-in-out;
|
||||
will-change: opacity;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dashboard-carousel-active {
|
||||
.dashboard-carousel-item.dashboard-carousel-visible {
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
pointer-events: auto;
|
||||
will-change: auto;
|
||||
}
|
||||
|
||||
.dashboard-carousel-inactive {
|
||||
.dashboard-carousel-item.dashboard-carousel-enter {
|
||||
opacity: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
animation: dashboardCarouselFadeIn var(--dashboard-carousel-duration, 1000ms)
|
||||
ease-in-out forwards;
|
||||
}
|
||||
|
||||
.dashboard-carousel-item.dashboard-carousel-exit {
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
animation: dashboardCarouselFadeOut var(--dashboard-carousel-duration, 1000ms)
|
||||
ease-in-out forwards;
|
||||
}
|
||||
|
||||
@keyframes dashboardCarouselFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dashboardCarouselFadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,9 +15,30 @@ function DashboardCarousel({
|
||||
onChange,
|
||||
onSave,
|
||||
}) {
|
||||
const { currentPanelIndex, setCurrentPanel, panelSelectorVisible } = useDevice();
|
||||
const { currentPanelIndex, setCurrentPanel, panelSelectorVisible } =
|
||||
useDevice();
|
||||
const intervalRef = useRef(null);
|
||||
const currentIndexRef = useRef(currentPanelIndex);
|
||||
const timeoutRef = useRef(null);
|
||||
const prevIndexRef = useRef(-1); // -1 = not yet initialized
|
||||
const isTransitioningRef = useRef(false);
|
||||
|
||||
// Two-slot flip-flop: only render the current and transitioning panels
|
||||
const [slot1, setSlot1] = useState(null);
|
||||
const [slot2, setSlot2] = useState(null);
|
||||
const [activeSlot, setActiveSlot] = useState(1); // 1 or 2
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
|
||||
// Initial sync: populate slot when we have panels and prevIndex is uninitialized
|
||||
useEffect(() => {
|
||||
if (!panels?.length || currentPanelIndex >= panels.length) return;
|
||||
if (prevIndexRef.current >= 0) return; // Already initialized
|
||||
|
||||
prevIndexRef.current = currentPanelIndex;
|
||||
const panel = panels[currentPanelIndex];
|
||||
setSlot1({ panel, index: currentPanelIndex });
|
||||
setActiveSlot(1);
|
||||
}, [panels, currentPanelIndex]);
|
||||
|
||||
// Keep ref in sync with context value
|
||||
useEffect(() => {
|
||||
@ -31,48 +52,110 @@ function DashboardCarousel({
|
||||
}
|
||||
}, [panels, currentPanelIndex, setCurrentPanel]);
|
||||
|
||||
// Sync slots when panels array changes (e.g. panel reordered or replaced)
|
||||
useEffect(() => {
|
||||
if (!panels || panels.length === 0) return;
|
||||
const currentPanel = panels[currentPanelIndex];
|
||||
if (!currentPanel) return;
|
||||
|
||||
setSlot1((prev) => {
|
||||
if (prev?.index === currentPanelIndex) {
|
||||
return { panel: currentPanel, index: currentPanelIndex };
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
setSlot2((prev) => {
|
||||
if (prev?.index === currentPanelIndex) {
|
||||
return { panel: currentPanel, index: currentPanelIndex };
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}, [panels, currentPanelIndex]);
|
||||
|
||||
// Handle index changes - transition between panels using two-slot pattern
|
||||
useEffect(() => {
|
||||
if (!panels || panels.length === 0) return;
|
||||
if (currentPanelIndex >= panels.length) return;
|
||||
|
||||
const newPanel = panels[currentPanelIndex];
|
||||
|
||||
// No change
|
||||
if (currentPanelIndex === prevIndexRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Already transitioning - queue for after transition completes
|
||||
if (isTransitioningRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any pending timeout
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
isTransitioningRef.current = true;
|
||||
setIsTransitioning(true);
|
||||
prevIndexRef.current = currentPanelIndex;
|
||||
|
||||
// Flip-flop: put new panel in inactive slot
|
||||
if (activeSlot === 1) {
|
||||
setSlot2({ panel: newPanel, index: currentPanelIndex });
|
||||
} else {
|
||||
setSlot1({ panel: newPanel, index: currentPanelIndex });
|
||||
}
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
if (activeSlot === 1) {
|
||||
setSlot1(null);
|
||||
setActiveSlot(2);
|
||||
} else {
|
||||
setSlot2(null);
|
||||
setActiveSlot(1);
|
||||
}
|
||||
isTransitioningRef.current = false;
|
||||
console.log("transitioning", isTransitioning);
|
||||
setIsTransitioning(false);
|
||||
timeoutRef.current = null;
|
||||
}, transitionDuration);
|
||||
}, [currentPanelIndex, panels, activeSlot, slot1, slot2, transitionDuration]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!panels || panels.length === 0) return;
|
||||
|
||||
// Clear any existing interval
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
|
||||
// If only one panel, don't create interval
|
||||
if (panels.length <= 1) {
|
||||
setCurrentPanel(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't create interval if carousel is disabled
|
||||
if (!carouselEnabled) {
|
||||
return;
|
||||
}
|
||||
if (!carouselEnabled) return;
|
||||
if (panelSelectorVisible) return;
|
||||
|
||||
// Pause carousel if panel selector is visible
|
||||
if (panelSelectorVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up interval to cycle through panels
|
||||
intervalRef.current = setInterval(() => {
|
||||
const nextIndex = (currentIndexRef.current + 1) % panels.length;
|
||||
setCurrentPanel(nextIndex);
|
||||
}, holdTime);
|
||||
|
||||
// Cleanup on unmount or dependency change
|
||||
return () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
};
|
||||
}, [panels, holdTime, setCurrentPanel, panelSelectorVisible, carouselEnabled]);
|
||||
}, [
|
||||
panels,
|
||||
holdTime,
|
||||
setCurrentPanel,
|
||||
panelSelectorVisible,
|
||||
carouselEnabled,
|
||||
]);
|
||||
|
||||
// Handle onChange callback for individual dashboards
|
||||
const handleDashboardChange = (index, updatedPanel) => {
|
||||
if (onChange) {
|
||||
// Create updated panels array
|
||||
const updatedPanels = [...panels];
|
||||
updatedPanels[index] = updatedPanel;
|
||||
onChange(updatedPanels);
|
||||
@ -86,10 +169,55 @@ function DashboardCarousel({
|
||||
}
|
||||
};
|
||||
|
||||
// Get CSS classes for each slot (like AnimatedOutlet)
|
||||
const getSlot1Class = () => {
|
||||
if (!slot1) return "";
|
||||
if (isTransitioning && activeSlot === 1) return "dashboard-carousel-exit";
|
||||
if (isTransitioning && activeSlot === 2) return "dashboard-carousel-enter";
|
||||
if (activeSlot === 1) return "dashboard-carousel-visible";
|
||||
return "";
|
||||
};
|
||||
|
||||
const getSlot2Class = () => {
|
||||
if (!slot2) return "";
|
||||
if (isTransitioning && activeSlot === 2) return "dashboard-carousel-exit";
|
||||
if (isTransitioning && activeSlot === 1) return "dashboard-carousel-enter";
|
||||
if (activeSlot === 2) return "dashboard-carousel-visible";
|
||||
return "";
|
||||
};
|
||||
|
||||
if (!panels || panels.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Single panel: render directly without transition machinery
|
||||
if (panels.length === 1) {
|
||||
return (
|
||||
<div
|
||||
className="dashboard-carousel"
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
position: "relative",
|
||||
"--dashboard-carousel-duration": `${transitionDuration}ms`,
|
||||
}}
|
||||
>
|
||||
<div className="dashboard-carousel-item dashboard-carousel-visible">
|
||||
<Dashboard
|
||||
panel={panels[0]}
|
||||
width="100%"
|
||||
height="100%"
|
||||
visible
|
||||
bordered={bordered}
|
||||
editMode={editMode}
|
||||
onChange={(updatedPanel) => handleDashboardChange(0, updatedPanel)}
|
||||
onSave={(savedPanel) => handleDashboardSave(0, savedPanel)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="dashboard-carousel"
|
||||
@ -97,37 +225,45 @@ function DashboardCarousel({
|
||||
width,
|
||||
height,
|
||||
position: "relative",
|
||||
"--dashboard-carousel-duration": `${transitionDuration}ms`,
|
||||
}}
|
||||
>
|
||||
{panels.map((panel, index) => {
|
||||
const isActive = index === currentPanelIndex;
|
||||
return (
|
||||
<div
|
||||
key={panel?.id || index}
|
||||
className={`dashboard-carousel-item ${
|
||||
isActive
|
||||
? "dashboard-carousel-active"
|
||||
: "dashboard-carousel-inactive"
|
||||
}`}
|
||||
style={{
|
||||
transitionDuration: `${transitionDuration}ms`,
|
||||
}}
|
||||
>
|
||||
{slot1 && (
|
||||
<div className={`dashboard-carousel-item ${getSlot1Class()}`}>
|
||||
<Dashboard
|
||||
panel={panel}
|
||||
panel={slot1.panel}
|
||||
width="100%"
|
||||
height="100%"
|
||||
visible={isActive}
|
||||
visible={activeSlot === 1 || isTransitioning}
|
||||
bordered={bordered}
|
||||
editMode={editMode}
|
||||
onChange={(updatedPanel) =>
|
||||
handleDashboardChange(index, updatedPanel)
|
||||
handleDashboardChange(slot1.index, updatedPanel)
|
||||
}
|
||||
onSave={(savedPanel) =>
|
||||
handleDashboardSave(slot1.index, savedPanel)
|
||||
}
|
||||
onSave={(savedPanel) => handleDashboardSave(index, savedPanel)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
)}
|
||||
{slot2 && (
|
||||
<div className={`dashboard-carousel-item ${getSlot2Class()}`}>
|
||||
<Dashboard
|
||||
panel={slot2.panel}
|
||||
width="100%"
|
||||
height="100%"
|
||||
visible={activeSlot === 2 || isTransitioning}
|
||||
bordered={bordered}
|
||||
editMode={editMode}
|
||||
onChange={(updatedPanel) =>
|
||||
handleDashboardChange(slot2.index, updatedPanel)
|
||||
}
|
||||
onSave={(savedPanel) =>
|
||||
handleDashboardSave(slot2.index, savedPanel)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ function AnimatedCollapse({ visible, children }) {
|
||||
|
||||
// Start polling after a short delay to ensure element is rendered
|
||||
const startId = setTimeout(() => {
|
||||
intervalId = setInterval(checkHeight, 150);
|
||||
intervalId = setInterval(checkHeight, 100);
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
|
||||
@ -77,7 +77,7 @@ function DashboardPanelSelector({ isOpen, onClose, onPanelSelect, animated = tru
|
||||
|
||||
// Start polling after a short delay to ensure element is rendered
|
||||
const startId = setTimeout(() => {
|
||||
intervalId = setInterval(checkHeight, 150);
|
||||
intervalId = setInterval(checkHeight, 100);
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
|
||||
@ -81,8 +81,8 @@ export const Entity = ({
|
||||
|
||||
// Start polling after a short delay to ensure element is rendered
|
||||
const startId = setTimeout(() => {
|
||||
intervalId = setInterval(checkWidth, 10);
|
||||
}, 10);
|
||||
intervalId = setInterval(checkWidth, 100);
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
clearTimeout(startId);
|
||||
|
||||
@ -97,7 +97,7 @@ const LegacySlideView = forwardRef(
|
||||
|
||||
// Start polling after a short delay to ensure element is rendered
|
||||
const startId = setTimeout(() => {
|
||||
intervalId = setInterval(checkHeight, 150);
|
||||
intervalId = setInterval(checkHeight, 100);
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
@ -439,7 +439,7 @@ const ModernSlideView = forwardRef(
|
||||
|
||||
// Start polling after a short delay to ensure element is rendered
|
||||
const startId = setTimeout(() => {
|
||||
intervalId = setInterval(checkHeight, 150);
|
||||
intervalId = setInterval(checkHeight, 100);
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
|
||||
@ -98,8 +98,8 @@ export const Widget = ({
|
||||
|
||||
// Start polling after a short delay to ensure element is rendered
|
||||
const startId = setTimeout(() => {
|
||||
intervalId = setInterval(checkWidth, 10);
|
||||
}, 10);
|
||||
intervalId = setInterval(checkWidth, 100);
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
clearTimeout(startId);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user