mirror of https://github.com/halo-dev/halo
297 lines
8.3 KiB
TypeScript
297 lines
8.3 KiB
TypeScript
import type { Attachment, Group, Policy } from "@halo-dev/api-client";
|
|
import { computed, nextTick, type Ref } from "vue";
|
|
import { ref, watch } from "vue";
|
|
import { apiClient } from "@/utils/api-client";
|
|
import { Dialog, Toast } from "@halo-dev/components";
|
|
import { useQuery } from "@tanstack/vue-query";
|
|
import { useI18n } from "vue-i18n";
|
|
import { useClipboard } from "@vueuse/core";
|
|
import { matchMediaType } from "@/utils/media-type";
|
|
|
|
interface useAttachmentControlReturn {
|
|
attachments: Ref<Attachment[] | undefined>;
|
|
isLoading: Ref<boolean>;
|
|
isFetching: Ref<boolean>;
|
|
selectedAttachment: Ref<Attachment | undefined>;
|
|
selectedAttachments: Ref<Set<Attachment>>;
|
|
checkedAll: Ref<boolean>;
|
|
total: Ref<number>;
|
|
handleFetchAttachments: () => void;
|
|
handleSelectPrevious: () => void;
|
|
handleSelectNext: () => void;
|
|
handleDeleteInBatch: () => void;
|
|
handleCheckAll: (checkAll: boolean) => void;
|
|
handleSelect: (attachment: Attachment | undefined) => void;
|
|
isChecked: (attachment: Attachment) => boolean;
|
|
handleReset: () => void;
|
|
}
|
|
|
|
export function useAttachmentControl(filterOptions: {
|
|
policy?: Ref<Policy | undefined>;
|
|
group?: Ref<Group | undefined>;
|
|
user?: Ref<string | undefined>;
|
|
keyword?: Ref<string | undefined>;
|
|
sort?: Ref<string | undefined>;
|
|
page: Ref<number>;
|
|
size: Ref<number>;
|
|
}): useAttachmentControlReturn {
|
|
const { t } = useI18n();
|
|
|
|
const { user, policy, group, keyword, sort, page, size } = filterOptions;
|
|
|
|
const selectedAttachment = ref<Attachment>();
|
|
const selectedAttachments = ref<Set<Attachment>>(new Set<Attachment>());
|
|
const checkedAll = ref(false);
|
|
|
|
const total = ref(0);
|
|
const hasPrevious = ref(false);
|
|
const hasNext = ref(false);
|
|
|
|
const { data, isLoading, isFetching, refetch } = useQuery<Attachment[]>({
|
|
queryKey: ["attachments", policy, keyword, group, user, page, size, sort],
|
|
queryFn: async () => {
|
|
const isUnGrouped = group?.value?.metadata.name === "ungrouped";
|
|
|
|
const fieldSelectorMap: Record<string, string | undefined> = {
|
|
"spec.policyName": policy?.value?.metadata.name,
|
|
"spec.ownerName": user?.value,
|
|
"spec.groupName": isUnGrouped ? undefined : group?.value?.metadata.name,
|
|
};
|
|
|
|
const fieldSelector = Object.entries(fieldSelectorMap)
|
|
.map(([key, value]) => {
|
|
if (value) {
|
|
return `${key}=${value}`;
|
|
}
|
|
})
|
|
.filter(Boolean) as string[];
|
|
|
|
const { data } = await apiClient.attachment.searchAttachments({
|
|
fieldSelector,
|
|
page: page.value,
|
|
size: size.value,
|
|
ungrouped: isUnGrouped,
|
|
keyword: keyword?.value,
|
|
sort: [sort?.value as string].filter(Boolean),
|
|
});
|
|
|
|
total.value = data.total;
|
|
hasPrevious.value = data.hasPrevious;
|
|
hasNext.value = data.hasNext;
|
|
|
|
return data.items;
|
|
},
|
|
refetchInterval(data) {
|
|
const deletingAttachments = data?.filter(
|
|
(attachment) => !!attachment.metadata.deletionTimestamp
|
|
);
|
|
return deletingAttachments?.length ? 1000 : false;
|
|
},
|
|
});
|
|
|
|
const handleSelectPrevious = async () => {
|
|
if (!data.value) return;
|
|
|
|
const index = data.value?.findIndex(
|
|
(attachment) =>
|
|
attachment.metadata.name === selectedAttachment.value?.metadata.name
|
|
);
|
|
|
|
if (index === undefined) return;
|
|
|
|
if (index > 0) {
|
|
selectedAttachment.value = data.value[index - 1];
|
|
return;
|
|
}
|
|
|
|
if (index === 0 && hasPrevious.value) {
|
|
page.value--;
|
|
await nextTick();
|
|
await refetch();
|
|
selectedAttachment.value = data.value[data.value.length - 1];
|
|
}
|
|
};
|
|
|
|
const handleSelectNext = async () => {
|
|
if (!data.value) return;
|
|
|
|
const index = data.value?.findIndex(
|
|
(attachment) =>
|
|
attachment.metadata.name === selectedAttachment.value?.metadata.name
|
|
);
|
|
|
|
if (index === undefined) return;
|
|
|
|
if (index < data.value?.length - 1) {
|
|
selectedAttachment.value = data.value[index + 1];
|
|
return;
|
|
}
|
|
|
|
if (index === data.value.length - 1 && hasNext.value) {
|
|
page.value++;
|
|
await nextTick();
|
|
await refetch();
|
|
selectedAttachment.value = data.value[0];
|
|
}
|
|
};
|
|
|
|
const handleDeleteInBatch = () => {
|
|
Dialog.warning({
|
|
title: t("core.attachment.operations.delete_in_batch.title"),
|
|
description: t("core.common.dialog.descriptions.cannot_be_recovered"),
|
|
confirmType: "danger",
|
|
confirmText: t("core.common.buttons.confirm"),
|
|
cancelText: t("core.common.buttons.cancel"),
|
|
onConfirm: async () => {
|
|
try {
|
|
const promises = Array.from(selectedAttachments.value).map(
|
|
(attachment) => {
|
|
return apiClient.extension.storage.attachment.deletestorageHaloRunV1alpha1Attachment(
|
|
{
|
|
name: attachment.metadata.name,
|
|
}
|
|
);
|
|
}
|
|
);
|
|
await Promise.all(promises);
|
|
selectedAttachments.value.clear();
|
|
|
|
Toast.success(t("core.common.toast.delete_success"));
|
|
} catch (e) {
|
|
console.error("Failed to delete attachments", e);
|
|
} finally {
|
|
await refetch();
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
const handleCheckAll = (checkAll: boolean) => {
|
|
if (checkAll) {
|
|
data.value?.forEach((attachment) => {
|
|
selectedAttachments.value.add(attachment);
|
|
});
|
|
} else {
|
|
selectedAttachments.value.clear();
|
|
}
|
|
};
|
|
|
|
const handleSelect = async (attachment: Attachment | undefined) => {
|
|
if (!attachment) return;
|
|
if (selectedAttachments.value.has(attachment)) {
|
|
selectedAttachments.value.delete(attachment);
|
|
return;
|
|
}
|
|
selectedAttachments.value.add(attachment);
|
|
};
|
|
|
|
watch(
|
|
() => selectedAttachments.value.size,
|
|
(newValue) => {
|
|
checkedAll.value = newValue === data.value?.length;
|
|
}
|
|
);
|
|
|
|
const isChecked = (attachment: Attachment) => {
|
|
return (
|
|
attachment.metadata.name === selectedAttachment.value?.metadata.name ||
|
|
Array.from(selectedAttachments.value)
|
|
.map((item) => item.metadata.name)
|
|
.includes(attachment.metadata.name)
|
|
);
|
|
};
|
|
|
|
const handleReset = () => {
|
|
page.value = 1;
|
|
selectedAttachment.value = undefined;
|
|
selectedAttachments.value.clear();
|
|
checkedAll.value = false;
|
|
};
|
|
|
|
return {
|
|
attachments: data,
|
|
isLoading,
|
|
isFetching,
|
|
selectedAttachment,
|
|
selectedAttachments,
|
|
checkedAll,
|
|
total,
|
|
handleFetchAttachments: refetch,
|
|
handleSelectPrevious,
|
|
handleSelectNext,
|
|
handleDeleteInBatch,
|
|
handleCheckAll,
|
|
handleSelect,
|
|
isChecked,
|
|
handleReset,
|
|
};
|
|
}
|
|
|
|
export function useAttachmentPermalinkCopy(
|
|
attachment: Ref<Attachment | undefined>
|
|
) {
|
|
const { copy } = useClipboard({ legacy: true });
|
|
const { t } = useI18n();
|
|
|
|
const mediaType = computed(() => {
|
|
return attachment.value?.spec.mediaType;
|
|
});
|
|
|
|
const isImage = computed(() => {
|
|
return mediaType.value && matchMediaType(mediaType.value, "image/*");
|
|
});
|
|
|
|
const isVideo = computed(() => {
|
|
return mediaType.value && matchMediaType(mediaType.value, "video/*");
|
|
});
|
|
|
|
const isAudio = computed(() => {
|
|
return mediaType.value && matchMediaType(mediaType.value, "audio/*");
|
|
});
|
|
|
|
const htmlText = computed(() => {
|
|
const { permalink } = attachment.value?.status || {};
|
|
const { displayName } = attachment.value?.spec || {};
|
|
|
|
if (isImage.value) {
|
|
return `<img src="${permalink}" alt="${displayName}" />`;
|
|
} else if (isVideo.value) {
|
|
return `<video src="${permalink}"></video>`;
|
|
} else if (isAudio.value) {
|
|
return `<audio src="${permalink}"></audio>`;
|
|
}
|
|
return `<a href="${permalink}">${displayName}</a>`;
|
|
});
|
|
|
|
const markdownText = computed(() => {
|
|
const { permalink } = attachment.value?.status || {};
|
|
const { displayName } = attachment.value?.spec || {};
|
|
if (isImage.value) {
|
|
return ``;
|
|
}
|
|
return `[${displayName}](${permalink})`;
|
|
});
|
|
|
|
const handleCopy = (format: "markdown" | "html" | "url") => {
|
|
const { permalink } = attachment.value?.status || {};
|
|
|
|
if (!permalink) return;
|
|
|
|
if (format === "url") {
|
|
copy(permalink);
|
|
} else if (format === "markdown") {
|
|
copy(markdownText.value);
|
|
} else if (format === "html") {
|
|
copy(htmlText.value);
|
|
}
|
|
|
|
Toast.success(t("core.common.toast.copy_success"));
|
|
};
|
|
|
|
return {
|
|
htmlText,
|
|
markdownText,
|
|
handleCopy,
|
|
};
|
|
}
|