Webapp: added LoadingIndicator component and extracted file fetching from MainComponent
This commit is contained in:
parent
c11bcafac3
commit
b93941e294
|
|
@ -1,15 +1,81 @@
|
||||||
<template>
|
<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>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import MainComponent from "./components/MainComponent.vue";
|
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 {
|
const entitiesURL = "/entities.json";
|
||||||
components: { MainComponent },
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "bootstrap/scss/bootstrap";
|
@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>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -1,37 +1,44 @@
|
||||||
<template>
|
<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">
|
<form v-if="entities.length > 0" @submit.prevent="submitGuess">
|
||||||
Enter a guess: <input type="text" v-model="guessFieldValue" /> <br />
|
Enter a guess: <input type="text" v-model="guessFieldValue" /> <br />
|
||||||
<div v-if="guessError" class="error">{{ guessError }} <br /></div>
|
<div v-if="guessError" class="error">{{ guessError }} <br /></div>
|
||||||
<span class="possibleTypes">It can be any {{ [...entityTypes].join(", ") }}.</span>
|
<span class="possibleTypes">It can be any {{ [...entityTypes].join(", ") }}.</span>
|
||||||
</form>
|
</form>
|
||||||
|
<div v-else>
|
||||||
|
Error: component instantiated but entities are empty.
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, type PropType } from "vue";
|
||||||
import type { NamedEntity } from "@/NamedEntity";
|
import type { NamedEntity } from "@/NamedEntity";
|
||||||
import type { Concept } from "@/Concept";
|
import type { Concept } from "@/Concept";
|
||||||
import { fetchJSON } from "@/Utils";
|
|
||||||
|
|
||||||
const entitiesURL = "/entities.json";
|
|
||||||
const conceptsURL = "/concepts.json";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "MainComponent",
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
guessFieldValue: "",
|
guessFieldValue: "",
|
||||||
fetchErrors: [] as Array<string>,
|
|
||||||
guessError: null as string | null,
|
guessError: null as string | null,
|
||||||
entities: [] as Array<NamedEntity>,
|
|
||||||
concepts: [] as Array<Concept>,
|
|
||||||
target: null as string | null,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -58,17 +65,6 @@ export default defineComponent({
|
||||||
return result;
|
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: {
|
methods: {
|
||||||
submitGuess() {
|
submitGuess() {
|
||||||
this.guessError = null;
|
this.guessError = null;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"./src/*"
|
"./src/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"lib": ["es2018"],
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
},
|
},
|
||||||
"references": [
|
"references": [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue