mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-11-21 01:21:54 +08:00
start with version page
This commit is contained in:
parent
2308944843
commit
3c745e2e82
@ -196,14 +196,14 @@ once QA has passed, the checkboxes can be removed and the page can be ~~striked
|
||||
- [ ] design
|
||||
- [ ] qa
|
||||
- [version]
|
||||
- [ ] fetch
|
||||
- [ ] layout
|
||||
- [ ] functionality
|
||||
- [x] fetch
|
||||
- [ ] layout (do we wanna do tabs again?)
|
||||
- [x] functionality
|
||||
- [ ] design
|
||||
- [ ] qa
|
||||
* [platform] (empty)
|
||||
- index
|
||||
- [ ] fetch
|
||||
- [x] fetch
|
||||
- [ ] layout
|
||||
- [ ] functionality
|
||||
- [ ] design
|
||||
|
@ -21,7 +21,9 @@ async function fetch() {
|
||||
content: props.raw,
|
||||
}).catch<any>((e) => handleRequestError(e, ctx, i18n));
|
||||
loading.value = false;
|
||||
await nextTick(setupAdmonition);
|
||||
if (!import.meta.env.SSR) {
|
||||
await nextTick(setupAdmonition);
|
||||
}
|
||||
}
|
||||
await fetch();
|
||||
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
Flag,
|
||||
HangarNotification,
|
||||
HangarProject,
|
||||
HangarVersion,
|
||||
HealthReport,
|
||||
Invites,
|
||||
LoggedAction,
|
||||
@ -71,6 +72,14 @@ export async function useProjectVersions(user: string, project: string, blocking
|
||||
return useInitialState("useProjectVersions", () => useApi<PaginatedResult<Version>>(`projects/${user}/${project}/versions`, false), blocking);
|
||||
}
|
||||
|
||||
export async function useProjectVersionsInternal(user: string, project: string, version: string, blocking = true) {
|
||||
return useInitialState(
|
||||
"useProjectVersions",
|
||||
() => useInternalApi<HangarVersion[]>(`versions/version/${user}/${project}/versions/${version}`, false),
|
||||
blocking
|
||||
);
|
||||
}
|
||||
|
||||
export async function usePage(user: string, project: string, path?: string, blocking = true) {
|
||||
return useInitialState("usePage", () => useInternalApi<ProjectPage>(`pages/page/${user}/${project}` + (path ? "/" + path : ""), false), blocking);
|
||||
}
|
||||
|
@ -1,4 +1,52 @@
|
||||
<script lang="ts" setup>
|
||||
import { useProjectVersionsInternal } from "~/composables/useApiHelper";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { HangarProject, HangarVersion } from "hangar-internal";
|
||||
import { Platform } from "~/types/enums";
|
||||
import { useErrorRedirect } from "~/composables/useErrorRedirect";
|
||||
|
||||
const ctx = useContext();
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const props = defineProps<{
|
||||
project: HangarProject;
|
||||
}>();
|
||||
|
||||
const versions = await useProjectVersionsInternal(route.params.user as string, route.params.project as string, route.params.version as string).catch((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
);
|
||||
if (!versions || !versions.value) {
|
||||
await useRouter().push(useErrorRedirect(route, 404, "Not found"));
|
||||
}
|
||||
|
||||
const versionMap: Map<Platform, HangarVersion> = new Map<Platform, HangarVersion>();
|
||||
const versionPlatforms: Set<Platform> = new Set<Platform>();
|
||||
if (versions && versions.value) {
|
||||
for (const version of versions.value) {
|
||||
for (const platformKey in version.platformDependencies) {
|
||||
versionPlatforms.add(platformKey as Platform);
|
||||
versionMap.set(platformKey as Platform, version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!route.params.platform) {
|
||||
let path = route.path;
|
||||
if (path.endsWith("/")) {
|
||||
path = path.substring(0, path.length - 1);
|
||||
}
|
||||
console.log("reeeeee 16");
|
||||
await useRouter().push({ path: `${path}/${versionMap.keys().next().value.toLowerCase()}` });
|
||||
console.log("reeeeee 17");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>version</h1>
|
||||
<router-view></router-view>
|
||||
<!-- todo tabs? -->
|
||||
<h1>version {{ route.params.version }}, platforms {{ versionPlatforms }}</h1>
|
||||
<router-view :project="project" :versions="versionMap"></router-view>
|
||||
</template>
|
||||
|
@ -1,3 +1,205 @@
|
||||
<script lang="ts" setup>
|
||||
import { NamedPermission, Platform, ReviewState } from "~/types/enums";
|
||||
import { HangarProject, HangarVersion, IPlatform } from "hangar-internal";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { Tag } from "hangar-api";
|
||||
import { useErrorRedirect } from "~/composables/useErrorRedirect";
|
||||
import TagComponent from "~/components/Tag.vue";
|
||||
import { hasPerms } from "~/composables/usePerm";
|
||||
import Button from "~/components/design/Button.vue";
|
||||
import Alert from "~/components/design/Alert.vue";
|
||||
import MarkdownEditor from "~/components/MarkdownEditor.vue";
|
||||
import Markdown from "~/components/Markdown.vue";
|
||||
import Card from "~/components/design/Card.vue";
|
||||
import Link from "~/components/design/Link.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
const backendData = useBackendDataStore();
|
||||
|
||||
const props = defineProps<{
|
||||
versions: Map<Platform, HangarVersion>;
|
||||
project: HangarProject;
|
||||
}>();
|
||||
|
||||
const p: Platform = (route.params.platform as string).toUpperCase() as Platform;
|
||||
const projectVersion = computed<HangarVersion | undefined>(() => props.versions.get(p));
|
||||
if (!projectVersion.value) {
|
||||
await useRouter().push(useErrorRedirect(route, 404, "Not found"));
|
||||
}
|
||||
const platform = computed<IPlatform | undefined>(() => backendData.platforms?.get(p));
|
||||
const isReviewStateChecked = computed<boolean>(
|
||||
() => projectVersion.value?.reviewState === ReviewState.PARTIALLY_REVIEWED || projectVersion.value?.reviewState === ReviewState.REVIEWED
|
||||
);
|
||||
const isUnderReview = computed<boolean>(() => projectVersion.value?.reviewState === ReviewState.UNDER_REVIEW);
|
||||
const channel = computed<Tag | null>(() => projectVersion.value?.tags?.find((t) => t.name === "Channel") || null);
|
||||
const approvalTooltip = computed<string>(() =>
|
||||
projectVersion.value?.reviewState === ReviewState.PARTIALLY_REVIEWED ? i18n.t("version.page.partiallyApproved") : i18n.t("version.page.approved")
|
||||
);
|
||||
const platformTag = computed<Tag | null>(() => projectVersion.value?.tags.find((t) => t.name === platform.value?.name) || null);
|
||||
const requiresConfirmation = computed<boolean>(() => projectVersion.value?.externalUrl !== null || projectVersion.value?.reviewState !== ReviewState.REVIEWED);
|
||||
const editingPage = ref(false);
|
||||
|
||||
async function savePage(content: string) {
|
||||
try {
|
||||
await useInternalApi(`versions/version/${props.project.id}/${projectVersion.value?.id}/saveDescription`, true, "post", {
|
||||
content,
|
||||
});
|
||||
if (projectVersion.value) {
|
||||
projectVersion.value.description = content;
|
||||
}
|
||||
editingPage.value = false;
|
||||
} catch (err) {
|
||||
// this.$refs.editor.loading.save = false; // TODO
|
||||
handleRequestError(err, ctx, i18n, "page.new.error.save");
|
||||
}
|
||||
}
|
||||
|
||||
async function setRecommended() {
|
||||
//this.loading.recommend = true;
|
||||
try {
|
||||
await useInternalApi(`versions/version/${props.project.id}/${projectVersion.value?.id}/${platform.value?.enumName}/recommend`, true, "post");
|
||||
// this.$util.success(this.$t('version.success.recommended', [this.platform.name])); // TODO
|
||||
// this.$nuxt.refresh();
|
||||
} catch (e) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
}
|
||||
// this.loading.recommend = false;
|
||||
}
|
||||
|
||||
async function deleteVersion(comment: string) {
|
||||
try {
|
||||
await useInternalApi(`versions/version/${props.project.id}/${projectVersion.value?.id}/delete`, true, "post", {
|
||||
content: comment,
|
||||
});
|
||||
// this.$util.success(this.$t('version.success.softDelete'));
|
||||
// this.$nuxt.refresh(); // TODO
|
||||
} catch (e) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
async function hardDeleteVersion(comment: string) {
|
||||
try {
|
||||
await useInternalApi(`versions/version/${props.project.id}/${projectVersion.value?.id}/hardDelete`, true, "post", {
|
||||
content: comment,
|
||||
});
|
||||
// this.$util.success(i18n.t('version.success.hardDelete')); // TODO
|
||||
await router.push({
|
||||
name: "user-project-versions",
|
||||
params: {
|
||||
...route.params,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreVersion() {
|
||||
try {
|
||||
await useInternalApi(`versions/version/${props.project.id}/${projectVersion.value?.id}/restore`, true, "post");
|
||||
// this.$util.success(i18n.t('version.success.restore'));
|
||||
// this.$nuxt.refresh(); // TODO
|
||||
} catch (e) {
|
||||
handleRequestError(e, ctx, i18n);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>version platform</h1>
|
||||
<div class="flex">
|
||||
<div>
|
||||
<h1>{{ projectVersion.name }}</h1>
|
||||
<TagComponent :tag="channel" :short-form="true" />
|
||||
<h2>{{ i18n.t("version.page.subheader", [projectVersion.author, i18n.d(projectVersion.createdAt, "date")]) }}</h2>
|
||||
</div>
|
||||
<div class="flex-right">
|
||||
<h2>
|
||||
<i v-if="hasPerms(NamedPermission.REVIEWER) && projectVersion.approvedBy" class="mr-1">
|
||||
{{ i18n.t("version.page.adminMsg", [projectVersion.approvedBy, i18n.d(projectVersion.createdAt, "date")]) }}
|
||||
</i>
|
||||
<IconMdiDiamondStone v-if="projectVersion.recommended.includes(platform.enumName)" :title="i18n.t('version.page.recommended')" />
|
||||
<IconMdiCheckCircleOutline v-if="isReviewStateChecked" :title="approvalTooltip" />
|
||||
</h2>
|
||||
|
||||
<template v-if="hasPerms(NamedPermission.REVIEWER)">
|
||||
<Button v-if="isReviewStateChecked" color="success" :to="route.path + '/reviews'">
|
||||
<IconMdiListStatus />
|
||||
{{ i18n.t("version.page.reviewLogs") }}
|
||||
</Button>
|
||||
<Button v-else-if="isUnderReview" color="info" :to="route.path + '/reviews'">
|
||||
<IconMdiListStatus />
|
||||
{{ i18n.t("version.page.reviewLogs") }}
|
||||
</Button>
|
||||
<Button v-else color="success" :to="route.path + '/reviews'">
|
||||
<IconMdiPlay />
|
||||
{{ i18n.t("version.page.reviewStart") }}
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<!-- todo set recommended -->
|
||||
<!-- todo delete -->
|
||||
<!-- todo download -->
|
||||
<!-- todo admin actions -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap md:flex-nowrap gap-4">
|
||||
<section class="basis-full md:basis-8/12 flex-grow">
|
||||
<Alert v-if="requiresConfirmation" class="mb-8">{{ i18n.t("version.page.unsafeWarning") }}</Alert>
|
||||
<MarkdownEditor
|
||||
v-if="hasPerms(NamedPermission.EDIT_VERSION)"
|
||||
ref="editor"
|
||||
v-model:editing="editingPage"
|
||||
:raw="projectVersion.description"
|
||||
:deletable="false"
|
||||
@save="savePage"
|
||||
/>
|
||||
<Markdown v-else :raw="projectVersion.description" />
|
||||
</section>
|
||||
|
||||
<section class="basis-full md:basis-4/12 flex-grow space-y-4">
|
||||
<Card>
|
||||
<template #header>
|
||||
{{ i18n.t("version.page.platform") }}
|
||||
<!-- todo PlatformVersionEditModal -->
|
||||
<!--<PlatformVersionEditModal :project="project" :versions="versions" /> -->
|
||||
</template>
|
||||
|
||||
<!-- todo platform icon -->
|
||||
{{ platform?.name }}
|
||||
{{ platformTag.data }}
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<template #header>
|
||||
{{ i18n.t("version.page.dependencies") }}
|
||||
<!-- todo DependencyEditModal -->
|
||||
<!-- <DependencyEditModal :project="project" :versions="versions" /> -->
|
||||
</template>
|
||||
|
||||
<ul>
|
||||
<li v-for="dep in projectVersion.pluginDependencies[platform?.name.toUpperCase()]" :key="dep.name">
|
||||
<Link
|
||||
:href="dep.externalUrl || undefined"
|
||||
:target="dep.externalUrl ? '_blank' : undefined"
|
||||
:to="!!dep.namespace ? { name: 'user-project', params: { user: dep.namespace.owner, project: dep.namespace.slug } } : undefined"
|
||||
>
|
||||
{{ dep.name }}
|
||||
<small v-if="dep.required">({{ i18n.t("general.required") }})</small>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
2
frontend-new/src/types/generated/icons.d.ts
vendored
2
frontend-new/src/types/generated/icons.d.ts
vendored
@ -8,9 +8,11 @@ declare module "vue" {
|
||||
IconMdiCalendar: typeof import("~icons/mdi/calendar")["default"];
|
||||
IconMdiCheckboxBlankCircleOutline: typeof import("~icons/mdi/checkbox-blank-circle-outline")["default"];
|
||||
IconMdiCheckCircle: typeof import("~icons/mdi/check-circle")["default"];
|
||||
IconMdiCheckCircleOutline: typeof import("~icons/mdi/check-circle-outline")["default"];
|
||||
IconMdiClipboardOutline: typeof import("~icons/mdi/clipboard-outline")["default"];
|
||||
IconMdiCloseCircle: typeof import("~icons/mdi/close-circle")["default"];
|
||||
IconMdiDelete: typeof import("~icons/mdi/delete")["default"];
|
||||
IconMdiDiamondStone: typeof import("~icons/mdi/diamond-stone")["default"];
|
||||
IconMdiDownload: typeof import("~icons/mdi/download")["default"];
|
||||
IconMdiEye: typeof import("~icons/mdi/eye")["default"];
|
||||
IconMdiFile: typeof import("~icons/mdi/file")["default"];
|
||||
|
Loading…
Reference in New Issue
Block a user