feat: prompt to upgrade theme when installing existing theme (#3970)

#### What type of PR is this?

/kind feature
/area console
/milestone 2.6.x

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

支持在安装已存在主题时提示是否升级主题。

<img width="611" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/2e99c781-96b6-4900-9a37-2d472ad65411">
<img width="604" alt="image" src="https://github.com/halo-dev/halo/assets/21301288/a7bfa9d8-1522-417f-91b4-6ea3f75f693e">


#### Which issue(s) this PR fixes:

Fixes #3969 

#### Special notes for your reviewer:

测试方式:

1. 通过本地上传和远程下载的方式重复安装某个主题,观察是否可以正常提示升级以及测试升级是否正常。

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

```release-note
Console 端支持安装已存在主题时提示升级主题。
```
pull/3983/head^2
Ryan Wang 2023-05-25 22:40:18 +08:00 committed by GitHub
parent 6dd98d2c0c
commit 533f0cfa66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 5 deletions

View File

@ -606,6 +606,10 @@ core:
titles:
install: Install theme
upgrade: Upgrade theme ({display_name})
operations:
existed_during_installation:
title: The theme already exists.
description: The currently installed theme already exists, do you want to upgrade?
tabs:
local: Local
remote:

View File

@ -606,6 +606,10 @@ core:
titles:
install: 安装主题
upgrade: 升级主题({display_name}
operations:
existed_during_installation:
title: 主题已存在
description: 当前安装的主题已存在,是否升级?
tabs:
local: 本地上传
remote:

View File

@ -606,6 +606,10 @@ core:
titles:
install: 安裝主題
upgrade: 升級主題({display_name}
operations:
existed_during_installation:
title: 主題已存在
description: 當前安裝的主題已存在,是否升級?
tabs:
local: 本地上傳
remote:

View File

@ -356,7 +356,6 @@ watch(
v-if="visible"
v-model:visible="themeUploadVisible"
:upgrade-theme="themeToUpgrade"
@close="refetch"
/>
<ThemePreviewModal

View File

@ -1,5 +1,12 @@
<script lang="ts" setup>
import { Toast, VButton, VModal, VTabItem, VTabs } from "@halo-dev/components";
import {
Dialog,
Toast,
VButton,
VModal,
VTabItem,
VTabs,
} from "@halo-dev/components";
import UppyUpload from "@/components/upload/UppyUpload.vue";
import { computed, ref, watch, nextTick } from "vue";
import type { Theme } from "@halo-dev/api-client";
@ -9,6 +16,8 @@ import { useThemeStore } from "@/stores/theme";
import { apiClient } from "@/utils/api-client";
import { useRouteQuery } from "@vueuse/router";
import { submitForm } from "@formkit/core";
import type { ErrorResponse } from "@uppy/core";
import type { UppyFile } from "@uppy/utils";
const { t } = useI18n();
const queryClient = useQueryClient();
@ -83,6 +92,71 @@ const onUploaded = () => {
handleVisibleChange(false);
};
interface ThemeInstallationErrorResponse {
detail: string;
instance: string;
themeName: string;
requestId: string;
status: number;
timestamp: string;
title: string;
type: string;
}
const THEME_ALREADY_EXISTS_TYPE = "https://halo.run/probs/theme-alreay-exists";
const onError = (file: UppyFile<unknown>, response: ErrorResponse) => {
const body = response.body as ThemeInstallationErrorResponse;
if (body.type === THEME_ALREADY_EXISTS_TYPE) {
handleCatchExistsException(body, file.data as File);
}
};
const handleCatchExistsException = async (
error: ThemeInstallationErrorResponse,
file?: File
) => {
Dialog.info({
title: t(
"core.theme.upload_modal.operations.existed_during_installation.title"
),
description: t(
"core.theme.upload_modal.operations.existed_during_installation.description"
),
confirmText: t("core.common.buttons.confirm"),
cancelText: t("core.common.buttons.cancel"),
onConfirm: async () => {
if (activeTabId.value === "local") {
if (!file) {
throw new Error("File is required");
}
await apiClient.theme.upgradeTheme({
name: error.themeName,
file: file,
});
} else if (activeTabId.value === "remote") {
await apiClient.theme.upgradeThemeFromUri({
name: error.themeName,
upgradeFromUriRequest: {
uri: remoteDownloadUrl.value,
},
});
} else {
throw new Error("Unknown tab id");
}
Toast.success(t("core.common.toast.upgrade_success"));
queryClient.invalidateQueries({ queryKey: ["themes"] });
themeStore.fetchActivatedTheme();
handleVisibleChange(false);
},
});
};
// remote download
const activeTabId = ref("local");
const remoteDownloadUrl = ref("");
@ -120,10 +194,16 @@ const handleDownloadTheme = async () => {
handleVisibleChange(false);
routeRemoteDownloadUrl.value = null;
} catch (error) {
console.log("Failed to download theme", error);
// eslint-disable-next-line
} catch (error: any) {
const data = error?.response.data as ThemeInstallationErrorResponse;
if (data?.type === THEME_ALREADY_EXISTS_TYPE) {
handleCatchExistsException(data);
}
console.error("Failed to download theme", error);
} finally {
routeRemoteDownloadUrl.value = null;
downloading.value = false;
}
};
@ -164,6 +244,7 @@ watch(
:endpoint="endpoint"
auto-proceed
@uploaded="onUploaded"
@error="onError"
/>
</VTabItem>
<VTabItem