mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-12-21 06:51:19 +08:00
parent
780d806d8d
commit
653feb0a4f
@ -0,0 +1,4 @@
|
||||
package io.papermc.hangar.model.common;
|
||||
|
||||
public record PlatformVersion(String version, String[] subVersions) {
|
||||
}
|
@ -4,10 +4,17 @@ import io.papermc.hangar.HangarComponent;
|
||||
import io.papermc.hangar.config.CacheConfig;
|
||||
import io.papermc.hangar.db.dao.internal.table.PlatformVersionDAO;
|
||||
import io.papermc.hangar.model.common.Platform;
|
||||
import io.papermc.hangar.model.common.PlatformVersion;
|
||||
import io.papermc.hangar.model.db.PlatformVersionTable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
@ -17,6 +24,8 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Service
|
||||
public class PlatformService extends HangarComponent {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PlatformService.class);
|
||||
private static final String[] ARR = new String[0];
|
||||
private final PlatformVersionDAO platformVersionDAO;
|
||||
|
||||
@Autowired
|
||||
@ -25,10 +34,45 @@ public class PlatformService extends HangarComponent {
|
||||
}
|
||||
|
||||
@Cacheable(CacheConfig.PLATFORMS)
|
||||
public List<String> getVersionsForPlatform(final Platform platform) {
|
||||
public List<String> getFullVersionsForPlatform(final Platform platform) {
|
||||
return this.platformVersionDAO.getVersionsForPlatform(platform);
|
||||
}
|
||||
|
||||
@Cacheable(CacheConfig.PLATFORMS)
|
||||
public List<PlatformVersion> getVersionsForPlatform(final Platform platform) {
|
||||
final List<String> versions = this.platformVersionDAO.getVersionsForPlatform(platform);
|
||||
final Map<String, List<String>> platformVersions = new LinkedHashMap<>();
|
||||
for (final String version : versions) {
|
||||
if (version.split("\\.").length <= 2) {
|
||||
platformVersions.put(version, new ArrayList<>());
|
||||
continue;
|
||||
}
|
||||
|
||||
final String parent = version.substring(0, version.lastIndexOf('.'));
|
||||
final List<String> subVersions = platformVersions.get(parent);
|
||||
if (subVersions == null) {
|
||||
LOGGER.warn("Version {} does not have a parent version", version);
|
||||
platformVersions.put(version, new ArrayList<>());
|
||||
continue;
|
||||
}
|
||||
|
||||
subVersions.add(version);
|
||||
}
|
||||
|
||||
// Reverse everything and add self
|
||||
for (final Map.Entry<String, List<String>> entry : platformVersions.entrySet()) {
|
||||
final List<String> subVersions = entry.getValue();
|
||||
if (!subVersions.isEmpty()) {
|
||||
Collections.reverse(subVersions);
|
||||
subVersions.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
final List<PlatformVersion> list = platformVersions.entrySet().stream().map(entry -> new PlatformVersion(entry.getKey(), entry.getValue().toArray(ARR))).collect(Collectors.toList());
|
||||
Collections.reverse(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@CacheEvict(value = CacheConfig.PLATFORMS, allEntries = true)
|
||||
public void updatePlatformVersions(final Map<Platform, List<String>> platformVersions) {
|
||||
|
@ -424,7 +424,7 @@ public class VersionFactory extends HangarComponent {
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingVersion.getPlatformDependencies().entrySet().stream().anyMatch(en -> !new HashSet<>(this.platformService.getVersionsForPlatform(en.getKey())).containsAll(en.getValue()))) {
|
||||
if (pendingVersion.getPlatformDependencies().entrySet().stream().anyMatch(en -> !new HashSet<>(this.platformService.getFullVersionsForPlatform(en.getKey())).containsAll(en.getValue()))) {
|
||||
throw new HangarApiException(HttpStatus.BAD_REQUEST, "version.new.error.invalidPlatformVersion");
|
||||
}
|
||||
}
|
||||
|
79
frontend/src/components/VersionSelector.vue
Normal file
79
frontend/src/components/VersionSelector.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch } from "vue";
|
||||
import { PlatformVersion } from "hangar-internal";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import ArrowSpoiler from "~/lib/components/design/ArrowSpoiler.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
versions: PlatformVersion[];
|
||||
modelValue: string[];
|
||||
open: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", selected: string[]): void;
|
||||
}>();
|
||||
const selected = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit("update:modelValue", value),
|
||||
});
|
||||
|
||||
// TODO All of this is horrible
|
||||
watch(selected, (oldValue, newValue) => {
|
||||
const removedVersions = [...newValue.filter((x) => !oldValue.includes(x))];
|
||||
const addedVersions = [...oldValue.filter((x) => !newValue.includes(x))];
|
||||
for (const version of removedVersions) {
|
||||
if (!version.endsWith(".x")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parentVersion = version.substring(0, version.length - 2);
|
||||
const platformVersion = props.versions.find((v) => v.version === parentVersion);
|
||||
if (!platformVersion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const subVersion of platformVersion.subVersions) {
|
||||
selected.value.splice(selected.value.indexOf(subVersion), 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const version of addedVersions) {
|
||||
if (!version.endsWith(".x")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parentVersion = version.substring(0, version.length - 2);
|
||||
const platformVersion = props.versions.find((v) => v.version === parentVersion);
|
||||
if (!platformVersion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const subVersion of platformVersion.subVersions) {
|
||||
if (!selected.value.includes(subVersion)) {
|
||||
selected.value.push(subVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="version in versions" :key="version.version">
|
||||
<div v-if="version.subVersions.length !== 0">
|
||||
<ArrowSpoiler :open="open">
|
||||
<template #title>
|
||||
<div class="mr-8">
|
||||
<InputCheckbox v-model="selected" :value="version.version + '.x'" :label="version.version" />
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="ml-5">
|
||||
<InputCheckbox v-for="subversion in version.subVersions" :key="subversion" v-model="selected" :value="subversion" :label="subversion" />
|
||||
</div>
|
||||
</template>
|
||||
</ArrowSpoiler>
|
||||
</div>
|
||||
<InputCheckbox v-else v-model="selected" :value="version.version" :label="version.version" />
|
||||
</div>
|
||||
</template>
|
@ -6,10 +6,9 @@ import { useRoute, useRouter } from "vue-router";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import Modal from "~/lib/components/modals/Modal.vue";
|
||||
import { Platform } from "~/types/enums";
|
||||
import { useBackendData } from "~/store/backendData";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
import InputTag from "~/lib/components/ui/InputTag.vue";
|
||||
import VersionSelector from "~/components/VersionSelector.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
project: HangarProject;
|
||||
@ -46,7 +45,9 @@ function save() {
|
||||
|
||||
<template>
|
||||
<Modal :title="i18n.t('version.edit.platformVersions', [platform.name])" window-classes="w-200">
|
||||
<InputTag v-model="selectedVersions" :options="platform.possibleVersions" />
|
||||
<div class="flex flex-row flex-wrap gap-5">
|
||||
<VersionSelector v-model="selectedVersions" :versions="platform.possibleVersions" open />
|
||||
</div>
|
||||
|
||||
<Button class="mt-3" :disabled="loading" @click="save">{{ i18n.t("general.save") }}</Button>
|
||||
<template #activator="{ on }">
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit f97c995535949a1ef9b1c44d98400f018cc6b27a
|
||||
Subproject commit 6cac2d63bb9d266665b4931cf99bfb6b5742c23e
|
@ -22,7 +22,7 @@ import { formatSize } from "~/lib/composables/useFile";
|
||||
import ChannelModal from "~/components/modals/ChannelModal.vue";
|
||||
import { useBackendData } from "~/store/backendData";
|
||||
import DependencyTable from "~/components/projects/DependencyTable.vue";
|
||||
import InputTag from "~/lib/components/ui/InputTag.vue";
|
||||
import VersionSelector from "~/components/VersionSelector.vue";
|
||||
import Tabs from "~/lib/components/design/Tabs.vue";
|
||||
import PlatformLogo from "~/components/logos/platforms/PlatformLogo.vue";
|
||||
import { useProjectChannels } from "~/composables/useApiHelper";
|
||||
@ -369,12 +369,13 @@ useHead(useSeo(i18n.t("version.new.title") + " | " + props.project.name, props.p
|
||||
<div class="flex flex-wrap space-y-5 mb-8">
|
||||
<div v-for="platform in selectedPlatformsData" :key="platform.enumName" class="basis-full">
|
||||
<span class="text-lg inline-flex items-center"><PlatformLogo :platform="platform.enumName" :size="25" class="mr-1" /> {{ platform.name }}</span>
|
||||
<div class="mt-2">
|
||||
<InputTag
|
||||
<div class="ml-1 flex flex-row flex-wrap gap-5">
|
||||
<VersionSelector
|
||||
v-if="pendingVersion"
|
||||
v-model="pendingVersion.platformDependencies[platform.enumName]"
|
||||
:options="platform.possibleVersions"
|
||||
:versions="platform.possibleVersions"
|
||||
:rules="platformVersionRules"
|
||||
open
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,9 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, Ref, ref } from "vue";
|
||||
import { cloneDeep, isEqual } from "lodash-es";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { PlatformVersion } from "hangar-internal";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useBackendData } from "~/store/backendData";
|
||||
import { useInternalApi } from "~/composables/useApi";
|
||||
@ -15,6 +16,7 @@ import Table from "~/lib/components/design/Table.vue";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { useNotificationStore } from "~/lib/store/notification";
|
||||
import { definePageMeta } from "#imports";
|
||||
import { Platform } from "~/types/enums";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["MANUAL_VALUE_CHANGES"],
|
||||
@ -26,17 +28,31 @@ const router = useRouter();
|
||||
const notification = useNotificationStore();
|
||||
|
||||
const platformMap = useBackendData.platforms;
|
||||
const originalPlatforms = platformMap ? [...platformMap.values()] : [];
|
||||
const platforms = ref(cloneDeep(originalPlatforms));
|
||||
const platforms = platformMap ? [...platformMap.values()] : [];
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
useHead(useSeo(i18n.t("platformVersions.title"), null, route, null));
|
||||
|
||||
const fullVersions: Ref<Record<Platform, string[]>> = ref({
|
||||
PAPER: [],
|
||||
WATERFALL: [],
|
||||
VELOCITY: [],
|
||||
});
|
||||
reset();
|
||||
|
||||
function versions(versions: PlatformVersion[]): string[] {
|
||||
const fullVersions = [];
|
||||
for (const version of versions) {
|
||||
fullVersions.push(version.version, ...version.subVersions);
|
||||
}
|
||||
return fullVersions;
|
||||
}
|
||||
|
||||
async function save() {
|
||||
loading.value = true;
|
||||
const data: { [key: string]: string[] } = {};
|
||||
for (const pl of platforms.value || []) {
|
||||
data[pl.enumName] = pl.possibleVersions;
|
||||
for (const pl of platforms || []) {
|
||||
data[pl.enumName] = fullVersions.value[pl.enumName];
|
||||
}
|
||||
try {
|
||||
await useInternalApi("admin/platformVersions", "post", data);
|
||||
@ -49,10 +65,10 @@ async function save() {
|
||||
}
|
||||
|
||||
function reset() {
|
||||
platforms.value = cloneDeep(originalPlatforms);
|
||||
for (const platform of useBackendData.platforms.values()) {
|
||||
fullVersions.value[platform.enumName] = versions(platform.possibleVersions);
|
||||
}
|
||||
}
|
||||
|
||||
const hasChanged = computed(() => !isEqual(platforms.value, originalPlatforms));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -70,7 +86,7 @@ const hasChanged = computed(() => !isEqual(platforms.value, originalPlatforms));
|
||||
<tr v-for="platform in platforms" :key="platform.name">
|
||||
<td>{{ platform.name }}</td>
|
||||
<td>
|
||||
<InputTag v-model="platform.possibleVersions"></InputTag>
|
||||
<InputTag v-model="fullVersions[platform.enumName]"></InputTag>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -79,8 +95,8 @@ const hasChanged = computed(() => !isEqual(platforms.value, originalPlatforms));
|
||||
<template #footer>
|
||||
<span class="flex justify-end items-center gap-2">
|
||||
Updates may take a while to take effect!
|
||||
<Button :disabled="!hasChanged" @click="reset">{{ i18n.t("general.reset") }}</Button>
|
||||
<Button :disabled="loading || !hasChanged" @click="save"> {{ i18n.t("platformVersions.saveChanges") }}</Button>
|
||||
<Button @click="reset">{{ i18n.t("general.reset") }}</Button>
|
||||
<Button :disabled="loading" @click="save"> {{ i18n.t("platformVersions.saveChanges") }}</Button>
|
||||
</span>
|
||||
</template>
|
||||
</Card>
|
||||
|
@ -5,6 +5,7 @@ import { computed, isRef, Ref, ref, watch } from "vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { PaginatedResult, Project } from "hangar-api";
|
||||
import { PlatformVersion } from "hangar-internal";
|
||||
import InputCheckbox from "~/lib/components/ui/InputCheckbox.vue";
|
||||
import { useBackendData, useVisibleCategories, useVisiblePlatforms } from "~/store/backendData";
|
||||
import ProjectList from "~/components/projects/ProjectList.vue";
|
||||
@ -20,6 +21,7 @@ import PlatformLogo from "~/components/logos/platforms/PlatformLogo.vue";
|
||||
import CategoryLogo from "~/components/logos/categories/CategoryLogo.vue";
|
||||
import LicenseLogo from "~/components/logos/licenses/LicenseLogo.vue";
|
||||
import { useConfig } from "~/lib/composables/useConfig";
|
||||
import VersionSelector from "~/components/VersionSelector.vue";
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
@ -102,23 +104,21 @@ async function checkOffsetLargerCount() {
|
||||
}
|
||||
}
|
||||
|
||||
function versions(platform: Platform) {
|
||||
function versions(platform: Platform): PlatformVersion[] {
|
||||
const platformData = useBackendData.platforms?.get(platform);
|
||||
if (!platformData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...platformData.possibleVersions].reverse().map((k) => {
|
||||
return { version: k };
|
||||
});
|
||||
return platformData.possibleVersions;
|
||||
}
|
||||
|
||||
function updatePlatform(platform: any) {
|
||||
filters.value.platform = platform;
|
||||
|
||||
const allowedVersion = versions(platform);
|
||||
const allowedVersion: PlatformVersion[] = versions(platform);
|
||||
filters.value.versions = filters.value.versions.filter((existingVersion) => {
|
||||
return allowedVersion.find((allowedNewVersion) => allowedNewVersion.version === existingVersion);
|
||||
return allowedVersion.find((allowedNewVersion) => allowedNewVersion === existingVersion);
|
||||
});
|
||||
}
|
||||
|
||||
@ -233,14 +233,8 @@ useHead(meta);
|
||||
</div>
|
||||
<div v-if="filters.platform" class="versions">
|
||||
<h4 class="font-bold mb-1">{{ i18n.t("hangar.projectSearch.versions") }}</h4>
|
||||
<div class="flex flex-col gap-1 max-h-30 overflow-auto">
|
||||
<InputCheckbox
|
||||
v-for="version in versions(filters.platform)"
|
||||
:key="version.version"
|
||||
v-model="filters.versions"
|
||||
:value="version.version"
|
||||
:label="version.version"
|
||||
/>
|
||||
<div class="flex flex-col gap-1 max-h-40 overflow-auto">
|
||||
<VersionSelector v-model="filters.versions" :versions="versions(filters.platform)" :open="false" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="categories">
|
||||
|
Loading…
Reference in New Issue
Block a user