start with version page

This commit is contained in:
MiniDigger 2022-03-26 21:47:02 +01:00
parent 2308944843
commit 3c745e2e82
6 changed files with 271 additions and 8 deletions

View File

@ -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

View File

@ -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();

View File

@ -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);
}

View File

@ -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>

View File

@ -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>

View File

@ -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"];