Compare commits
2 Commits
a8e4404b56
...
f1d155529a
| Author | SHA1 | Date | |
|---|---|---|---|
| f1d155529a | |||
| 2fc93a0dc3 |
@ -12,18 +12,45 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: opacity ease-in-out;
|
will-change: opacity;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-carousel-active {
|
.dashboard-carousel-item.dashboard-carousel-visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
will-change: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-carousel-inactive {
|
.dashboard-carousel-item.dashboard-carousel-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
z-index: 0;
|
z-index: 2;
|
||||||
pointer-events: none;
|
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,
|
onChange,
|
||||||
onSave,
|
onSave,
|
||||||
}) {
|
}) {
|
||||||
const { currentPanelIndex, setCurrentPanel, panelSelectorVisible } = useDevice();
|
const { currentPanelIndex, setCurrentPanel, panelSelectorVisible } =
|
||||||
|
useDevice();
|
||||||
const intervalRef = useRef(null);
|
const intervalRef = useRef(null);
|
||||||
const currentIndexRef = useRef(currentPanelIndex);
|
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
|
// Keep ref in sync with context value
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -31,48 +52,110 @@ function DashboardCarousel({
|
|||||||
}
|
}
|
||||||
}, [panels, currentPanelIndex, setCurrentPanel]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (!panels || panels.length === 0) return;
|
if (!panels || panels.length === 0) return;
|
||||||
|
|
||||||
// Clear any existing interval
|
|
||||||
if (intervalRef.current) {
|
if (intervalRef.current) {
|
||||||
clearInterval(intervalRef.current);
|
clearInterval(intervalRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If only one panel, don't create interval
|
|
||||||
if (panels.length <= 1) {
|
if (panels.length <= 1) {
|
||||||
setCurrentPanel(0);
|
setCurrentPanel(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't create interval if carousel is disabled
|
if (!carouselEnabled) return;
|
||||||
if (!carouselEnabled) {
|
if (panelSelectorVisible) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause carousel if panel selector is visible
|
|
||||||
if (panelSelectorVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up interval to cycle through panels
|
|
||||||
intervalRef.current = setInterval(() => {
|
intervalRef.current = setInterval(() => {
|
||||||
const nextIndex = (currentIndexRef.current + 1) % panels.length;
|
const nextIndex = (currentIndexRef.current + 1) % panels.length;
|
||||||
setCurrentPanel(nextIndex);
|
setCurrentPanel(nextIndex);
|
||||||
}, holdTime);
|
}, holdTime);
|
||||||
|
|
||||||
// Cleanup on unmount or dependency change
|
|
||||||
return () => {
|
return () => {
|
||||||
if (intervalRef.current) {
|
if (intervalRef.current) {
|
||||||
clearInterval(intervalRef.current);
|
clearInterval(intervalRef.current);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [panels, holdTime, setCurrentPanel, panelSelectorVisible, carouselEnabled]);
|
}, [
|
||||||
|
panels,
|
||||||
|
holdTime,
|
||||||
|
setCurrentPanel,
|
||||||
|
panelSelectorVisible,
|
||||||
|
carouselEnabled,
|
||||||
|
]);
|
||||||
|
|
||||||
// Handle onChange callback for individual dashboards
|
// Handle onChange callback for individual dashboards
|
||||||
const handleDashboardChange = (index, updatedPanel) => {
|
const handleDashboardChange = (index, updatedPanel) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
// Create updated panels array
|
|
||||||
const updatedPanels = [...panels];
|
const updatedPanels = [...panels];
|
||||||
updatedPanels[index] = updatedPanel;
|
updatedPanels[index] = updatedPanel;
|
||||||
onChange(updatedPanels);
|
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) {
|
if (!panels || panels.length === 0) {
|
||||||
return null;
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className="dashboard-carousel"
|
className="dashboard-carousel"
|
||||||
@ -97,37 +225,45 @@ function DashboardCarousel({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
"--dashboard-carousel-duration": `${transitionDuration}ms`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{panels.map((panel, index) => {
|
{slot1 && (
|
||||||
const isActive = index === currentPanelIndex;
|
<div className={`dashboard-carousel-item ${getSlot1Class()}`}>
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={panel?.id || index}
|
|
||||||
className={`dashboard-carousel-item ${
|
|
||||||
isActive
|
|
||||||
? "dashboard-carousel-active"
|
|
||||||
: "dashboard-carousel-inactive"
|
|
||||||
}`}
|
|
||||||
style={{
|
|
||||||
transitionDuration: `${transitionDuration}ms`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Dashboard
|
<Dashboard
|
||||||
panel={panel}
|
panel={slot1.panel}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
visible={isActive}
|
visible={activeSlot === 1 || isTransitioning}
|
||||||
bordered={bordered}
|
bordered={bordered}
|
||||||
editMode={editMode}
|
editMode={editMode}
|
||||||
onChange={(updatedPanel) =>
|
onChange={(updatedPanel) =>
|
||||||
handleDashboardChange(index, updatedPanel)
|
handleDashboardChange(slot1.index, updatedPanel)
|
||||||
|
}
|
||||||
|
onSave={(savedPanel) =>
|
||||||
|
handleDashboardSave(slot1.index, savedPanel)
|
||||||
}
|
}
|
||||||
onSave={(savedPanel) => handleDashboardSave(index, savedPanel)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,7 +47,7 @@ function AnimatedCollapse({ visible, children }) {
|
|||||||
|
|
||||||
// Start polling after a short delay to ensure element is rendered
|
// Start polling after a short delay to ensure element is rendered
|
||||||
const startId = setTimeout(() => {
|
const startId = setTimeout(() => {
|
||||||
intervalId = setInterval(checkHeight, 150);
|
intervalId = setInterval(checkHeight, 100);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@ -77,7 +77,7 @@ function DashboardPanelSelector({ isOpen, onClose, onPanelSelect, animated = tru
|
|||||||
|
|
||||||
// Start polling after a short delay to ensure element is rendered
|
// Start polling after a short delay to ensure element is rendered
|
||||||
const startId = setTimeout(() => {
|
const startId = setTimeout(() => {
|
||||||
intervalId = setInterval(checkHeight, 150);
|
intervalId = setInterval(checkHeight, 100);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@ -81,8 +81,8 @@ export const Entity = ({
|
|||||||
|
|
||||||
// Start polling after a short delay to ensure element is rendered
|
// Start polling after a short delay to ensure element is rendered
|
||||||
const startId = setTimeout(() => {
|
const startId = setTimeout(() => {
|
||||||
intervalId = setInterval(checkWidth, 10);
|
intervalId = setInterval(checkWidth, 100);
|
||||||
}, 10);
|
}, 100);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(startId);
|
clearTimeout(startId);
|
||||||
|
|||||||
@ -97,7 +97,7 @@ const LegacySlideView = forwardRef(
|
|||||||
|
|
||||||
// Start polling after a short delay to ensure element is rendered
|
// Start polling after a short delay to ensure element is rendered
|
||||||
const startId = setTimeout(() => {
|
const startId = setTimeout(() => {
|
||||||
intervalId = setInterval(checkHeight, 150);
|
intervalId = setInterval(checkHeight, 100);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -439,7 +439,7 @@ const ModernSlideView = forwardRef(
|
|||||||
|
|
||||||
// Start polling after a short delay to ensure element is rendered
|
// Start polling after a short delay to ensure element is rendered
|
||||||
const startId = setTimeout(() => {
|
const startId = setTimeout(() => {
|
||||||
intervalId = setInterval(checkHeight, 150);
|
intervalId = setInterval(checkHeight, 100);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@ -98,8 +98,8 @@ export const Widget = ({
|
|||||||
|
|
||||||
// Start polling after a short delay to ensure element is rendered
|
// Start polling after a short delay to ensure element is rendered
|
||||||
const startId = setTimeout(() => {
|
const startId = setTimeout(() => {
|
||||||
intervalId = setInterval(checkWidth, 10);
|
intervalId = setInterval(checkWidth, 100);
|
||||||
}, 10);
|
}, 100);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(startId);
|
clearTimeout(startId);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user