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