Webapp: implemented list of guesses
This commit is contained in:
parent
130c9dac23
commit
6128093102
|
|
@ -13,8 +13,10 @@ module.exports = {
|
|||
},
|
||||
rules: {
|
||||
indent: ["error", "tab"],
|
||||
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
|
||||
quotes: ["error", "double"],
|
||||
"no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import type { Concept } from "@/Concept";
|
||||
|
||||
export class Guess {
|
||||
uri: string;
|
||||
displayName: string;
|
||||
distance: number;
|
||||
|
||||
constructor(uri: string, displayName: string, distance: number) {
|
||||
this.uri = uri;
|
||||
this.displayName = displayName;
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
static score(concept: Concept, nbEntities: number): number {
|
||||
return (1 - (concept.extensionalDistance - 1) / nbEntities) * 100;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
<template>
|
||||
<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 class="game" v-if="entities.length > 0">
|
||||
<form @submit.prevent="submitGuess">
|
||||
Enter a guess: <input type="text" v-model="guessFieldValue" ref="guessField" /> <br />
|
||||
<div v-if="guessError" class="error">{{ guessError }} <br /></div>
|
||||
<span class="possibleTypes">It can be any {{ [...entityTypes].join(", ") }}.</span>
|
||||
</form>
|
||||
<div class="row">
|
||||
<guess-list :guesses="guesses" class="col-xl-6"></guess-list>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
Error: component instantiated but entities are empty.
|
||||
</div>
|
||||
|
|
@ -14,10 +19,13 @@
|
|||
import { defineComponent, type PropType } from "vue";
|
||||
import type { NamedEntity } from "@/NamedEntity";
|
||||
import type { Concept } from "@/Concept";
|
||||
import GuessList from "@/components/guessList/GuessList.vue";
|
||||
import { Guess } from "@/Guess";
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: "MainComponent",
|
||||
components: { GuessList },
|
||||
props: {
|
||||
entities: {
|
||||
type: Object as PropType<Array<NamedEntity>>,
|
||||
|
|
@ -39,6 +47,7 @@ export default defineComponent({
|
|||
return {
|
||||
guessFieldValue: "",
|
||||
guessError: null as string | null,
|
||||
guesses: [] as Guess[],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -68,21 +77,50 @@ export default defineComponent({
|
|||
methods: {
|
||||
submitGuess() {
|
||||
this.guessError = null;
|
||||
if (!this.entitiesByLowerName.has(this.guessFieldValue.toLowerCase())) {
|
||||
this.guessError = `${(this.guessFieldValue)} is unknown to me...`;
|
||||
return;
|
||||
}
|
||||
const matchingEntities = this.entitiesByLowerName.get(this.guessFieldValue.toLowerCase()) as NamedEntity[];
|
||||
const matchingUris = new Set(matchingEntities.map(e => e.uri));
|
||||
console.debug(matchingUris);
|
||||
const matchingConcept = this.concepts.find(concept => concept.elements.some(uri => matchingUris.has(uri)));
|
||||
if (typeof matchingConcept == "undefined") {
|
||||
this.guessError = "Entity is known but concept can't be found: this should not happen...";
|
||||
return;
|
||||
}
|
||||
console.info("Found matching guess concept");
|
||||
console.info(matchingConcept);
|
||||
this.guessFieldValue = "";
|
||||
this.entitiesWithName(this.guessFieldValue)
|
||||
.then(entities => {
|
||||
if (entities.some(e => e.uri == this.target))
|
||||
alert("You won!");
|
||||
return entities;
|
||||
})
|
||||
.then(this.firstConceptMatchingEntities)
|
||||
.then(match => {
|
||||
const entity = match.matchingEntity;
|
||||
const displayName = this.entitiesByLowerName.get(entity.name.toLowerCase())!.length > 1 ? `${entity.name} (${entity.type})` : entity.name;
|
||||
this.guesses.push(new Guess(
|
||||
entity.uri,
|
||||
displayName,
|
||||
Guess.score(match.concept, this.entities.length),
|
||||
));
|
||||
this.guessFieldValue = "";
|
||||
})
|
||||
.catch(err => {
|
||||
this.guessError = err.message;
|
||||
(this.$refs.guessField as HTMLInputElement).select();
|
||||
});
|
||||
},
|
||||
entitiesWithName(name: string): Promise<NamedEntity[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const result = this.entitiesByLowerName.get(name.toLowerCase());
|
||||
if (result === undefined)
|
||||
reject(new Error(`${name} is unknown to me...`));
|
||||
else
|
||||
resolve(result);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Return the first concept that matches any one of the given entities, along with the matched entity.
|
||||
*/
|
||||
firstConceptMatchingEntities(entities: NamedEntity[]): Promise<{ matchingEntity: NamedEntity, concept: Concept }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const entitiesByTheirURI: Map<string, NamedEntity> = new Map(entities.map(e => [e.uri, e]));
|
||||
for (const concept of this.concepts) {
|
||||
const matchingURI = concept.elements.find(el => entitiesByTheirURI.has(el));
|
||||
if (matchingURI !== undefined)
|
||||
return resolve({ matchingEntity: entitiesByTheirURI.get(matchingURI)!, concept: concept });
|
||||
}
|
||||
return reject(new Error("This should not happen: impossible to find concept for entity."));
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<table v-if="guesses.length > 0" class="table">
|
||||
<thead>
|
||||
<guess-table-row :guess="latestGuess" :index="sortedGuesses.indexOf(latestGuess)+1"></guess-table-row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<guess-table-row v-for="guess in sortedGuesses" :key="guess.uri"
|
||||
:guess="guess" :index="guesses.indexOf(guess) + 1"
|
||||
:class="{'table-active': guess.uri === latestGuess.uri}">
|
||||
</guess-table-row>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { Guess } from "@/Guess";
|
||||
import GuessTableRow from "@/components/guessList/GuessTableRow.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "GuessList",
|
||||
components: { GuessTableRow },
|
||||
props: {
|
||||
guesses: {
|
||||
type: Object as PropType<Guess[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
sortedGuesses(): Guess[] {
|
||||
return this.guesses.concat().sort((a, b) => b.distance - a.distance);
|
||||
},
|
||||
latestGuess(): Guess {
|
||||
return this.guesses[this.guesses.length - 1];
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../../node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
table {
|
||||
@extend .mt-4;
|
||||
|
||||
th {
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<tr>
|
||||
<td>{{ index }}</td>
|
||||
<td>{{ guess.displayName }}</td>
|
||||
<td>{{ guess.distance.toFixed(2) }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { Guess } from "@/Guess";
|
||||
|
||||
export default defineComponent({
|
||||
name: "GuessTableRow",
|
||||
props: {
|
||||
guess: {
|
||||
type: Object as PropType<Guess>,
|
||||
required: true,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
Loading…
Reference in New Issue