Skip to content
Merged
2 changes: 1 addition & 1 deletion app/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ const Modal: React.FC<ModalProps> = ({
{renderIcon()}
<div className="flex flex-col gap-1">
<h2
className="text-lg font-semibold"
className="text-lg font-medium"
style={{ color: colors['text-primary'] }}
>
{title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DropdownDatas, Path } from '../types';
import { BlockEndType } from '@/types/block';
import { useClipboardStore } from '../store/clipboardStore';
import { useModalStore } from '../store/modalStore';
import { useColors } from '@/app/theme/hooks';

interface AddBlockDropdownMenuProps {
dropdownDatas: DropdownDatas;
Expand All @@ -22,24 +23,26 @@ const AddBlockDropdownMenu: React.FC<AddBlockDropdownMenuProps> = ({
workflowId,
onPathsUpdate,
}) => {
const colors = useColors();
const copiedBlock = useClipboardStore((state) => state.copiedBlock);
const { setShowModal, setModalData } = useModalStore();
const [hoveredItem, setHoveredItem] = useState<string | null>(null);

const menuItems = [
{
type: 'STEP' as const,
label: 'Step Block',
icon: '/step-icons/default-icons/container.svg',
label: 'Step',
icon: `${process.env.NEXT_PUBLIC_SUPABASE_URL}${process.env.NEXT_PUBLIC_SUPABASE_STORAGE_PATH}/assets/shared_components/git-commit.svg`,
},
{
type: 'PATH' as const,
label: 'Path Block',
icon: '/step-icons/default-icons/path.svg',
label: 'Condition',
icon: `${process.env.NEXT_PUBLIC_SUPABASE_URL}${process.env.NEXT_PUBLIC_SUPABASE_STORAGE_PATH}/assets/shared_components/dataflow-04.svg`,
},
{
type: 'DELAY' as const,
label: 'Delay Block',
icon: '/step-icons/default-icons/delay.svg',
label: 'Delay',
icon: `${process.env.NEXT_PUBLIC_SUPABASE_URL}${process.env.NEXT_PUBLIC_SUPABASE_STORAGE_PATH}/assets/shared_components/clock-stopwatch-1.svg`,
},
];

Expand Down Expand Up @@ -104,80 +107,150 @@ const AddBlockDropdownMenu: React.FC<AddBlockDropdownMenuProps> = ({
<>
<div className="fixed inset-0" onClick={onClose} />
<div
className="absolute bg-white rounded-lg shadow-lg border border-gray-200 w-48 z-50"
className="absolute shadow-[0px_4px_6px_-2px_rgba(16,24,40,0.03),0px_12px_16px_-4px_rgba(16,24,40,0.08)] rounded-lg border z-50 py-1 flex flex-col overflow-hidden cursor-pointer"
style={{
top: dropdownDatas.y,
left: dropdownDatas.x,
transform: 'translate(-50%, -100%)',
backgroundColor: colors['bg-secondary'],
borderColor: colors['border-primary'],
zIndex: 99999999
}}
>
{menuItems.map((item) => (
<button
key={item.type}
className="w-full px-4 py-2 flex items-center gap-2 hover:bg-gray-50 text-left"
onClick={() => handleSelect(item.type)}
<div className="py-1">
<div
className="w-[240px] px-2.5 py-[9px] text-sm font-normal"
style={{ color: colors['text-secondary'] }}
>
<img src={item.icon} alt={item.label} className="w-5 h-5" />
<span>{item.label}</span>
</button>
))}

{isLastBlock && (
<button
onClick={async () => {
try {
await fetch(`/api/blocks/${block.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: BlockEndType.END,
}),
});

// Update the block type in paths store
onPathsUpdate((currentPaths) =>
currentPaths.map((path) => {
if (path.id === dropdownDatas.path.id) {
return {
...path,
blocks: path.blocks.map((b) =>
b.id === block.id
? { ...b, type: BlockEndType.END }
: b
),
};
}
return path;
})
);

onClose();
} catch (error) {
console.error('Error converting block to END:', error);
}
}}
className="w-full text-left px-4 py-2 hover:bg-gray-100 rounded"
>
Convert to End Block
</button>
)}

{copiedBlock && (
<button
onClick={(e) => {
e.stopPropagation();
handlePasteBlock();
}}
className="w-full px-4 py-2 flex items-center gap-2 hover:bg-gray-50 text-left"
>
<img
src="/step-icons/default-icons/paste.svg"
alt="Paste"
className="w-5 h-5"
/>
<span>Paste Block</span>
</button>
)}
Add under this a:
</div>

<div
className="h-px my-1"
style={{ backgroundColor: colors['border-secondary'] }}
/>

{menuItems.map((item) => (
<div
key={item.type}
className="self-stretch px-1.5 py-px flex items-center gap-3 transition duration-300"
onClick={() => handleSelect(item.type)}
>
<div
style={{
'--hover-bg': colors['bg-quaternary']
} as React.CSSProperties}
className="grow shrink basis-0 px-2.5 py-[9px] rounded-md justify-start items-center gap-3 flex hover:bg-[var(--hover-bg)] transition-all duration-300 overflow-hidden"
>
<div className="grow shrink basis-0 h-5 justify-start items-center gap-2 flex">
<div className="w-4 h-4 relative overflow-hidden">
<img src={item.icon} alt={item.label} className="w-4 h-4" />
</div>
<div
style={{ color: colors['text-primary'] }}
className="grow shrink basis-0 text-sm font-normal font-['Inter'] leading-tight"
>
{item.label}
</div>
</div>
</div>
</div>
))}

{isLastBlock && (
<div
className="self-stretch px-1.5 py-px flex items-center gap-3 transition duration-300"
onClick={async () => {
try {
await fetch(`/api/blocks/${block.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: BlockEndType.END,
}),
});

// Update the block type in paths store
onPathsUpdate((currentPaths) =>
currentPaths.map((path) => {
if (path.id === dropdownDatas.path.id) {
return {
...path,
blocks: path.blocks.map((b) =>
b.id === block.id
? { ...b, type: BlockEndType.END }
: b
),
};
}
return path;
})
);

onClose();
} catch (error) {
console.error('Error converting block to END:', error);
}
}}
>
<div
style={{
'--hover-bg': colors['bg-quaternary']
} as React.CSSProperties}
className="grow shrink basis-0 px-2.5 py-[9px] rounded-md justify-start items-center gap-3 flex hover:bg-[var(--hover-bg)] transition-all duration-300 overflow-hidden"
>
<div className="grow shrink basis-0 h-5 justify-start items-center gap-2 flex">
<div className="w-4 h-4 relative overflow-hidden">
<img
src={`${process.env.NEXT_PUBLIC_SUPABASE_URL}${process.env.NEXT_PUBLIC_SUPABASE_STORAGE_PATH}/assets/shared_components/stop-circle.svg`}
alt="End of the Flow"
className="w-4 h-4"
/>
</div>
<div
style={{ color: colors['text-primary'] }}
className="grow shrink basis-0 text-sm font-normal font-['Inter'] leading-tight"
>
End of the Flow
</div>
</div>
</div>
</div>
)}

{copiedBlock && (
<div
onClick={(e) => {
e.stopPropagation();
handlePasteBlock();
}}
className="self-stretch px-1.5 py-px flex items-center gap-3 transition duration-300"
>
<div
style={{
'--hover-bg': colors['bg-quaternary']
} as React.CSSProperties}
className="grow shrink basis-0 px-2.5 py-[9px] rounded-md justify-start items-center gap-3 flex hover:bg-[var(--hover-bg)] transition-all duration-300 overflow-hidden"
>
<div className="grow shrink basis-0 h-5 justify-start items-center gap-2 flex">
<div className="w-4 h-4 relative overflow-hidden">
<img
src={`${process.env.NEXT_PUBLIC_SUPABASE_URL}${process.env.NEXT_PUBLIC_SUPABASE_STORAGE_PATH}/assets/shared_components/copy-icon.svg`}
alt="Paste"
className="w-4 h-4"
/>
</div>
<div
style={{ color: colors['text-primary'] }}
className="grow shrink basis-0 text-sm font-normal font-['Inter'] leading-tight"
>
Paste Block
</div>
</div>
</div>
</div>
)}
</div>
</div>
</>
);
Expand Down
33 changes: 32 additions & 1 deletion app/workspace/[id]/[workflowId]/reactflow/components/Flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ interface FlowProps {
) => Promise<void>;
strokeLines: any[];
setStrokeLines: React.Dispatch<React.SetStateAction<any[]>>;
newBlockId: number | null;
clearNewBlockId: () => void;
}

export function Flow({
Expand All @@ -86,6 +88,8 @@ export function Flow({
onBlockAdd,
strokeLines,
setStrokeLines,
newBlockId,
clearNewBlockId,
}: FlowProps) {
const colors = useColors();
const { paths, setPaths } = usePathsStore();
Expand All @@ -112,6 +116,7 @@ export function Flow({
y: 0,
zoom: 1,
});
const { setEditMode } = useEditModeStore();

// Get viewport dimensions from ReactFlow store
const viewportWidth = useStore((store) => store.width);
Expand Down Expand Up @@ -537,6 +542,32 @@ export function Flow({
setAllPaths(paths);
}, [paths, setAllPaths]);

useEffect(() => {
if (newBlockId && nodes.length > 0) {
// Find the node with the new block ID
const nodeId = `block-${newBlockId}`;
const node = nodes.find(n => n.id === nodeId);

if (node) {
// Set this node as selected in edit mode
setEditMode(true, newBlockId.toString());

// Center the view on the node and offset to make room for sidebar
setViewport(
{
x: -(node.position.x - window.innerWidth / 2 + 400),
y: -(node.position.y - window.innerHeight / 2 + 200),
zoom: 1,
},
{ duration: 800 }
);

// Clear the newBlockId after handling it
clearNewBlockId();
}
}
}, [newBlockId, nodes, setEditMode, setViewport, clearNewBlockId]);

return (
<div
className={`flex-1 w-full h-full relative ${
Expand Down Expand Up @@ -647,4 +678,4 @@ export function Flow({
</div>
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function ReactFlowPageClient({
string | undefined
>();
const setPaths = usePathsStore((state) => state.setPaths);
const [newBlockId, setNewBlockId] = useState<number | null>(null);

const searchParams = useSearchParams();

Expand Down Expand Up @@ -68,6 +69,10 @@ export function ReactFlowPageClient({
return;
}

// Get the newly created block's ID from the response
const newBlock = await response.json();
setNewBlockId(newBlock.id);

// Refresh paths data
const pathsResponse = await fetch(
`/api/workspace/${workspaceId}/paths?workflow_id=${workflowId}`
Expand Down Expand Up @@ -130,6 +135,8 @@ export function ReactFlowPageClient({
onBlockAdd={handleBlockAdd}
strokeLines={strokeLines}
setStrokeLines={setStrokeLines}
newBlockId={newBlockId}
clearNewBlockId={() => setNewBlockId(null)}
/>
</ReactFlowProvider>
</div>
Expand Down
12 changes: 6 additions & 6 deletions app/workspace/[id]/[workflowId]/reactflow/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -409,11 +409,11 @@ export function Sidebar({ workspaceId, workflowId }: SidebarProps) {
style={{ backgroundColor: colors['bg-primary'] }}
>
{/* Sidebar with icons */}
<div
className="w-[80px] h-full flex flex-col justify-between border-r"
style={{
<div
className="w-fit px-2 h-full flex flex-col justify-between border-r"
style={{
backgroundColor: colors['bg-primary'],
borderColor: colors['border-secondary'],
borderColor: colors['border-primary'],
}}
>
<div className="flex flex-col pt-4 items-center gap-2">
Expand Down Expand Up @@ -455,15 +455,15 @@ export function Sidebar({ workspaceId, workflowId }: SidebarProps) {
width: sidebarWidth,
minWidth: '250px',
backgroundColor: colors['bg-primary'],
borderColor: colors['border-secondary'],
borderColor: colors['border-primary'],
}}
>
{/* Header Section */}
<div
className="sticky top-0 z-10 px-4 pt-4 pb-3 border-b"
style={{
backgroundColor: colors['bg-primary'],
borderColor: colors['border-secondary'],
borderColor: colors['border-primary'],
}}
>
<div
Expand Down
Loading