Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,53 +1,57 @@
'use client'

import { useEffect, useState } from 'react'
import { AlertTriangle, RefreshCw } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'

interface ConnectionStatusProps {
isConnected: boolean
}

export function ConnectionStatus({ isConnected }: ConnectionStatusProps) {
const [showOfflineNotice, setShowOfflineNotice] = useState(false)
const userPermissions = useUserPermissionsContext()

useEffect(() => {
let timeoutId: NodeJS.Timeout

if (!isConnected) {
// Show offline notice after 6 seconds of being disconnected
timeoutId = setTimeout(() => {
setShowOfflineNotice(true)
}, 6000) // 6 seconds
} else {
// Hide notice immediately when reconnected
setShowOfflineNotice(false)
}

return () => {
if (timeoutId) {
clearTimeout(timeoutId)
}
}
}, [isConnected])
const handleRefresh = () => {
window.location.reload()
}

// Don't render anything if connected or if we haven't been disconnected long enough
if (!showOfflineNotice) {
// Don't render anything if not in offline mode
if (!userPermissions.isOfflineMode) {
return null
}

return (
<div className='flex items-center gap-1.5'>
<div className='flex items-center gap-1.5 text-red-600'>
<div className='flex items-center gap-2 rounded-md border border-red-200 bg-red-50 px-3 py-2'>
<div className='flex items-center gap-2 text-red-700'>
<div className='relative flex items-center justify-center'>
<div className='absolute h-3 w-3 animate-ping rounded-full bg-red-500/20' />
<div className='relative h-2 w-2 rounded-full bg-red-500' />
{!isConnected && (
<div className='absolute h-4 w-4 animate-ping rounded-full bg-red-500/20' />
)}
<AlertTriangle className='relative h-4 w-4' />
</div>
<div className='flex flex-col'>
<span className='font-medium text-xs leading-tight'>Connection lost</span>
<span className='text-xs leading-tight opacity-90'>
Changes not saved - please refresh
<span className='font-medium text-xs leading-tight'>
{isConnected ? 'Reconnected' : 'Connection lost - please refresh'}
</span>
<span className='text-red-600 text-xs leading-tight'>
{isConnected ? 'Refresh to continue editing' : 'Read-only mode active'}
</span>
</div>
</div>
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={handleRefresh}
variant='ghost'
size='sm'
className='h-7 w-7 p-0 text-red-700 hover:bg-red-100 hover:text-red-800'
>
<RefreshCw className='h-4 w-4' />
</Button>
</TooltipTrigger>
<TooltipContent className='z-[9999]'>Refresh page to continue editing</TooltipContent>
</Tooltip>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,6 @@ export function UserAvatarStack({
}
}, [users, maxVisible])

// Show connection status component regardless of user count
// This will handle the offline notice when disconnected for 15 seconds
const connectionStatusElement = <ConnectionStatus isConnected={isConnected} />

// Only show presence when there are multiple users (>1)
// But always show connection status
if (users.length <= 1) {
return connectionStatusElement
}

// Determine spacing based on size
const spacingClass = {
sm: '-space-x-1',
Expand All @@ -62,46 +52,55 @@ export function UserAvatarStack({
}[size]

return (
<div className={`flex items-center ${spacingClass} ${className}`}>
{/* Connection status - always present */}
{connectionStatusElement}
<div className={`flex items-center gap-3 ${className}`}>
{/* Connection status - always check, shows when offline */}
<ConnectionStatus isConnected={isConnected} />

{/* Render visible user avatars */}
{visibleUsers.map((user, index) => (
<UserAvatar
key={user.connectionId}
connectionId={user.connectionId}
name={user.name}
color={user.color}
size={size}
index={index}
tooltipContent={
user.name ? (
<div className='text-center'>
<div className='font-medium'>{user.name}</div>
{user.info && <div className='mt-1 text-muted-foreground text-xs'>{user.info}</div>}
</div>
) : null
}
/>
))}
{/* Only show avatar stack when there are multiple users (>1) */}
{users.length > 1 && (
<div className={`flex items-center ${spacingClass}`}>
{/* Render visible user avatars */}
{visibleUsers.map((user, index) => (
<UserAvatar
key={user.connectionId}
connectionId={user.connectionId}
name={user.name}
color={user.color}
size={size}
index={index}
tooltipContent={
user.name ? (
<div className='text-center'>
<div className='font-medium'>{user.name}</div>
{user.info && (
<div className='mt-1 text-muted-foreground text-xs'>{user.info}</div>
)}
</div>
) : null
}
/>
))}

{/* Render overflow indicator if there are more users */}
{overflowCount > 0 && (
<UserAvatar
connectionId='overflow-indicator' // Use a unique string identifier
name={`+${overflowCount}`}
size={size}
index={visibleUsers.length}
tooltipContent={
<div className='text-center'>
<div className='font-medium'>
{overflowCount} more user{overflowCount > 1 ? 's' : ''}
</div>
<div className='mt-1 text-muted-foreground text-xs'>{users.length} total online</div>
</div>
}
/>
{/* Render overflow indicator if there are more users */}
{overflowCount > 0 && (
<UserAvatar
connectionId='overflow-indicator' // Use a unique string identifier
name={`+${overflowCount}`}
size={size}
index={visibleUsers.length}
tooltipContent={
<div className='text-center'>
<div className='font-medium'>
{overflowCount} more user{overflowCount > 1 ? 's' : ''}
</div>
<div className='mt-1 text-muted-foreground text-xs'>
{users.length} total online
</div>
</div>
}
/>
)}
</div>
)}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,11 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
</h2>
</TooltipTrigger>
{!canEdit && (
<TooltipContent>Edit permissions required to rename workflows</TooltipContent>
<TooltipContent>
{userPermissions.isOfflineMode
? 'Connection lost - please refresh'
: 'Edit permissions required to rename workflows'}
</TooltipContent>
)}
</Tooltip>
)}
Expand Down Expand Up @@ -934,7 +938,11 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
)}
</TooltipTrigger>
<TooltipContent>
{canEdit ? 'Duplicate Workflow' : 'Admin permission required to duplicate workflows'}
{canEdit
? 'Duplicate Workflow'
: userPermissions.isOfflineMode
? 'Connection lost - please refresh'
: 'Admin permission required to duplicate workflows'}
</TooltipContent>
</Tooltip>
)
Expand Down Expand Up @@ -975,7 +983,9 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
</TooltipTrigger>
<TooltipContent command='Shift+L'>
{!userPermissions.canEdit
? 'Admin permission required to use auto-layout'
? userPermissions.isOfflineMode
? 'Connection lost - please refresh'
: 'Admin permission required to use auto-layout'
: 'Auto Layout'}
</TooltipContent>
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback } from 'react'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
import type { BlockConfig } from '@/blocks/types'

export type ToolbarBlockProps = {
Expand All @@ -9,6 +10,8 @@ export type ToolbarBlockProps = {
}

export function ToolbarBlock({ config, disabled = false }: ToolbarBlockProps) {
const userPermissions = useUserPermissionsContext()

const handleDragStart = (e: React.DragEvent) => {
if (disabled) {
e.preventDefault()
Expand Down Expand Up @@ -66,7 +69,11 @@ export function ToolbarBlock({ config, disabled = false }: ToolbarBlockProps) {
return (
<Tooltip>
<TooltipTrigger asChild>{blockContent}</TooltipTrigger>
<TooltipContent>Edit permissions required to add blocks</TooltipContent>
<TooltipContent>
{userPermissions.isOfflineMode
? 'Connection lost - please refresh'
: 'Edit permissions required to add blocks'}
</TooltipContent>
</Tooltip>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback } from 'react'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
import { LoopTool } from '../../../loop-node/loop-config'

type LoopToolbarItemProps = {
Expand All @@ -9,6 +10,8 @@ type LoopToolbarItemProps = {

// Custom component for the Loop Tool
export default function LoopToolbarItem({ disabled = false }: LoopToolbarItemProps) {
const userPermissions = useUserPermissionsContext()

const handleDragStart = (e: React.DragEvent) => {
if (disabled) {
e.preventDefault()
Expand Down Expand Up @@ -74,7 +77,11 @@ export default function LoopToolbarItem({ disabled = false }: LoopToolbarItemPro
return (
<Tooltip>
<TooltipTrigger asChild>{blockContent}</TooltipTrigger>
<TooltipContent>Edit permissions required to add blocks</TooltipContent>
<TooltipContent>
{userPermissions.isOfflineMode
? 'Connection lost - please refresh'
: 'Edit permissions required to add blocks'}
</TooltipContent>
</Tooltip>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback } from 'react'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
import { ParallelTool } from '../../../parallel-node/parallel-config'

type ParallelToolbarItemProps = {
Expand All @@ -9,6 +10,7 @@ type ParallelToolbarItemProps = {

// Custom component for the Parallel Tool
export default function ParallelToolbarItem({ disabled = false }: ParallelToolbarItemProps) {
const userPermissions = useUserPermissionsContext()
const handleDragStart = (e: React.DragEvent) => {
if (disabled) {
e.preventDefault()
Expand Down Expand Up @@ -75,7 +77,11 @@ export default function ParallelToolbarItem({ disabled = false }: ParallelToolba
return (
<Tooltip>
<TooltipTrigger asChild>{blockContent}</TooltipTrigger>
<TooltipContent>Edit permissions required to add blocks</TooltipContent>
<TooltipContent>
{userPermissions.isOfflineMode
? 'Connection lost - please refresh'
: 'Edit permissions required to add blocks'}
</TooltipContent>
</Tooltip>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, Copy, Trash2 } from 'lu
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'

Expand All @@ -22,9 +23,17 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
const horizontalHandles = useWorkflowStore(
(state) => state.blocks[blockId]?.horizontalHandles ?? false
)
const userPermissions = useUserPermissionsContext()

const isStarterBlock = blockType === 'starter'

const getTooltipMessage = (defaultMessage: string) => {
if (disabled) {
return userPermissions.isOfflineMode ? 'Connection lost - please refresh' : 'Read-only mode'
}
return defaultMessage
}

return (
<div
className={cn(
Expand Down Expand Up @@ -68,7 +77,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
</Button>
</TooltipTrigger>
<TooltipContent side='right'>
{disabled ? 'Read-only mode' : isEnabled ? 'Disable Block' : 'Enable Block'}
{getTooltipMessage(isEnabled ? 'Disable Block' : 'Enable Block')}
</TooltipContent>
</Tooltip>

Expand All @@ -89,9 +98,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
<Copy className='h-4 w-4' />
</Button>
</TooltipTrigger>
<TooltipContent side='right'>
{disabled ? 'Read-only mode' : 'Duplicate Block'}
</TooltipContent>
<TooltipContent side='right'>{getTooltipMessage('Duplicate Block')}</TooltipContent>
</Tooltip>
)}

Expand All @@ -116,7 +123,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
</Button>
</TooltipTrigger>
<TooltipContent side='right'>
{disabled ? 'Read-only mode' : horizontalHandles ? 'Vertical Ports' : 'Horizontal Ports'}
{getTooltipMessage(horizontalHandles ? 'Vertical Ports' : 'Horizontal Ports')}
</TooltipContent>
</Tooltip>

Expand All @@ -140,9 +147,7 @@ export function ActionBar({ blockId, blockType, disabled = false }: ActionBarPro
<Trash2 className='h-4 w-4' />
</Button>
</TooltipTrigger>
<TooltipContent side='right'>
{disabled ? 'Read-only mode' : 'Delete Block'}
</TooltipContent>
<TooltipContent side='right'>{getTooltipMessage('Delete Block')}</TooltipContent>
</Tooltip>
)}
</div>
Expand Down
Loading