Make application and logo configurable (#336)
* Adds settings for server name and logo * Implements ApplicationLogo and replaces site name based on settings * Refactors component for changing the company logo * Removes unused variable * Uses message instead of statusMessage * Replaces favicon with logo if set
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<template v-if="!mLogoObjectId">
|
||||||
|
<DropLogo />
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<img :src="useObject(mLogoObjectId)" :alt="`${serverName} logo`" />
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { serverName, mLogoObjectId } = await $dropFetch("/api/v1");
|
||||||
|
</script>
|
||||||
@@ -10,9 +10,18 @@
|
|||||||
d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z"
|
d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<DropLogo aria-hidden="true" class="h-6" />
|
<ApplicationLogo aria-hidden="true" class="h-6" />
|
||||||
<span class="text-blue-400 font-display font-bold text-xl uppercase">
|
<span class="text-blue-400 font-display font-bold text-xl uppercase">
|
||||||
{{ $t("drop.drop") }}
|
<template v-if="serverName">
|
||||||
|
{{ serverName }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ $t("drop.drop") }}
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { serverName } = await $dropFetch("/api/v1");
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ const props = defineProps<{
|
|||||||
width?: number;
|
width?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { showGamePanelTextDecoration } = await $dropFetch(`/api/v1/settings`);
|
const {
|
||||||
|
store: { showGamePanelTextDecoration },
|
||||||
|
} = await $dropFetch(`/api/v1/settings`);
|
||||||
|
|
||||||
const currentComponent = ref<HTMLDivElement>();
|
const currentComponent = ref<HTMLDivElement>();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="relative group/iconupload rounded-xl overflow-hidden w-20 mx-auto"
|
||||||
|
>
|
||||||
|
<img v-if="objectId" :src="useObject(objectId)" :alt="imageAlt" />
|
||||||
|
<ArrowUpTrayIcon v-else />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="rounded-xl transition duration-200 absolute inset-0 opacity-0 group-hover/iconupload:opacity-100 focus-visible/iconupload:opacity-100 cursor-pointer bg-zinc-900/80 text-zinc-100 flex flex-col items-center justify-center text-center text-xs font-semibold ring-1 ring-inset ring-zinc-800 px-2"
|
||||||
|
@click="openModal"
|
||||||
|
>
|
||||||
|
<ArrowUpTrayIcon class="size-5" />
|
||||||
|
<span>{{ hoverText }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ArrowUpTrayIcon } from "@heroicons/vue/24/solid";
|
||||||
|
|
||||||
|
const { objectId, openModal, hoverText, imageAlt } = defineProps<{
|
||||||
|
objectId: string | null;
|
||||||
|
openModal: () => void;
|
||||||
|
hoverText: string;
|
||||||
|
imageAlt: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
@@ -377,7 +377,9 @@ import {
|
|||||||
import type { SerializeObject } from "nitropack";
|
import type { SerializeObject } from "nitropack";
|
||||||
import type { GameModel, GameTagModel } from "~/prisma/client/models";
|
import type { GameModel, GameTagModel } from "~/prisma/client/models";
|
||||||
import { Platform } from "~/prisma/client/enums";
|
import { Platform } from "~/prisma/client/enums";
|
||||||
const { showGamePanelTextDecoration } = await $dropFetch(`/api/v1/settings`);
|
const {
|
||||||
|
store: { showGamePanelTextDecoration },
|
||||||
|
} = await $dropFetch(`/api/v1/settings`);
|
||||||
|
|
||||||
const mobileFiltersOpen = ref(false);
|
const mobileFiltersOpen = ref(false);
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,7 @@
|
|||||||
>
|
>
|
||||||
<div class="flex shrink-0 h-16 items-center justify-between">
|
<div class="flex shrink-0 h-16 items-center justify-between">
|
||||||
<NuxtLink :to="homepageURL">
|
<NuxtLink :to="homepageURL">
|
||||||
<DropLogo class="h-8 w-auto" />
|
<ApplicationLogo class="h-8 w-auto" />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
||||||
<UserHeaderUserWidget />
|
<UserHeaderUserWidget />
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ if (import.meta.client) {
|
|||||||
<header
|
<header
|
||||||
class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8"
|
class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8"
|
||||||
>
|
>
|
||||||
<DropLogo class="h-10 w-auto sm:h-12" />
|
<ApplicationLogo class="h-10 w-auto sm:h-12" />
|
||||||
</header>
|
</header>
|
||||||
<main
|
<main
|
||||||
class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8"
|
class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8"
|
||||||
|
|||||||
@@ -77,8 +77,8 @@
|
|||||||
},
|
},
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"adminTitle": "Admin Dashboard - Drop",
|
"adminTitle": "Admin Dashboard - {0}",
|
||||||
"adminTitleTemplate": "{0} - Admin - Drop",
|
"adminTitleTemplate": "{0} - Admin - {1}",
|
||||||
"auth": {
|
"auth": {
|
||||||
"2fa": {
|
"2fa": {
|
||||||
"backToOptions": "{arrow} Back to options",
|
"backToOptions": "{arrow} Back to options",
|
||||||
@@ -330,6 +330,7 @@
|
|||||||
"library": "Library",
|
"library": "Library",
|
||||||
"metadata": "Meta",
|
"metadata": "Meta",
|
||||||
"settings": {
|
"settings": {
|
||||||
|
"general": "General Settings",
|
||||||
"store": "Store",
|
"store": "Store",
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"tokens": "API tokens"
|
"tokens": "API tokens"
|
||||||
@@ -617,6 +618,17 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"admin": {
|
"admin": {
|
||||||
|
"general": {
|
||||||
|
"applicationLogo": "Application logo",
|
||||||
|
"customLogo": "Custom logo",
|
||||||
|
"defaultLogo": "Default logo",
|
||||||
|
"logo": "Logo",
|
||||||
|
"serverName": "Server name",
|
||||||
|
"serverNameDescription": "The name of the server",
|
||||||
|
"serverNamePlaceholder": "My Drop Instance",
|
||||||
|
"title": "General settings",
|
||||||
|
"uploadLogo": "Upload logo"
|
||||||
|
},
|
||||||
"store": {
|
"store": {
|
||||||
"dropGameAltPlaceholder": "Example Game icon",
|
"dropGameAltPlaceholder": "Example Game icon",
|
||||||
"dropGameDescriptionPlaceholder": "This is an example game. It will be replaced if you import a game.",
|
"dropGameDescriptionPlaceholder": "This is an example game. It will be replaced if you import a game.",
|
||||||
|
|||||||
@@ -95,7 +95,7 @@
|
|||||||
class="hidden lg:fixed lg:inset-y-0 lg:left-0 lg:z-50 lg:block lg:w-20 lg:overflow-y-auto lg:bg-zinc-950 lg:pb-4"
|
class="hidden lg:fixed lg:inset-y-0 lg:left-0 lg:z-50 lg:block lg:w-20 lg:overflow-y-auto lg:bg-zinc-950 lg:pb-4"
|
||||||
>
|
>
|
||||||
<div class="flex flex-col h-24 shrink-0 items-center justify-center">
|
<div class="flex flex-col h-24 shrink-0 items-center justify-center">
|
||||||
<DropLogo class="h-8 w-auto" />
|
<ApplicationLogo class="h-8 w-auto" />
|
||||||
<span
|
<span
|
||||||
class="mt-1 bg-blue-400 px-1 py-0.5 rounded-md text-xs font-bold font-display"
|
class="mt-1 bg-blue-400 px-1 py-0.5 rounded-md text-xs font-bold font-display"
|
||||||
>{{ $t("header.admin.admin") }}</span
|
>{{ $t("header.admin.admin") }}</span
|
||||||
@@ -170,6 +170,7 @@ import type { NavigationItem } from "~/composables/types";
|
|||||||
import { useCurrentNavigationIndex } from "~/composables/current-page-engine";
|
import { useCurrentNavigationIndex } from "~/composables/current-page-engine";
|
||||||
import { ArrowLeftIcon } from "@heroicons/vue/16/solid";
|
import { ArrowLeftIcon } from "@heroicons/vue/16/solid";
|
||||||
import { XMarkIcon } from "@heroicons/vue/24/solid";
|
import { XMarkIcon } from "@heroicons/vue/24/solid";
|
||||||
|
import type { Settings } from "~/server/internal/utils/types";
|
||||||
|
|
||||||
const i18nHead = useLocaleHead();
|
const i18nHead = useLocaleHead();
|
||||||
|
|
||||||
@@ -231,6 +232,15 @@ router.afterEach(() => {
|
|||||||
sidebarOpen.value = false;
|
sidebarOpen.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
generalSettings: { serverName, mLogoObjectId },
|
||||||
|
} = await $dropFetch<Settings>("/api/v1/settings");
|
||||||
|
|
||||||
|
const favicon = mLogoObjectId ? useObject(mLogoObjectId) : "/favicon.ico";
|
||||||
|
useFavicon(favicon, { rel: "icon" });
|
||||||
|
|
||||||
|
const applicationName = serverName || $t("drop.drop");
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
lang: i18nHead.value.htmlAttrs.lang,
|
lang: i18nHead.value.htmlAttrs.lang,
|
||||||
@@ -238,7 +248,9 @@ useHead({
|
|||||||
dir: i18nHead.value.htmlAttrs.dir,
|
dir: i18nHead.value.htmlAttrs.dir,
|
||||||
},
|
},
|
||||||
titleTemplate(title) {
|
titleTemplate(title) {
|
||||||
return title ? $t("adminTitleTemplate", [title]) : $t("adminTitle");
|
return title
|
||||||
|
? $t("adminTitleTemplate", [title, applicationName])
|
||||||
|
: $t("adminTitle", [applicationName]);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -34,4 +34,7 @@ useHead({
|
|||||||
return title ? t("titleTemplate", [title]) : t("title");
|
return title ? t("titleTemplate", [title]) : t("title");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const { mLogoObjectId } = await $dropFetch("/api/v1");
|
||||||
|
const favicon = mLogoObjectId ? useObject(mLogoObjectId) : "/favicon.ico";
|
||||||
|
useFavicon(favicon, { rel: "icon" });
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<TileWithLink>
|
<TileWithLink>
|
||||||
<div class="h-full flex">
|
<div class="h-full flex">
|
||||||
<div class="flex-1 my-auto">
|
<div class="flex-1 my-auto">
|
||||||
<DropLogo />
|
<ApplicationLogo />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex-6 lg:flex-2 my-auto text-center flex lg:inline mx-4"
|
class="flex-6 lg:flex-2 my-auto text-center flex lg:inline mx-4"
|
||||||
@@ -192,7 +192,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { formatBytes } from "~/server/internal/utils/files";
|
import { formatBytes } from "~/server/internal/utils/files";
|
||||||
import GamepadIcon from "~/components/Icons/GamepadIcon.vue";
|
import GamepadIcon from "~/components/Icons/GamepadIcon.vue";
|
||||||
import DropLogo from "~/components/DropLogo.vue";
|
|
||||||
import { ServerStackIcon, UserGroupIcon } from "@heroicons/vue/24/outline";
|
import { ServerStackIcon, UserGroupIcon } from "@heroicons/vue/24/outline";
|
||||||
import { getPercentage } from "~/utils/utils";
|
import { getPercentage } from "~/utils/utils";
|
||||||
import { getBarColor } from "~/utils/colors";
|
import { getBarColor } from "~/utils/colors";
|
||||||
|
|||||||
@@ -11,18 +11,12 @@
|
|||||||
|
|
||||||
<div class="relative inline-flex items-center gap-4">
|
<div class="relative inline-flex items-center gap-4">
|
||||||
<!-- icon image -->
|
<!-- icon image -->
|
||||||
<div class="relative group/iconupload rounded-xl overflow-hidden">
|
<ImageUpload
|
||||||
<img :src="useObject(company.mLogoObjectId)" class="size-20" />
|
:object-id="company.mLogoObjectId"
|
||||||
<button
|
:open-modal="() => (uploadLogoOpen = true)"
|
||||||
class="rounded-xl transition duration-200 absolute inset-0 opacity-0 group-hover/iconupload:opacity-100 focus-visible/iconupload:opacity-100 cursor-pointer bg-zinc-900/80 text-zinc-100 flex flex-col items-center justify-center text-center text-xs font-semibold ring-1 ring-inset ring-zinc-800 px-2"
|
:hover-text="$t('library.admin.metadata.companies.editor.uploadIcon')"
|
||||||
@click="() => (uploadLogoOpen = true)"
|
:image-alt="`${company.mName} logo`"
|
||||||
>
|
/>
|
||||||
<ArrowUpTrayIcon class="size-5" />
|
|
||||||
<span>{{
|
|
||||||
$t("library.admin.metadata.companies.editor.uploadIcon")
|
|
||||||
}}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h1
|
<h1
|
||||||
class="group/name inline-flex items-center gap-x-3 text-5xl font-bold font-display text-zinc-100"
|
class="group/name inline-flex items-center gap-x-3 text-5xl font-bold font-display text-zinc-100"
|
||||||
|
|||||||
@@ -43,13 +43,20 @@ import {
|
|||||||
BuildingStorefrontIcon,
|
BuildingStorefrontIcon,
|
||||||
CodeBracketIcon,
|
CodeBracketIcon,
|
||||||
ServerIcon,
|
ServerIcon,
|
||||||
|
ServerStackIcon,
|
||||||
} from "@heroicons/vue/24/outline";
|
} from "@heroicons/vue/24/outline";
|
||||||
|
|
||||||
const navigation: Array<NavigationItem & { icon: Component }> = [
|
const navigation: Array<NavigationItem & { icon: Component }> = [
|
||||||
{
|
{
|
||||||
label: $t("header.admin.settings.store"),
|
label: $t("header.admin.settings.general"),
|
||||||
route: "/admin/settings",
|
route: "/admin/settings",
|
||||||
prefix: "/admin/settings",
|
prefix: "/admin/settings",
|
||||||
|
icon: ServerIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t("header.admin.settings.store"),
|
||||||
|
route: "/admin/settings/store",
|
||||||
|
prefix: "/admin/settings/store",
|
||||||
icon: BuildingStorefrontIcon,
|
icon: BuildingStorefrontIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -62,7 +69,7 @@ const navigation: Array<NavigationItem & { icon: Component }> = [
|
|||||||
label: "Services",
|
label: "Services",
|
||||||
route: "/admin/settings/services",
|
route: "/admin/settings/services",
|
||||||
prefix: "/admin/settings/services",
|
prefix: "/admin/settings/services",
|
||||||
icon: ServerIcon,
|
icon: ServerStackIcon,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +1,103 @@
|
|||||||
<template>
|
<template>
|
||||||
<form class="space-y-4" @submit.prevent="() => saveSettings()">
|
<div>
|
||||||
<div class="pb-4 border-b border-zinc-700">
|
<form class="space-y-4" @submit.prevent="() => saveSettings()">
|
||||||
<h2 class="text-xl font-semibold text-zinc-100">
|
<div class="pb-4 border-b border-zinc-700 w-2xl mt-2">
|
||||||
{{ $t("settings.admin.store.title") }}
|
<h2 class="text-xl font-semibold text-zinc-100">
|
||||||
</h2>
|
{{ $t("settings.admin.general.title") }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
<h3 class="text-base font-medium text-zinc-400 mb-3 m-x-0">
|
<div class="mt-4">
|
||||||
{{ $t("settings.admin.store.showGamePanelTextDecoration") }}
|
<label
|
||||||
</h3>
|
for="serverName"
|
||||||
<ul class="flex gap-3">
|
class="block text-sm/6 font-medium text-zinc-100"
|
||||||
<li class="inline-block">
|
>{{ $t("settings.admin.general.serverName") }}</label
|
||||||
<OptionWrapper
|
|
||||||
:active="showGamePanelTextDecoration"
|
|
||||||
@click="setShowTitleDescription(true)"
|
|
||||||
>
|
>
|
||||||
<div class="flex">
|
<div class="mt-2">
|
||||||
<GamePanel
|
<input
|
||||||
:animate="false"
|
id="name"
|
||||||
:game="game"
|
v-model="settings.generalSettings.serverName"
|
||||||
:default-placeholder="true"
|
type="text"
|
||||||
/>
|
name="serverName"
|
||||||
</div>
|
:placeholder="$t('settings.admin.general.serverNamePlaceholder')"
|
||||||
</OptionWrapper>
|
class="block w-full rounded-md bg-zinc-800 px-3 py-1.5 text-base text-zinc-100 outline outline-1 -outline-offset-1 outline-zinc-700 placeholder:text-zinc-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:text-sm/6"
|
||||||
</li>
|
@input="(event) => updateServerName(event)"
|
||||||
<li class="inline-block">
|
/>
|
||||||
<OptionWrapper
|
</div>
|
||||||
:active="!showGamePanelTextDecoration"
|
</div>
|
||||||
@click="setShowTitleDescription(false)"
|
|
||||||
>
|
|
||||||
<div class="flex">
|
|
||||||
<GamePanel
|
|
||||||
:game="game"
|
|
||||||
:show-title-description="false"
|
|
||||||
:animate="false"
|
|
||||||
:default-placeholder="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</OptionWrapper>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<LoadingButton
|
<div class="mt-4">
|
||||||
type="submit"
|
<p for="logo" class="block text-sm/6 font-medium text-zinc-100">
|
||||||
class="inline-flex w-full shadow-sm sm:w-auto"
|
{{ $t("settings.admin.general.logo") }}
|
||||||
:loading="saving"
|
</p>
|
||||||
:disabled="!allowSave"
|
<ul class="flex gap-3">
|
||||||
>
|
<li class="w-40 flex flex-col items-center">
|
||||||
{{ allowSave ? $t("common.save") : $t("common.saved") }}
|
<div class="flex items-center max-w-25 mt-2 mb-2 h-full">
|
||||||
</LoadingButton>
|
<ImageUpload
|
||||||
</form>
|
:hover-text="$t('settings.admin.general.uploadLogo')"
|
||||||
|
:open-modal="openModal"
|
||||||
|
:object-id="mCustomLogoObjectId"
|
||||||
|
:image-alt="$t('settings.admin.general.applicationLogo')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label class="flex flex-col text-zinc-100 text-sm items-center">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
v-model="settings.generalSettings.mLogoObjectId"
|
||||||
|
class="mr-1"
|
||||||
|
type="radio"
|
||||||
|
name="mLogoObjectId"
|
||||||
|
:value="mCustomLogoObjectId"
|
||||||
|
@input="updateFormLogo"
|
||||||
|
/>
|
||||||
|
{{ $t("settings.admin.general.customLogo") }}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li class="w-40 flex flex-col items-center">
|
||||||
|
<div class="flex w-25 mt-2 mb-2 h-full">
|
||||||
|
<DropLogo @click="() => updateFormLogo(null)" />
|
||||||
|
</div>
|
||||||
|
<label class="flex flex-col text-zinc-100 text-sm items-center">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input
|
||||||
|
v-model="settings.generalSettings.mLogoObjectId"
|
||||||
|
class="mr-1"
|
||||||
|
type="radio"
|
||||||
|
name="isDefaultLogo"
|
||||||
|
:checked="settings.generalSettings.mLogoObjectId === null"
|
||||||
|
:value="null"
|
||||||
|
@input="() => updateFormLogo(null)"
|
||||||
|
/>
|
||||||
|
{{ $t("settings.admin.general.defaultLogo") }}
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ModalUploadFile
|
||||||
|
v-model="uploadLogoOpen"
|
||||||
|
:endpoint="`/api/v1/admin/settings/logo`"
|
||||||
|
accept="image/*"
|
||||||
|
@upload="updateLogo"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LoadingButton
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex w-full shadow-sm sm:w-auto"
|
||||||
|
:loading="saving"
|
||||||
|
:disabled="!allowSave"
|
||||||
|
>
|
||||||
|
{{ allowSave ? $t("common.save") : $t("common.saved") }}
|
||||||
|
</LoadingButton>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FetchError } from "ofetch";
|
import { FetchError } from "ofetch";
|
||||||
|
import type { Settings } from "~/server/internal/utils/types";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -65,30 +109,40 @@ useHead({
|
|||||||
title: t("settings.admin.title"),
|
title: t("settings.admin.title"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const settings = await $dropFetch("/api/v1/settings");
|
const settings = ref<Settings>(await $dropFetch("/api/v1/settings"));
|
||||||
const { game } = await $dropFetch("/api/v1/admin/settings/dummy-data");
|
|
||||||
|
|
||||||
const allowSave = ref(false);
|
const allowSave = ref<boolean>(false);
|
||||||
|
const uploadLogoOpen = ref<boolean>(false);
|
||||||
|
|
||||||
const showGamePanelTextDecoration = ref<boolean>(
|
const mCustomLogoObjectId = ref<string>(
|
||||||
settings.showGamePanelTextDecoration,
|
settings.value.generalSettings.mLogoObjectId || "",
|
||||||
);
|
);
|
||||||
|
|
||||||
function setShowTitleDescription(value: boolean) {
|
const updateServerName = (event: InputEvent) => {
|
||||||
showGamePanelTextDecoration.value = value;
|
settings.value.generalSettings.serverName =
|
||||||
|
(event.target as HTMLInputElement)?.value || "";
|
||||||
allowSave.value = true;
|
allowSave.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
uploadLogoOpen.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
const saving = ref<boolean>(false);
|
const saving = ref<boolean>(false);
|
||||||
|
|
||||||
async function saveSettings() {
|
async function saveSettings() {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
try {
|
try {
|
||||||
await $dropFetch("/api/v1/admin/settings", {
|
settings.value = await $dropFetch("/api/v1/admin/settings", {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: {
|
body: {
|
||||||
showGamePanelTextDecoration: showGamePanelTextDecoration.value,
|
generalSettings: {
|
||||||
|
serverName: settings.value.generalSettings.serverName,
|
||||||
|
mLogoObjectId: settings.value.generalSettings.mLogoObjectId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
window.location.reload();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
createModal(
|
createModal(
|
||||||
ModalType.Notification,
|
ModalType.Notification,
|
||||||
@@ -105,4 +159,16 @@ async function saveSettings() {
|
|||||||
saving.value = false;
|
saving.value = false;
|
||||||
allowSave.value = false;
|
allowSave.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateLogo(response: { id: string }) {
|
||||||
|
mCustomLogoObjectId.value = response.id;
|
||||||
|
settings.value.generalSettings.mLogoObjectId = response.id;
|
||||||
|
allowSave.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFormLogo = (event: InputEvent | null) => {
|
||||||
|
settings.value.generalSettings.mLogoObjectId =
|
||||||
|
(event?.target as HTMLInputElement)?.value || null;
|
||||||
|
allowSave.value = true;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<form class="space-y-4" @submit.prevent="() => saveSettings()">
|
||||||
|
<div class="pb-4 border-b border-zinc-700">
|
||||||
|
<h2 class="text-xl font-semibold text-zinc-100">
|
||||||
|
{{ $t("settings.admin.store.title") }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<h3 class="text-base font-medium text-zinc-400 mb-3 m-x-0">
|
||||||
|
{{ $t("settings.admin.store.showGamePanelTextDecoration") }}
|
||||||
|
</h3>
|
||||||
|
<ul class="flex gap-3">
|
||||||
|
<li class="inline-block">
|
||||||
|
<OptionWrapper
|
||||||
|
:active="showGamePanelTextDecoration"
|
||||||
|
@click="setShowTitleDescription(true)"
|
||||||
|
>
|
||||||
|
<div class="flex">
|
||||||
|
<GamePanel
|
||||||
|
:animate="false"
|
||||||
|
:game="game"
|
||||||
|
:default-placeholder="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</OptionWrapper>
|
||||||
|
</li>
|
||||||
|
<li class="inline-block">
|
||||||
|
<OptionWrapper
|
||||||
|
:active="!showGamePanelTextDecoration"
|
||||||
|
@click="setShowTitleDescription(false)"
|
||||||
|
>
|
||||||
|
<div class="flex">
|
||||||
|
<GamePanel
|
||||||
|
:game="game"
|
||||||
|
:show-title-description="false"
|
||||||
|
:animate="false"
|
||||||
|
:default-placeholder="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</OptionWrapper>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LoadingButton
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex w-full shadow-sm sm:w-auto"
|
||||||
|
:loading="saving"
|
||||||
|
:disabled="!allowSave"
|
||||||
|
>
|
||||||
|
{{ allowSave ? $t("common.save") : $t("common.saved") }}
|
||||||
|
</LoadingButton>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { FetchError } from "ofetch";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: "admin",
|
||||||
|
});
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: t("settings.admin.title"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const settings = await $dropFetch("/api/v1/settings");
|
||||||
|
const { game } = await $dropFetch("/api/v1/admin/settings/dummy-data");
|
||||||
|
|
||||||
|
const allowSave = ref(false);
|
||||||
|
|
||||||
|
const showGamePanelTextDecoration = ref<boolean>(
|
||||||
|
settings.store.showGamePanelTextDecoration,
|
||||||
|
);
|
||||||
|
|
||||||
|
function setShowTitleDescription(value: boolean) {
|
||||||
|
showGamePanelTextDecoration.value = value;
|
||||||
|
allowSave.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const saving = ref<boolean>(false);
|
||||||
|
async function saveSettings() {
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
await $dropFetch("/api/v1/admin/settings", {
|
||||||
|
method: "PATCH",
|
||||||
|
body: {
|
||||||
|
store: {
|
||||||
|
showGamePanelTextDecoration: showGamePanelTextDecoration.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
createModal(
|
||||||
|
ModalType.Notification,
|
||||||
|
{
|
||||||
|
title: `Failed to save settings.`,
|
||||||
|
description:
|
||||||
|
e instanceof FetchError
|
||||||
|
? (e.statusMessage ?? e.message)
|
||||||
|
: (e as string).toString(),
|
||||||
|
},
|
||||||
|
(_, c) => c(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
saving.value = false;
|
||||||
|
allowSave.value = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="mx-auto w-full max-w-7xl px-6 pt-10 pb-16 sm:pb-24 lg:px-8">
|
<main class="mx-auto w-full max-w-7xl px-6 pt-10 pb-16 sm:pb-24 lg:px-8">
|
||||||
<DropLogo class="mx-auto h-10 w-auto sm:h-12" />
|
<ApplicationLogo class="mx-auto h-10 w-auto sm:h-12" />
|
||||||
<div class="mx-auto mt-20 max-w-md text-center sm:mt-24">
|
<div class="mx-auto mt-20 max-w-md text-center sm:mt-24">
|
||||||
<h1
|
<h1
|
||||||
class="mt-4 text-3xl font-semibold tracking-tight text-balance text-white sm:text-4xl"
|
class="mt-4 text-3xl font-semibold tracking-tight text-balance text-white sm:text-4xl"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
>
|
>
|
||||||
<div class="mx-auto w-full max-w-sm lg:w-96">
|
<div class="mx-auto w-full max-w-sm lg:w-96">
|
||||||
<div>
|
<div>
|
||||||
<DropLogo class="h-8 w-auto" />
|
<ApplicationLogo class="h-8 w-auto" />
|
||||||
<h2
|
<h2
|
||||||
class="mt-4 text-2xl font-bold font-display leading-9 tracking-tight text-zinc-100"
|
class="mt-4 text-2xl font-bold font-display leading-9 tracking-tight text-zinc-100"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
>
|
>
|
||||||
<div class="mx-auto w-full max-w-sm lg:w-96">
|
<div class="mx-auto w-full max-w-sm lg:w-96">
|
||||||
<div>
|
<div>
|
||||||
<DropLogo class="h-10 w-auto" />
|
<ApplicationLogo class="h-10 w-auto" />
|
||||||
<h2
|
<h2
|
||||||
class="mt-8 text-2xl font-bold font-display leading-9 tracking-tight text-zinc-100"
|
class="mt-8 text-2xl font-bold font-display leading-9 tracking-tight text-zinc-100"
|
||||||
>
|
>
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { AuthMec } from "~/prisma/client/enums";
|
import type { AuthMec } from "~/prisma/client/enums";
|
||||||
import DropLogo from "~/components/DropLogo.vue";
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { enabledAuthProviders, oidcProviderName } =
|
const { enabledAuthProviders, oidcProviderName } =
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "Game_mName_idx";
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "GameTag_name_idx";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ApplicationSettings" ADD COLUMN "mLogoObjectId" TEXT,
|
||||||
|
ADD COLUMN "serverName" TEXT NOT NULL DEFAULT 'Drop';
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Game_mName_idx" ON "Game" USING GIST ("mName" gist_trgm_ops(siglen=32));
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||||
@@ -8,6 +8,8 @@ model ApplicationSettings {
|
|||||||
saveSlotHistoryLimit Int @default(3)
|
saveSlotHistoryLimit Int @default(3)
|
||||||
|
|
||||||
showGamePanelTextDecoration Boolean @default(true)
|
showGamePanelTextDecoration Boolean @default(true)
|
||||||
|
serverName String @default("Drop")
|
||||||
|
mLogoObjectId String?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Platform {
|
enum Platform {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default defineEventHandler(async (h3) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
await dump();
|
dump();
|
||||||
throw createError({ statusCode: 404, message: "Company not found" });
|
throw createError({ statusCode: 404, message: "Company not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,21 +3,49 @@ import { applicationSettings } from "~/server/internal/config/application-config
|
|||||||
import { readDropValidatedBody } from "~/server/arktype";
|
import { readDropValidatedBody } from "~/server/arktype";
|
||||||
import { defineEventHandler, createError } from "h3";
|
import { defineEventHandler, createError } from "h3";
|
||||||
import aclManager from "~/server/internal/acls";
|
import aclManager from "~/server/internal/acls";
|
||||||
|
import objectHandler from "~/server/internal/objects";
|
||||||
|
import type { Settings } from "~/server/internal/utils/types";
|
||||||
|
|
||||||
const UpdateSettings = type({
|
const UpdateSettings = type({
|
||||||
showGamePanelTextDecoration: "boolean",
|
"store?": {
|
||||||
|
showGamePanelTextDecoration: "boolean",
|
||||||
|
},
|
||||||
|
"generalSettings?": {
|
||||||
|
serverName: "string",
|
||||||
|
mLogoObjectId: "string | null",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineEventHandler<{ body: typeof UpdateSettings.infer }>(
|
export default defineEventHandler<{ body: typeof UpdateSettings.infer }>(
|
||||||
async (h3) => {
|
async (h3): Promise<Settings> => {
|
||||||
const allowed = await aclManager.allowSystemACL(h3, ["settings:update"]);
|
const allowed = await aclManager.allowSystemACL(h3, ["settings:update"]);
|
||||||
if (!allowed) throw createError({ statusCode: 403 });
|
if (!allowed) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
const body = await readDropValidatedBody(h3, UpdateSettings);
|
const body = await readDropValidatedBody(h3, UpdateSettings);
|
||||||
|
|
||||||
await applicationSettings.set(
|
if (body.store) {
|
||||||
"showGamePanelTextDecoration",
|
await applicationSettings.set(
|
||||||
body.showGamePanelTextDecoration,
|
"showGamePanelTextDecoration",
|
||||||
);
|
body.store.showGamePanelTextDecoration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (body.generalSettings) {
|
||||||
|
const previousMLogoObjectId =
|
||||||
|
await applicationSettings.get("mLogoObjectId");
|
||||||
|
await applicationSettings.set(
|
||||||
|
"serverName",
|
||||||
|
body.generalSettings.serverName,
|
||||||
|
);
|
||||||
|
if (body.generalSettings.mLogoObjectId !== previousMLogoObjectId) {
|
||||||
|
if (previousMLogoObjectId) {
|
||||||
|
await objectHandler.deleteAsSystem(previousMLogoObjectId);
|
||||||
|
}
|
||||||
|
applicationSettings.set(
|
||||||
|
"mLogoObjectId",
|
||||||
|
body.generalSettings.mLogoObjectId || null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await applicationSettings.getSettings();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import aclManager from "~/server/internal/acls";
|
||||||
|
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
|
||||||
|
|
||||||
|
export default defineEventHandler(async (h3) => {
|
||||||
|
const allowed = await aclManager.allowSystemACL(h3, ["settings:update"]);
|
||||||
|
if (!allowed) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
|
const result = await handleFileUpload(h3, {}, ["anonymous:read"], 1);
|
||||||
|
if (!result)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
message: "File upload required (multipart form)",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [ids, , pull] = result;
|
||||||
|
const id = ids.at(0);
|
||||||
|
if (!id)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: "Upload at least one file.",
|
||||||
|
});
|
||||||
|
|
||||||
|
await pull();
|
||||||
|
|
||||||
|
return { id: id };
|
||||||
|
});
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import { applicationSettings } from "~/server/internal/config/application-configuration";
|
||||||
import { systemConfig } from "~/server/internal/config/sys-conf";
|
import { systemConfig } from "~/server/internal/config/sys-conf";
|
||||||
|
|
||||||
export default defineEventHandler((_h3) => {
|
export default defineEventHandler(async (_h3) => {
|
||||||
return {
|
return {
|
||||||
appName: "Drop",
|
appName: "Drop",
|
||||||
version: systemConfig.getDropVersion(),
|
version: systemConfig.getDropVersion(),
|
||||||
gitRef: `#${systemConfig.getGitRef()}`,
|
gitRef: `#${systemConfig.getGitRef()}`,
|
||||||
external: systemConfig.getExternalUrl(),
|
external: systemConfig.getExternalUrl(),
|
||||||
|
serverName: await applicationSettings.get("serverName"),
|
||||||
|
mLogoObjectId: await applicationSettings.get("mLogoObjectId"),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import aclManager from "~/server/internal/acls";
|
import aclManager from "~/server/internal/acls";
|
||||||
import { applicationSettings } from "~/server/internal/config/application-configuration";
|
import { applicationSettings } from "~/server/internal/config/application-configuration";
|
||||||
|
import type { Settings } from "~/server/internal/utils/types";
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3): Promise<Settings> => {
|
||||||
const allowed = await aclManager.getUserACL(h3, ["settings:read"]);
|
const allowed = await aclManager.getUserACL(h3, ["settings:read"]);
|
||||||
if (!allowed) throw createError({ statusCode: 403 });
|
if (!allowed) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
const showGamePanelTextDecoration = await applicationSettings.get(
|
return applicationSettings.getSettings();
|
||||||
"showGamePanelTextDecoration",
|
|
||||||
);
|
|
||||||
|
|
||||||
return { showGamePanelTextDecoration };
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { ApplicationSettingsModel } from "~/prisma/client/models";
|
import type { ApplicationSettingsModel } from "~/prisma/client/models";
|
||||||
import prisma from "../db/database";
|
import prisma from "../db/database";
|
||||||
|
import type { Settings } from "../utils/types";
|
||||||
|
|
||||||
class ApplicationConfiguration {
|
class ApplicationConfiguration {
|
||||||
// Reference to the currently selected application configuration
|
// Reference to the currently selected application configuration
|
||||||
@@ -80,6 +81,20 @@ class ApplicationConfiguration {
|
|||||||
|
|
||||||
return this.currentApplicationSettings[key];
|
return this.currentApplicationSettings[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSettings(): Promise<Settings> {
|
||||||
|
return {
|
||||||
|
store: {
|
||||||
|
showGamePanelTextDecoration: await applicationSettings.get(
|
||||||
|
"showGamePanelTextDecoration",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
generalSettings: {
|
||||||
|
serverName: await applicationSettings.get("serverName"),
|
||||||
|
mLogoObjectId: await applicationSettings.get("mLogoObjectId"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const applicationSettings = new ApplicationConfiguration();
|
export const applicationSettings = new ApplicationConfiguration();
|
||||||
|
|||||||
+10
@@ -11,3 +11,13 @@ export type KeyOfType<T, V> = keyof {
|
|||||||
type EnumDictionary<T extends string | symbol | number, U> = {
|
type EnumDictionary<T extends string | symbol | number, U> = {
|
||||||
[K in T]: U;
|
[K in T]: U;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Settings = {
|
||||||
|
store: {
|
||||||
|
showGamePanelTextDecoration: boolean;
|
||||||
|
};
|
||||||
|
generalSettings: {
|
||||||
|
serverName: string;
|
||||||
|
mLogoObjectId: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user