feat: Add vue-content-loader for loaders
parent
0f7995f0c2
commit
88969111ad
|
@ -20,6 +20,7 @@
|
|||
"pinia": "^2.2.4",
|
||||
"radix-vue": "^1.9.10",
|
||||
"vue": "^3.5.12",
|
||||
"vue-content-loader": "^2.0.1",
|
||||
"vue-router": "^4.4.5",
|
||||
"vue-select": "^4.0.0-beta.6",
|
||||
"vue3-tooltip": "^2.2.4"
|
||||
|
@ -7657,6 +7658,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-content-loader": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-content-loader/-/vue-content-loader-2.0.1.tgz",
|
||||
"integrity": "sha512-pkof4+q2xmzNEdhqelxtJejeP/vQUJtLle4/v2ueG+HURqM9Q/GIGC8GJ2bVVWeLfTDET51jqimwQdmxJTlu0g==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^3"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-eslint-parser": {
|
||||
"version": "9.4.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
"pinia": "^2.2.4",
|
||||
"radix-vue": "^1.9.10",
|
||||
"vue": "^3.5.12",
|
||||
"vue-content-loader": "^2.0.1",
|
||||
"vue-router": "^4.4.5",
|
||||
"vue-select": "^4.0.0-beta.6",
|
||||
"vue3-tooltip": "^2.2.4"
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
--surface-0: #ccd0da;
|
||||
--base: #eff1f5;
|
||||
--base-extra: #f5f6f7;
|
||||
--mantle: #e6e9ef;
|
||||
--crust: #dce0e8;
|
||||
|
||||
|
|
|
@ -1,16 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useTeamsStore } from "../stores/teams";
|
||||
import { RouterLink } from "vue-router";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import InviteKeyDialog from "./InviteKeyDialog.vue";
|
||||
import { ContentLoader } from "vue-content-loader";
|
||||
|
||||
const teams = useTeamsStore();
|
||||
const isLoading = ref(false);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
onMounted(() => {
|
||||
teams.fetchTeams();
|
||||
isLoading.value = true;
|
||||
authStore.getUser()
|
||||
.then(() => {
|
||||
teams.fetchTeams()
|
||||
.then(() => {
|
||||
isLoading.value = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -34,8 +43,20 @@ onMounted(() => {
|
|||
<div v-if="!authStore.isLoggedIn">
|
||||
Log in to view your teams.
|
||||
</div>
|
||||
<div v-else-if="isLoading || true">
|
||||
<ContentLoader :speed="1">
|
||||
<circle cx="10" cy="20" r="8" />
|
||||
<rect x="25" y="15" rx="5" ry="5" width="220" height="10" />
|
||||
<circle cx="10" cy="50" r="8" />
|
||||
<rect x="25" y="45" rx="5" ry="5" width="220" height="10" />
|
||||
<circle cx="10" cy="80" r="8" />
|
||||
<rect x="25" y="75" rx="5" ry="5" width="220" height="10" />
|
||||
<circle cx="10" cy="110" r="8" />
|
||||
<rect x="25" y="105" rx="5" ry="5" width="220" height="10" />
|
||||
</ContentLoader>
|
||||
</div>
|
||||
<div
|
||||
v-if="teams.teamsWithRole"
|
||||
v-else-if="teams.teamsWithRole"
|
||||
v-for="(team, _, i) in teams.teamsWithRole"
|
||||
>
|
||||
<div class="team-item">
|
||||
|
|
|
@ -2,11 +2,13 @@ import { defineStore } from "pinia";
|
|||
import { ref } from "vue";
|
||||
import { useClientStore } from "./client";
|
||||
import { useRouter, type LocationQuery } from "vue-router";
|
||||
import { type PlayerSchema } from "@/client";
|
||||
|
||||
export const useAuthStore = defineStore("auth", () => {
|
||||
const clientStore = useClientStore();
|
||||
const client = clientStore.client;
|
||||
|
||||
const user = ref<PlayerSchema | null>(null);
|
||||
const steamId = ref("");
|
||||
const username = ref("");
|
||||
const isLoggedIn = ref(false);
|
||||
|
@ -16,16 +18,27 @@ export const useAuthStore = defineStore("auth", () => {
|
|||
const router = useRouter();
|
||||
|
||||
async function getUser() {
|
||||
hasCheckedAuth.value = true;
|
||||
if (hasCheckedAuth.value) {
|
||||
if (!isLoggedIn.value) {
|
||||
throw new Error("Not logged in");
|
||||
}
|
||||
|
||||
return user.value;
|
||||
}
|
||||
|
||||
return clientStore.call(
|
||||
getUser.name,
|
||||
() => client.default.getUser(),
|
||||
(response) => {
|
||||
hasCheckedAuth.value = true;
|
||||
isLoggedIn.value = true;
|
||||
steamId.value = response.steamId;
|
||||
username.value = response.username;
|
||||
user.value = response;
|
||||
return response;
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
() => hasCheckedAuth.value = true,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ export const useClientStore = defineStore("client", () => {
|
|||
function call<T>(
|
||||
key: string,
|
||||
apiCall: () => CancelablePromise<T>,
|
||||
thenOnce?: (result: T) => T
|
||||
thenOnce?: (result: T) => T,
|
||||
catchOnce?: (error: any) => any,
|
||||
finallyOnce?: () => void,
|
||||
): Promise<T> {
|
||||
console.log("Fetching call " + key);
|
||||
if (!calls.has(key)) {
|
||||
|
@ -26,6 +28,14 @@ export const useClientStore = defineStore("client", () => {
|
|||
promise.then(thenOnce);
|
||||
}
|
||||
|
||||
if (catchOnce) {
|
||||
promise.catch(catchOnce);
|
||||
}
|
||||
|
||||
if (finallyOnce) {
|
||||
promise.finally(finallyOnce);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
return calls.get(key) as Promise<T>;
|
||||
|
|
|
@ -10,6 +10,7 @@ import EventList from "@/components/EventList.vue";
|
|||
import { useTeamsEventsStore } from "@/stores/teams/events";
|
||||
import MatchCard from "@/components/MatchCard.vue";
|
||||
import { useMatchesStore } from "@/stores/matches";
|
||||
import { ContentLoader } from "vue-content-loader";
|
||||
|
||||
const route = useRoute();
|
||||
const teamsStore = useTeamsStore();
|
||||
|
@ -28,14 +29,17 @@ const key = computed(() => route.query.key);
|
|||
const teamsEventsStore = useTeamsEventsStore();
|
||||
const events = computed(() => teamsEventsStore.teamEvents[teamId.value]);
|
||||
const matches = computed(() => matchesStore.recentMatches);
|
||||
const isLoading = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
isLoading.value = true;
|
||||
let doFetchTeam = () => {
|
||||
teamsStore.fetchTeam(teamId.value)
|
||||
.then(() => {
|
||||
teamsStore.fetchTeamMembers(teamId.value);
|
||||
teamsEventsStore.fetchTeamEvents(teamId.value);
|
||||
matchesStore.fetchRecentMatchesForTeam(teamId.value, 5);
|
||||
isLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -55,7 +59,14 @@ onMounted(() => {
|
|||
<div class="left">
|
||||
<center class="margin">
|
||||
<h1>
|
||||
{{ team.teamName }}
|
||||
<template v-if="isLoading || true">
|
||||
<content-loader view-box="0 0 250 10">
|
||||
<rect x="0" y="0" rx="3" ry="3" width="250" height="10" />
|
||||
</content-loader>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ team.teamName }}
|
||||
</template>
|
||||
</h1>
|
||||
<span class="aside">
|
||||
Formed on {{ creationDate }}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useTeamDetails } from "@/composables/team-details";
|
|||
import { useTeamsStore } from "@/stores/teams";
|
||||
import { useIntegrationsStore } from "@/stores/teams/integrations";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { ContentLoader } from "vue-content-loader";
|
||||
|
||||
const teamsStore = useTeamsStore();
|
||||
const integrationsStore = useIntegrationsStore();
|
||||
|
@ -32,7 +33,14 @@ onMounted(() => {
|
|||
<template>
|
||||
<div class="team-integrations">
|
||||
<div v-if="isLoading">
|
||||
<LoaderContainer />
|
||||
<ContentLoader>
|
||||
<rect x="0" y="0" rx="3" ry="3" width="250" height="10" />
|
||||
<rect x="20" y="20" rx="3" ry="3" width="220" height="10" />
|
||||
<rect x="20" y="40" rx="3" ry="3" width="170" height="10" />
|
||||
<rect x="0" y="60" rx="3" ry="3" width="250" height="10" />
|
||||
<rect x="20" y="80" rx="3" ry="3" width="200" height="10" />
|
||||
<rect x="20" y="100" rx="3" ry="3" width="80" height="10" />
|
||||
</ContentLoader>
|
||||
</div>
|
||||
<template v-else>
|
||||
<DiscordIntegrationForm v-model="integrationsStore.discordIntegration" />
|
||||
|
|
|
@ -20,41 +20,39 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<div class="header">
|
||||
<h1>
|
||||
<i class="bi bi-trophy-fill margin"></i>
|
||||
Matches
|
||||
</h1>
|
||||
<div class="button-group">
|
||||
<AddMatchDialog />
|
||||
</div>
|
||||
<div class="header">
|
||||
<h2>
|
||||
<i class="bi bi-trophy-fill margin"></i>
|
||||
Matches
|
||||
</h2>
|
||||
<div class="button-group">
|
||||
<AddMatchDialog />
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>RED</th>
|
||||
<th>BLU</th>
|
||||
<th>Team</th>
|
||||
<th>Match Date</th>
|
||||
<th>logs.tf URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="teamMatch in matches">
|
||||
<td>{{ teamMatch.match.redScore }}</td>
|
||||
<td>{{ teamMatch.match.blueScore }}</td>
|
||||
<td>{{ teamMatch.teamColor == 'Blue' ? 'BLU' : 'RED' }}</td>
|
||||
<td>{{ moment(teamMatch.match.matchTime).format("LL LT") }}</td>
|
||||
<td>
|
||||
<a :href="`https://logs.tf/${teamMatch.match.logsTfId}`" target="_blank">
|
||||
#{{ teamMatch.match.logsTfId }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>RED</th>
|
||||
<th>BLU</th>
|
||||
<th>Team</th>
|
||||
<th>Match Date</th>
|
||||
<th>logs.tf URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="teamMatch in matches">
|
||||
<td>{{ teamMatch.match.redScore }}</td>
|
||||
<td>{{ teamMatch.match.blueScore }}</td>
|
||||
<td>{{ teamMatch.teamColor == 'Blue' ? 'BLU' : 'RED' }}</td>
|
||||
<td>{{ moment(teamMatch.match.matchTime).format("LL LT") }}</td>
|
||||
<td>
|
||||
<a :href="`https://logs.tf/${teamMatch.match.logsTfId}`" target="_blank">
|
||||
#{{ teamMatch.match.logsTfId }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -100,7 +100,10 @@ class Event(app_db.BaseModel):
|
|||
|
||||
ringers_needed_msg = ""
|
||||
if ringers_needed > 0:
|
||||
ringers_needed_msg = f" **({ringers_needed} ringer(s) needed)**"
|
||||
if ringers_needed == 1:
|
||||
ringers_needed_msg = " **(1 ringer needed)**"
|
||||
else:
|
||||
ringers_needed_msg = f" **({ringers_needed} ringers needed)**"
|
||||
|
||||
domain = os.environ.get("DOMAIN", "availabili.tf")
|
||||
|
||||
|
@ -111,7 +114,7 @@ class Event(app_db.BaseModel):
|
|||
"",
|
||||
f"<t:{start_timestamp}:f>",
|
||||
"\n".join(players_info),
|
||||
f"Max bipartite matching size: {matchings}" + ringers_needed_msg,
|
||||
f"Maximum roles filled: {matchings}" + ringers_needed_msg,
|
||||
"",
|
||||
"[Confirm attendance here]" +
|
||||
f"(https://{domain}/team/id/{self.team.id}/events/{self.id})",
|
||||
|
|
Loading…
Reference in New Issue