Webapp: added LoadingIndicator component and extracted file fetching from MainComponent

This commit is contained in:
Francesco 2022-04-27 00:07:59 +02:00
parent c11bcafac3
commit b93941e294
4 changed files with 146 additions and 29 deletions

View File

@ -1,15 +1,81 @@
<template>
<main-component></main-component>
<div class="container">
<loading-indicator :loaded="!isLoading" :error="hasErrors" :class="{'loading': isLoading}">
<main-component :entities="entities" :concepts="concepts" :target="targetEntity??''"></main-component>
<template #error-message>
<span>{{entitiesError}} {{conceptsError}}</span>
</template>
</loading-indicator>
</div>
</template>
<script lang="ts">
import MainComponent from "./components/MainComponent.vue";
import LoadingIndicator from "@/components/LoadingIndicator.vue";
import { defineComponent } from "vue";
import { fetchJSON } from "@/Utils";
import type { NamedEntity } from "@/NamedEntity";
import type { Concept } from "@/Concept";
export default {
components: { MainComponent },
const entitiesURL = "/entities.json";
const conceptsURL = "/concepts.json";
export default defineComponent({
components: { LoadingIndicator, MainComponent },
data() {
return {
entitiesLoading: true,
entitiesError: "",
entities: [] as Array<NamedEntity>,
conceptsLoading: true,
conceptsError: "",
concepts: [] as Array<Concept>,
targetEntity: null as string | null,
};
},
computed: {
isLoading(): boolean {
return this.entitiesLoading || this.conceptsLoading;
},
hasErrors(): boolean {
return this.entitiesError.length > 0 || this.conceptsError.length > 0;
},
},
mounted() {
this.entitiesLoading = true;
this.entitiesError = "";
this.entities = [];
fetchJSON(entitiesURL)
.then(json => this.entities = json as Array<NamedEntity>) //TODO: Create NamedEntity.fromJSON
.catch((err:Error) => this.entitiesError = `Error fetching entities: ${err.message}`)
.finally(() => this.entitiesLoading = false);
this.conceptsLoading = true;
this.conceptsError = "";
this.concepts = [];
this.targetEntity = null;
fetchJSON(conceptsURL)
.then(json => {
// TODO: Create Concept.fromJSON
this.concepts = (json as any).concepts as Array<Concept>; // eslint-disable-line @typescript-eslint/no-explicit-any
// TODO: verify presence of target in json
this.targetEntity = (json as any).target as string; // eslint-disable-line @typescript-eslint/no-explicit-any
})
.catch((err:Error) => this.conceptsError = `Error fetching concepts: ${err.message}`)
.finally(() => this.conceptsLoading = false);
},
});
</script>
<style lang="scss">
@import "bootstrap/scss/bootstrap";
@import "bootstrap-icons/font/bootstrap-icons";
</style>
<style scoped lang="scss">
@import "bootstrap/scss/bootstrap";
.loading {
@extend .position-absolute, .top-50, .start-50, .translate-middle;
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<slot v-if="error" name="error">
<div class="error">
<i class="bi bi-exclamation-triangle-fill"></i>
<slot name="error-message">
There was en error during loading.
</slot>
</div>
</slot>
<div v-else>
<slot v-if="loaded" name="default"></slot>
<div v-else class="loading-indicator">
<span>Loading...</span>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "LoadingIndicator",
props: {
loaded: {
type: Boolean,
default: false,
},
error: {
type: Boolean,
default: false,
},
},
});
</script>
<style scoped lang="scss">
@import "bootstrap/scss/bootstrap";
.error {
@extend .alert, .alert-danger, .d-flex, .align-items-center;
i {
@extend .me-2;
}
}
.loading-indicator {
@extend .spinner-grow, .text-primary;
span {
@extend .visually-hidden;
}
}
</style>

View File

@ -1,37 +1,44 @@
<template>
<div v-if="fetchErrors" class="error">
<div v-for="err in fetchErrors" :key="err">
{{ err }}<br />
</div>
</div>
<form v-if="entities.length > 0" @submit.prevent="submitGuess">
Enter a guess: <input type="text" v-model="guessFieldValue" /> <br />
<div v-if="guessError" class="error">{{ guessError }} <br /></div>
<span class="possibleTypes">It can be any {{ [...entityTypes].join(", ") }}.</span>
</form>
<div v-else>
Error: component instantiated but entities are empty.
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { defineComponent, type PropType } from "vue";
import type { NamedEntity } from "@/NamedEntity";
import type { Concept } from "@/Concept";
import { fetchJSON } from "@/Utils";
const entitiesURL = "/entities.json";
const conceptsURL = "/concepts.json";
export default defineComponent({
name: "MainComponent",
props: {
entities: {
type: Object as PropType<Array<NamedEntity>>,
required: true,
validator: (value: Array<NamedEntity>) => value.length > 0,
},
concepts: {
type: Object as PropType<Array<Concept>>,
required: true,
validator: (value: Array<Concept>) => value.length > 0,
},
target: {
type: String,
required: true,
validator: (value: string) => value.length > 0,
},
},
data() {
return {
guessFieldValue: "",
fetchErrors: [] as Array<string>,
guessError: null as string | null,
entities: [] as Array<NamedEntity>,
concepts: [] as Array<Concept>,
target: null as string | null,
};
},
computed: {
@ -58,17 +65,6 @@ export default defineComponent({
return result;
},
},
created() {
this.fetchErrors = [];
fetchJSON(entitiesURL)
.then(json => this.entities = json as Array<NamedEntity>)
.catch(this.fetchErrors.push);
fetchJSON(conceptsURL)
.then(json => {
this.concepts = (json as any).concepts as Array<Concept>; // eslint-disable-line @typescript-eslint/no-explicit-any
this.target = (json as any).target as string; // eslint-disable-line @typescript-eslint/no-explicit-any
});
},
methods: {
submitGuess() {
this.guessError = null;

View File

@ -12,6 +12,7 @@
"./src/*"
]
},
"lib": ["es2018"],
"noImplicitThis": true,
},
"references": [