Skip to content
Snippets Groups Projects
CopyObjectModal.vue 6.19 KiB
<script setup lang="ts">
import type { S3Client } from "@aws-sdk/client-s3";
import { CopyObjectCommand } from "@aws-sdk/client-s3";
import BootstrapModal from "@/components/modals/BootstrapModal.vue";
import { Modal, Toast } from "bootstrap";
import { onMounted, reactive, watch } from "vue";
import type { S3ObjectMetaInformation } from "@/client/s3proxy";
import dayjs from "dayjs";
import { useBucketStore } from "@/stores/buckets";

const props = defineProps<{
  modalID: string;
  sourceObject: S3ObjectMetaInformation;
  s3Client: S3Client;
}>();

const formState = reactive<{
  destKey: string;
  destBucket: string;
  uploading: boolean;
}>({
  destKey: "",
  destBucket: "",
  uploading: false,
});
const bucketRepository = useBucketStore();

const emit = defineEmits<{
  (e: "object-copied", object: S3ObjectMetaInformation): void;
}>();

const randomIDSuffix = Math.random().toString(16).substring(2, 8);
let copyModal: Modal | null = null;
let successToast: Toast | null = null;
let errorToast: Toast | null = null;

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

function copyObject() {
  const command = new CopyObjectCommand({
    Bucket: formState.destBucket,
    CopySource: encodeURI(
      `/${props.sourceObject.bucket}/${props.sourceObject.key}`,
    ),
    Key: formState.destKey,
  });
  formState.uploading = true;
  props.s3Client
    .send(command)
    .then(() => {
      emit("object-copied", {
        key: formState.destKey,
        bucket: formState.destBucket,
        size: props.sourceObject.size,
        last_modified: dayjs().toISOString(),
        content_type: props.sourceObject.content_type,
      });
      copyModal?.hide();
      successToast?.show();
      formState.destBucket = "";
    })
    .catch((e) => {
      console.error(e);
      errorToast?.show();
    })
    .finally(() => {
      formState.uploading = false;
    });
}

function modalClosed() {
  formState.destBucket = "";
}

watch(
  () => props.sourceObject.key,
  (newKey) => {
    formState.destKey = newKey;
  },
);

onMounted(() => {
  copyModal = new Modal("#" + props.modalID);
  successToast = new Toast("#successToast-" + randomIDSuffix);
  errorToast = new Toast("#errorToast-" + randomIDSuffix);
});
</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 copied file</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>
  <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-danger align-items-center border-0"
      data-bs-autohide="true"
      :id="'errorToast-' + randomIDSuffix"
    >
      <div class="d-flex">
        <div class="toast-body">
          There has been some Error.<br />
          Try again later
        </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>
  <bootstrap-modal
    :modalID="modalID"
    :static-backdrop="true"
    modal-label="Copy Object Modal"
    v-on="{ 'hidden.bs.modal': modalClosed }"
  >
    <template v-slot:header>
      <h4>Copy file {{ getFileName(props.sourceObject.key) }}</h4>
    </template>
    <template v-slot:body>
      <div class="container-fluid">
        <div class="row">
          <form
            class="col-7"
            :id="'copyObjectForm' + randomIDSuffix"
            @submit.prevent="copyObject"
          >
            <div class="mb-3">
              <label
                :for="'destinationBucket' + randomIDSuffix"
                class="form-label"
              >
                Destination Bucket *
              </label>
              <select
                class="form-select text-lowercase"
                :id="'destinationBucket' + randomIDSuffix"
                required
                v-model="formState.destBucket"
              >
                <option disabled selected>Select one...</option>
                <option
                  v-for="bucket in bucketRepository.writableBuckets"
                  :key="bucket.name"
                  :value="bucket.name"
                >
                  {{ bucket.name }}
                </option>
              </select>
            </div>
            <div class="mb-3">
              <label :for="'objectKey' + randomIDSuffix" class="form-label"
                >Destination Filename *</label
              >
              <input
                type="text"
                class="form-control"
                :id="'objectKey' + randomIDSuffix"
                required
                v-model="formState.destKey"
              />
            </div>
          </form>
          <div class="col-5">
            You can copy objects. You have to create destination buckets prior
            to copy.<br />
            You can specify folder by using '/' at destination object field. For
            example, if you want to copy object under the folder named
            'folder1', you need to specify destination object like
            'folder1/[your object name]'.
          </div>
        </div>
      </div>
    </template>
    <template v-slot:footer>
      <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
        Close
      </button>
      <button
        :disabled="formState.uploading"
        type="submit"
        :form="'copyObjectForm' + randomIDSuffix"
        class="btn btn-primary"
      >
        <span
          v-if="formState.uploading"
          class="spinner-border spinner-border-sm"
          role="status"
          aria-hidden="true"
        ></span>
        Copy
      </button>
    </template>
  </bootstrap-modal>
</template>

<style scoped></style>