123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- <template>
- <div class="label-selector">
- <input v-debounce:400ms="setQuery"
- class="search-field"
- ref="input"
- type="text"
- placeholder="Search...">
- <div class="filters">
- <span @click="resetHierarchyFilter"
- class="filter active">
- Show all
- </span>
- <span v-for="hierarchy in hierarchies"
- :key="hierarchy.key"
- @click="toggleHierarchyFilter(hierarchy.key)"
- :class="{active: isFilterActive(hierarchy.key)}"
- class="filter">
- {{ hierarchy.name }}
- </span>
- </div>
- <div class="labels">
- <div v-for="(label, index) in results"
- :key="index"
- @click="select(label)"
- class="label"
- :class="{selected: index === selectedIndex}"
- >
- <div class="name">
- <span v-html="highlight(label.name)"></span>
- <span v-if="label.hierarchy_level"
- class="hierarchy">
- ({{ label.hierarchy_level }})
- </span>
- </div>
- </div>
- <div v-if="results.length < 1" class="label">
- No results found...
- </div>
- </div>
- </div>
- </template>
- <script>
- export default {
- name: "label-selector",
- props: [
- "labels"
- ],
- mounted: function () {
- // register events to prevent global hotkeys while typing
- window.addEventListener('keypress', this.keypress, true);
- window.addEventListener('keydown', this.keypress, true);
- window.addEventListener('wheel', this.keypress, true);
- // focus text input
- this.$refs.input.focus();
- },
- destroyed: function () {
- window.removeEventListener('keypress', this.keypress, true);
- window.removeEventListener('keydown', this.keypress, true);
- window.removeEventListener('wheel', this.keypress, true);
- },
- data: function () {
- return {
- selectedIndex: 0,
- query: "",
- hierarchy_filters: [],
- deleteLabel: {
- hierarchy_level: null,
- identifier: null,
- name: 'None',
- },
- }
- },
- computed: {
- hierarchies: function() {
- let hierarchies = [...new Set(this.labels.map(l => l.hierarchy_level))]
- hierarchies.sort((a, b) => {
- if(a === null)
- return +1;
- if(b === null)
- return -1;
- if (a < b)
- return +1;
- else
- return -1;
- });
- return hierarchies.map(
- l => {
- return {
- key: l,
- name: (l === null ? "Art" : l)
- }
- }
- );
- },
- results: function() {
- const query = this.query;
- let labels = this.labels;
- const n_filters = this.hierarchy_filters.length;
- if (n_filters !== 0 && n_filters !== this.hierarchies.length)
- labels = labels.filter(
- l => this.hierarchy_filters.indexOf(l.hierarchy_level) > -1
- )
- labels = labels.filter(
- l => l.name.toLowerCase().indexOf(query) > -1
- );
- this.sort(labels);
- return [this.deleteLabel, ...labels];
- }
- },
- methods: {
- resetHierarchyFilter: function() {
- this.hierarchy_filters = [];
- },
- isFilterActive: function (key){
- return this.hierarchy_filters.length == 0 ||
- this.hierarchy_filters.indexOf(key) != -1;
- },
- toggleHierarchyFilter: function(key) {
- const idx = this.hierarchy_filters.indexOf(key);
- if (idx == -1)
- this.hierarchy_filters.push(key)
- else
- this.hierarchy_filters.splice(idx, 1)
- console.debug("Current hierarchy filters:",
- this.hierarchy_filters)
- },
- setQuery: function(query) {
- this.query = query.toLowerCase();
- },
- n_visible: function () {
- return this.results.length;
- },
- sort: function(labels) {
- labels.sort((a, b) => {
- if (a.hierarchy_level !== b.hierarchy_level) {
- if (a.hierarchy_level === null)
- return +1;
- if (b.hierarchy_level === null)
- return -1;
- if (a.hierarchy_level < b.hierarchy_level)
- return +1;
- else
- return -1;
- }
- if (a.name < b.name)
- return -1;
- else
- return +1;
- });
- },
- keypress: function (event) {
- event.stopPropagation();
- switch (event.key) {
- case 'ArrowDown':
- this.selectedIndex += 1;
- if (this.selectedIndex >= this.n_visible())
- this.selectedIndex = this.n_visible() - 1;
- break;
- case 'ArrowUp':
- this.selectedIndex -= 1;
- if (this.selectedIndex < 0)
- this.selectedIndex = 0;
- break;
- case 'Enter':
- this.select(this.results[this.selectedIndex]);
- break;
- case 'Escape':
- this.closeDelayed();
- break;
- default:
- this.selectedIndex = Math.min(1, this.n_visible() - 1);
- break;
- }
- },
- highlight: function (name) {
- const query = this.query
- if (!query)
- return name;
- const start = name.toLowerCase().indexOf(query);
- if (start == -1)
- return name
- const end = start + query.length;
- const pre = name.substring(0, start);
- const match = name.substring(start, end);
- const post = name.substring(end);
- return `${pre}<strong>${match}</strong>${post}`;
- },
- select: function (label) {
- console.debug("selected label", label);
- this.$emit('label', label);
- this.closeDelayed();
- },
- closeDelayed: function () {
- setTimeout(() => this.$emit('close', true), 100);
- },
- }
- }
- </script>
- <style scoped>
- .label-selector {
- position: absolute;
- top: 0;
- left: 50%;
- z-index: 101;
- width: 30rem;
- max-width: 90vw;
- max-height: 90%;
- display: flex;
- flex-direction: column;
- border-bottom-right-radius: 0.5rem;
- border-bottom-left-radius: 0.5rem;
- padding: 1rem;
- background-color: rgba(0, 0, 0, 0.75);
- color: whitesmoke;
- animation: label-selector-animation 0.3s ease-out forwards;
- }
- @keyframes label-selector-animation {
- from {
- transform: translateX(-50%) translateY(-100%);
- }
- to {
- transform: translateX(-50%) translateY(0%);
- }
- }
- input {
- margin-bottom: 0.5rem;
- padding: 0.5rem;
- border-radius: 0.5rem;
- border: 0px;
- }
- .labels {
- flex-grow: 1;
- overflow: auto;
- }
- .label {
- padding: 0.25rem 0.5rem;
- cursor: pointer;
- border-radius: 0.3rem;
- margin: 0.2rem;
- }
- .label:not(:last-child) {
- border-bottom: 1px solid rgba(255, 255, 255, 0.2);
- }
- .label:hover, .label.selected {
- background-color: var(--primary);
- }
- .hierarchy {
- font-size: 77%;
- opacity: 0.8;
- float: right;
- }
- .filters {
- display: flex;
- flex-direction: row;
- justify-content: space-evenly;
- align-items: baseline;
- padding: 0.25rem;
- margin-bottom: 0.25rem;
- border-bottom: 2px solid rgba(255, 255, 255, 0.2);
- }
- .filter {
- flex-grow: 1;
- border-radius: 0.5rem;
- margin: 0rem 0.5rem;
- padding: 0.25rem;
- text-align: center;
- background-color: rgb(50%, 50%, 50%);
- border: 1px solid rgb(10%,10%,10%);
- color: rgb(20%, 20%, 20%);
- cursor: pointer;
- }
- .filter.active {
- background-color: rgb(25%, 25%, 25%);
- color: whitesmoke;
- }
- .filter:hover {
- background-color: rgb(40%, 40%, 40%);
- }
- .filter.active:hover {
- background-color: rgb(20%, 20%, 20%);
- }
- </style>
|