mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
fix(notifications): clear on workflow load; remove from DOM after fade
This commit is contained in:
@@ -24,8 +24,43 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
const logger = createLogger('Notifications')
|
||||
|
||||
// Constants
|
||||
const NOTIFICATION_TIMEOUT = 4000
|
||||
const FADE_DURATION = 300
|
||||
const NOTIFICATION_TIMEOUT = 4000 // Show notification for 4 seconds
|
||||
const FADE_DURATION = 500 // Fade out over 500ms
|
||||
|
||||
// Define keyframes for the animations in a style tag
|
||||
const AnimationStyles = () => (
|
||||
<style jsx global>{`
|
||||
@keyframes notification-slide {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes notification-fade-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-10%);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-notification-slide {
|
||||
animation: notification-slide 300ms ease forwards;
|
||||
}
|
||||
|
||||
.animate-notification-fade-out {
|
||||
animation: notification-fade-out ${FADE_DURATION}ms ease forwards;
|
||||
}
|
||||
`}</style>
|
||||
)
|
||||
|
||||
// Icon mapping for notification types
|
||||
const NotificationIcon = {
|
||||
@@ -92,15 +127,23 @@ function DeleteApiConfirmation({
|
||||
*/
|
||||
export function NotificationList() {
|
||||
// Store access
|
||||
const { notifications, hideNotification } = useNotificationStore()
|
||||
const { notifications, hideNotification, markAsRead, removeNotification } = useNotificationStore()
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
|
||||
// Local state
|
||||
const [fadingNotifications, setFadingNotifications] = useState<Set<string>>(new Set())
|
||||
const [removedIds, setRemovedIds] = useState<Set<string>>(new Set())
|
||||
|
||||
// Filter to only show visible notifications for the current workflow
|
||||
// Filter to only show:
|
||||
// 1. Visible notifications for the current workflow
|
||||
// 2. That are either unread OR marked as persistent
|
||||
// 3. And have not been marked for removal
|
||||
const visibleNotifications = notifications.filter(
|
||||
(n) => n.isVisible && n.workflowId === activeWorkflowId
|
||||
(n) =>
|
||||
n.isVisible &&
|
||||
n.workflowId === activeWorkflowId &&
|
||||
(!n.read || n.options?.isPersistent) &&
|
||||
!removedIds.has(n.id)
|
||||
)
|
||||
|
||||
// Handle auto-dismissal of non-persistent notifications
|
||||
@@ -117,9 +160,14 @@ export function NotificationList() {
|
||||
setFadingNotifications((prev) => new Set([...prev, notification.id]))
|
||||
}, NOTIFICATION_TIMEOUT)
|
||||
|
||||
// Hide notification after fade completes
|
||||
// Hide notification after fade completes and mark for removal from DOM
|
||||
const hideTimer = setTimeout(() => {
|
||||
hideNotification(notification.id)
|
||||
markAsRead(notification.id)
|
||||
|
||||
// Mark this notification ID as removed to exclude it from rendering
|
||||
setRemovedIds((prev) => new Set([...prev, notification.id]))
|
||||
|
||||
setFadingNotifications((prev) => {
|
||||
const next = new Set(prev)
|
||||
next.delete(notification.id)
|
||||
@@ -132,28 +180,45 @@ export function NotificationList() {
|
||||
|
||||
// Cleanup timers on unmount or when notifications change
|
||||
return () => timers.forEach(clearTimeout)
|
||||
}, [visibleNotifications, hideNotification])
|
||||
}, [visibleNotifications, hideNotification, markAsRead])
|
||||
|
||||
// Early return if no notifications to show
|
||||
if (visibleNotifications.length === 0) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute left-1/2 z-50 space-y-2 max-w-lg w-full"
|
||||
style={{
|
||||
top: '30px',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
>
|
||||
{visibleNotifications.map((notification) => (
|
||||
<NotificationAlert
|
||||
key={notification.id}
|
||||
notification={notification}
|
||||
isFading={fadingNotifications.has(notification.id)}
|
||||
onHide={hideNotification}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<AnimationStyles />
|
||||
<div
|
||||
className="absolute left-1/2 z-50 space-y-2 max-w-lg w-full pointer-events-none"
|
||||
style={{
|
||||
top: '30px',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
>
|
||||
{visibleNotifications.map((notification) => (
|
||||
<NotificationAlert
|
||||
key={notification.id}
|
||||
notification={notification}
|
||||
isFading={fadingNotifications.has(notification.id)}
|
||||
onHide={(id) => {
|
||||
hideNotification(id)
|
||||
markAsRead(id)
|
||||
// Start the fade out animation
|
||||
setFadingNotifications((prev) => new Set([...prev, id]))
|
||||
// Remove from DOM after animation completes
|
||||
setTimeout(() => {
|
||||
setRemovedIds((prev) => new Set([...prev, id]))
|
||||
setFadingNotifications((prev) => {
|
||||
const next = new Set(prev)
|
||||
next.delete(id)
|
||||
return next
|
||||
})
|
||||
}, FADE_DURATION)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -168,13 +233,14 @@ interface NotificationAlertProps {
|
||||
|
||||
function NotificationAlert({ notification, isFading, onHide }: NotificationAlertProps) {
|
||||
const { id, type, message, options, workflowId } = notification
|
||||
const Icon = NotificationIcon[type]
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
|
||||
const { setDeploymentStatus } = useWorkflowStore()
|
||||
const { isDeployed } = useWorkflowStore((state) => ({
|
||||
isDeployed: state.isDeployed,
|
||||
}))
|
||||
|
||||
const Icon = NotificationIcon[type]
|
||||
|
||||
const handleDeleteApi = async () => {
|
||||
if (!workflowId) return
|
||||
|
||||
@@ -202,8 +268,10 @@ function NotificationAlert({ notification, isFading, onHide }: NotificationAlert
|
||||
<>
|
||||
<Alert
|
||||
className={cn(
|
||||
'transition-all duration-300 ease-in-out opacity-0 translate-y-[-100%]',
|
||||
isFading ? 'animate-notification-fade-out' : 'animate-notification-slide',
|
||||
'transition-all duration-300 ease-in-out opacity-0 translate-y-[-100%] pointer-events-auto',
|
||||
isFading
|
||||
? 'animate-notification-fade-out pointer-events-none'
|
||||
: 'animate-notification-slide',
|
||||
NotificationColors[type]
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -12,6 +12,7 @@ import ReactFlow, {
|
||||
} from 'reactflow'
|
||||
import 'reactflow/dist/style.css'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useNotificationStore } from '@/stores/notifications/store'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { initializeSyncManagers, isSyncInitialized } from '@/stores/sync-registry'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
@@ -51,6 +52,7 @@ function WorkflowContent() {
|
||||
const { blocks, edges, loops, addBlock, updateBlockPosition, addEdge, removeEdge } =
|
||||
useWorkflowStore()
|
||||
const { setValue: setSubBlockValue } = useSubBlockStore()
|
||||
const { markAllAsRead } = useNotificationStore()
|
||||
|
||||
// Initialize workflow
|
||||
useEffect(() => {
|
||||
@@ -157,16 +159,26 @@ function WorkflowContent() {
|
||||
if (!isActivelyLoadingFromDB()) {
|
||||
clearInterval(checkInterval)
|
||||
setActiveWorkflow(currentId)
|
||||
markAllAsRead(currentId)
|
||||
}
|
||||
}, 100)
|
||||
return
|
||||
}
|
||||
|
||||
setActiveWorkflow(currentId)
|
||||
markAllAsRead(currentId)
|
||||
}
|
||||
|
||||
validateAndNavigate()
|
||||
}, [params.id, workflows, setActiveWorkflow, createWorkflow, router, isInitialized])
|
||||
}, [
|
||||
params.id,
|
||||
workflows,
|
||||
setActiveWorkflow,
|
||||
createWorkflow,
|
||||
router,
|
||||
isInitialized,
|
||||
markAllAsRead,
|
||||
])
|
||||
|
||||
// Transform blocks and loops into ReactFlow nodes
|
||||
const nodes = useMemo(() => {
|
||||
|
||||
@@ -34,6 +34,7 @@ export const useNotificationStore = create<NotificationStore>()(
|
||||
message,
|
||||
timestamp: Date.now(),
|
||||
isVisible: true,
|
||||
read: false,
|
||||
workflowId,
|
||||
options,
|
||||
}
|
||||
@@ -52,7 +53,7 @@ export const useNotificationStore = create<NotificationStore>()(
|
||||
hideNotification: (id) =>
|
||||
set((state) => {
|
||||
const newNotifications = state.notifications.map((n) =>
|
||||
n.id === id ? { ...n, isVisible: false } : n
|
||||
n.id === id ? { ...n, isVisible: false, read: true } : n
|
||||
)
|
||||
persistNotifications(newNotifications)
|
||||
return { notifications: newNotifications }
|
||||
@@ -67,6 +68,24 @@ export const useNotificationStore = create<NotificationStore>()(
|
||||
return { notifications: newNotifications }
|
||||
}),
|
||||
|
||||
markAsRead: (id) =>
|
||||
set((state) => {
|
||||
const newNotifications = state.notifications.map((n) =>
|
||||
n.id === id ? { ...n, read: true } : n
|
||||
)
|
||||
persistNotifications(newNotifications)
|
||||
return { notifications: newNotifications }
|
||||
}),
|
||||
|
||||
markAllAsRead: (workflowId) =>
|
||||
set((state) => {
|
||||
const newNotifications = state.notifications.map((n) =>
|
||||
n.workflowId === workflowId ? { ...n, read: true } : n
|
||||
)
|
||||
persistNotifications(newNotifications)
|
||||
return { notifications: newNotifications }
|
||||
}),
|
||||
|
||||
removeNotification: (id) =>
|
||||
set((state) => {
|
||||
const newNotifications = state.notifications.filter((n) => n.id !== id)
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface Notification {
|
||||
timestamp: number
|
||||
isVisible: boolean
|
||||
workflowId: string | null
|
||||
read: boolean
|
||||
options?: NotificationOptions
|
||||
}
|
||||
|
||||
@@ -31,6 +32,8 @@ export interface NotificationStore {
|
||||
) => void
|
||||
hideNotification: (id: string) => void
|
||||
showNotification: (id: string) => void
|
||||
markAsRead: (id: string) => void
|
||||
markAllAsRead: (workflowId: string) => void
|
||||
removeNotification: (id: string) => void
|
||||
clearNotifications: () => void
|
||||
getWorkflowNotifications: (workflowId: string) => Notification[]
|
||||
|
||||
Reference in New Issue
Block a user