matthew-matvei/freeman

View on GitHub
src/renderer/components/App.tsx

Summary

Maintainability
C
1 day
Test Coverage
import autobind from "autobind-decorator";
import os from "os";
import * as React from "react";
import { HotKeys } from "react-hotkeys";
import SplitPane from "react-split-pane";

import { CommandPalette } from "components/modals";
import { Status } from "components/panels";
import { DirectoryWrapper } from "components/wrappers";
import { IHandlers, IStatusNotifier } from "models";
import { ApplicationCommander } from "objects";
import { IAppProps } from "props";
import { IAppState } from "states";
import { DirectoryPaneSide, StatusUpdate } from "types";

import "styles/App.scss";

/** The main application component. */
class App extends React.Component<IAppProps, IAppState> {

    /** Handler functions for the given events this component handles. */
    private handlers: IHandlers = {
        openCommandPalette: this.openCommandPalette,
        switchPane: this.switchPane
    };

    /** Notifies the status component of current application state. */
    private statusNotifier: IStatusNotifier;

    /** A timer used for the status message. */
    private statusMessageTimeout?: NodeJS.Timer | number;

    /**
     * Defines how the main app component is rendered.
     *
     * @param props the props for the component
     *
     * @returns a JSX element representing the app view
     */
    constructor(props: IAppProps) {
        super(props);

        this.statusNotifier = {
            notify: (payload: string) => this.updateStatus("notification", payload),
            setChosenCount: (payload: number) => this.updateStatus("chosenCount", payload),
            setItemCount: (payload: number) => this.updateStatus("itemCount", payload)
        };

        this.state = {
            isCommandPaletteOpen: false,
            selectedPane: "left",
            status: {
                chosenCount: 0,
                itemCount: 0,
                message: ""
            }
        };

    }

    /**
     * Defines how the main application component is rendered
     *
     * @returns a JSX element representing the main application view
     */
    public render(): JSX.Element {
        const { directoryManager, keysManager, settingsManager, themeManager } = this.props;
        const appStyle = { color: themeManager.theme.primaryColour };
        const resizerStyle: React.CSSProperties = {
            backgroundColor: this.props.themeManager.theme.resizers.colour
        };

        return <div>
            <HotKeys keyMap={keysManager.keyMap} handlers={this.handlers}>
                <div className="App" style={appStyle}>
                    <div className="main">
                        <SplitPane
                            split="vertical"
                            defaultSize="50vw"
                            resizerStyle={resizerStyle}>
                            <DirectoryWrapper
                                id="left"
                                initialPath={os.homedir()}
                                isSelectedPane={this.state.selectedPane === "left"}
                                sendSelectedPaneUp={this.selectPane}
                                directoryManager={directoryManager}
                                statusNotifier={this.statusNotifier}
                                settingsManager={settingsManager}
                                theme={themeManager.theme}
                                integratedTerminal={this.props.leftTerminal}
                                persister={this.props.persister} />
                            <DirectoryWrapper
                                id="right"
                                initialPath={os.homedir()}
                                isSelectedPane={this.state.selectedPane === "right"}
                                sendSelectedPaneUp={this.selectPane}
                                directoryManager={directoryManager}
                                statusNotifier={this.statusNotifier}
                                settingsManager={settingsManager}
                                theme={themeManager.theme}
                                integratedTerminal={this.props.rightTerminal}
                                persister={this.props.persister} />
                        </SplitPane>
                    </div>
                    <Status {...this.state.status} theme={themeManager.theme} />
                </div>
            </HotKeys>
            <CommandPalette
                isOpen={this.state.isCommandPaletteOpen}
                onClose={this.closeCommandPalette}
                applicationCommands={ApplicationCommander.commands}
                theme={this.props.themeManager.theme} />
        </div>;
    }

    /** Handles closing the quick select modal, if not already closed. */
    @autobind
    private closeCommandPalette() {
        if (this.state.isCommandPaletteOpen) {
            this.setState({ isCommandPaletteOpen: false } as IAppState);
        }
    }

    /** Handles opening the quick select modal, if not already open. */
    @autobind
    private openCommandPalette() {
        if (!this.state.isCommandPaletteOpen) {
            this.setState({ isCommandPaletteOpen: true } as IAppState);
        }
    }

    /**
     * Handles updating the status component's message.
     *
     * @param updateType the type of the status update
     * @param payload the data to use in the status update
     */
    @autobind
    private updateStatus(updateType: StatusUpdate, payload: string | number) {
        const status = this.state.status;
        const statusTimeout = 2000;

        if (updateType === "itemCount") {
            status.itemCount = payload as number;
        } else if (updateType === "chosenCount") {
            status.chosenCount = payload as number;
        } else {
            status.message = payload as string;
            this.statusMessageTimeout && clearTimeout(this.statusMessageTimeout as NodeJS.Timer);
            this.statusMessageTimeout = setTimeout(() => {
                status.message = "";
                this.setState({ status } as IAppState);
            }, statusTimeout);
        }

        this.setState({ status } as IAppState);
    }

    /** Handles switching selected pane. */
    @autobind
    private switchPane() {
        const { selectedPane } = this.state;

        if (selectedPane === "left") {
            this.selectPane("right");
        } else {
            this.selectPane("left");
        }
    }

    /**
     * Handles selecting the given pane.
     *
     * @param paneToSelect the pane to select, if not currently selected
     */
    @autobind
    private selectPane(paneToSelect: DirectoryPaneSide) {
        if (paneToSelect !== this.state.selectedPane) {
            this.setState({ selectedPane: paneToSelect } as IAppState);
        }
    }
}

export default App;