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:
@@ -18,7 +18,7 @@
|
||||
<TileWithLink>
|
||||
<div class="h-full flex">
|
||||
<div class="flex-1 my-auto">
|
||||
<DropLogo />
|
||||
<ApplicationLogo />
|
||||
</div>
|
||||
<div
|
||||
class="flex-6 lg:flex-2 my-auto text-center flex lg:inline mx-4"
|
||||
@@ -192,7 +192,6 @@
|
||||
<script setup lang="ts">
|
||||
import { formatBytes } from "~/server/internal/utils/files";
|
||||
import GamepadIcon from "~/components/Icons/GamepadIcon.vue";
|
||||
import DropLogo from "~/components/DropLogo.vue";
|
||||
import { ServerStackIcon, UserGroupIcon } from "@heroicons/vue/24/outline";
|
||||
import { getPercentage } from "~/utils/utils";
|
||||
import { getBarColor } from "~/utils/colors";
|
||||
|
||||
@@ -11,18 +11,12 @@
|
||||
|
||||
<div class="relative inline-flex items-center gap-4">
|
||||
<!-- icon image -->
|
||||
<div class="relative group/iconupload rounded-xl overflow-hidden">
|
||||
<img :src="useObject(company.mLogoObjectId)" class="size-20" />
|
||||
<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="() => (uploadLogoOpen = true)"
|
||||
>
|
||||
<ArrowUpTrayIcon class="size-5" />
|
||||
<span>{{
|
||||
$t("library.admin.metadata.companies.editor.uploadIcon")
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<ImageUpload
|
||||
:object-id="company.mLogoObjectId"
|
||||
:open-modal="() => (uploadLogoOpen = true)"
|
||||
:hover-text="$t('library.admin.metadata.companies.editor.uploadIcon')"
|
||||
:image-alt="`${company.mName} logo`"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<h1
|
||||
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,
|
||||
CodeBracketIcon,
|
||||
ServerIcon,
|
||||
ServerStackIcon,
|
||||
} from "@heroicons/vue/24/outline";
|
||||
|
||||
const navigation: Array<NavigationItem & { icon: Component }> = [
|
||||
{
|
||||
label: $t("header.admin.settings.store"),
|
||||
label: $t("header.admin.settings.general"),
|
||||
route: "/admin/settings",
|
||||
prefix: "/admin/settings",
|
||||
icon: ServerIcon,
|
||||
},
|
||||
{
|
||||
label: $t("header.admin.settings.store"),
|
||||
route: "/admin/settings/store",
|
||||
prefix: "/admin/settings/store",
|
||||
icon: BuildingStorefrontIcon,
|
||||
},
|
||||
{
|
||||
@@ -62,7 +69,7 @@ const navigation: Array<NavigationItem & { icon: Component }> = [
|
||||
label: "Services",
|
||||
route: "/admin/settings/services",
|
||||
prefix: "/admin/settings/services",
|
||||
icon: ServerIcon,
|
||||
icon: ServerStackIcon,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,59 +1,103 @@
|
||||
<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>
|
||||
<div>
|
||||
<form class="space-y-4" @submit.prevent="() => saveSettings()">
|
||||
<div class="pb-4 border-b border-zinc-700 w-2xl mt-2">
|
||||
<h2 class="text-xl font-semibold text-zinc-100">
|
||||
{{ $t("settings.admin.general.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="mt-4">
|
||||
<label
|
||||
for="serverName"
|
||||
class="block text-sm/6 font-medium text-zinc-100"
|
||||
>{{ $t("settings.admin.general.serverName") }}</label
|
||||
>
|
||||
<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>
|
||||
<div class="mt-2">
|
||||
<input
|
||||
id="name"
|
||||
v-model="settings.generalSettings.serverName"
|
||||
type="text"
|
||||
name="serverName"
|
||||
:placeholder="$t('settings.admin.general.serverNamePlaceholder')"
|
||||
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"
|
||||
@input="(event) => updateServerName(event)"
|
||||
/>
|
||||
</div>
|
||||
</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>
|
||||
<div class="mt-4">
|
||||
<p for="logo" class="block text-sm/6 font-medium text-zinc-100">
|
||||
{{ $t("settings.admin.general.logo") }}
|
||||
</p>
|
||||
<ul class="flex gap-3">
|
||||
<li class="w-40 flex flex-col items-center">
|
||||
<div class="flex items-center max-w-25 mt-2 mb-2 h-full">
|
||||
<ImageUpload
|
||||
: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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FetchError } from "ofetch";
|
||||
import type { Settings } from "~/server/internal/utils/types";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -65,30 +109,40 @@ useHead({
|
||||
title: t("settings.admin.title"),
|
||||
});
|
||||
|
||||
const settings = await $dropFetch("/api/v1/settings");
|
||||
const { game } = await $dropFetch("/api/v1/admin/settings/dummy-data");
|
||||
const settings = ref<Settings>(await $dropFetch("/api/v1/settings"));
|
||||
|
||||
const allowSave = ref(false);
|
||||
const allowSave = ref<boolean>(false);
|
||||
const uploadLogoOpen = ref<boolean>(false);
|
||||
|
||||
const showGamePanelTextDecoration = ref<boolean>(
|
||||
settings.showGamePanelTextDecoration,
|
||||
const mCustomLogoObjectId = ref<string>(
|
||||
settings.value.generalSettings.mLogoObjectId || "",
|
||||
);
|
||||
|
||||
function setShowTitleDescription(value: boolean) {
|
||||
showGamePanelTextDecoration.value = value;
|
||||
const updateServerName = (event: InputEvent) => {
|
||||
settings.value.generalSettings.serverName =
|
||||
(event.target as HTMLInputElement)?.value || "";
|
||||
allowSave.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const openModal = () => {
|
||||
uploadLogoOpen.value = true;
|
||||
};
|
||||
|
||||
const saving = ref<boolean>(false);
|
||||
|
||||
async function saveSettings() {
|
||||
saving.value = true;
|
||||
try {
|
||||
await $dropFetch("/api/v1/admin/settings", {
|
||||
settings.value = await $dropFetch("/api/v1/admin/settings", {
|
||||
method: "PATCH",
|
||||
body: {
|
||||
showGamePanelTextDecoration: showGamePanelTextDecoration.value,
|
||||
generalSettings: {
|
||||
serverName: settings.value.generalSettings.serverName,
|
||||
mLogoObjectId: settings.value.generalSettings.mLogoObjectId,
|
||||
},
|
||||
},
|
||||
});
|
||||
window.location.reload();
|
||||
} catch (e) {
|
||||
createModal(
|
||||
ModalType.Notification,
|
||||
@@ -105,4 +159,16 @@ async function saveSettings() {
|
||||
saving.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>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user