Website Generator Implementation

Technologies Used

The core of the application is built with React and Electron. React provides a dynamic and responsive user interface, while Electron wraps the React application into a desktop application, enabling access to the operating system's native functionalities. Node.js is utilised in the main.js backend process to handle file system operations and inter-process communication (IPC).

User Interface

The user interface consists of menu items on the left side, allowing users to switch between different sections of the form. Each section is represented by a MenuItem component that displays the section's name and a progress bar, which visualizes the completion status of the section using a CircularProgressBar component. Here's a snippet demonstrating the MenuItem component:


          import CircularProgressBar from '../../shared/circleprogressbar/CircularProgressBar';
          import './MenuItem.scss';
          
          export default function MenuItem({
            sectionName,
            activeSection,
            setActiveSection,
            sectionProgress,
          }) {
            // ...
            return (
              <li className={`menu-item ${activeSection === sectionName ? 'active' : ''}`}
                  onClick={() => setActiveSection(sectionName)}>
                {capitalise(sectionName)}
                <CircularProgressBar percentage={sectionProgress[sectionName] ? 100 : 0} />
              </li>
            );
          }
        

Dynamic Content Rendering

The active section of the form and its content are managed by the activeSection state and rendered by the renderSection function, which returns a Page component with specific props that control the form's behavior and data handling:


          const renderSection = () => {
            return (
              <Page
                pageType={activeSection}
                data={sectionData[activeSection]}
                onInputChange={handleInputChange}
                onSave={handleSave}
              />
            );
          } 
        

The Page component is responsible for rendering form inputs for the current section. It uses FormInput and ListFormInput components for individual fields and lists of fields, respectively. These components update the sectionData state as the user interacts with the form.

Data Management

The data from the form is stored in a

sectionData

state object within Home.js. This object is structured with keys corresponding to different content sections, and it holds the values, types, and labels for each form input. For example:


          // Partial structure of sectionData for Shared Content
          general: {
            title: { value: '', type: 'input', label: 'Title' },
            subtitle: { value: '', type: 'input', label: 'Subtitle' },
            // ...
          } 
        

Saving Data

When the user presses the "Save Section" button, the handleSave function is called, updating the sectionData state with the new data from the form and progressing to the next section if all fields are complete.

Website Generation

The generateWebsite function orchestrates the creation of the website. It starts by clearing the image directory, then uploads images, randomizes filenames, and updates the sectionData state with the new filenames. This updated state is then transformed into objects structured to match the content JSON files of the website template:


          const jsonData = {
            sharedContent: generateSharedContent(sectionDataToSave.general),
            // ...
          };           
        

Example of a generateVideoPage function implementation:


          const generateVideoPage = (sectionData) => {
            return {
              videosSection: {
                header: sectionData.header.value,
                videoUrls: sectionData.videoUrls.value,
              }
            }
          };          
        

Finally, the save-json IPC event is triggered. It overwrites the template's existing JSON files with new data reflecting the user's inpu, effectively updating the template with content ready for deployment. This action finalises the website generation.

IPC Communication

IPC (Inter-Process Communication) plays a pivotal role in facilitating communication between the Electron application's frontend and backend, handling essential tasks like file management and data transformation. The backend, structured around Node.js, listens for specific IPC events to execute operations. The key IPC events include:

    • clear-img-dir: Initiates the clearing of the image directory within the website template. This ensures that old or unrelated images are removed before uploading new ones.
      
                    ipcMain.on('clear-img-dir', (event) => {
                      const imgDirPath = path.join(__dirname, '../../template/src/pageContent/img/')
                    
                      // read the directory and delete all files
                      fs.readdir(imgDirPath, (err, files) => {
                        if (err) {
                          console.error('Error reading images directory:', err)
                          event.reply('clear-img-dir-reply', { success: false, error: err })
                        } else {
                          files.forEach((file) => {
                            fs.unlink(path.join(imgDirPath, file), (err) => {
                              if (err) {
                                console.error('Error deleting file:', err)
                                event.reply('clear-img-dir-reply', { success: false, error: err })
                              }
                            })
                          })
                          event.reply('clear-img-dir-reply', { success: true })
                        }
                      })
                    })          
                  
      file-upload: Handles the uploading of images to the template/src/pageContent/img/ directory. This process involves receiving the original file path and the new randomised filename, copying the file to the designated directory to make it accessible within the generated website.
      
                    ipcMain.on('file-upload', async (event, filePath, newFileName) => {
                      const imgDirPath = path.join(__dirname, '../../template/src/pageContent/img/');
                      const newFilePath = path.join(imgDirPath, newFileName);
                    
                      fs.copyFile(filePath, newFilePath, (err) => {
                        if (err) {
                          event.reply('file-upload-reply', { success: false, error: err });
                        } else {
                          event.reply('file-upload-reply', { success: true, filePath: newFilePath });
                        }
                      });
                    });          
                  
      save-json: final step in the website generation process, where JSON data representing with the website's content is saved to corresponding files within the template. This event iterates through each content section, mapping them to their respective JSON filenames and writing the updated content to the template's directory.
      
                    ipcMain.on('save-json', (event, jsonData) => {
                      const dictionary = {
                        sharedContent: 'sharedContent.json',
                        homePageContent: 'homePageContent.json',
                        // Additional mappings
                      };
                    
                      Object.keys(jsonData).forEach((key) => {
                        const filePath = path.join(__dirname, '../../template/src/pageContent/', dictionary[key]);
                        fs.writeFile(filePath, JSON.stringify(jsonData[key], null, 2), (err) => {
                          if (err) {
                            event.reply('save-json-reply', { success: false, error: err });
                          } else {
                            event.reply('save-json-reply', { success: true, filePath });
                          }
                        });
                      });
                    });              
                  
  • This pattern of sending requests and handling responses via IPC is repeated for other events like clearing the image directory or saving JSON data, allowing the renderer process to remain lightweight and focused on the user interface, while the main process deals with the heavy lifting of file management and other OS-level operations.