How I made a Desktop Environment in the Browser (Part 1: Window Manager)
Tags: webdev, tutorial, javascript, react
Published: 2022-02-06 06:19:52

Welcome to my series on how I built a [Desktop Environment](https://en.wikipedia.org/wiki/Desktop_environment) in the browser.

This is actually my 3rd version of a desktop environment and by far the one which I gave my most dedication. The [first try](https://github.com/DustinBrett/muse) was using Angular 8 and I only spent a month working on it with no intention of taking it too serious. The [second try](https://github.com/DustinBrett/daedalOS/tree/v1) took me 4 months to make and was a bit more functional. But for my [latest version](https://github.com/DustinBrett/daedalOS) I decided to do a total rewrite from scratch.

{% youtube https://youtu.be/dn7_9R1oxiw %}

I took the opportunity of a total redo to begin [live streaming](https://youtu.be/S-rF5rkhaJ0) my coding progress for the project on my [YouTube channel](https://www.youtube.com/c/DustinBrett). In the end I streamed [52 weeks](https://www.youtube.com/playlist?list=PLM88opVjBuU7xSRoHhs3hZBz3JmHHBMMN) in a row until the end of 2021 at which time I [published](https://youtu.be/U9nsxgCQZjk) the web app as my [personal website](https://dustinbrett.com/).

## Window Manager

![Window Manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vabwgbhddpbw8hwupjcd.png) 

The first topic I'd like to discuss in this series is the [Window Manager](https://en.wikipedia.org/wiki/Window_manager) which is responsible for displaying the component for each of the [apps](https://github.com/DustinBrett/daedalOS/tree/main/components/apps) that exists within the processes object. The processes are stored in a [React context](https://reactjs.org/docs/context.html). Then within the [Next.js's](https://nextjs.org/) index page I load a component called the [AppsLoader](https://github.com/DustinBrett/daedalOS/blob/main/components/system/Apps/AppsLoader.tsx) which will render the component in each process.

`AppsLoader.tsx`
```ts
import { ProcessConsumer } from "contexts/process";
import dynamic from "next/dynamic";

const RenderComponent = dynamic(
  () => import("components/RenderComponent")
);

const AppsLoader = (): JSX.Element => (
  <ProcessConsumer>
    {({ processes = {} }) => (
      {Object.entries(processes)
        .map(([id, { Component, hasWindow }]) => (
          <RenderComponent
            key={id}
            Component={Component}
            hasWindow={hasWindow}
            id={id}
          />
        ))}
    )}
  </ProcessConsumer>
);

export default AppsLoader;
```

Rather than just render the component directly I pass it to an wrapper component called [RenderComponent](https://github.com/DustinBrett/daedalOS/blob/main/components/system/Apps/RenderComponent.tsx) that will conditionally render the component wrapped around a [Window](https://github.com/DustinBrett/daedalOS/blob/main/components/system/Window/index.tsx) depending on if it needs to be contained in a window.

`RenderComponent.ts`
```ts
import dynamic from "next/dynamic";

const Window = dynamic(() => import("components/Window"));

export type ComponentProcessProps = {
  id: string;
};

type RenderComponentProps = {
  Component: React.ComponentType<ComponentProcessProps>;
  hasWindow?: boolean;
  id: string;
};

const RenderComponent = ({
  Component,
  hasWindow = true,
  id,
}: RenderComponentProps): JSX.Element =>
  hasWindow ? (
    <Window id={id}>
      <Component id={id} />
    </Window>
  ) : (
    <Component id={id} />
  );

export default RenderComponent;
```

If the process is removed from the process context object then it is equivalent to closing the window as once it's no longer in the process context the `<AppsLoader />` will re-render without that component. Now that we have the ability to open an app which shows it's component wrapped in a window, we can look at what the window does with that component.

The way I have setup this component is yet another wrapper, this time wrapping the functionality of dragging and resizing as well as styling the [section](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section) HTML5 container element. The library I used for dragging and resizing is called [react-rnd](https://github.com/bokuweb/react-rnd). For the styling I used [Styled Components](https://styled-components.com/).

`Window.ts`
```ts
import type {
  ComponentProcessProps
} from "components/RenderComponent";
import { StyledTitlebar, Titlebar } from "components/Window/Titlebar";
import React from "react";
import { Rnd } from "react-rnd";
import styled from "styled-components";

const StyledWindow = styled.section`
  contain: strict;
  height: 100%;
  overflow: hidden;
  width: 100%;

  ${StyledTitlebar} + * {
    height: ${({ theme }) => `calc(100% - ${theme.titleBar.height})`};
  }
`;

const Window = ({
  children,
  id,
}: React.PropsWithChildren<ComponentProcessProps>): JSX.Element => (
  <Rnd dragHandleClassName="dragHandle">
    <StyledWindow>
      <Titlebar id={id} />
      {children}
    </StyledWindow>
  </Rnd>
);

export default Window;
```

The usage of `<Rnd />` is best explained on their [GitHub page](https://github.com/bokuweb/react-rnd#usage), I've assigned a `dragHandleClassName` which will be added to the titlebar to allow dragging the window from there. The `<StyledWindow />` is define above the component and has a few styling choices worth mentioning.

I've used [contain](https://developer.mozilla.org/en-US/docs/Web/CSS/contain) in an attempt to eke out a little performance boost. Then I've set this section to be 100% dimensions so it takes up all space within the `<Rnd />` container element. I've also set [overflow](https://developer.mozilla.org/en-US/docs/Web/CSS/overflow) to hidden to prevent any content getting outside the window. Finally I have the process component which comes directly after the `<StyledTitlebar />` set it's height to be 100% but subtracting the height of the `<Titlebar />` component which will be at the top of the window to show it's icon, title & minimize/maximize/close buttons.

`Titlebar.tsx`
```ts
import {
  CloseIcon,
  MaximizeIcon,
  MinimizeIcon
} from "components/Window/Icons";
import { useProcesses } from "contexts/process";
import styled from "styled-components";
import { Button, Icon } from "styles/common";

type TitlebarProps = {
  id: string;
};

const StyledTitlebar = styled.header`
  height: ${({ theme }) => theme.titleBar.height};
  position: relative;
  top: 0;
`;

const Titlebar = ({ id }: TitlebarProps): JSX.Element => {
  const {
    close,
    maximize,
    minimize,
    processes: { [id]: process },
  } = useProcesses();
  const { icon, title } = process || {};

  return (
    <StyledTitlebar className="dragHandle">
      <h1>
        <figure>
          <Icon alt={title} imgSize={16} src={icon} />
          <figcaption>{title}</figcaption>
        </figure>
      </h1>
      <nav>
        <Button onClick={() => minimize(id)} title="Minimize">
          <MinimizeIcon />
        </Button>
        <Button onClick={() => maximize(id)} title="Maximize">
          <MaximizeIcon />
        </Button>
        <Button onClick={() => close(id)} title="Close">
          <CloseIcon />
        </Button>
      </nav>
    </StyledTitlebar>
  );
};

export default Titlebar;
```

Finally the titlebar component above displays information about the process as well as controls for setting the minimize/maximize states within the process context or to close the window by removing the process from the context. I've left out details about the icons and styling but at this point you could style this anyway you like.

With this code we now have a mechanism of representing components in our app dynamically as they are added to the process context. The elements will be added to the DOM and go through the typical lifecycle for a React component.

If you would like a detailed overview of my desktop environment features while you wait for my next part to be released, please check it out below as well as like/subscribe if you enjoy the content.

{% youtube geCNiJnw8iE %}

Thanks for reading this topic in my series. I am not sure which topic I will cover for the next part but in this series I at least plan to discuss the Start Menu, Taskbar, File System/Manager, Wallpaper, Clock & lots more. I will also get into some specifics "apps" that were fun/challenging to make.