refactor: improve ui/ux of attachment group and policy selector (#5996)

#### What type of PR is this?

/area ui
/kind improvement
/milestone 2.16.x

#### What this PR does / why we need it:

优化附件分组、存储策略选择组件的 UI。

<img width="1649" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/194d6334-e600-4235-85f7-affb1d44c003">
<img width="953" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/d8cb2470-2500-4b2c-a251-5fdf45a9cccb">

#### Does this PR introduce a user-facing change?

```release-note
优化附件分组、存储策略选择组件的 UI。
```
pull/5994/head^2
Ryan Wang 2024-05-27 16:30:57 +08:00 committed by GitHub
parent 19fb1c2311
commit 94826d44c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 439 additions and 312 deletions

View File

@ -1,4 +1,9 @@
<script lang="ts" setup>
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
import LazyImage from "@/components/image/LazyImage.vue";
import { apiClient } from "@/utils/api-client";
import { isImage } from "@/utils/image";
import type { Attachment, Group } from "@halo-dev/api-client";
import {
IconArrowLeft,
IconArrowRight,
@ -20,25 +25,20 @@ import {
VPagination,
VSpace,
} from "@halo-dev/components";
import LazyImage from "@/components/image/LazyImage.vue";
import AttachmentDetailModal from "./components/AttachmentDetailModal.vue";
import AttachmentUploadModal from "./components/AttachmentUploadModal.vue";
import AttachmentPoliciesModal from "./components/AttachmentPoliciesModal.vue";
import AttachmentGroupList from "./components/AttachmentGroupList.vue";
import { useLocalStorage } from "@vueuse/core";
import { useRouteQuery } from "@vueuse/router";
import { cloneDeep } from "lodash-es";
import type { Ref } from "vue";
import { computed, onMounted, provide, ref, watch } from "vue";
import type { Attachment, Group } from "@halo-dev/api-client";
import { useFetchAttachmentPolicy } from "./composables/use-attachment-policy";
import { useAttachmentControl } from "./composables/use-attachment";
import { apiClient } from "@/utils/api-client";
import { cloneDeep } from "lodash-es";
import { isImage } from "@/utils/image";
import { useRouteQuery } from "@vueuse/router";
import { useFetchAttachmentGroup } from "./composables/use-attachment-group";
import { useI18n } from "vue-i18n";
import { useLocalStorage } from "@vueuse/core";
import UserFilterDropdown from "@/components/filter/UserFilterDropdown.vue";
import AttachmentDetailModal from "./components/AttachmentDetailModal.vue";
import AttachmentGroupList from "./components/AttachmentGroupList.vue";
import AttachmentListItem from "./components/AttachmentListItem.vue";
import AttachmentPoliciesModal from "./components/AttachmentPoliciesModal.vue";
import AttachmentUploadModal from "./components/AttachmentUploadModal.vue";
import { useAttachmentControl } from "./composables/use-attachment";
import { useFetchAttachmentGroup } from "./composables/use-attachment-group";
import { useFetchAttachmentPolicy } from "./composables/use-attachment-policy";
const { t } = useI18n();
@ -47,7 +47,7 @@ const uploadVisible = ref(false);
const detailVisible = ref(false);
const { policies } = useFetchAttachmentPolicy();
const { groups, handleFetchGroups } = useFetchAttachmentGroup();
const { groups } = useFetchAttachmentGroup();
const selectedGroup = useRouteQuery<string | undefined>("group");
@ -465,11 +465,7 @@ onMounted(() => {
</template>
<div :style="`${viewType === 'list' ? 'padding:12px 16px 0' : ''}`">
<AttachmentGroupList
@select="handleReset"
@update="handleFetchGroups"
@reload-attachments="handleFetchAttachments"
/>
<AttachmentGroupList @select="handleReset" />
</div>
<VLoading v-if="isLoading" />

View File

@ -0,0 +1,212 @@
<script lang="ts" setup>
import { apiClient } from "@/utils/api-client";
import type { Group } from "@halo-dev/api-client";
import {
Dialog,
IconCheckboxCircle,
IconMore,
Toast,
VDropdown,
VDropdownItem,
VStatusDot,
} from "@halo-dev/components";
import { useQueryClient } from "@tanstack/vue-query";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import AttachmentGroupEditingModal from "./AttachmentGroupEditingModal.vue";
const props = withDefaults(
defineProps<{
group?: Group;
isSelected?: boolean;
features?: { actions: boolean; checkIcon?: boolean };
}>(),
{
group: undefined,
isSelected: false,
features: () => {
return {
actions: true,
checkIcon: false,
};
},
}
);
const { t } = useI18n();
const queryClient = useQueryClient();
const handleDelete = () => {
Dialog.warning({
title: t("core.attachment.group_list.operations.delete.title"),
description: t("core.attachment.group_list.operations.delete.description"),
confirmType: "danger",
confirmText: t("core.common.buttons.confirm"),
cancelText: t("core.common.buttons.cancel"),
onConfirm: async () => {
if (!props.group) {
return;
}
// TODO:
const { data } = await apiClient.attachment.searchAttachments({
fieldSelector: [`spec.groupName=${props.group.metadata.name}`],
page: 0,
size: 0,
});
await apiClient.extension.storage.group.deleteStorageHaloRunV1alpha1Group(
{ name: props.group.metadata.name }
);
// move attachments to none group
const moveToUnGroupRequests = data.items.map((attachment) => {
attachment.spec.groupName = undefined;
return apiClient.extension.storage.attachment.updateStorageHaloRunV1alpha1Attachment(
{
name: attachment.metadata.name,
attachment: attachment,
}
);
});
await Promise.all(moveToUnGroupRequests);
queryClient.invalidateQueries({ queryKey: ["attachment-groups"] });
queryClient.invalidateQueries({ queryKey: ["attachments"] });
Toast.success(
t("core.attachment.group_list.operations.delete.toast_success", {
total: data.total,
})
);
},
});
};
const handleDeleteWithAttachments = () => {
Dialog.warning({
title: t(
"core.attachment.group_list.operations.delete_with_attachments.title"
),
description: t(
"core.attachment.group_list.operations.delete_with_attachments.description"
),
confirmType: "danger",
confirmText: t("core.common.buttons.confirm"),
cancelText: t("core.common.buttons.cancel"),
onConfirm: async () => {
if (!props.group) {
return;
}
// TODO:
const { data } = await apiClient.attachment.searchAttachments({
fieldSelector: [`spec.groupName=${props.group.metadata.name}`],
page: 0,
size: 0,
});
await apiClient.extension.storage.group.deleteStorageHaloRunV1alpha1Group(
{ name: props.group.metadata.name }
);
const deleteAttachmentRequests = data.items.map((attachment) => {
return apiClient.extension.storage.attachment.deleteStorageHaloRunV1alpha1Attachment(
{ name: attachment.metadata.name }
);
});
await Promise.all(deleteAttachmentRequests);
queryClient.invalidateQueries({ queryKey: ["attachment-groups"] });
queryClient.invalidateQueries({ queryKey: ["attachments"] });
Toast.success(
t(
"core.attachment.group_list.operations.delete_with_attachments.toast_success",
{ total: data.total }
)
);
},
});
};
// Editing
const editingModalVisible = ref(false);
const onEditingModalClose = () => {
queryClient.invalidateQueries({ queryKey: ["attachment-groups"] });
editingModalVisible.value = false;
};
</script>
<template>
<button
type="button"
class="inline-flex h-full w-full items-center gap-2 rounded-md border border-gray-200 bg-white px-3 py-2.5 text-sm font-medium text-gray-800 hover:bg-gray-50 hover:shadow-sm"
:class="{ '!bg-gray-100 shadow-sm': isSelected }"
>
<div class="inline-flex w-full flex-1 gap-x-2 break-all text-left">
<slot name="text">
{{ group?.spec.displayName }}
</slot>
<VStatusDot
v-if="group?.metadata.deletionTimestamp"
v-tooltip="$t('core.common.status.deleting')"
state="warning"
animate
/>
</div>
<div class="flex-none">
<HasPermission
v-if="features.actions"
:permissions="['system:attachments:manage']"
>
<VDropdown>
<IconMore @click.stop />
<template #popper>
<VDropdownItem @click="editingModalVisible = true">
{{ $t("core.attachment.group_list.operations.rename.button") }}
</VDropdownItem>
<VDropdown placement="right" :triggers="['click']">
<VDropdownItem type="danger">
{{ $t("core.common.buttons.delete") }}
</VDropdownItem>
<template #popper>
<VDropdownItem type="danger" @click="handleDelete()">
{{
$t("core.attachment.group_list.operations.delete.button")
}}
</VDropdownItem>
<VDropdownItem
type="danger"
@click="handleDeleteWithAttachments()"
>
{{
$t(
"core.attachment.group_list.operations.delete_with_attachments.button"
)
}}
</VDropdownItem>
</template>
</VDropdown>
</template>
</VDropdown>
</HasPermission>
<IconCheckboxCircle
v-if="isSelected && features.checkIcon"
class="text-primary"
/>
<slot name="actions" />
</div>
<AttachmentGroupEditingModal
v-if="editingModalVisible"
:group="group"
@close="onEditingModalClose"
/>
</button>
</template>

View File

@ -1,12 +1,12 @@
<script lang="ts" setup>
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
import SubmitButton from "@/components/button/SubmitButton.vue";
import type { Group } from "@halo-dev/api-client";
import { onMounted, ref } from "vue";
import { apiClient } from "@/utils/api-client";
import { setFocus } from "@/formkit/utils/focus";
import { useI18n } from "vue-i18n";
import { apiClient } from "@/utils/api-client";
import type { Group } from "@halo-dev/api-client";
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
import { cloneDeep } from "lodash-es";
import { onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
const props = withDefaults(
defineProps<{
@ -35,7 +35,7 @@ const formState = ref<Group>({
generateName: "attachment-group-",
},
});
const saving = ref(false);
const isSubmitting = ref(false);
const modalTitle = props.group
? t("core.attachment.group_editing_modal.titles.update")
@ -43,7 +43,7 @@ const modalTitle = props.group
const handleSave = async () => {
try {
saving.value = true;
isSubmitting.value = true;
if (props.group) {
await apiClient.extension.storage.group.updateStorageHaloRunV1alpha1Group(
{
@ -64,7 +64,7 @@ const handleSave = async () => {
} catch (e) {
console.error("Failed to save attachment group", e);
} finally {
saving.value = false;
isSubmitting.value = false;
}
};
@ -77,7 +77,13 @@ onMounted(() => {
});
</script>
<template>
<VModal ref="modal" :title="modalTitle" :width="500" @close="emit('close')">
<VModal
ref="modal"
mount-to-body
:title="modalTitle"
:width="500"
@close="emit('close')"
>
<FormKit
id="attachment-group-form"
name="attachment-group-form"
@ -100,7 +106,7 @@ onMounted(() => {
<template #footer>
<VSpace>
<SubmitButton
:loading="saving"
:loading="isSubmitting"
type="secondary"
:text="$t('core.common.buttons.submit')"
@submit="$formkit.submit('attachment-group-form')"

View File

@ -1,26 +1,13 @@
<script lang="ts" setup>
// core libs
import { ref } from "vue";
// components
import {
Dialog,
IconAddCircle,
IconMore,
Toast,
VDropdown,
VDropdownItem,
VStatusDot,
} from "@halo-dev/components";
import AttachmentGroupEditingModal from "./AttachmentGroupEditingModal.vue";
// types
import type { Group } from "@halo-dev/api-client";
import { IconAddCircle } from "@halo-dev/components";
import { useQueryClient } from "@tanstack/vue-query";
import { useRouteQuery } from "@vueuse/router";
import { useFetchAttachmentGroup } from "../composables/use-attachment-group";
import { apiClient } from "@/utils/api-client";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useFetchAttachmentGroup } from "../composables/use-attachment-group";
import AttachmentGroupBadge from "./AttachmentGroupBadge.vue";
import AttachmentGroupEditingModal from "./AttachmentGroupEditingModal.vue";
const { t } = useI18n();
@ -35,10 +22,10 @@ const props = withDefaults(
const emit = defineEmits<{
(event: "select", group: Group): void;
(event: "update"): void;
(event: "reload-attachments"): void;
}>();
const queryClient = useQueryClient();
const defaultGroups: Group[] = [
{
spec: {
@ -62,11 +49,10 @@ const defaultGroups: Group[] = [
},
];
const { groups, handleFetchGroups } = useFetchAttachmentGroup();
const { groups } = useFetchAttachmentGroup();
const groupToUpdate = ref<Group>();
const loading = ref<boolean>(false);
const editingModal = ref(false);
const creationModalVisible = ref(false);
const selectedGroup = props.readonly
? ref("")
@ -77,189 +63,52 @@ const handleSelectGroup = (group: Group) => {
selectedGroup.value = group.metadata.name;
};
const handleOpenEditingModal = (group: Group) => {
groupToUpdate.value = group;
editingModal.value = true;
};
const onEditingModalClose = () => {
emit("update");
handleFetchGroups();
editingModal.value = false;
};
const handleDelete = (group: Group) => {
Dialog.warning({
title: t("core.attachment.group_list.operations.delete.title"),
description: t("core.attachment.group_list.operations.delete.description"),
confirmType: "danger",
confirmText: t("core.common.buttons.confirm"),
cancelText: t("core.common.buttons.cancel"),
onConfirm: async () => {
// TODO:
const { data } = await apiClient.attachment.searchAttachments({
fieldSelector: [`spec.groupName=${group.metadata.name}`],
page: 0,
size: 0,
});
await apiClient.extension.storage.group.deleteStorageHaloRunV1alpha1Group(
{ name: group.metadata.name }
);
// move attachments to none group
const moveToUnGroupRequests = data.items.map((attachment) => {
attachment.spec.groupName = undefined;
return apiClient.extension.storage.attachment.updateStorageHaloRunV1alpha1Attachment(
{
name: attachment.metadata.name,
attachment: attachment,
}
);
});
await Promise.all(moveToUnGroupRequests);
handleFetchGroups();
emit("reload-attachments");
emit("update");
Toast.success(
t("core.attachment.group_list.operations.delete.toast_success", {
total: data.total,
})
);
},
});
};
const handleDeleteWithAttachments = (group: Group) => {
Dialog.warning({
title: t(
"core.attachment.group_list.operations.delete_with_attachments.title"
),
description: t(
"core.attachment.group_list.operations.delete_with_attachments.description"
),
confirmType: "danger",
confirmText: t("core.common.buttons.confirm"),
cancelText: t("core.common.buttons.cancel"),
onConfirm: async () => {
// TODO:
const { data } = await apiClient.attachment.searchAttachments({
fieldSelector: [`spec.groupName=${group.metadata.name}`],
page: 0,
size: 0,
});
await apiClient.extension.storage.group.deleteStorageHaloRunV1alpha1Group(
{ name: group.metadata.name }
);
const deleteAttachmentRequests = data.items.map((attachment) => {
return apiClient.extension.storage.attachment.deleteStorageHaloRunV1alpha1Attachment(
{ name: attachment.metadata.name }
);
});
await Promise.all(deleteAttachmentRequests);
handleFetchGroups();
emit("reload-attachments");
emit("update");
Toast.success(
t(
"core.attachment.group_list.operations.delete_with_attachments.toast_success",
{ total: data.total }
)
);
},
});
const onCreationModalClose = () => {
queryClient.invalidateQueries({ queryKey: ["attachment-groups"] });
creationModalVisible.value = false;
};
</script>
<template>
<AttachmentGroupEditingModal
v-if="!readonly && editingModal"
:group="groupToUpdate"
@close="onEditingModalClose"
v-if="!readonly && creationModalVisible"
@close="onCreationModalClose"
/>
<div class="mb-5 grid grid-cols-2 gap-x-2 gap-y-3 sm:grid-cols-6">
<div
<div
class="mb-5 grid grid-cols-2 gap-x-2 gap-y-3 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-6"
>
<AttachmentGroupBadge
v-for="defaultGroup in defaultGroups"
:key="defaultGroup.metadata.name"
:class="{
'!bg-gray-200 !text-gray-900':
defaultGroup.metadata.name === selectedGroup,
}"
class="flex cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
:group="defaultGroup"
:is-selected="defaultGroup.metadata.name === selectedGroup"
:features="{ actions: false, checkIcon: readonly }"
@click="handleSelectGroup(defaultGroup)"
>
<div class="flex flex-1 items-center">
<span class="text-sm">{{ defaultGroup.spec.displayName }}</span>
</div>
</div>
<div
/>
<AttachmentGroupBadge
v-for="group in groups"
:key="group.metadata.name"
:class="{
'!bg-gray-200 !text-gray-900': group.metadata.name === selectedGroup,
}"
class="flex cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
:group="group"
:is-selected="group.metadata.name === selectedGroup"
:features="{ actions: !readonly, checkIcon: readonly }"
@click="handleSelectGroup(group)"
>
<div class="flex flex-1 items-center gap-2 truncate">
<span class="truncate text-sm">
{{ group.spec.displayName }}
</span>
<VStatusDot
v-if="group.metadata.deletionTimestamp"
v-tooltip="$t('core.common.status.deleting')"
state="warning"
animate
/>
</div>
<VDropdown v-if="!readonly" v-permission="['system:attachments:manage']">
<IconMore @click.stop />
<template #popper>
<VDropdownItem @click="handleOpenEditingModal(group)">
{{ $t("core.attachment.group_list.operations.rename.button") }}
</VDropdownItem>
<VDropdown placement="right" :triggers="['click']">
<VDropdownItem type="danger">
{{ $t("core.common.buttons.delete") }}
</VDropdownItem>
<template #popper>
<VDropdownItem type="danger" @click="handleDelete(group)">
{{ $t("core.attachment.group_list.operations.delete.button") }}
</VDropdownItem>
<VDropdownItem
type="danger"
@click="handleDeleteWithAttachments(group)"
>
{{
$t(
"core.attachment.group_list.operations.delete_with_attachments.button"
)
}}
</VDropdownItem>
</template>
</VDropdown>
</template>
</VDropdown>
</div>
<div
/>
<HasPermission
v-if="!loading && !readonly"
v-permission="['system:attachments:manage']"
class="flex cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
@click="editingModal = true"
:permissions="['system:attachments:manage']"
>
<div class="flex flex-1 items-center truncate">
<span class="truncate text-sm">
{{ $t("core.common.buttons.new") }}
</span>
</div>
<IconAddCircle />
</div>
<AttachmentGroupBadge
:features="{ actions: false }"
@click="creationModalVisible = true"
>
<template #text>
<span>{{ $t("core.common.buttons.new") }}</span>
</template>
<template #actions>
<IconAddCircle />
</template>
</AttachmentGroupBadge>
</HasPermission>
</div>
</template>

View File

@ -1,4 +1,7 @@
<script lang="ts" setup>
import { apiClient } from "@/utils/api-client";
import { formatDatetime } from "@/utils/date";
import type { Policy, PolicyTemplate } from "@halo-dev/api-client";
import {
Dialog,
IconAddCircle,
@ -13,16 +16,13 @@ import {
VSpace,
VStatusDot,
} from "@halo-dev/components";
import AttachmentPolicyEditingModal from "./AttachmentPolicyEditingModal.vue";
import { ref } from "vue";
import type { Policy, PolicyTemplate } from "@halo-dev/api-client";
import { formatDatetime } from "@/utils/date";
import { useI18n } from "vue-i18n";
import {
useFetchAttachmentPolicy,
useFetchAttachmentPolicyTemplate,
} from "../composables/use-attachment-policy";
import { apiClient } from "@/utils/api-client";
import { useI18n } from "vue-i18n";
import AttachmentPolicyEditingModal from "./AttachmentPolicyEditingModal.vue";
const emit = defineEmits<{
(event: "close"): void;
@ -63,7 +63,7 @@ const handleDelete = async (policy: Policy) => {
"core.attachment.policies_modal.operations.can_not_delete.description"
),
confirmText: t("core.common.buttons.confirm"),
cancelText: t("core.common.buttons.cancel"),
showCancel: false,
});
return;
}
@ -88,9 +88,17 @@ const handleDelete = async (policy: Policy) => {
const onEditingModalClose = () => {
selectedPolicy.value = undefined;
selectedTemplateName.value = undefined;
handleFetchPolicies();
policyEditingModal.value = false;
};
function getPolicyTemplateDisplayName(templateName: string) {
const policyTemplate = policyTemplates.value?.find(
(template) => template.metadata.name === templateName
);
return policyTemplate?.spec?.displayName || "--";
}
</script>
<template>
<VModal
@ -108,8 +116,8 @@ const onEditingModalClose = () => {
</span>
<template #popper>
<VDropdownItem
v-for="(policyTemplate, index) in policyTemplates"
:key="index"
v-for="policyTemplate in policyTemplates"
:key="policyTemplate.metadata.name"
@click="handleOpenCreateNewPolicyModal(policyTemplate)"
>
{{ policyTemplate.spec?.displayName }}
@ -157,7 +165,9 @@ const onEditingModalClose = () => {
<template #start>
<VEntityField
:title="policy.spec.displayName"
:description="policy.spec.templateName"
:description="
getPolicyTemplateDisplayName(policy.spec.templateName)
"
></VEntityField>
</template>
<template #end>

View File

@ -0,0 +1,58 @@
<script lang="ts" setup>
import type { Policy } from "@halo-dev/api-client";
import { IconCheckboxCircle } from "@halo-dev/components";
import { computed } from "vue";
import { useFetchAttachmentPolicyTemplate } from "../composables/use-attachment-policy";
const props = withDefaults(
defineProps<{
policy?: Policy;
isSelected?: boolean;
features?: { checkIcon?: boolean };
}>(),
{
policy: undefined,
isSelected: false,
features: () => {
return {
checkIcon: false,
};
},
}
);
const { policyTemplates } = useFetchAttachmentPolicyTemplate();
const policyTemplate = computed(() => {
return policyTemplates.value?.find(
(template) => template.metadata.name === props.policy?.spec.templateName
);
});
</script>
<template>
<button
type="button"
class="inline-flex h-full w-full items-center gap-2 rounded-md border border-gray-200 bg-white px-3 py-2.5 text-sm font-medium text-gray-800 hover:bg-gray-50 hover:shadow-sm"
:class="{ '!bg-gray-100 shadow-sm': isSelected }"
>
<div class="inline-flex w-full flex-1 flex-col space-y-0.5 text-left">
<slot name="text">
<div>
{{ policy?.spec.displayName }}
</div>
<div class="text-xs font-normal text-gray-600">
{{ policyTemplate?.spec?.displayName || "--" }}
</div>
</slot>
</div>
<div class="flex-none">
<IconCheckboxCircle
v-if="isSelected && features.checkIcon"
class="text-primary"
/>
<slot name="actions" />
</div>
</button>
</template>

View File

@ -1,14 +1,14 @@
<script lang="ts" setup>
import { Toast, VButton, VLoading, VModal, VSpace } from "@halo-dev/components";
import SubmitButton from "@/components/button/SubmitButton.vue";
import { setFocus } from "@/formkit/utils/focus";
import { apiClient } from "@/utils/api-client";
import { useSettingFormConvert } from "@console/composables/use-setting-form";
import type { Policy } from "@halo-dev/api-client";
import { Toast, VButton, VLoading, VModal, VSpace } from "@halo-dev/components";
import { useQuery } from "@tanstack/vue-query";
import { cloneDeep } from "lodash-es";
import { computed, onMounted, ref, toRaw, toRefs } from "vue";
import { useSettingFormConvert } from "@console/composables/use-setting-form";
import { apiClient } from "@/utils/api-client";
import { setFocus } from "@/formkit/utils/focus";
import { useI18n } from "vue-i18n";
import { useQuery } from "@tanstack/vue-query";
const props = withDefaults(
defineProps<{
@ -63,6 +63,7 @@ const { data: policyTemplate } = useQuery({
"core:attachment:policy-template",
formState.value.spec.templateName,
],
cacheTime: 0,
queryFn: async () => {
const { data } =
await apiClient.extension.storage.policyTemplate.getStorageHaloRunV1alpha1PolicyTemplate(
@ -81,6 +82,7 @@ const { data: setting, isLoading } = useQuery({
"core:attachment:policy-template:setting",
policyTemplate.value?.spec?.settingName,
],
cacheTime: 0,
queryFn: async () => {
if (!policyTemplate.value?.spec?.settingName) {
throw new Error("No setting found");
@ -101,6 +103,7 @@ const { data: configMap } = useQuery({
"core:attachment:policy-template:configMap",
policy.value?.spec.configMapName,
],
cacheTime: 0,
initialData: {
data: {},
apiVersion: "v1alpha1",
@ -181,7 +184,13 @@ const modalTitle = props.policy
});
</script>
<template>
<VModal ref="modal" :title="modalTitle" :width="600" @close="emit('close')">
<VModal
ref="modal"
mount-to-body
:title="modalTitle"
:width="600"
@close="emit('close')"
>
<div>
<VLoading v-if="isLoading" />
<template v-else>

View File

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { PolicyTemplate } from "@halo-dev/api-client";
import {
IconAddCircle,
VAlert,
@ -6,15 +7,16 @@ import {
VDropdownItem,
VModal,
} from "@halo-dev/components";
import { useLocalStorage } from "@vueuse/core";
import { ref, watch } from "vue";
import type { PolicyTemplate } from "@halo-dev/api-client";
import { useFetchAttachmentGroup } from "../composables/use-attachment-group";
import {
useFetchAttachmentPolicy,
useFetchAttachmentPolicyTemplate,
} from "../composables/use-attachment-policy";
import { useFetchAttachmentGroup } from "../composables/use-attachment-group";
import AttachmentGroupBadge from "./AttachmentGroupBadge.vue";
import AttachmentPolicyBadge from "./AttachmentPolicyBadge.vue";
import AttachmentPolicyEditingModal from "./AttachmentPolicyEditingModal.vue";
import { useLocalStorage } from "@vueuse/core";
const emit = defineEmits<{
(event: "close"): void;
@ -67,6 +69,7 @@ const handleOpenCreateNewPolicyModal = (policyTemplate: PolicyTemplate) => {
const onEditingModalClose = async () => {
await handleFetchPolicies();
policyTemplateNameToCreate.value = undefined;
selectedPolicyName.value = policies.value?.[0].metadata.name;
policyEditingModal.value = false;
};
@ -75,80 +78,37 @@ const onEditingModalClose = async () => {
<VModal
ref="modal"
:body-class="['!p-0']"
:width="650"
:centered="false"
:width="920"
height="calc(100vh - 20px)"
:title="$t('core.attachment.upload_modal.title')"
mount-to-body
@close="emit('close')"
>
<div class="w-full p-4">
<div class="mb-2">
<span class="text-sm text-gray-800">
{{ $t("core.attachment.upload_modal.filters.group.label") }}
</span>
</div>
<div class="mb-3 grid grid-cols-2 gap-x-2 gap-y-3 sm:grid-cols-4">
<div
v-for="(group, index) in [
{
metadata: { name: '' },
spec: {
displayName: $t('core.attachment.common.text.ungrouped'),
},
},
...(groups || []),
]"
:key="index"
:class="{
'!bg-gray-200 !text-gray-900':
group.metadata.name === selectedGroupName,
}"
class="flex cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
@click="selectedGroupName = group.metadata.name"
>
<div class="flex flex-1 items-center gap-2 truncate">
<span class="truncate text-sm">
{{ group.spec.displayName }}
</span>
</div>
</div>
</div>
<div class="mb-2">
<span class="text-sm text-gray-800">
{{ $t("core.attachment.upload_modal.filters.policy.label") }}
</span>
</div>
<div class="mb-3 grid grid-cols-2 gap-x-2 gap-y-3 sm:grid-cols-4">
<div
v-for="(policy, index) in policies"
:key="index"
:class="{
'!bg-gray-200 !text-gray-900':
selectedPolicyName === policy.metadata.name,
}"
class="flex cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
<AttachmentPolicyBadge
v-for="policy in policies"
:key="policy.metadata.name"
:policy="policy"
:is-selected="selectedPolicyName === policy.metadata.name"
:features="{ checkIcon: true }"
@click="selectedPolicyName = policy.metadata.name"
>
<div class="flex flex-1 flex-col items-start truncate">
<span class="truncate text-sm">
{{ policy.spec.displayName }}
</span>
<span class="text-xs">
{{ policy.spec.templateName }}
</span>
</div>
</div>
/>
<VDropdown>
<div
class="flex h-full cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-500 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
>
<div class="flex flex-1 items-center truncate">
<span class="truncate text-sm">
{{ $t("core.common.buttons.new") }}
</span>
</div>
<IconAddCircle />
</div>
<AttachmentPolicyBadge>
<template #text>
<span>{{ $t("core.common.buttons.new") }}</span>
</template>
<template #actions>
<IconAddCircle />
</template>
</AttachmentPolicyBadge>
<template #popper>
<VDropdownItem
v-for="(policyTemplate, index) in policyTemplates"
@ -169,6 +129,32 @@ const onEditingModalClose = async () => {
:closable="false"
/>
</div>
<div class="mb-2">
<span class="text-sm text-gray-800">
{{ $t("core.attachment.upload_modal.filters.group.label") }}
</span>
</div>
<div class="mb-3 grid grid-cols-2 gap-x-2 gap-y-3 sm:grid-cols-4">
<AttachmentGroupBadge
v-for="group in [
{
metadata: { name: '' },
apiVersion: '',
kind: '',
spec: {
displayName: $t('core.attachment.common.text.ungrouped'),
},
},
...(groups || []),
]"
:key="group.metadata.name"
:group="group"
:is-selected="group.metadata.name === selectedGroupName"
:features="{ actions: false, checkIcon: true }"
@click="selectedGroupName = group.metadata.name"
>
</AttachmentGroupBadge>
</div>
<UppyUpload
endpoint="/apis/api.console.halo.run/v1alpha1/attachments/upload"
:disabled="!selectedPolicyName"
@ -176,6 +162,7 @@ const onEditingModalClose = async () => {
policyName: selectedPolicyName,
groupName: selectedGroupName,
}"
width="100%"
:allowed-meta-fields="['policyName', 'groupName']"
:note="
selectedPolicyName

View File

@ -1,4 +1,8 @@
<script lang="ts" setup>
import LazyImage from "@/components/image/LazyImage.vue";
import { isImage } from "@/utils/image";
import { matchMediaTypes } from "@/utils/media-type";
import type { Attachment, Group } from "@halo-dev/api-client";
import {
IconArrowLeft,
IconArrowRight,
@ -12,16 +16,12 @@ import {
VPagination,
VSpace,
} from "@halo-dev/components";
import { computed, ref, watchEffect } from "vue";
import { isImage } from "@/utils/image";
import { useAttachmentControl } from "../../composables/use-attachment";
import LazyImage from "@/components/image/LazyImage.vue";
import type { AttachmentLike } from "@halo-dev/console-shared";
import type { Attachment, Group } from "@halo-dev/api-client";
import AttachmentUploadModal from "../AttachmentUploadModal.vue";
import { computed, ref, watchEffect } from "vue";
import { useAttachmentControl } from "../../composables/use-attachment";
import AttachmentDetailModal from "../AttachmentDetailModal.vue";
import AttachmentGroupList from "../AttachmentGroupList.vue";
import { matchMediaTypes } from "@/utils/media-type";
import AttachmentUploadModal from "../AttachmentUploadModal.vue";
const props = withDefaults(
defineProps<{