diff --git a/src/client/components/AdvancedSearch.vue b/src/client/components/AdvancedSearch.vue index 86b49c7f59bb97a1e6b230397f1dcf7a470b0109..2c752a148199dec103d129b0b4280d34faf6f397 100644 --- a/src/client/components/AdvancedSearch.vue +++ b/src/client/components/AdvancedSearch.vue @@ -40,7 +40,7 @@ ORGANISM <button class="select-all-button" - :class="{ 'all-selected': allSelected('species', speciesList) }" + :class="{ 'all-selected': allSelected('species', speciesList.length) }" type="button" @click="selectAll('species', speciesList)" > @@ -53,15 +53,12 @@ :key="species" class="checkbox" > - <input - :id="species.replace(' ', '-') + '-checkbox'" + <AppCheckbox v-model="filters.species" :value="species" - type="checkbox" > - <label :for="species.replace(' ', '-') + '-checkbox'"> {{ species }} - </label> + </AppCheckbox> </div> </div> </div> @@ -71,7 +68,7 @@ SOURCES <button class="select-all-button" - :class="{ 'all-selected': allSelected('sources', sourcesList) }" + :class="{ 'all-selected': allSelected('sources', sourcesList.length) }" type="button" @click="selectAll('sources', sourcesList)" > @@ -84,15 +81,12 @@ :key="source" class="checkbox" > - <input - :id="source + '-checkbox'" + <AppCheckbox v-model="filters.sources" :value="source" - type="checkbox" > - <label :for="source + '-checkbox'"> {{ source }} - </label> + </AppCheckbox> </div> </div> </div> @@ -102,7 +96,7 @@ HEAVY CHAIN GENE SEGMENTS <button class="select-all-button" - :class="{ 'all-selected': allSelected('heavySegments', heavySegments) }" + :class="{ 'all-selected': allSelected('heavySegments', heavySegments.length) }" type="button" @click="selectAll('heavySegments', heavySegments)" > @@ -115,15 +109,12 @@ :key="heavySegment" class="checkbox" > - <input - :id="heavySegment + '-checkbox'" + <AppCheckbox v-model="filters.heavySegments" :value="heavySegment" - type="checkbox" > - <label :for="heavySegment + '-checkbox'"> {{ heavySegment }} - </label> + </AppCheckbox> </div> </div> </div> @@ -133,7 +124,7 @@ LIGHT CHAIN GENE SEGMENTS <button class="select-all-button" - :class="{ 'all-selected': allSelected('lightSegments', lightSegments) }" + :class="{ 'all-selected': allSelected('lightSegments', lightSegments.length) }" type="button" @click="selectAll('lightSegments', lightSegments)" > @@ -146,15 +137,12 @@ :key="lightSegment" class="checkbox" > - <input - :id="lightSegment + '-checkbox'" + <AppCheckbox v-model="filters.lightSegments" :value="lightSegment" - type="checkbox" > - <label :for="lightSegment + '-checkbox'"> {{ lightSegment }} - </label> + </AppCheckbox> </div> </div> </div> @@ -176,6 +164,7 @@ import { type Ref, computed, onMounted, ref } from 'vue' import AppDialog from './AppDialog.vue' import { useStore } from '../store' import { type AdvancedFilters, type FilterChip } from '../types' +import AppCheckbox from './AppCheckbox.vue'; onMounted(() => { if (store.antibodiesSources.length === 0) { @@ -246,16 +235,28 @@ function updateFilters(): void { emit('filtersUpdate', filters.value) } -function selectAll(filter: keyof AdvancedFilters, list: Array<string>): void { +/** + * Select all the values of a filter group. + * @param filter - The name of the filter group + * @param list - The list of values to select + */ +function selectAll(filter: keyof AdvancedFilters, list: Readonly<Array<string>>): void { if (filters.value[filter].length === list.length) { filters.value[filter] = [] } else { - filters.value[filter] = list + filters.value[filter] = Array.from(list) } } -function allSelected(filter: keyof AdvancedFilters, list: Array<string>): boolean { - return filters.value[filter].length === list.length +/** + * A marker to know if the values of filter group + * are currently all selected. + * @param filter - The name of the filter group + * @param listLength - The number of values in this group + * @return If all the values are currently selected + */ +function allSelected(filter: keyof AdvancedFilters, valuesNumber: number): boolean { + return filters.value[filter].length === valuesNumber } /** @@ -331,7 +332,7 @@ function removeFilter(filterChip: FilterChip): void { color: var(--black); } -.options-group .checkbox-list { +.checkbox-list { display: flex; flex-flow: row wrap; gap: var(--spacing); @@ -344,29 +345,6 @@ function removeFilter(filterChip: FilterChip): void { transition: color 0.1s; } -.checkbox input { - display: none; -} - -.checkbox label { - border-radius: var(--big-radius); - border: 1px solid var(--white); - cursor: pointer; - padding: calc(var(--spacing) / 3) var(--half-spacing); - transition: all 0.2s; - - &:hover { - border-color: var(--primary); - color: var(--primary); - } -} - -.checkbox input:checked+label { - background-color: var(--primary); - border-color: var(--primary); - color: var(--black); -} - .advanced-search-dialog footer { align-items: center; display: flex; diff --git a/src/client/components/AppCheckbox.vue b/src/client/components/AppCheckbox.vue new file mode 100644 index 0000000000000000000000000000000000000000..eac8a96873d6f5753370159684a6c348e5204550 --- /dev/null +++ b/src/client/components/AppCheckbox.vue @@ -0,0 +1,44 @@ +<template> + <input + :id="value + '-checkbox'" + v-model="modelValue" + :value="value" + type="checkbox" + > + <label :for="value + '-checkbox'"> + <slot /> + </label> +</template> + +<script setup lang="ts"> +defineProps<{ + value: string +}>() + +const modelValue = defineModel<Array<string>>() +</script> + +<style scoped> +input { + display: none; +} + +label { + border-radius: var(--big-radius); + border: 1px solid var(--white); + cursor: pointer; + padding: calc(var(--spacing) / 3) var(--half-spacing); + transition: all 0.2s; + + &:hover { + border-color: var(--primary); + color: var(--primary); + } +} + +input:checked+label { + background-color: var(--primary); + border-color: var(--primary); + color: var(--black); +} +</style> diff --git a/src/client/store.ts b/src/client/store.ts index e9620c6150595b1f25bc28bdd1cc031229017bd1..5ba0948a6f5af12455e0a3170d6a0c83f9c1e574 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -15,16 +15,16 @@ import alphanumSort from './utils/alphanumSort' interface State { antibodies: Antibody[] antibodiesCount: number - antibodiesSources: Array<FastaHeader['source']> - antibodiesSpecies: Array<Antibody['species']> - antibodiesHeavySegments: Array<FastaHeader['vGeneSegment']> - antibodiesLightSegments: Array<FastaHeader['vGeneSegment']> + antibodiesSources: Readonly<Array<FastaHeader['source']>> + antibodiesSpecies: Readonly<Array<Antibody['species']>> + antibodiesHeavySegments: Readonly<Array<FastaHeader['vGeneSegment']>> + antibodiesLightSegments: Readonly<Array<FastaHeader['vGeneSegment']>> archives: Array<string> isDownloading: boolean isFetchingCount: boolean noResults: boolean request: APIFetchParams - sourceMeta: SourceMeta + readonly sourceMeta: SourceMeta } export const useStore = defineStore('store', { @@ -89,18 +89,18 @@ export const useStore = defineStore('store', { }) }, - getAntibodiesSources (): void { - api.getAntibodiesSources().then(response => { - this.antibodiesSources = response.data + getAntibodiesSpecies (): void { + api.getAntibodiesSpecies().then(response => { + this.antibodiesSpecies = Object.freeze(response.data) }).catch((err: AxiosError) => { alert(err.message) console.log(err) }) }, - getAntibodiesSpecies (): void { - api.getAntibodiesSpecies().then(response => { - this.antibodiesSpecies = response.data + getAntibodiesSources (): void { + api.getAntibodiesSources().then(response => { + this.antibodiesSources = Object.freeze(response.data) }).catch((err: AxiosError) => { alert(err.message) console.log(err) @@ -109,8 +109,8 @@ export const useStore = defineStore('store', { getAntibodiesSegments (): void { api.getAntibodiesSegments().then(response => { - this.antibodiesHeavySegments = response.data.heavySegments.sort(alphanumSort) - this.antibodiesLightSegments = response.data.lightSegments.sort(alphanumSort) + this.antibodiesHeavySegments = Object.freeze(response.data.heavySegments.sort(alphanumSort)) + this.antibodiesLightSegments = Object.freeze(response.data.lightSegments.sort(alphanumSort)) }).catch((err: AxiosError) => { alert(err.message) console.log(err)