Skip to content

Commit

Permalink
feat: Add block editor components and context
Browse files Browse the repository at this point in the history
  • Loading branch information
ozhanefemeral committed Aug 1, 2024
1 parent ee12367 commit 241effc
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 50 deletions.
4 changes: 4 additions & 0 deletions apps/next/app/features/code-generator/CodeGenerator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useSavedFunctions } from "@/contexts/SavedFunctionsContext";
import { SortableBlockList } from "./SortableBlockList";
import { SaveDialog } from "./Dialogs";
import { CodeViewer } from "@/components/CodeViewer";
import { BlockEditor } from "@/components/editor";

export const CodeGenerator: React.FC = () => {
const { state, setState, code } = useCodeGenerator();
Expand All @@ -28,6 +29,9 @@ export const CodeGenerator: React.FC = () => {

return (
<section className="h-fit p-4 border border-gray-200 border-dashed rounded-md">
<div>
<BlockEditor />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="col-span-1">
<SortableBlockList />
Expand Down
5 changes: 4 additions & 1 deletion apps/next/components/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
import { DndContext } from "@dnd-kit/core";
import { CodeGeneratorProvider } from "@/contexts/CodeGeneratorContext";
import { SavedFunctionsProvider } from "@/contexts/SavedFunctionsContext";
import { BlockEditorProvider } from "@/contexts/BlockEditorContext";

export const Providers: React.FC<React.PropsWithChildren<{}>> = ({
children,
}) => {
return (
<DndContext>
<CodeGeneratorProvider>
<SavedFunctionsProvider>{children}</SavedFunctionsProvider>
<SavedFunctionsProvider>
<BlockEditorProvider>{children}</BlockEditorProvider>
</SavedFunctionsProvider>
</CodeGeneratorProvider>
</DndContext>
);
Expand Down
125 changes: 76 additions & 49 deletions apps/next/components/blocks/BlockContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"use client";
import { CodeBlock } from '@ozhanefe/ts-codegenerator';
import { ChevronDownIcon, ChevronRightIcon } from '@radix-ui/react-icons';
import React from 'react';
import { IfBlockView } from './IfBlockView';
import { WhileBlockView } from './WhileBlockView';
import { FunctionCallBlockView } from './FunctionCallBlockView';
import { CodeBlock } from "@ozhanefe/ts-codegenerator";
import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons";
import React, { useCallback } from "react";
import { IfBlockView } from "./IfBlockView";
import { WhileBlockView } from "./WhileBlockView";
import { FunctionCallBlockView } from "./FunctionCallBlockView";
import { useBlockEditor } from "@/contexts/BlockEditorContext";

interface BlockContainerProps {
title: string;
Expand All @@ -17,65 +18,91 @@ interface BlockViewRendererProps {
block: CodeBlock;
}

export const BlockViewRenderer: React.FC<BlockViewRendererProps> = React.memo(({ block }) => {
switch (block.blockType) {
case 'if':
return (
<IfBlockView
condition={block.condition}
thenBlocks={block.thenBlocks.map((b, i) => <BlockViewRenderer key={i} block={b} />)}
elseIfBlocks={block.elseIfBlocks?.map((elseIf) => ({
condition: elseIf.condition,
blocks: elseIf.blocks.map((b, j) => <BlockViewRenderer key={j} block={b} />)
}))}
elseBlock={block.elseBlock && {
blocks: block.elseBlock.blocks.map((b, i) => <BlockViewRenderer key={i} block={b} />)
}}
/>
);
case 'while':
return (
<WhileBlockView condition={block.condition}>
{block.loopBlocks.map((b, i) => <BlockViewRenderer key={i} block={b} />)}
</WhileBlockView>
);
case 'functionCall':
return <FunctionCallBlockView block={block} />;
default:
return null;
}
});
export const BlockViewRenderer: React.FC<BlockViewRendererProps> = React.memo(
({ block }) => {
const { setCurrentBlock } = useBlockEditor();

const handleClick = useCallback(() => {
setCurrentBlock(block);
}, [block, setCurrentBlock]);

export const BlockContainer: React.FC<BlockContainerProps> = ({ title, type, children, isCollapsible = false }) => {
return (
<div className="cursor-pointer" onClick={handleClick}>
{block.blockType === "if" && (
<IfBlockView
condition={block.condition}
thenBlocks={block.thenBlocks.map((b, i) => (
<BlockViewRenderer key={i} block={b} />
))}
elseIfBlocks={block.elseIfBlocks?.map((elseIf) => ({
condition: elseIf.condition,
blocks: elseIf.blocks.map((b, j) => (
<BlockViewRenderer key={j} block={b} />
)),
}))}
elseBlock={
block.elseBlock && {
blocks: block.elseBlock.blocks.map((b, i) => (
<BlockViewRenderer key={i} block={b} />
)),
}
}
/>
)}
{block.blockType === "while" && (
<WhileBlockView condition={block.condition}>
{block.loopBlocks.map((b, i) => (
<BlockViewRenderer key={i} block={b} />
))}
</WhileBlockView>
)}
{block.blockType === "functionCall" && (
<FunctionCallBlockView block={block} />
)}
</div>
);
}
);

export const BlockContainer: React.FC<BlockContainerProps> = ({
title,
type,
children,
isCollapsible = false,
}) => {
const [isOpen, setIsOpen] = React.useState(true);

const blockColors = {
functionCall: 'bg-blue-100 border-blue-200',
if: 'bg-green-100 border-green-200',
elseif: 'bg-yellow-100 border-yellow-200',
else: 'bg-orange-100 border-orange-200',
while: 'bg-purple-100 border-purple-200',
default: 'bg-gray-100 border-gray-200'
functionCall: "bg-blue-100 border-blue-200",
if: "bg-green-100 border-green-200",
elseif: "bg-yellow-100 border-yellow-200",
else: "bg-orange-100 border-orange-200",
while: "bg-purple-100 border-purple-200",
default: "bg-gray-100 border-gray-200",
};

const color = blockColors[type] || blockColors.default;

const handleCollapse = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
setIsOpen(!isOpen);
};

return (
<div className={`${color} border-2 rounded-lg p-2 my-1`}>
<div className="flex items-center justify-between">
<span className="font-semibold">{title}</span>
{isCollapsible && (
<button onClick={() => setIsOpen(!isOpen)} className="focus:outline-none">
{isOpen ? <ChevronDownIcon width={16} /> : <ChevronRightIcon width={16} />}
<button onClick={handleCollapse} className="focus:outline-none">
{isOpen ? (
<ChevronDownIcon width={16} />
) : (
<ChevronRightIcon width={16} />
)}
</button>
)}
</div>
{(!isCollapsible || isOpen) && (
<div className="mt-1">
{children}
</div>
)}
{(!isCollapsible || isOpen) && <div className="mt-1">{children}</div>}
</div>
);
};
};
32 changes: 32 additions & 0 deletions apps/next/components/editor/BlockEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use client";

import { useBlockEditor } from "@/contexts/BlockEditorContext";
import { FunctionEditor } from "./FunctionEditor";
import { IfEditor } from "./IfEditor";
import { WhileEditor } from "./WhileEditor";

export const BlockEditor = () => {
const { currentBlock } = useBlockEditor();

if (!currentBlock) return <EmptyBlockEditor />;

return (
<div className="flex flex-1 w-full pb-4">
{currentBlock.blockType === "functionCall" && (
<FunctionEditor block={currentBlock} />
)}
{currentBlock.blockType === "if" && <IfEditor block={currentBlock} />}
{currentBlock.blockType === "while" && (
<WhileEditor block={currentBlock} />
)}
</div>
);
};

const EmptyBlockEditor = () => {
return (
<div>
<p className="text-center">Select a block to edit</p>
</div>
);
};
12 changes: 12 additions & 0 deletions apps/next/components/editor/FunctionEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FunctionCallBlock } from "@ozhanefe/ts-codegenerator";

export const FunctionEditor: React.FC<{ block: FunctionCallBlock }> = ({
block,
}) => {
const { functionInfo } = block;
return (
<div className="">
Editing: <span className="font-bold">{functionInfo.name}</span>
</div>
);
};
9 changes: 9 additions & 0 deletions apps/next/components/editor/IfEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IfBlock } from "@ozhanefe/ts-codegenerator";

export const IfEditor: React.FC<{ block: IfBlock }> = ({ block }) => {
return (
<div className="">
Editing: <span className="font-bold">if({block.condition})</span>
</div>
);
};
9 changes: 9 additions & 0 deletions apps/next/components/editor/WhileEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { WhileLoopBlock } from "@ozhanefe/ts-codegenerator";

export const WhileEditor: React.FC<{ block: WhileLoopBlock }> = ({ block }) => {
return (
<div className="">
Editing: <span className="font-bold">while({block.condition})</span>
</div>
);
};
1 change: 1 addition & 0 deletions apps/next/components/editor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./BlockEditor";
43 changes: 43 additions & 0 deletions apps/next/contexts/BlockEditorContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
createContext,
Dispatch,
PropsWithChildren,
SetStateAction,
useContext,
useEffect,
useState,
} from "react";
import { CodeBlock } from "@ozhanefe/ts-codegenerator";

interface Context {
currentBlock: CodeBlock | null;
setCurrentBlock: Dispatch<SetStateAction<CodeBlock | null>>;
}

const BlockEditorContext = createContext<Context>({
currentBlock: null,
setCurrentBlock: () => {},
});

export const BlockEditorProvider = ({ children }: PropsWithChildren<{}>) => {
const [currentBlock, setCurrentBlock] = useState<CodeBlock | null>(null);

useEffect(() => {
console.log("currentBlock:", currentBlock);
}, [currentBlock]);

return (
<BlockEditorContext.Provider
value={{
currentBlock,
setCurrentBlock,
}}
>
{children}
</BlockEditorContext.Provider>
);
};

export const useBlockEditor = () => {
return useContext(BlockEditorContext);
};

0 comments on commit 241effc

Please sign in to comment.