Add a search function to the role modal.

When assigning a role to a player, it is now possible to type a case-
and whitespace-insensitive prefix of it to quickly find the desired
role. Pressing Enter will set the role, if the query is specific enough
to have a unique match.
This commit is contained in:
Jeremy Roman 2021-09-19 22:51:17 +00:00
parent c00b89824c
commit c116721684

View file

@ -1,5 +1,5 @@
<template> <template>
<Modal v-if="modals.role && availableRoles.length" @close="close"> <Modal v-if="isDisplayed" @close="close">
<h3> <h3>
Choose a new character for Choose a new character for
{{ {{
@ -8,20 +8,10 @@
: "bluffing" : "bluffing"
}} }}
</h3> </h3>
<ul class="tokens" v-if="tab === 'editionRoles' || !otherTravelers.size"> <ul class="tokens">
<li <li
v-for="role in availableRoles" v-for="role in displayedRoles"
:class="[role.team]" :class="[role.team, { match: queryMatches(role.name) }]"
:key="role.id"
@click="setRole(role)"
>
<Token :role="role" />
</li>
</ul>
<ul class="tokens" v-if="tab === 'otherTravelers' && otherTravelers.size">
<li
v-for="role in otherTravelers.values()"
:class="[role.team]"
:key="role.id" :key="role.id"
@click="setRole(role)" @click="setRole(role)"
> >
@ -45,6 +35,13 @@
>Other Travelers</span >Other Travelers</span
> >
</div> </div>
<input
ref="searchInput"
class="role-search"
placeholder="Search"
v-model="query"
@keyup="keyup"
/>
</Modal> </Modal>
</template> </template>
@ -73,13 +70,22 @@ export default {
availableRoles.push({}); availableRoles.push({});
return availableRoles; return availableRoles;
}, },
isDisplayed() {
return this.modals.role && this.availableRoles.length;
},
displayedRoles() {
if (this.tab === "editionRoles" || !this.otherTravelers.size)
return this.availableRoles;
else return [...this.otherTravelers.values()];
},
...mapState(["modals", "roles", "session"]), ...mapState(["modals", "roles", "session"]),
...mapState("players", ["players"]), ...mapState("players", ["players"]),
...mapState(["otherTravelers"]) ...mapState(["otherTravelers"])
}, },
data() { data() {
return { return {
tab: "editionRoles" tab: "editionRoles",
query: ""
}; };
}, },
methods: { methods: {
@ -100,14 +106,39 @@ export default {
value: role value: role
}); });
} }
this.tab = "editionRoles"; this.reset();
this.$store.commit("toggleModal", "role"); this.$store.commit("toggleModal", "role");
}, },
close() { close() {
this.tab = "editionRoles"; this.reset();
this.toggleModal("role"); this.toggleModal("role");
}, },
reset() {
this.tab = "editionRoles";
this.query = "";
},
queryMatches(name) {
const simplify = str => str.replaceAll(/\W+/g, "").toLowerCase();
return simplify(name || "").startsWith(simplify(this.query));
},
keyup(event) {
// Allow Escape for modal dialog dismissal.
if (event.key == "Esc" || event.key == "Escape") return;
event.stopPropagation();
if (event.key == "Enter") {
const matchingRoles = this.displayedRoles.filter(r =>
this.queryMatches(r.name)
);
if (matchingRoles.length === 1) this.setRole(matchingRoles[0]);
}
},
...mapMutations(["toggleModal"]) ...mapMutations(["toggleModal"])
},
watch: {
isDisplayed(shown) {
if (shown) this.$nextTick(() => this.$refs.searchInput.focus());
}
} }
}; };
</script> </script>
@ -140,9 +171,27 @@ ul.tokens li {
transform: scale(1.2); transform: scale(1.2);
z-index: 10; z-index: 10;
} }
&:not(.match) {
opacity: 0.4;
}
} }
#townsquare.spectator ul.tokens li.traveler { #townsquare.spectator ul.tokens li.traveler {
display: none; display: none;
} }
input.role-search {
display: block;
width: 100%;
background: transparent;
border: solid white;
border-width: 0 0 1px 0;
outline: none;
color: white;
font-size: 1em;
&:not(:focus) {
border-bottom-color: #777;
}
}
</style> </style>