download button, set recommended button

This commit is contained in:
MiniDigger | Martin 2022-06-17 15:25:37 +02:00
parent 7c47ca0969
commit 34b12c5269
6 changed files with 203 additions and 9 deletions

View File

@ -3,7 +3,6 @@ import { Menu, MenuButton, MenuItems } from "@headlessui/vue";
import IconMdiMenuDown from "~icons/mdi/menu-down";
import IconMdiMenuUp from "~icons/mdi/menu-up";
import Button from "~/components/design/Button.vue";
import ServerOnly from "~/components/design/Tooltip.vue";
const props = withDefaults(
defineProps<{

View File

@ -2,6 +2,17 @@
import Popper from "vue3-popper";
import { defineComponent, onMounted, ref } from "vue";
const props = withDefaults(
defineProps<{
hover?: boolean;
show?: boolean | null;
}>(),
{
hover: true,
show: null,
}
);
const ServerOnly = defineComponent({
name: "ServerOnly",
setup(_, { slots }) {
@ -16,7 +27,7 @@ const ServerOnly = defineComponent({
<template>
<ClientOnly>
<Popper v-bind="$attrs" hover open-delay="200" close-delay="100">
<Popper v-bind="$attrs" :hover="hover" open-delay="200" close-delay="100" :show="show">
<slot />
<template #content="props">
<slot name="content" v-bind="props" />

View File

@ -16,6 +16,11 @@ function open() {
function close() {
isOpen.value = false;
}
defineExpose({
open: open,
close: close,
});
</script>
<template>

View File

@ -0,0 +1,157 @@
<script lang="ts" setup>
import Button from "~/components/design/Button.vue";
import Tooltip from "~/components/design/Tooltip.vue";
import { useI18n } from "vue-i18n";
import { HangarProject, HangarVersion, IPlatform } from "hangar-internal";
import { computed, ref } from "vue";
import { Platform } from "~/types/enums";
import DropdownButton from "~/components/design/DropdownButton.vue";
import { useBackendDataStore } from "~/store/backendData";
import DropdownItem from "~/components/design/DropdownItem.vue";
import { useInternalApi } from "~/composables/useApi";
import Modal from "~/components/modals/Modal.vue";
import Alert from "~/components/design/Alert.vue";
const i18n = useI18n();
const backendData = useBackendDataStore();
const props = withDefaults(
defineProps<{
project: HangarProject;
copyButton?: boolean;
platformSelection?: boolean;
small?: boolean;
version?: HangarVersion;
platform?: IPlatform;
}>(),
{
copyButton: true,
platformSelection: false,
small: true,
}
);
const loading = ref<boolean>(false);
const selectedPlatform = ref<Platform | null>(null);
const copySuccessful = ref<boolean>(false);
const token = ref<string | null>(null);
const confirmModal = ref<Modal>(null);
if (props.platformSelection) {
const keys = props.project.recommendedVersions ? Object.keys(props.project.recommendedVersions) : [];
selectedPlatform.value = keys.length > 0 ? Platform[keys[0] as keyof typeof Platform] : Platform.PAPER;
}
const external = computed(() =>
props.platformSelection ? props.project.recommendedVersions[selectedPlatform.value!] !== null : props.version!.externalUrl !== null
);
const externalUrl = computed(() => (props.platformSelection ? props.project.recommendedVersions[selectedPlatform.value!] : props.version!.externalUrl));
function copyDownloadUrl() {
let url;
if (external.value) {
url = externalUrl.value;
} else {
const versionString = props.platformSelection ? "recommended" : props.version!.name;
const platform = props.platformSelection ? selectedPlatform.value : props.platform!.name;
url = `${window.location.protocol}//${window.location.host}/api/v1/projects/${props.project.namespace.owner}/${props.project.namespace.slug}/versions/${versionString}/${platform}/download`;
}
if (url) {
navigator.clipboard.writeText(url);
copySuccessful.value = true;
setTimeout(() => (copySuccessful.value = false), 1000);
}
}
async function checkAndDownload() {
if (await requiresConfirmation()) {
confirmModal.value?.open();
return;
}
return download();
}
function download(): Promise<any> {
if (external.value) {
confirmModal.value?.close();
return Promise.resolve(window.open(externalUrl.value || "", "_blank"));
}
const versionString = props.platformSelection ? "recommended" : props.version!.name;
const platform = props.platformSelection ? selectedPlatform.value : props.platform!.name;
window.open(
`/api/internal/versions/version/${props.project.namespace.owner}/${props.project.namespace.slug}/versions/${versionString}/${platform}/download?token=${token.value}`,
"_blank"
);
token.value = null;
confirmModal.value?.close();
return Promise.resolve();
}
async function requiresConfirmation() {
if (token.value != null) {
return true;
}
if (external.value) {
return false;
}
loading.value = true;
const versionString = props.platformSelection ? "recommended" : props.version!.name;
const platform = props.platformSelection ? selectedPlatform.value : props.platform!.name.toLowerCase();
try {
await useInternalApi(
`versions/version/${props.project.namespace.owner}/${props.project.namespace.slug}/versions/${versionString}/${platform}/downloadCheck`
)
.then(() => (loading.value = false))
.catch((err) => {
if (err.response && err.response.status === 428) {
token.value = err.response.data;
}
throw err;
});
loading.value = false;
return false;
} catch (ex) {
loading.value = false;
return true;
}
}
</script>
<template>
<!-- todo make this actually look nice -->
<div class="flex">
<DropdownButton v-if="platformSelection" :button-size="small ? 'small' : 'large'">
<template #button-label> <IconMdiAlertOutline /><!-- todo platform icons --> </template>
<DropdownItem v-for="(pl, i) in Object.keys(project.recommendedVersions)" :key="i" class="flex items-center" @click="selectedPlatform = pl">
<IconMdiAlertOutline class="mr-1" /><!-- todo platform icons -->
{{ backendData.platforms.get(pl).name }}
</DropdownItem>
</DropdownButton>
<Button :size="small ? 'small' : 'large'" :loading="loading" @click="checkAndDownload">
<IconMdiDownloadOutline />
{{ external ? i18n.t("version.page.downloadExternal") : i18n.t("version.page.download") }}
</Button>
<Modal ref="confirmModal" :title="i18n.t('version.page.confirmation.title', [project.name, version ? version.name : '', project.owner.name])">
<Alert type="danger" class="my-2">
{{ i18n.t("version.page.confirmation.alert") }}
</Alert>
<em>{{ i18n.t("version.page.confirmation.disclaimer") }}</em>
<div class="mt-2 flex flex-wrap gap-2">
<Button button-type="secondary" @click="confirmModal?.close()">{{ i18n.t("version.page.confirmation.deny") }}</Button>
<Button button-type="red" @click="download">{{ i18n.t("version.page.confirmation.agree") }}</Button>
</div>
</Modal>
<Tooltip v-if="copyButton" :hover="false" :show="copySuccessful">
<template #content>
<span>{{ i18n.t("version.page.downloadUrlCopied") }}</span>
</template>
<Button :size="small ? 'small' : 'large'" @click="copyDownloadUrl">
<IconMdiContentCopy />
</Button>
</Tooltip>
</div>
</template>

View File

@ -19,6 +19,7 @@ import { NamedPermission, Visibility } from "~/types/enums";
import Markdown from "~/components/Markdown.vue";
import { AxiosError } from "axios";
import { useRouter } from "vue-router";
import DownloadButton from "~/components/projects/DownloadButton.vue";
const ctx = useContext();
const i18n = useI18n();
@ -117,8 +118,12 @@ async function sendForApproval() {
<p>{{ project.description }}</p>
</div>
<div class="flex sm:flex-col space-y-2 items-end justify-between sm:justify-around flex-shrink-0">
<!-- TODO: download button component with functionality -->
<Button size="large">Download latest</Button>
<DownloadButton
v-if="project.recommendedVersions && Object.keys(project.recommendedVersions).length > 0"
:project="project"
:platform-selection="true"
:small="false"
/>
<div class="flex">
<Tooltip>
<template #content>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { NamedPermission, Platform, ReviewState } from "~/types/enums";
import { NamedPermission, Platform, ReviewState, Visibility } from "~/types/enums";
import { HangarProject, HangarVersion, IPlatform } from "hangar-internal";
import { computed, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
@ -26,6 +26,8 @@ import DropdownButton from "~/components/design/DropdownButton.vue";
import DropdownItem from "~/components/design/DropdownItem.vue";
import PlatformVersionEditModal from "~/components/modals/PlatformVersionEditModal.vue";
import { AxiosError } from "axios";
import Tooltip from "~/components/design/Tooltip.vue";
import DownloadButton from "~/components/projects/DownloadButton.vue";
const route = useRoute();
const i18n = useI18n();
@ -85,7 +87,6 @@ async function savePage(content: string) {
}
async function setRecommended() {
//this.loading.recommend = true;
try {
await useInternalApi(`versions/version/${props.project.id}/${projectVersion.value?.id}/${platform.value?.enumName}/recommend`, true, "post");
notification.success(i18n.t("version.success.recommended", [platform.value?.name]));
@ -93,7 +94,6 @@ async function setRecommended() {
} catch (e) {
handleRequestError(e as AxiosError, ctx, i18n);
}
// this.loading.recommend = false;
}
async function deleteVersion(comment: string) {
@ -153,13 +153,30 @@ async function restoreVersion() {
<em v-if="hasPerms(NamedPermission.REVIEWER) && projectVersion.approvedBy" class="ml-2 text-lg">
{{ i18n.t("version.page.adminMsg", [projectVersion.approvedBy, i18n.d(projectVersion.createdAt, "date")]) }}
</em>
<!-- todo set recommended -->
<!-- todo delete -->
<!-- todo download -->
<!-- todo admin actions -->
</div>
<div class="flex-grow"></div>
<div class="inline-flex items-center">
<DownloadButton :small="true" :version="projectVersion" :project="project" :platform="platform" class="mr-2" />
<Tooltip
v-if="
hasPerms(NamedPermission.EDIT_VERSION) &&
projectVersion.visibility !== Visibility.SOFT_DELETE &&
!projectVersion.recommended.includes(platform?.enumName)
"
>
<template #content>
<span>{{ i18n.t("version.page.setRecommendedTooltip", [platform?.name]) }}</span>
</template>
<Button size="small" class="mr-2" @click="setRecommended">
<IconMdiDiamond />
{{ i18n.t("version.page.setRecommended") }}
</Button>
</Tooltip>
<!-- todo Make these nicer/put somewhere else -->
<template v-if="hasPerms(NamedPermission.REVIEWER)">
<Button v-if="isReviewStateChecked" color="success" :to="route.path + '/reviews'">