Move arcade api calls to dedicated file, save arcade settings.
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
Trenton Zimmer 2024-12-16 16:40:15 -05:00
parent f351d291ac
commit 5e00903ef9
10 changed files with 268 additions and 258 deletions

View File

@ -50,10 +50,11 @@
<div id="particles-js"></div>
<script src="/assets/particles.js"></script>
<script src="/assets/snow.js"></script>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<script src="/assets/snow.js"></script>
<!-- Bats! -->
<!-- <script src="https://code.jquery.com/jquery.js"></script>

60
src/constants/values.js Normal file
View File

@ -0,0 +1,60 @@
export function getNestedValue(obj, path) {
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
}
export function setNestedValue(obj, path, value) {
const keys = path.split(".");
const lastKey = keys.pop();
const nestedObj = keys.reduce((acc, key) => (acc[key] = acc[key] || {}), obj);
nestedObj[lastKey] = value;
}
export function transformNonUnicode(value, maxLength) {
const allowedCharsRegex = /^[0-9A-Z!?#$&*-. ]*$/;
const transformedValue = value.toUpperCase().slice(0, maxLength);
return (
allowedCharsRegex.test(transformedValue) ? transformedValue : ""
).toUpperCase();
}
export function transformUnicode(value, maxLength) {
let transformedValue = "";
for (
let i = 0;
i < value.length && transformedValue.length < maxLength;
i++
) {
let c = value.charCodeAt(i);
if (c >= 0x30 && c <= 0x39) {
// '0' to '9'
c = 0xff10 + (c - 0x30);
} else if (c >= 0x41 && c <= 0x5a) {
// 'A' to 'Z'
c = 0xff21 + (c - 0x41);
} else if (c >= 0x61 && c <= 0x7a) {
// 'a' to 'z'
c = 0xff41 + (c - 0x61);
} else if (c === 0x40) {
// '@'
c = 0xff20;
} else if (c === 0x2c) {
// ','
c = 0xff0c;
} else if (c === 0x2e) {
// '.'
c = 0xff0e;
} else if (c === 0x5f) {
// '_'
c = 0xff3f;
}
transformedValue += String.fromCharCode(c);
}
const allowedCharsRegex =
/^[\uFF20-\uFF3A\uFF41-\uFF5A\uFF10-\uFF19\uFF0C\uFF0E\uFF3F\u3041-\u308D\u308F\u3092\u3093\u30A1-\u30ED\u30EF\u30F2\u30F3\u30FC]*$/;
return (
allowedCharsRegex.test(transformedValue) ? transformedValue : ""
).toUpperCase();
}

View File

@ -45,6 +45,7 @@ img {
}
#particles-js {
height: full;
min-width: screen;
min-height: screen;
position: absolute;
}

View File

@ -10,3 +10,37 @@ export async function APIGetArcade(arcadeId) {
throw error;
}
}
export async function APIGetArcadeVPN(arcadeId) {
try {
const data = await mainStore.callApi(`/arcade/${arcadeId}/exportVPN`);
return data;
} catch (error) {
console.log("Error fetching arcade VPN:", error);
throw error;
}
}
export async function APIUpdateArcade(arcadeId, newArcade) {
try {
const data = await mainStore.callApi(
`/arcade/${arcadeId}`,
"POST",
newArcade
);
return data;
} catch (error) {
console.log("Error updating arcade:", error);
throw error;
}
}
export async function APIGetArcadePASELI(arcadeId) {
try {
const data = await mainStore.callApi(`/arcade/${arcadeId}/paseli`);
return data.data;
} catch (error) {
console.log("Error fetching arcade PASELI data:", error);
throw error;
}
}

View File

@ -266,36 +266,6 @@ export const useMainStore = defineStore("main", {
}
},
async getArcade(arcadeId) {
try {
const data = await this.callApi(`/arcade/${arcadeId}`);
return data.arcade;
} catch (error) {
console.log("Error fetching arcade:", error);
throw error;
}
},
async getArcadeVPN(arcadeId) {
try {
const data = await this.callApi(`/arcade/${arcadeId}/exportVPN`);
return data;
} catch (error) {
console.log("Error fetching arcade VPN:", error);
throw error;
}
},
async getPaseliData(arcadeId) {
try {
const data = await this.callApi(`/arcade/${arcadeId}/paseli`);
return data.data;
} catch (error) {
console.log("Error fetching PASELI:", error);
throw error;
}
},
async getMusicData(game, version, songIds = null, oneChart = false) {
try {
const data = await this.callApi(

View File

@ -1,37 +1,88 @@
<script setup>
import { useRoute } from "vue-router";
import { ref, onMounted } from "vue";
import { mdiStore, mdiCogOutline } from "@mdi/js";
import { mdiStore, mdiStoreCog, mdiCogOutline } from "@mdi/js";
import SectionMain from "@/components/SectionMain.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionTitleLine from "@/components/SectionTitleLine.vue";
import ArcadeCard from "@/components/ArcadeCard.vue";
import CardBox from "@/components/CardBox.vue";
import FormField from "@/components/FormField.vue";
// import FormFilePicker from "@/components/FormFilePicker.vue";
import FormCheckRadio from "@/components/FormCheckRadio.vue";
// import FormControl from "@/components/FormControl.vue";
import PillTag from "@/components/PillTag.vue";
import BaseButton from "@/components/BaseButton.vue";
// import CardBoxWidget from "@/components/CardBoxWidget.vue";
// import { listCodes } from "@/constants/area";
import CardBoxWidget from "@/components/CardBoxWidget.vue";
import { getNestedValue, setNestedValue } from "@/constants/values";
import {
APIGetArcade,
APIUpdateArcade,
APIGetArcadeVPN,
} from "@/stores/api/arcade";
import { useMainStore } from "@/stores/main";
const mainStore = useMainStore();
const arcadeData = ref({});
const optionForm = ref(null);
const bareForm = ref(null);
const arcadeData = ref(null);
const loading = ref(true);
const $route = useRoute();
const arcadeId = parseInt($route.params.id);
onMounted(async () => {
var arcadeOptions = [
{
id: "paseli_enabled",
name: "PASELI Services",
help: "Enable PASELI for this arcade.",
type: "Boolean",
},
{
id: "paseli_infinite",
name: "Infinite PASELI",
help: "Enable infinite PASELI for this arcade.",
type: "Boolean",
},
{
id: "maint",
name: "Maintenance Mode",
help: "Place this arcade under maintenance.",
type: "Boolean",
},
{
id: "hide_network",
name: "Incognito Mode",
help: "Hide the network and all ranking data.",
type: "Boolean",
},
];
async function loadArcade() {
try {
const data = await mainStore.getArcade(arcadeId);
arcadeData.value = {};
optionForm.value = {};
bareForm.value = {};
const data = await APIGetArcade(arcadeId);
arcadeData.value = data;
for (const setting of arcadeOptions) {
const value = getNestedValue(arcadeData.value.data, setting.id);
setNestedValue(optionForm.value, setting.id, value);
setNestedValue(bareForm.value, setting.id, value);
}
loading.value = false;
} catch (error) {
console.log("Failed to fetch arcade data:", error);
console.error("Failed to fetch arcade data:", error);
}
}
async function updateArcade() {
const response = await APIUpdateArcade(arcadeId, optionForm.value);
if (response.status != "error") {
await loadArcade();
}
}
onMounted(() => {
loadArcade();
});
function formatName(inputString, replaceWith = "NA_") {
@ -52,7 +103,7 @@ function formatName(inputString, replaceWith = "NA_") {
async function exportVPN() {
try {
const data = await mainStore.getArcadeVPN(arcadeId);
const data = await APIGetArcadeVPN(arcadeId);
const blob = new Blob([data], { type: "application/ovpn" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
@ -76,9 +127,13 @@ async function exportVPN() {
<template v-if="!loading">
<ArcadeCard class="mb-6" :arcade="arcadeData" />
<SectionTitleLine :icon="mdiStore" title="Arcade Overview" main />
<SectionTitleLine
:icon="mdiStoreCog"
title="Arcade Administration"
main
/>
<CardBox is-form class="mb-6" @submit.prevent="">
<CardBox is-form class="mb-6" @submit.prevent="updateArcade">
<PillTag
color="info"
label="Settings"
@ -89,155 +144,57 @@ async function exportVPN() {
class="grid grid-cols-1 w-full gap-2 md:gap-6 md:flex md:place-content-stretch"
>
<FormField
label="PASELI Enabled"
help="Enable or disable PASELI services."
v-for="setting of arcadeOptions"
:key="setting.id"
:label="setting.name"
:help="setting.help"
>
<FormCheckRadio
v-model="arcadeData.data.paseli_enabled"
:input-value="arcadeData.data.paseli_enabled"
name="paseli"
type="switch"
/>
</FormField>
<FormField
label="Infinite PASELI"
help="Enable or disable infinite PASELI."
>
<FormCheckRadio
v-model="arcadeData.data.paseli_infinite"
:input-value="arcadeData.data.paseli_infinite"
name="infinitePaseli"
type="switch"
/>
</FormField>
<FormField
label="Maintenance Mode"
help="Place this arcade under Maintenance."
>
<FormCheckRadio
v-model="arcadeData.data.maint"
:input-value="arcadeData.data.maint"
name="maintenance"
type="switch"
/>
</FormField>
<FormField
label="Incognito Mode"
help="Hide the eAmusement network and ranking data."
>
<FormCheckRadio
v-model="arcadeData.data.hide_network"
:input-value="arcadeData.data.hide_network"
name="incognito"
v-if="setting.type == 'Boolean'"
:name="setting.id"
:model-value="
Boolean(getNestedValue(optionForm, setting.id) ?? 0)
"
:input-value="true"
type="switch"
@update:model-value="
(value) =>
setNestedValue(optionForm, setting.id, Number(value) ?? 0)
"
/>
</FormField>
</div>
<BaseButton color="success" type="submit" label="Save" />
<div
v-if="JSON.stringify(optionForm) !== JSON.stringify(bareForm)"
class="space-x-2 mt-6"
>
<BaseButton color="success" label="Save" type="submit" />
<BaseButton color="danger" label="Revert" @click="loadArcade()" />
</div>
</CardBox>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 mb-6">
<CardBox>
<h1 class="text-lg md:text-xl">OpenVPN Configuration</h1>
<p class="pb-1 dark:text-white/50">
Use this to export your VPN config.
</p>
<BaseButton
:small="false"
color="success"
class="my-2"
label="Export"
@click="exportVPN()"
/>
</CardBox>
<!-- <CardBox>
<h1 class="text-lg md:text-xl">Cover Photo</h1>
<p class="pb-1 dark:text-white/50">
NSFW = BAN. Children use this server. 60MB max.
</p>
<FormFilePicker label="Upload" name="upload" accept="image/*" />
<BaseButton
:small="false"
color="success"
class="mt-4"
label="Save"
/>
</CardBox> -->
<!-- <CardBox>
<h1 class="text-lg md:text-xl mb-2">Location Settings</h1>
<FormField label="Area" help="Set your arcade's area.">
<FormControl
v-model="thisArcade.area"
name="area"
:options="listCodes()"
/>
</FormField>
<FormField label="Address" help="Set your arcade's address.">
<FormControl
v-model="thisArcade.address"
name="address"
placeholder="No address"
/>
</FormField>
<FormField
label="Show Address"
help="Allow your arcade's address to be seen publicly."
>
<FormCheckRadio
:model-value="thisArcade.show_address"
:input-value="thisArcade.show_address"
name="show_address"
type="switch"
/>
</FormField>
<CardBox class="w-1/2 mb-6">
<h1 class="text-lg md:text-xl">OpenVPN Configuration</h1>
<p class="pb-1 dark:text-white/50">
Use this to download your VPN config.
</p>
<BaseButton
:small="false"
color="success"
class="mt-4"
label="Save"
class="my-2"
label="Export"
@click="exportVPN()"
/>
</CardBox> -->
</div>
</CardBox>
<!-- <SectionTitleLine
:icon="mdiChartTimelineVariant"
title="Arcade Overview"
main
/>
<SectionTitleLine :icon="mdiStore" title="Arcade Overview" main />
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 mb-6">
<CardBoxWidget
trend="12% (from last week)"
trend-type="up"
:number="37"
label="Arcade Scores (This Week)"
:number="arcadeData?.machines?.length"
label="Active Machines"
/>
<CardBoxWidget
trend="40% (from last month)"
trend-type="up"
:number="3"
label="Unique Games (All Time)"
/>
<CardBoxWidget
trend="No new players this week"
:number="2"
label="Unique Players (All Time)"
/>
<CardBoxWidget
trend="40% (from last month)"
trend-type="up"
:number="123456789"
:prefix="'¥'"
label="PASELI Spent (This Month)"
/>
</div> -->
</div>
</template>
</SectionMain>
</LayoutAuthenticated>

View File

@ -12,23 +12,47 @@ import FormControl from "@/components/FormControl.vue";
import FormCheckRadio from "@/components/FormCheckRadio.vue";
import BaseButton from "@/components/BaseButton.vue";
import { gameData, getGameInfo } from "@/constants";
import { useMainStore } from "@/stores/main";
import { getNestedValue, setNestedValue } from "@/constants/values";
import { APIGetArcade } from "@/stores/api/arcade";
const mainStore = useMainStore();
const arcadeData = ref({});
const optionForm = ref(null);
const bareForm = ref(null);
const arcadeData = ref(null);
const loading = ref(true);
const $route = useRoute();
const arcadeId = parseInt($route.params.id);
onMounted(async () => {
var arcadeOptions = [
{
id: "data.paseli_enabled",
name: "PASELI Enabled.",
help: "Enable PASELI for this arcade.",
type: "Boolean",
},
];
async function loadArcade() {
try {
const data = await mainStore.getArcade(arcadeId);
arcadeData.value = null;
optionForm.value = {};
bareForm.value = {};
const data = await APIGetArcade(arcadeId);
arcadeData.value = data;
for (const setting of arcadeOptions) {
const value = getNestedValue(arcadeData.value, setting.id);
setNestedValue(optionForm.value, setting.id, value);
setNestedValue(bareForm.value, setting.id, value);
}
loading.value = false;
} catch (error) {
console.log("Failed to fetch arcade data:", error);
console.error("Failed to fetch arcade data:", error);
}
}
onMounted(() => {
loadArcade();
});
const eventSettings = [

View File

@ -8,23 +8,27 @@ import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionTitleLine from "@/components/SectionTitleLine.vue";
import ArcadeCard from "@/components/ArcadeCard.vue";
import GeneralTable from "@/components/GeneralTable.vue";
import { useMainStore } from "@/stores/main";
import { APIGetArcade } from "@/stores/api/arcade";
const mainStore = useMainStore();
const arcadeData = ref({});
const loading = ref(true);
const $route = useRoute();
const arcadeId = parseInt($route.params.id);
onMounted(async () => {
async function loadArcade() {
try {
const data = await mainStore.getArcade(arcadeId);
arcadeData.value = null;
const data = await APIGetArcade(arcadeId);
arcadeData.value = data;
loading.value = false;
} catch (error) {
console.log("Failed to fetch arcade data:", error);
console.error("Failed to fetch arcade data:", error);
}
}
onMounted(() => {
loadArcade();
});
const headers = [

View File

@ -8,9 +8,8 @@ import CardBox from "@/components/CardBox.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionTitleLine from "@/components/SectionTitleLine.vue";
import ArcadeCard from "@/components/ArcadeCard.vue";
import { useMainStore } from "@/stores/main";
import { APIGetArcade, APIGetArcadePASELI } from "@/stores/api/arcade";
const mainStore = useMainStore();
const arcadeData = ref({});
const paseliData = ref({});
const loading = ref(true);
@ -18,16 +17,31 @@ const loading = ref(true);
const $route = useRoute();
const arcadeId = parseInt($route.params.id);
onMounted(async () => {
async function loadArcade() {
try {
const loadArcadeData = await mainStore.getArcade(arcadeId);
const loadPaseliData = await mainStore.getPaseliData(arcadeId);
arcadeData.value = loadArcadeData;
paseliData.value = loadPaseliData;
arcadeData.value = null;
const data = await APIGetArcade(arcadeId);
arcadeData.value = data;
loading.value = false;
} catch (error) {
console.log("Failed to fetch arcade data:", error);
console.error("Failed to fetch arcade data:", error);
}
}
async function loadPASELI() {
try {
paseliData.value = null;
const data = await APIGetArcadePASELI(arcadeId);
paseliData.value = data;
loading.value = false;
} catch (error) {
console.error("Failed to fetch arcade PASELI:", error);
}
}
onMounted(() => {
loadArcade();
loadPASELI();
});
const transactionHeaders = [

View File

@ -16,6 +16,12 @@ import QproCardBox from "@/components/Cards/QproCardBox.vue";
import PillTag from "@/components/PillTag.vue";
import { APIGetProfile, APIUpdateProfile } from "@/stores/api/profile";
import {
getNestedValue,
setNestedValue,
transformNonUnicode,
transformUnicode,
} from "@/constants/values";
import { getGameInfo } from "@/constants";
import { getVideoSource, getCardStyle } from "@/constants/sources";
import { getGameOptions } from "@/constants/options";
@ -96,67 +102,6 @@ async function loadProfile() {
}
}
function getNestedValue(obj, path) {
return path.split(".").reduce((acc, part) => acc && acc[part], obj);
}
function setNestedValue(obj, path, value) {
const keys = path.split(".");
const lastKey = keys.pop();
const nestedObj = keys.reduce((acc, key) => (acc[key] = acc[key] || {}), obj);
nestedObj[lastKey] = value;
}
function transformNonUnicode(value, maxLength) {
const allowedCharsRegex = /^[0-9A-Z!?#$&*-. ]*$/;
const transformedValue = value.toUpperCase().slice(0, maxLength);
return (
allowedCharsRegex.test(transformedValue) ? transformedValue : ""
).toUpperCase();
}
function transformUnicode(value, maxLength) {
let transformedValue = "";
for (
let i = 0;
i < value.length && transformedValue.length < maxLength;
i++
) {
let c = value.charCodeAt(i);
if (c >= 0x30 && c <= 0x39) {
// '0' to '9'
c = 0xff10 + (c - 0x30);
} else if (c >= 0x41 && c <= 0x5a) {
// 'A' to 'Z'
c = 0xff21 + (c - 0x41);
} else if (c >= 0x61 && c <= 0x7a) {
// 'a' to 'z'
c = 0xff41 + (c - 0x61);
} else if (c === 0x40) {
// '@'
c = 0xff20;
} else if (c === 0x2c) {
// ','
c = 0xff0c;
} else if (c === 0x2e) {
// '.'
c = 0xff0e;
} else if (c === 0x5f) {
// '_'
c = 0xff3f;
}
transformedValue += String.fromCharCode(c);
}
const allowedCharsRegex =
/^[\uFF20-\uFF3A\uFF41-\uFF5A\uFF10-\uFF19\uFF0C\uFF0E\uFF3F\u3041-\u308D\u308F\u3092\u3093\u30A1-\u30ED\u30EF\u30F2\u30F3\u30FC]*$/;
return (
allowedCharsRegex.test(transformedValue) ? transformedValue : ""
).toUpperCase();
}
async function updateProfile() {
const response = await APIUpdateProfile(
thisGame.id,