Skip to content

Chapter 1: Application Views#

Welcome to the tutorial for the mapbox-gl_deck.gl_turf.js-ts project! This first chapter is all about understanding a fundamental concept in building applications: Application Views. Don't worry if you're new to web development or these specific libraries; we'll take it step by step.

What's the Problem?#

Imagine you're building an app that helps people explore buildings using photos and maps. Sometimes the user needs to upload a photo. Other times, they need to see that photo's location and building details on a map. Maybe they also want to browse a gallery of all the photos they've uploaded.

You can't show everything on the screen at once! You need a way to switch between different screens or sections depending on what the user is doing. This is where the concept of "Application Views" comes in.

Think of your app like a building. Different "rooms" are designed for different activities:

  • A Photo Upload Room where you handle taking or uploading pictures.
  • A Map Room showing the location and details of a specific building related to a photo.
  • A Gallery Room showcasing many photos on a map.
  • A Login Room where users enter their credentials.

Application Views are simply these different "rooms" or distinct screens your application can display.

What are Application Views?#

In our project, Application Views represent the major sections or modes of the application. They control:

  • What you see: The specific user interface elements displayed.
  • What you can do: The interactions available to the user in that mode.

Switching between views is like walking from one room to another in our building analogy. The "main brain" of the application decides which room you should be in based on what you're trying to do (like uploading a photo or viewing the gallery).

How Views Work in This Project#

Our project uses React, a popular JavaScript library for building user interfaces. In React, we often represent different parts of the UI as components. Our "views" are essentially just different React components.

The key idea is that one main component is responsible for deciding which view component to show at any given time.

Let's look at how we define these different "rooms" in our project.

Defining the Rooms: The LAYOUT Enum#

To keep track of which view (or room) we are currently in, we use a simple list of possibilities. In TypeScript, we can use something called an enum (short for enumeration) for this. It's like giving a specific name to each possible view state.

Look at the src/types/layout.ts file:

// src/types/layout.ts
export enum LAYOUT {
  LOGIN,
  PHOTO,
  RESULT,
  SHOWCASE,
}

This LAYOUT enum defines our four main views:

  • LAYOUT.LOGIN: The login screen.
  • LAYOUT.PHOTO: The screen for uploading or taking a photo.
  • LAYOUT.RESULT: The screen showing the map and details for a single photo upload result.
  • LAYOUT.SHOWCASE: The screen showing a gallery of photos on a map.

This enum gives us clear, easy-to-read names to represent the different states of our application's UI.

The Building Manager: MainView.tsx#

Now, let's look at the "main brain" component that manages which view is currently displayed. This is handled by the MainView component in src/views/main-view.tsx.

MainView keeps track of the current view using a piece of state. State is just data that a component can hold and manage, and when it changes, the component usually re-renders to update the display.

Here's a simplified look at how MainView manages the current view using state:

// Inside src/views/main-view.tsx (simplified)
import React, { useState } from "react";
import { LAYOUT } from "../types/layout"; // Import our view definitions

// Import the components for each view
import LoginView from "./login-view";
import { PhotoView } from "./photo-view";
import { MapResultView } from "./map-result-view";
import { MapShowcaseView } from "./map-showcase-view";

export const MainView = () => {
  // This piece of state tracks the currently active view
  const [activeLayout, setActiveLayout] = useState(LAYOUT.LOGIN); // Start at the Login view

  // ... other state variables and functions ...

  // Function to change the active view
  const onLayoutChange = (newLayout: number) => {
      // Here you might add logic, e.g., check if geo data is available
      if (geo || newLayout === LAYOUT.SHOWCASE || newLayout === LAYOUT.LOGIN) {
        setActiveLayout(newLayout); // Update the state
      } else {
        alert("Please upload a photo first!");
        setActiveLayout(LAYOUT.PHOTO); // Stay on or go back to Photo view
      }
  };

  const handleLogin = () => {
    setActiveLayout(LAYOUT.SHOWCASE); // Change view after successful login
  };

  return (
    <Box sx={{ display: "flex", height: "100vh" }} ref={ref}>
      {/* ... other global UI like CSS Baseline ... */}

      {/* Conditional Rendering: Show the view based on activeLayout */}
      {activeLayout === LAYOUT.LOGIN && (
        <LoginView onClick={handleLogin} setBearerToken={setBearerToken} />
      )}

      {activeLayout === LAYOUT.PHOTO && (
        <PhotoView
          tags={tags}
          previewImg={previewImg}
          extractedDrawerOpen={extractedDrawerOpen}
          onImageChange={(result) => setSelectedImg(result)}
          onShowcaseClick={() => setActiveLayout(LAYOUT.SHOWCASE)}
          setExtractedDrawerOpen={setExtractedDrawerOpen}
        />
      )}

      {activeLayout === LAYOUT.RESULT && (
        <MapResultView
          geo={geo}
          view={view}
          imageUrl={previewImg}
          drawLaz={drawLaz}
          lazFile={lazFile}
          onLazChange={onLazChangeHandler}
          drawLaz_={drawLazHandler}
          tags={tags}
          previewImg={previewImg}
          onImageChange={(result) => setSelectedImg(result)}
          onShowcaseClick={() => setActiveLayout(LAYOUT.SHOWCASE)}
          setExtractedDrawerOpen={setExtractedDrawerOpen}
          extractedDrawerOpen={extractedDrawerOpen}
        />
      )}

      {activeLayout === LAYOUT.SHOWCASE && (
        <MapShowcaseView
          view={view}
          geo={geo}
          drawLaz_={drawLazHandler}
          lazFile={lazFile}
          onLazChange={onLazChangeHandler}
          tags={tags}
          previewImg={previewImg}
          onImageChange={(result) => setSelectedImg(result)}
          setGeo={setGeo}
          onShowcaseClick={() => setActiveLayout(LAYOUT.SHOWCASE)}
          setExtractedDrawerOpen={setExtractedDrawerOpen}
          extractedDrawerOpen={extractedDrawerOpen}
          drawLaz={drawLaz}
          bearerToken={bearerToken}
        />
      )}

      {/* ... other global UI like BottomNav, Snackbar, Backdrop, Modal ... */}
    </Box>
  );
};

This code snippet shows the core idea:

  1. MainView uses useState to hold the activeLayout, initialized to LAYOUT.LOGIN.
  2. The return part uses conditional rendering (activeLayout === LAYOUT.LOGIN && <LoginView ... />). This means: "If activeLayout is LAYOUT.LOGIN, then render the LoginView component."
  3. Only one of these conditions will be true at a time, so only one view component is rendered and displayed.
  4. Functions like onLayoutChange and handleLogin are defined in MainView. When these functions are called (usually triggered by user interaction inside one of the view components, passed down as props), they call setActiveLayout.
  5. Calling setActiveLayout updates the state, React sees the state has changed, and re-renders MainView, showing the component that now matches the new activeLayout value.

The Flow of Switching Views#

Let's trace a simple path: logging in and seeing the showcase gallery.

sequenceDiagram
    participant User
    participant LoginView
    participant MainView
    participant MapShowcaseView

    User->LoginView: Enters credentials and Clicks "ENTER"
    LoginView->MainView: Calls onClick prop (handleLogin function)
    MainView->MainView: Updates activeLayout state to LAYOUT.SHOWCASE
    MainView->MapShowcaseView: Renders MapShowcaseView component
    MapShowcaseView-->>User: Displays the Gallery/Map UI
    Note over User, MapShowcaseView: User is now in the Showcase view
  1. The application starts, MainView renders, and its initial state activeLayout is LAYOUT.LOGIN.
  2. The conditional rendering in MainView shows the LoginView component.
  3. The user interacts with LoginView (enters details, clicks "ENTER").
  4. The "ENTER" button click in LoginView calls the onClick function that was passed down as a prop from MainView. This function in MainView is handleLogin.
  5. handleLogin calls setActiveLayout(LAYOUT.SHOWCASE).
  6. MainView's state changes, triggering a re-render.
  7. During the re-render, MainView checks the conditions again. activeLayout === LAYOUT.LOGIN is now false, but activeLayout === LAYOUT.SHOWCASE is true.
  8. MainView stops rendering LoginView and starts rendering MapShowcaseView.
  9. The user now sees the UI defined by MapShowcaseView.

The MainView component acts like a central controller, managing the overall application flow by switching between these different view components based on actions.

Conclusion#

In this chapter, we learned that Application Views are the distinct screens or modes of our application. We saw how the LAYOUT enum helps us define these views and how the MainView component uses state and conditional rendering to decide which view component to display at any given time. This structure helps organize our application and separate different functionalities into manageable parts.

Next, we'll dive into how the application handles user actions, specifically focusing on file uploads and other interactions that trigger changes in our application, including switching between the views we just discussed.

Next Chapter: User Input Handling (Files & Interaction)


Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/types/layout.ts), 2(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/views/login-view.tsx), 3(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/views/main-view.tsx), 4(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/views/map-result-view.tsx), 5(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/views/map-showcase-view.tsx), 6(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/views/photo-view.tsx)