<script setup lang="ts">
import { onMounted, reactive, watch, computed } from "vue";
import type {
  FolderTree,
  S3PseudoFolder,
  S3ObjectWithFolder,
} from "@/types/PseudoFolder";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import { filesize } from "filesize";
import dayjs from "dayjs";
import { Toast, Tooltip } from "bootstrap";
import PermissionListModal from "@/components/object-storage/modals/PermissionListModal.vue";
import UploadObjectModal from "@/components/object-storage/modals/UploadObjectModal.vue";
import CopyObjectModal from "@/components/object-storage/modals/CopyObjectModal.vue";
import PermissionModal from "@/components/object-storage/modals/PermissionModal.vue";
import ObjectDetailModal from "@/components/object-storage/modals/ObjectDetailModal.vue";
import CreateFolderModal from "@/components/object-storage/modals/CreateFolderModal.vue";
import DeleteModal from "@/components/modals/DeleteModal.vue";
import type { _Object as S3Object } from "@aws-sdk/client-s3";
import { useAuthStore } from "@/stores/users";
import { useBucketStore } from "@/stores/buckets";
import { useS3ObjectStore } from "@/stores/s3objects";
import { useS3KeyStore } from "@/stores/s3keys";

const authStore = useAuthStore();
const bucketRepository = useBucketStore();
const objectRepository = useS3ObjectStore();
const s3KeyRepository = useS3KeyStore();

// Constants
// -----------------------------------------------------------------------------

const props = defineProps<{
  bucketName: string;
  subFolders: string[] | string;
}>();

const randomIDSuffix = Math.random().toString(16).substring(2, 8);
let successToast: Toast | null = null;
let refreshTimeout: NodeJS.Timeout | undefined = undefined;

// Reactive State
// -----------------------------------------------------------------------------
const deleteObjectsState = reactive<{
  deletedItem: string;
  potentialObjectToDelete: string;
  deleteFolder: boolean;
}>({
  deletedItem: "",
  potentialObjectToDelete: "",
  deleteFolder: true,
});

const objectState = reactive<{
  loading: boolean;
  filterString: string;
  bucketNotFoundError: boolean;
  bucketPermissionError: boolean;
  editObjectKey: string;
  copyObject: S3Object;
  viewDetailKey: string | undefined;
}>({
  loading: true,
  filterString: "",
  bucketNotFoundError: false,
  bucketPermissionError: false,
  editObjectKey: "",
  copyObject: {
    Key: "",
    Size: 0,
    LastModified: new Date(),
  },
  viewDetailKey: undefined,
});

// Computed Properties
// -----------------------------------------------------------------------------
const filteredObjects = computed<(S3ObjectWithFolder | S3PseudoFolder)[]>(
  () => {
    return objectState.filterString.length > 0
      ? visibleObjects.value.filter(
          (obj) => obj.Key?.includes(objectState.filterString),
        )
      : visibleObjects.value;
  },
);

const s3Objects = computed<S3Object[]>(
  () => objectRepository.objectMapping[props.bucketName] ?? [],
);

const folderStructure = computed<FolderTree>(() => {
  /**
   * Store the entire folder structure in a bucket in a tree-like data structure
   */
  return objectsWithFolders.value.reduce(
    // Add one object after another to the folder structure
    (fTree, currentObject) => {
      // If the object is not in a sub folder, but it in the top level 'folder'
      if (currentObject.folder.length === 0) {
        fTree.files.push(currentObject);
      } else {
        // If the object is in a sub folder
        let currentFolder: FolderTree = fTree;
        // For every sub folder the object is in , navigate into the sub folder
        for (const folderName of currentObject.folder) {
          // If the sub folder doesn't exist yet, create it
          if (
            Object.keys(currentFolder.subFolders).find(
              (subFolderName) => subFolderName === folderName,
            ) == undefined
          ) {
            currentFolder.subFolders[folderName] = {
              subFolders: {},
              files: [],
            };
          }
          // navigate into the sub folder
          currentFolder = currentFolder.subFolders[folderName] as FolderTree;
        }
        // Add object to the folder
        currentFolder.files.push(currentObject);
      }
      return fTree;
    },
    // Empty folder structure as initial value
    {
      subFolders: {},
      files: [],
    } as FolderTree,
  );
});

const objectsWithFolders = computed<S3ObjectWithFolder[]>(() => {
  /**
   * Add to the meta information from objects the pseudo filename and their pseudo folder
   * This can be inferred from the key of the object where the '/' character is the delimiter, e.g.
   * dir1/dir2/text.txt ->
   *  folder: dir1, dir2
   *  filename: text.txt
   */
  return s3Objects.value.map((obj) => {
    const splittedKey = obj.Key?.split("/") ?? [""];
    return {
      ...obj,
      pseudoFileName: splittedKey[splittedKey.length - 1],
      folder: splittedKey.slice(0, splittedKey.length - 1),
    };
  });
});

const currentSubFolders = computed<string[]>(() => {
  /**
   * Transform a single sub folder from a string to an array containing the string and
   * replace an empty string with an empty list
   */
  return props.subFolders instanceof Array
    ? props.subFolders
    : props.subFolders.length > 0
    ? [props.subFolders]
    : [];
});

const visibleObjects = computed<(S3ObjectWithFolder | S3PseudoFolder)[]>(() => {
  /**
   * Compute the visible objects based on the current sub folder
   */
  let currentFolder = folderStructure.value;
  // Navigate into right sub folder
  for (const subFolder of currentSubFolders.value) {
    if (currentFolder.subFolders[subFolder] == null) {
      // If sub folder doesn't exist, no object is visible
      return [];
    } else {
      currentFolder = currentFolder.subFolders[subFolder];
    }
  }
  // Add all objects and sub folders from the current sub folder as visible object
  const arr = [];
  arr.push(...currentFolder.files);
  arr.push(
    ...Object.keys(currentFolder.subFolders).map((subFolderName) => {
      const folderSize = calculateFolderSize(
        currentFolder.subFolders[subFolderName],
      );
      const folderLastModified = dayjs(
        calculateFolderLastModified(currentFolder.subFolders[subFolderName]),
      ).toDate();
      return {
        name: subFolderName,
        Size: folderSize,
        Key: subFolderName,
        parentFolder: currentSubFolders.value,
        LastModified: folderLastModified,
      } as S3PseudoFolder;
    }),
  );
  return arr.filter(
    (obj) => !obj.Key?.endsWith("/") && (obj.Key?.length ?? 0) > 0,
  );
});

const subFolderInUrl = computed<boolean>(
  () => currentSubFolders.value.length > 0,
);
const errorLoadingObjects = computed<boolean>(
  () => objectState.bucketPermissionError || objectState.bucketNotFoundError,
);
const writableBucket = computed<boolean>(() => {
  // Allow only upload in bucket folder with respect to permission prefix
  let prefixWritable = true;
  if (
    bucketRepository.ownPermissions[props.bucketName]?.file_prefix != undefined
  ) {
    prefixWritable =
      bucketRepository.ownPermissions[props.bucketName]?.file_prefix ===
      currentSubFolders.value.join("/") + "/";
  }
  return bucketRepository.writableBucket(props.bucketName) && prefixWritable;
});
const readableBucket = computed<boolean>(() =>
  bucketRepository.readableBucket(props.bucketName),
);

// Watchers
// -----------------------------------------------------------------------------
watch(
  () => props.bucketName,
  (newBucketName, oldBucketName) => {
    if (oldBucketName !== newBucketName) {
      objectState.viewDetailKey = undefined;
      // If bucket is changed, update the objects
      objectState.bucketPermissionError = false;
      objectState.bucketNotFoundError = false;
      fetchObjects();
      objectState.filterString = "";
    }
  },
);

watch(
  visibleObjects,
  (visObjs) => {
    if (visObjs.length > 0) {
      // Initialise tooltips after DOM changes
      setTimeout(() => {
        document
          .querySelectorAll("span.date-tooltip")
          .forEach((tooltipTriggerEl) => new Tooltip(tooltipTriggerEl));
      }, 500);
    }
  },
  { flush: "post" },
);

// Lifecycle Hooks
// -----------------------------------------------------------------------------
onMounted(() => {
  let counter = 0;
  const onFinally = () => {
    counter++;
    if (counter > 1) {
      fetchObjects();
    }
  };
  // wait till s3keys and ownPermissions are available before fetching objects
  s3KeyRepository.fetchS3Keys(onFinally);
  bucketRepository.fetchOwnPermissions(onFinally);

  document
    .querySelectorAll(".tooltip-container")
    .forEach(
      (tooltipTriggerEl) => new Tooltip(tooltipTriggerEl, { trigger: "hover" }),
    );
  successToast = new Toast("#successToast-" + randomIDSuffix);
});

// Functions
// -----------------------------------------------------------------------------
/**
 * Calculate recursively the cumulative file size of all o objects in a folder
 * @param folder Folder to inspect
 * @returns The size of this folder in bytes
 */
function calculateFolderSize(folder: FolderTree): number {
  let folderSize = 0;
  folderSize += folder.files.reduce((acc, file) => acc + (file.Size ?? 0), 0);
  for (const subFolderName of Object.keys(folder.subFolders)) {
    folderSize += calculateFolderSize(folder.subFolders[subFolderName]);
  }
  return folderSize;
}

/**
 * Calculate recursively when an object in a folder were modified the last time
 * @param folder Folder to inspect
 * @returns The last modified timestamp as ISO string
 */
function calculateFolderLastModified(folder: FolderTree): string {
  let lastModified: dayjs.Dayjs;
  lastModified = folder.files
    .map((f) => dayjs(f.LastModified))
    .reduce(
      (acc, fileAccessed) => (fileAccessed.isAfter(acc) ? fileAccessed : acc),
      dayjs("2000-01-01"),
    );
  for (const subFolderName of Object.keys(folder.subFolders)) {
    const lastModifiedSubFolder = dayjs(
      calculateFolderLastModified(folder.subFolders[subFolderName]),
    );
    if (lastModifiedSubFolder.isAfter(lastModified)) {
      lastModified = lastModifiedSubFolder;
    }
  }
  return lastModified.toISOString();
}

/**
 * Fetch object from bucket with loading animation
 */
function fetchObjects() {
  objectState.loading = true;
  objectState.bucketPermissionError = false;
  objectState.bucketNotFoundError = false;
  const prefix: string | undefined =
    bucketRepository.ownPermissions[props.bucketName]?.file_prefix ?? undefined;
  objectRepository
    .fetchS3Objects(props.bucketName, prefix, () => {
      objectState.loading = false;
      objectState.bucketPermissionError = false;
      objectState.bucketNotFoundError = false;
    })
    .catch((error) => {
      if (error.Code == "AccessDenied") {
        objectState.bucketPermissionError = true;
      } else {
        objectState.bucketNotFoundError = true;
      }
    });
}

/**
 * Fetch the meta information about objects from a bucket
 */
function refreshObjects() {
  clearTimeout(refreshTimeout);
  refreshTimeout = setTimeout(() => {
    fetchObjects();
  }, 500);
}

function isS3Object(
  obj: S3PseudoFolder | S3ObjectWithFolder,
): obj is S3ObjectWithFolder {
  return (obj as S3ObjectWithFolder).folder !== undefined;
}

function deleteObject(key?: string) {
  if (key == undefined) {
    return;
  }
  deleteObjectsState.potentialObjectToDelete = key;
  deleteObjectsState.deleteFolder = false;
}

/**
 * Delete an Object in the current folder
 * @param key Key of the Object
 */
function confirmedDeleteObject(key: string) {
  objectRepository.deleteObject(props.bucketName, key).then(() => {
    const splittedKey = key.split("/");
    deleteObjectsState.deletedItem = splittedKey[splittedKey.length - 1];
    successToast?.show();
  });
}

/**
 * Initiate the download of the provided object
 * @param key Key of the object
 * @param bucket Bucket of the object
 */
async function downloadObject(bucket: string, key?: string) {
  if (key == undefined) {
    return;
  }
  const url = await objectRepository.getPresignedUrl(bucket, key);
  //creating an invisible element
  const element = document.createElement("a");
  element.setAttribute("href", url);
  element.setAttribute("target", "_blank");
  document.body.appendChild(element);
  element.click();
  document.body.removeChild(element);
}

function deleteFolder(folderPath: string) {
  deleteObjectsState.potentialObjectToDelete = folderPath;
  deleteObjectsState.deleteFolder = true;
}

/**
 * Delete a folder in the current Bucket
 * @param folderPath Path to the folder with a trailing "/", e.g. some/path/to/a/folder/
 */
function confirmedDeleteFolder(folderPath: string) {
  objectRepository
    .deleteObjectsWithPrefix(props.bucketName, folderPath)
    .then(() => {
      const splittedPath = folderPath.split("/");
      deleteObjectsState.deletedItem = splittedPath[splittedPath.length - 2];
      successToast?.show();
    });
}

function getObjectFileName(key: string): string {
  const splittedKey = key.split("/");
  return splittedKey[splittedKey.length - 1];
}
</script>

<template>
  <div class="toast-container position-fixed top-toast end-0 p-3">
    <div
      role="alert"
      aria-live="assertive"
      aria-atomic="true"
      class="toast text-bg-success align-items-center border-0"
      data-bs-autohide="true"
      :id="'successToast-' + randomIDSuffix"
    >
      <div class="d-flex">
        <div class="toast-body">
          Successfully deleted {{ deleteObjectsState.deletedItem }}
        </div>
        <button
          type="button"
          class="btn-close btn-close-white me-2 m-auto"
          data-bs-dismiss="toast"
          aria-label="Close"
        ></button>
      </div>
    </div>
  </div>
  <DeleteModal
    modalID="delete-object-modal"
    :object-name-delete="deleteObjectsState.potentialObjectToDelete"
    :back-modal-id="undefined"
    @confirm-delete="
      deleteObjectsState.deleteFolder
        ? confirmedDeleteFolder(deleteObjectsState.potentialObjectToDelete)
        : confirmedDeleteObject(deleteObjectsState.potentialObjectToDelete)
    "
  />
  <!-- Navbar Breadcrumb -->
  <nav aria-label="breadcrumb" class="fs-2">
    <ol class="breadcrumb">
      <li class="breadcrumb-item" :class="{ active: subFolderInUrl }">
        <router-link
          v-if="subFolderInUrl"
          :to="{
            name: 'bucket',
            params: { bucketName: props.bucketName, subFolders: [] },
          }"
          >{{ props.bucketName }}
        </router-link>
        <span v-else class="text-secondary">{{ props.bucketName }}</span>
      </li>
      <li
        class="breadcrumb-item"
        v-for="(folder, index) in currentSubFolders"
        :key="folder"
        :class="{ active: index === currentSubFolders.length }"
      >
        <router-link
          v-if="index !== currentSubFolders.length - 1"
          :to="{
            name: 'bucket',
            params: {
              bucketName: props.bucketName,
              subFolders: currentSubFolders.slice(0, index + 1),
            },
          }"
          >{{ folder }}
        </router-link>
        <span v-else class="text-secondary">{{ folder }}</span>
      </li>
    </ol>
  </nav>
  <!-- Inputs on top -->
  <!-- Search bucket text input -->
  <div class="d-flex justify-content-between align-items-center">
    <div class="flex-grow-1 me-2">
      <div class="input-group rounded shadow-sm">
        <span class="input-group-text" id="objects-search-wrapping"
          ><font-awesome-icon icon="fa-solid fa-magnifying-glass"
        /></span>
        <input
          type="text"
          class="form-control"
          placeholder="Search Files"
          aria-label="Search Files"
          aria-describedby="objects-search-wrapping"
          :disabled="errorLoadingObjects"
          v-model.trim="objectState.filterString"
        />
      </div>
    </div>
    <!-- Upload object button -->
    <div id="BucketViewButtons" class="">
      <button
        type="button"
        class="btn btn-light me-3 tooltip-container border shadow-sm"
        :disabled="errorLoadingObjects"
        data-bs-toggle="tooltip"
        data-bs-title="Refresh Objects"
        @click="refreshObjects"
      >
        <font-awesome-icon icon="fa-solid fa-arrow-rotate-right" />
        <span class="visually-hidden">Refresh Objects</span>
      </button>
      <button
        type="button"
        class="btn btn-light me-2 tooltip-container border shadow-sm"
        :disabled="errorLoadingObjects || !writableBucket"
        data-bs-toggle="modal"
        data-bs-title="Upload File"
        data-bs-target="#upload-object-modal"
      >
        <font-awesome-icon icon="fa-solid fa-upload" />
        <span class="visually-hidden">Upload File</span>
      </button>
      <upload-object-modal
        :bucket-name="props.bucketName"
        modalID="upload-object-modal"
        :key-prefix="currentSubFolders.join('/')"
        :edit-object-file-name="undefined"
      />
      <!-- Add folder button -->
      <button
        type="button"
        class="btn btn-light me-3 tooltip-container border shadow-sm"
        :disabled="errorLoadingObjects || !writableBucket"
        data-bs-toggle="modal"
        data-bs-title="Create Folder"
        data-bs-target="#create-folder-modal"
      >
        <font-awesome-icon icon="fa-solid fa-plus" />
        Folder
        <span class="visually-hidden">Add Folder</span>
      </button>
      <create-folder-modal
        :bucket-name="props.bucketName"
        modalID="create-folder-modal"
        :key-prefix="currentSubFolders.join('/')"
      />
      <!-- Add bucket permission button -->
      <button
        v-if="!authStore.foreignUser"
        :hidden="!bucketRepository.permissionFeatureAllowed(props.bucketName)"
        type="button"
        class="btn btn-light me-2 tooltip-container border shadow-sm"
        :disabled="errorLoadingObjects"
        data-bs-toggle="modal"
        data-bs-title="Create Bucket Permission"
        data-bs-target="#create-permission-modal"
      >
        <font-awesome-icon icon="fa-solid fa-user-plus" />
        <span class="visually-hidden">Add Bucket Permission</span>
      </button>
      <permission-modal
        v-if="!authStore.foreignUser"
        modalID="create-permission-modal"
        :bucket-name="props.bucketName"
        :sub-folders="folderStructure"
        :edit-user-permission="undefined"
        :editable="false"
        :readonly="false"
        :deletable="false"
        :back-modal-id="undefined"
      />
      <button
        v-if="!authStore.foreignUser"
        :hidden="!bucketRepository.permissionFeatureAllowed(props.bucketName)"
        type="button"
        class="btn btn-light tooltip-container border shadow-sm"
        :disabled="errorLoadingObjects"
        data-bs-title="List Bucket Permission"
        data-bs-toggle="modal"
        data-bs-target="#permission-list-modal"
      >
        <font-awesome-icon icon="fa-solid fa-users-line" />
        <span class="visually-hidden">View Bucket Permissions</span>
      </button>
      <permission-list-modal
        v-if="
          objectState.loading == false &&
          bucketRepository.ownPermissions[props.bucketName] == undefined &&
          !authStore.foreignUser
        "
        :bucket-name="props.bucketName"
        :sub-folders="folderStructure"
        modalID="permission-list-modal"
      />
    </div>
  </div>
  <!-- Body -->
  <div class="pt-3">
    <!-- If bucket not found -->
    <div v-if="objectState.bucketNotFoundError" class="text-center fs-2 mt-5">
      <font-awesome-icon
        icon="fa-solid fa-magnifying-glass"
        class="mb-3 fs-0"
        style="color: var(--bs-secondary)"
      />
      <p>
        Bucket <i>{{ props.bucketName }}</i> not found
      </p>
    </div>
    <!-- If no permission for bucket -->
    <div
      v-else-if="objectState.bucketPermissionError"
      class="text-center fs-2 mt-5"
    >
      <font-awesome-icon
        icon="fa-solid fa-folder-xmark"
        class="mb-3 fs-0"
        style="color: var(--bs-secondary)"
      />
      <p>You don't have permission for this bucket</p>
    </div>
    <!-- Show content of bucket -->
    <div v-else>
      <!-- Table header -->
      <table class="table table-sm table-hover caption-top align-middle">
        <caption>
          Displaying
          {{
            objectState.loading ? 0 : filteredObjects.length
          }}
          Items
        </caption>
        <thead>
          <tr>
            <th scope="col">Name</th>
            <th scope="col">Last Accessed</th>
            <th scope="col">Size</th>
            <th scope="col"></th>
          </tr>
        </thead>
        <!-- Table body when loading the objects -->
        <tbody v-if="objectState.loading">
          <tr v-for="n in 5" :key="n" class="placeholder-glow">
            <th scope="row">
              <span class="placeholder w-100 bg-secondary"></span>
            </th>
            <td><span class="placeholder w-50 bg-secondary"></span></td>
            <td><span class="placeholder w-50 bg-secondary"></span></td>
            <td></td>
          </tr>
        </tbody>
        <!-- Table body when no objects are in the bucket -->
        <tbody v-else-if="filteredObjects.length === 0">
          <tr>
            <td colspan="4" class="text-center fst-italic fw-light">
              No objects to display
            </td>
          </tr>
        </tbody>
        <!-- Table body when showing objects -->
        <tbody v-else>
          <tr v-for="obj in filteredObjects" :key="obj.Key">
            <th scope="row" class="text-truncate">
              <!-- Show file name if row is an object -->
              <div v-if="isS3Object(obj)">{{ obj.pseudoFileName }}</div>
              <!-- Show link to subfolder if row is a folder -->
              <div v-else>
                <router-link
                  class="text-decoration-none"
                  :to="{
                    name: 'bucket',
                    params: {
                      bucketName: props.bucketName,
                      subFolders: obj.parentFolder.concat(obj.name),
                    },
                  }"
                  >{{ obj.name }}
                </router-link>
              </div>
            </th>
            <td>
              <span
                class="date-tooltip"
                data-bs-toggle="tooltip"
                :data-bs-title="
                  dayjs(obj.LastModified).format('DD.MM.YYYY HH:mm:ss')
                "
                >{{ dayjs(obj.LastModified).fromNow() }}</span
              >
            </td>
            <td>
              {{ filesize(obj.Size ?? 0, { base: 2, standard: "jedec" }) }}
            </td>
            <!-- Show buttons with dropdown menu if row is an object -->
            <td class="text-end">
              <div
                v-if="isS3Object(obj)"
                class="btn-group btn-group-sm dropdown-center dropdown-menu-start"
              >
                <!-- Download Button -->
                <button
                  type="button"
                  class="btn btn-secondary"
                  @click="downloadObject(props.bucketName, obj.Key)"
                  :disabled="!readableBucket"
                >
                  Download
                </button>
                <button
                  type="button"
                  class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
                  data-bs-toggle="dropdown"
                  aria-expanded="false"
                >
                  <span class="visually-hidden">Toggle Dropdown</span>
                </button>
                <!-- Dropdown menu -->
                <ul class="dropdown-menu dropdown-menu">
                  <li>
                    <button
                      class="dropdown-item"
                      type="button"
                      data-bs-toggle="modal"
                      data-bs-target="#detail-object-modal"
                      @click="objectState.viewDetailKey = obj.Key"
                    >
                      Details
                    </button>
                  </li>
                  <li>
                    <button
                      class="dropdown-item"
                      type="button"
                      :disabled="!writableBucket"
                      data-bs-toggle="modal"
                      data-bs-target="#edit-object-modal"
                      @click="objectState.editObjectKey = obj.Key ?? ''"
                    >
                      Edit
                    </button>
                  </li>
                  <li>
                    <button
                      class="dropdown-item"
                      type="button"
                      :disabled="!readableBucket"
                      data-bs-toggle="modal"
                      data-bs-target="#copy-object-modal"
                      @click="objectState.copyObject = obj"
                    >
                      Copy
                    </button>
                  </li>
                  <li>
                    <button
                      class="dropdown-item text-danger align-middle"
                      type="button"
                      @click="deleteObject(obj.Key)"
                      data-bs-toggle="modal"
                      data-bs-target="#delete-object-modal"
                      :disabled="!writableBucket"
                    >
                      <font-awesome-icon icon="fa-solid fa-trash" />
                      <span class="ms-1">Delete</span>
                    </button>
                  </li>
                </ul>
              </div>
              <!-- Show delete button when row is a folder -->
              <div v-else>
                <button
                  type="button"
                  class="btn btn-danger btn-sm align-baseline"
                  :disabled="!writableBucket"
                  data-bs-toggle="modal"
                  data-bs-target="#delete-object-modal"
                  @click="
                    deleteFolder(
                      obj.parentFolder.concat(['']).join('/') + obj.name + '/',
                    )
                  "
                >
                  <font-awesome-icon icon="fa-solid fa-trash" class="me-2" />
                  <span>Delete</span>
                </button>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
      <upload-object-modal
        :bucket-name="props.bucketName"
        modalID="edit-object-modal"
        :key-prefix="currentSubFolders.join('/')"
        :edit-object-file-name="getObjectFileName(objectState.editObjectKey)"
      />
      <copy-object-modal
        :src-object="objectState.copyObject"
        :src-bucket="bucketName"
        modalID="copy-object-modal"
      />
      <object-detail-modal
        :bucket="bucketName"
        :object-key="objectState.viewDetailKey"
        modalID="detail-object-modal"
      />
    </div>
  </div>
</template>

<style scoped></style>