DEV Community

Ramu Narasinga
Ramu Narasinga

Posted on

How does file upload work in Immich codebase - Part 1.

In this article, we review file upload mechanism in Immich codebase. We will look at:

  1. What is Immich?

  2. Locating the Upload button

  3. openFileUploadDialog function

  4. fileUploadHandler function

In this part 1, let’s focus 1–3 and in the part 2, we will review the fileUploadHandler at great detail.

What is Immich?

Immich is a self-hosted photo and video management solution. Easily back up, organize, and manage your photos on your own server. Immich helps you browse, search and organize your photos and videos with ease, without sacrificing your privacy.

Check out immich.app

Locating the Upload button

In a new codebase, you don’t know where to look for. In the demo — https://demo.immich.app/photos, there is the “Upload” button.

Searching for the Upload, gave me 200+ hits on Github in this repository. I narrowed down svelte files. Yeah, this is when I realised Immich uses Sevlte for its frontend and finally was able to locate the Upload button in the file, navigation-bar.svelte

You will find the below code in navigation-bar.ts at L108.


{#if !page.url.pathname.includes('/admin') && showUploadButton && onUploadClick}
  <Button
    leadingIcon={mdiTrayArrowUp}
    onclick={onUploadClick}
    class="hidden lg:flex"
    variant="ghost"
    size="medium"
    color="secondary"
    >{$t('upload')}
  </Button>
Enter fullscreen mode Exit fullscreen mode

onUploadClick is a prop passed down from user-page-layout.svelte as shown below:

<header>
  {#if !hideNavbar}
    <NavigationBar 
      {showUploadButton} 
      onUploadClick={() => openFileUploadDialog()} 
    />
  {/if}
Enter fullscreen mode Exit fullscreen mode

openFileUploadDialog function

In lib/utils/file-uploader.ts, you will find the following code:

export const openFileUploadDialog = async (options: FileUploadParam = {}) => {
  const { albumId, multiple = true } = options;
  const extensions = uploadManager.getExtensions();

  return new Promise<string[]>((resolve, reject) => {
    try {
      const fileSelector = document.createElement('input');

      fileSelector.type = 'file';
      fileSelector.multiple = multiple;
      fileSelector.accept = extensions.join(',');
      fileSelector.addEventListener(
        'change',
        (e: Event) => {
          const target = e.target as HTMLInputElement;
          if (!target.files) {
            return;
          }
          const files = Array.from(target.files);

          resolve(fileUploadHandler({ files, albumId }));
        },
        { passive: true },
      );

      fileSelector.click();
    } catch (error) {
      console.log('Error selecting file', error);
      reject(error);
    }
  });
};
Enter fullscreen mode Exit fullscreen mode

This function returns a promise and this promise resolves with a value returned by the function, fileUploadhandler. We are reviewing this in the part-2.

I would be interested in learning what uploadManager.getExtenstions function call returns or what uploadManager is about for that matter.

uploadManager

There isn’t much going on in the uploadManager, you will find the following code:

import { eventManager } from '$lib/managers/event-manager.svelte';
import { uploadAssetsStore } from '$lib/stores/upload';
import { getSupportedMediaTypes, type ServerMediaTypesResponseDto } from '@immich/sdk';

class UploadManager {
  mediaTypes = $state<ServerMediaTypesResponseDto>({ image: [], sidecar: [], video: [] });

  constructor() {
    eventManager.on('AppInit', () => void this.#loadExtensions()).on('AuthLogout', () => void this.reset());
  }

  reset() {
    uploadAssetsStore.reset();
  }

  async #loadExtensions() {
    try {
      this.mediaTypes = await getSupportedMediaTypes();
    } catch {
      console.error('Failed to load supported media types');
    }
  }

  getExtensions() {
    return [...this.mediaTypes.image, ...this.mediaTypes.video];
  }
}

export const uploadManager = new UploadManager();
Enter fullscreen mode Exit fullscreen mode

getExtentions returns an array of values returned by the function, getSupportedMediaTypes.

About me:

Hey, my name is Ramu Narasinga. I study codebase architecture in large open-source projects.

Email: ramu.narasinga@gmail.com

I spent 200+ hours analyzing Supabase, shadcn/ui, LobeChat. Found the patterns that separate AI slop from production code. Stop refactoring AI slop. Start with proven patterns. Check out production-grade projects at thinkthroo.com

References:

  1. search?q=repo%3Aimmich-app%2Fimmich+Go+to+search+language%3ASvelte&type=code&l=Svelte

  2. immich/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte#L101

  3. search?q=repo%3Aimmich-app%2Fimmich+++navigation-bar&type=code

  4. immich/blob/web/src/lib/components/layouts/user-page-layout.svelte#L7

Top comments (0)