Webapp: added LoadingIndicator component and extracted file fetching from MainComponent
This commit is contained in:
parent
c11bcafac3
commit
b93941e294
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
<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;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"./src/*"
|
||||
]
|
||||
},
|
||||
"lib": ["es2018"],
|
||||
"noImplicitThis": true,
|
||||
},
|
||||
"references": [
|
||||
|
|
|
|||
Loading…
Reference in New Issue