The Editor in Detail | Frontend - Marcel-TO/DL2_Save_Editor GitHub Wiki

If you are interested in contributing to the editor or want to change something that suits you more, than keep on reading. This page is dedicated to explaining the frontend code structure in detail. With the help of the following chapters you should have no trouble navigating through the codebase to add your own code.

Prerequisites

When coding, adding and navigating through different files, an IDE is always helpful. Suggested setup:

  • VS Code
    • VS Code Extension: rust-analyzer
    • VS Code Extension: tauri Framework
    • VS Code Extension: React

Where can I find the source code of the frontend?

At first it might seem like there are too many folders and files. But where can I find the ones I actually need? Inside the savegame-editor directory you will find 2 different src files (src and src-tauri).

  • src: Contains the Frontend (UI)
  • src-tauri: Contains the Backend

Therefore the main focus of this page will be dedicated to src.

What to do in src?

The editor itself uses a frontend framework called React. A react application has the following base structure:

src
  - assets
  - App.css
  - App.tsx
  - main.tsx
  - styles.css

assets

savegame-editor/src/assets

This directory contains all images you want to use inside the editor.

App.css / styles.css

savegame-editor/src/*.css

They contain the CSS styles specifically for the App.tsx or main.tsx. Those files are for the root components and only need adjustments when changing something for the whole editor (like for example: font)

main.tsx

savegame-editor/src/main.tsx

This file serves as the entry point for the application. It typically renders the root component of the React application into the DOM.

App.tsx

savegame-editor/src/App.tsx

This file contains the main component of the application. It also includes routing logic and a state management setup. To add more routing options, please follow dedicated section Routing inside the editor to learn more.

The other files (components, models, pages)

The other directories contain different elements of the editor.

For example, the pages directory contains all pages that the editor could use (as long as if a route is set to the page). The components directory contains components that are used inside one or multiple pages. This prevents code clustering when using the same code block in multiple sections and a better maintainability when changing

Routing inside the editor

Inside the App.tsx you will find the route provider that prepares relative links to specific pages. Here an example on how the current route provider could be set up.

function App() {
  const router = createBrowserRouter(
    createRoutesFromElements(
      [
        (<Route path={'/'} element={<MainPage currentSaveFile={currentSaveFile} setCurrentSaveFile={setCurrentSaveFile} setIdData={setCurrentIdDatas}/>}></Route>),
        (<Route path={'/skills'} element={<SkillPage currentSaveFile={currentSaveFile} setCurrentSaveFile={setCurrentSaveFile}/>}></Route>),
        (<Route path={'/unlockables'} element={<UnlockablePage currentSaveFile={currentSaveFile}/>}></Route>),
        (<Route path={'/inventory'} element={<InventoryPage currentSaveFile={currentSaveFile} setCurrentSaveFile={setCurrentSaveFile} idDatas={idDatas}/>}></Route>),
        (<Route path={'/info'} element={<InfoPage/>}></Route>),
      ]
    )
  )

  return (
    <>
      <Background/>
      <RouterProvider router={router}></RouterProvider>
    </>
  );
}

To add a new page to the router, simply import the new page and create a new route:

(<Route path={'/new-page'} element={<NewPage/>}></Route>),

Now if the editor gets directed to a new route that is called /new-page it will load the new page component.

How does the Navbar work?

savegame-editor/src/components/navbar-drawer/navbar-drawer.tsx

The navigation tool for navigating between pages is being displayed at the left side of the screen.

image

But how can you change an element or add another to the Navbar? Since the Navbar iterates through lists, you basically just have to enter a new element to the list. For editing purpose is only the part from line >130 interesting.

Here an example of the infoPages section:

const infoPages: [string, JSX.Element, string, boolean][] = [
  ['Home', <HomeRoundedIcon/>, '/', false],
  ['Info', <HelpOutlineRoundedIcon/>, '/info', false],
];

Each element contains of 4 components. The first element is the name of the page. For example Home or Info. The second component is the Icon that gets displayed on the Navbar. If you want to change the icons, feel free to browse through MUI Material Icons. The third component is the link to the dedicated page. The example in the Routing inside the editor chapter would be as displayed /new-page. And the last component represents the isDisabled variable. If set to true the Navbar item will be dark and not selectable.

How is a page set up?

savegame-editor/src/pages/new-page/new-page.tsx

The structure of a page will be demonstrated with the ongoing new-page example.

As explained in the previous chapter What to do in src?, if you want a global css format, you can use the styles.css. Since every page has to be the same position, size and proportion, every page uses the same class called container which is set inside the styles.css.

.container {
  margin: 0;
  padding-top: 10vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  width: 100% !important;
  height: 100%;
}

Lets start with preparing the page and including the Navbar for the page:

import './new-page.css'
import { NavbarDrawer } from '../../components/navbar-drawer/navbar-drawer'

export const NewPage = (): JSX.Element => {
    return (
        <>
        <div className="container">
            <NavbarDrawer pagename={"New Page"} pagecontent={<h1>Welcome to New Page</h1>}></NavbarDrawer>
        </div>
        </>
    )
}

At first it may seem strange why the Navbar has 2 properties. pagename and pagecontent. The following image will help explaining the situation.

image

Square 1 inside the image is the page name that you enter in the pagename property and square 2 inside the image is the content of the page. But why are the properties inside the Navbar you may ask? Well you can view the Navbar as a drawer container that opens and closes (The animation that happens when you increase the width of the sidebar). The content has to adjust to the width left after the increase of the sidebar. Therefore the navbar-drawer stretches across the whole screen and contains the pagecontent inside.

In the example block above the content of the new page is only a header with the text "Welcome to New Page". Lets create a bit more complex page content:

First you need to create a React Hook (or class depending on your preferences):

const NewPageContent = () => {
    const [counter, setCounter] = useState(0);

    return (
        <>
            <Button onClick={() => setCounter(counter--)}>-</Button>
            <h1>{{counter}}</h1>
            <Button onClick={() => setCounter(counter++)}>+</Button>
        <>
    )
}

The example content is just displaying a counter where you can increase or decrease the value for showcasing purpose only. For more detail on how to implement content, please visit the react docs.

To display the content on the page you need to replace the pagecontent property with the new page content hook:

import './new-page.css'
import { NavbarDrawer } from '../../components/navbar-drawer/navbar-drawer'

export const NewPage = (): JSX.Element => {
    return (
        <>
        <div className="container">
            <NavbarDrawer pagename={"New Page"} pagecontent={<NewPageContent/>}></NavbarDrawer>
        </div>
        </>
    )
}

How to use the savefile properties for a new page

savegame-editor/src/pages/new-page/new-page.tsx

In the previous example we explained how a new page is being implemented into the editor and how to customise the content of the new page. But what if you want to display or change something from the save? How can you link the current selected save to your new page?

In this case we take advantage of reacts useState hook. With this hook you can not only declare a value, but also create a function to change the value. For further explanations, please read the following documentation: State-Hook

In our Use-case we declare an empty save inside the root directory and give every page that needs it the save and the change function as property. The chapter Routing inside the editor already gave a little insight on how the pages are declared. But lets start from the beginning.

Inside the App.tsx (root) we declare the savefile (currently empty):

const [currentSaveFile, setCurrentSaveFile] = useState<SaveFile>();

The Page itself expects the properties:

import './new-page.css'
import { NavbarDrawer } from '../../components/navbar-drawer/navbar-drawer'

export const NewPage = ({currentSaveFile, setCurrentSaveFile}: {currentSaveFile: SaveFile | undefined, setCurrentSaveFile: Function}): JSX.Element => {
    return (
        <>
        <div className="container">
            <NavbarDrawer pagename={"New Page"} pagecontent={<NewPageContent currentSaveFile={currentSaveFile} setCurrentSaveFile={setCurrentSaveFile}/>}></NavbarDrawer>
        </div>
        </>
    )
}

export const NewPageContent = ({currentSaveFile, setCurrentSaveFile}: {currentSaveFile: SaveFile | undefined, setCurrentSaveFile: Function}): JSX.Element => {
    return (
        <>
        // Do something!
        </>
    )
}

And the route where the page gets initialised:

(<Route path={'/new-page'} element={<NewPage currentSaveFile={currentSaveFile} setCurrentSaveFile={setCurrentSaveFile}/>}></Route>),

Now the new page can display the data from the savefile.

How to change the savefile?

If you try to edit the savefile and use the setCurrentSaveFile function it might appear as if the save actually changed. But if you would save the file, you would realise that all changes where visual and the save itself did not change. So, how do we fix it? We say hello to backend. The backend has multiple endpoints being prepared. Please visit the (not yet published) backend documentation for further details.

In order to communicate with the backend we use a function called invoke and invoke a backend endpoint. For better explanation we will take a deeper look with the following example:

async function submitChangingSomething(someParameter: number) {
        invoke("change_something_from_save", {
            parameter: someParameter,
            save_connten: currentSaveFile.file_content
        }).then((new_save_content) => {
            currentSave.file_content = new_save_content
            setCurrentSaveFile(currentSave);
        });
    }

First we invoke a backend function called change_something_from_save (this is just an example and not a real backend function) and this function needs for example someParameter as a parameter. We await the function and if the call succeeds, then replace the current file_content with the new content and do a setCurrentSaveFile to change the currentSaveFile for all pages.

⚠️ **GitHub.com Fallback** ⚠️