Skip to content

Commit

Permalink
Merge pull request #50 from INTO-CPS-Association/9-provide-a-block-te…
Browse files Browse the repository at this point in the history
…mplates-gallery

initial functionality for block templates.
  • Loading branch information
omar-perpetuallabs authored Jul 26, 2023
2 parents f76d109 + 7c4c7f3 commit 22de9ac
Show file tree
Hide file tree
Showing 20 changed files with 1,387 additions and 87 deletions.
840 changes: 829 additions & 11 deletions components/block-template/package-lock.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions components/block-template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@
"private": true,
"dependencies": {
"@fluentui/react": "^8.110.7",
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "^4.11.3",
"@mui/icons-material": "^5.14.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.5",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^28.1.8",
"@types/node": "^14.18.52",
"@types/react": "^17.0.62",
"@types/react-dom": "^17.0.20",
"ace-builds": "^1.23.4",
"quill": "^1.3.7",
"react": "^17.0.2",
"react-ace": "^10.1.0",
"react-dom": "^17.0.2",
"react-quill": "^2.0.0",
"react-scripts": "5.0.1",
"react-vega": "^7.6.0",
"typescript": "^4.9.5",
Expand Down
48 changes: 48 additions & 0 deletions components/block-template/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,51 @@
transform: rotate(360deg);
}
}

/* App.css */

.app-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}

.app-title {
font-size: 2em;
color: #333;
margin-bottom: 20px;
text-align: center;

}

.app-description {
font-size: 1.2em;
color: #666;
margin-bottom: 30px;
}

.blocks-container > div {
margin-top: 20px;
padding-top: 20px;
margin-bottom: 50px;

}

select, button {
padding: 10px 15px;
margin-right: 10px;
font-size: 1em;
border: 1px solid #ccc;
border-radius: 4px;
}

button {
background-color: #007BFF;
color: white;
cursor: pointer;
}

button:hover {
background-color: #0056b3;
}
45 changes: 36 additions & 9 deletions components/block-template/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
import React from 'react';
import ChoiceGroupComponent from './choiceGroup';
import Block from './Components/SingleBlock';
import BlockDropdownMenu from './Components/BlockDropdownMenu';
import { useBlockContext } from './Components/BlockContext';
import { BlockData } from './Data/interface';
import './App.css';
import { nanoid } from 'nanoid'
import blockService from './Components/BlocksComponentsMap';


export const App: React.FC = () => {
return (
<div className = "App">
<header className = "App-header">
<ChoiceGroupComponent />
</header>
</div>
);
}
//state
const { projectData, setProjectData } = useBlockContext();


//------------------ FUNCTIONALITY ------------------//
//add block
const handleBlockAdd = (block: Omit<BlockData, "id">) => {
setProjectData([...projectData, { ...block, id: nanoid() }]);
};


//------------------ RENDER ------------------//
return (
<div className="app-container">
<h1 className="app-title">My Project</h1>
<p className="app-description">A project description</p>

<BlockDropdownMenu onBlockAdd={handleBlockAdd} blockTypes={blockService.getBlockTypes()} />

<div className="blocks-container">
{projectData.map((block) => (
<Block key={block.id} {...block} />
))}
</div>
</div>
);
};


27 changes: 27 additions & 0 deletions components/block-template/src/Blocks/AudioBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { useState } from 'react';
import { BlockProps } from '../Data/interface';


const AudioBlock: React.FC<BlockProps> = ({ data, onDataChange }) => {
//state variables
const [src, setSrc] = useState(data);

//update the src when the data changes
const handleSrcChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newSrc = e.target.value;
setSrc(newSrc);
onDataChange(newSrc);
};

//------------------ RENDER ------------------//
return (
<div className="audio-block">
<audio controls src={src}>
Your browser does not support the audio element.
</audio>
<input type="text" placeholder="Enter audio URL..." value={src} onChange={handleSrcChange} />
</div>
);
};

export default AudioBlock;
36 changes: 36 additions & 0 deletions components/block-template/src/Blocks/CodeSnippetBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useState } from 'react';
import { BlockProps } from '../Data/interface';
import AceEditor from 'react-ace';
import "../Styling/CodeSnippetBlock.css"
import 'ace-builds/src-noconflict/mode-javascript';
import 'ace-builds/src-noconflict/theme-monokai';
import 'ace-builds/src-noconflict/worker-javascript'; // this line is important


//------------------ CODE SNIPPET BLOCK COMPONENT ------------------//
const CodeSnippetBlock: React.FC<BlockProps> = ({ data, onDataChange }) => {
//state variables
const [code, setCode] = useState(data);

//update the code when the data changes
const handleCodeChange = (newValue: string) => {
setCode(newValue);
onDataChange(newValue);
};

//------------------ RENDER ------------------//
return (
<AceEditor
mode="javascript"
theme="monokai"
value={code}
onChange={handleCodeChange}
name="UNIQUE_ID_OF_DIV"
editorProps={{ $blockScrolling: true }}
setOptions={{ useWorker: false }}
/>
);
};


export default CodeSnippetBlock;
72 changes: 72 additions & 0 deletions components/block-template/src/Blocks/ImageBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, {useState} from 'react';
import { BlockProps } from '../Data/interface';
import '../Styling/ImageBlock.css';



const ImageBlock: React.FC<BlockProps> = ({ id, data, onDataChange }) => {
// state variables
const [inputSrc, setInputSrc] = useState(data);
const [isValidSrc, setIsValidSrc] = useState(true);

// update the src when the data changes
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputSrc(e.target.value);
setIsValidSrc(true); // Reset the validation state
};

const handleBlur = () => {
// Check if the input is a valid URL
try {
new URL(inputSrc);
} catch (_) {
setIsValidSrc(false); // If the input is not a valid URL, set the validation state to false
}
onDataChange(inputSrc);
}

const imageUrl = isValidSrc ? inputSrc : '/path-to-default-image.jpg';

// RENDER
return (
<div>
<input
type="text"
placeholder="Enter image URL..."
value={inputSrc}
onChange={handleImageChange}
onBlur={handleBlur}
/>
<div className="block-container" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: '10px' }}>
<img src={imageUrl} alt="Block content" />
</div>
</div>
);
};

export default ImageBlock;

/*
const ImageBlock: React.FC<BlockProps> = ({ id, data, onDataChange }) => {
//state variables
const [src, setSrc] = useState(data);
//update the src when the data changes
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newSrc = e.target.value;
setSrc(newSrc);
onDataChange(newSrc); // notify the parent about the change
};
//------------------ RENDER ------------------//
return (
<div className="image-block">
<input type="text" placeholder="Enter image URL..." value={src} onChange={handleImageChange} />
<img src={src} onError={(e) => (e.target as HTMLImageElement).src = '/path-to-default-image.jpg'} alt="Block content" />
</div>
);
};
export default ImageBlock;
*/
46 changes: 46 additions & 0 deletions components/block-template/src/Blocks/TextBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useState } from 'react';
import ReactQuill from 'react-quill';
import { BlockProps } from '../Data/interface';
import 'react-quill/dist/quill.snow.css'; // import styles

// Quill modules to attach to editor
const modules = {
toolbar: [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
[{ 'font': [] }],
[{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
],
};

// Quill formats to allow in editor
const formats = [
'bold', 'italic', 'underline', 'strike',
'font', 'size'
];


//------------------ TEXT BLOCK COMPONENT ------------------//
const TextBlock: React.FC<BlockProps> = ({ id, data, onDataChange }) => {
//state variable
const [text, setText] = useState(data);

//------------------ FUNCTIONALITY ------------------//
const handleChange = (value: string) => {
setText(value);
onDataChange(value); // notify the parent about the change
}

//------------------ RENDER ------------------//
return (
<div className="text-block">
<ReactQuill
value={text}
onChange={handleChange}
modules={modules}
formats={formats}
/>
</div>
);
}

export default TextBlock;
31 changes: 31 additions & 0 deletions components/block-template/src/Components/BlockContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { createContext, useState, useContext } from 'react';
import { BlockContextProps, BlockData} from '../Data/interface';

//create context
export const BlockContext = createContext<BlockContextProps>({} as BlockContextProps);


export const BlockContextProvider: React.FC = ({ children }) => {
//state variables
const [projectData, setProjectData] = useState<BlockData[]>([]);

//------------------ FUNCTIONALITY ------------------//
const handleBlockDelete = (id: string) => {
setProjectData(prevData => prevData.filter(block => block.id !== id));
};

return (
<BlockContext.Provider value={{ projectData, setProjectData, handleBlockDelete }}>
{children}
</BlockContext.Provider>
);
};


export const useBlockContext = () => {
const context = useContext(BlockContext);
if (!context) {
throw new Error("useBlockContext must be used within BlockContextProvider");
}
return context;
};
38 changes: 38 additions & 0 deletions components/block-template/src/Components/BlockDropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useState } from 'react';
import { BlockData } from '../Data/interface';


//------------------ DROPDOWN COMPONENT ------------------//
const BlockDropdownMenu: React.FC<{ onBlockAdd: (block: Omit<BlockData, 'id'>) => void; // Change the type of block
blockTypes: string[];
}> = ({ onBlockAdd, blockTypes }) => {

//state variables
const [selectedBlockType, setSelectedBlockType] = useState<string>('');


//------------------ FUNCTIONALITY ------------------//
//add block
const handleAddBlock = () => {
if (selectedBlockType) {
onBlockAdd({ type: selectedBlockType, data: 'Default Data' }); // remove id from here
}
};

//------------------ RENDER ------------------//
return (
<>
<select value={selectedBlockType} onChange={(e) => setSelectedBlockType(e.target.value)}>
<option value="">--Select a block--</option>
{blockTypes.map((type) => (
<option key={type} value={type}>
{type}
</option>
))}
</select>
<button onClick={handleAddBlock}>Add Block</button>
</>
);
};

export default BlockDropdownMenu;
Loading

0 comments on commit 22de9ac

Please sign in to comment.