Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make ancestry route nodes selectable #60

Merged
merged 2 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/assets/styles/component/sidebar/_sidebar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ $sidebar-width: 280px !default;

&--body {
display: flex;
height: calc(100% - 28px);
height: calc(100% - 64px);
flex-direction: column;
padding: 12px 16px 16px 16px;
gap: 24px;
Expand Down
5 changes: 5 additions & 0 deletions src/assets/styles/component/step-list/_step-list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
display: flex;
align-items: center;
z-index: 0;
cursor: pointer;

&:not(:first-child):before {
@include line(0);
Expand All @@ -54,6 +55,10 @@
margin: 0 12px;
z-index: 2;

&.-selected {
@include circle(10, $global-fill-color-path-start, $global-fill-color-shade-100);
}

&.-emphasized:before {
@include pseudo;
@include on-top-circle($global-color-deep-pink-30, $global-color-deep-pink);
Expand Down
6 changes: 5 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import VueMatomo from 'vue-matomo';
import { createView } from '@/primary/tree/map/view/createView';
import { createCompositeLayer } from './primary/tree/map/layer/vector-tile/createCompositeLayer';
import { createCompositeStyleFunction } from './primary/tree/map/style/createCompositeStyleFunction';
import { createLUCATooltipOverlay } from '@/primary/tree/map/overlay/createLUCATooltipOverlay';

const browserLocale = navigator.language && navigator.language.startsWith('fr') ? 'fr' : 'en';
const wikipediaPreferredLanguage = window.localStorage.getItem('wikipedia-preferred-language');
Expand Down Expand Up @@ -81,6 +82,7 @@ const ancestorRouteLayer = createAncestorRouteLayer();
const subtreeLayer = createSubtreeLayer();

const taxonTooltipOverlay = createTaxonTooltipOverlay();
const lucaTooltipOverlay = createLUCATooltipOverlay();

const selectOptions = {
id: 'select',
Expand All @@ -95,6 +97,7 @@ const lucaSelectOptions = {
layer: lucaLayer,
selectedStyle: createSelectedLUCAStyle() as StyleLike,
selectableStyle: createSelectableLUCAStyle() as StyleLike,
overlay: lucaTooltipOverlay,
};

const select = new Select(selectOptions);
Expand All @@ -106,7 +109,7 @@ const map = createMap(
[compositeLayer],
[subtreeLayer, ancestorRouteLayer, taxonLayer, lucaLayer],
[select, lucaSelect, clickable],
[taxonTooltipOverlay]
[taxonTooltipOverlay, lucaTooltipOverlay]
);

map.on('movestart', () => {
Expand Down Expand Up @@ -141,6 +144,7 @@ app.provide('inputBus', () => inputBus);
app.provide('alertBus', () => alertBus);
app.provide('clickBus', () => clickBus);
app.provide('taxonLayer', () => taxonLayer);
app.provide('lucaLayer', () => lucaLayer);
app.provide('ancestorRouteLayer', () => ancestorRouteLayer);
app.provide('subtreeLayer', () => subtreeLayer);
app.provide('taxonRepository', () => restTaxonRepository);
Expand Down
2 changes: 2 additions & 0 deletions src/primary/tree/Tree.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ComponentState } from '@/primary/ComponentState';
import { MessageVue } from '@/primary/common/message';
import { LUCAMixin } from '@/primary/tree/luca/LUCAMixin';
import { LUCAPopupVue } from '@/primary/tree/luca/luca-popup';
import { LUCATooltipVue } from '@/primary/tree/luca/luca-tooltip';

@Component({
components: {
Expand All @@ -36,6 +37,7 @@ import { LUCAPopupVue } from '@/primary/tree/luca/luca-popup';
TaxonSearchBarVue,
TaxonTooltipVue,
WikimediaTaxonPopupVue,
LUCATooltipVue,
LUCAPopupVue,
MessageVue,
},
Expand Down
12 changes: 11 additions & 1 deletion src/primary/tree/Tree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,21 @@
</button>
</template>
<template #left-bar>
<SearchSidebarVue v-if="tool === 'search'" @select="searchTaxon" @close="changeTool('search')"></SearchSidebarVue>
<SearchSidebarVue
v-if="tool === 'search'"
@select="searchTaxon($event, true, !mobile())"
@close="changeTool('search')"
></SearchSidebarVue>
<AncestorSidebarVue
v-if="tool === 'ancestor'"
:ancestor="ancestor"
:ancestorRoute="ancestorRoute"
:selected="selectedTaxon ? selectedTaxon.getProperties().ncbiId : lucaSelected ? 0 : undefined"
:onAncestorRouteFit="fitToAncestorRoute"
@select="
$event =>
($event.id === 'root' ? [unselectTaxon, selectLUCA] : [searchTaxon, unselectLUCA]).forEach(cb => cb($event, false))
"
@close="changeTool('ancestor')"
></AncestorSidebarVue>
<SubtreeSidebarVue
Expand Down Expand Up @@ -60,6 +69,7 @@
</keep-alive>
</template>
<TaxonTooltipVue :taxon="highlightedTaxon" ref="taxon-tooltip"></TaxonTooltipVue>
<LUCATooltipVue ref="luca-tooltip"></LUCATooltipVue>
</MapLayoutVue>
</template>
<template v-else-if="state === 'ERROR'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import { AncestorFormVue } from '@/primary/tree/ancestor/ancestor-sidebar/ancest
import type { AppBus } from '@/primary/common/AppBus';
import { MessageVue } from '@/primary/common/message';

@Component({ components: { AncestorFormVue, MessageVue }, emits: ['close'] })
@Component({ components: { AncestorFormVue, MessageVue }, emits: ['select', 'close'] })
export default class AncestorSidebarComponent extends Vue {
@Prop({ type: Object, required: false })
ancestor?: Taxon;

@Prop({ type: Array, required: false })
ancestorRoute?: Taxon[];

@Prop({ type: Number, required: false })
selected?: number;

@Prop({ type: Function, required: true })
onAncestorRouteFit!: () => void;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@
v-for="(taxon, index) in ancestorRoute"
:key="`${ancestorRouteExtremes.join('-')}-${taxon.id}`"
class="step-list--item"
@click.stop.prevent="$emit('select', taxon)"
>
<div class="step-list--item--marker" :class="{ '-emphasized': taxon.id === ancestorId }"></div>
<div
class="step-list--item--marker"
:class="{ '-emphasized': taxon.id === ancestorId, '-selected': taxon.ncbiId === selected }"
></div>
<div class="step-list--item--text">
<div
class="text"
Expand Down
28 changes: 27 additions & 1 deletion src/primary/tree/luca/LUCAMixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,48 @@ import { Component, Inject, Vue } from 'vue-facing-decorator';
import { markRaw } from 'vue';
import { MittModalBus } from '@/primary/common/modal/MittModalBus';
import { LUCAModalVue } from '@/primary/tree/luca/luca-modal';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import { Point } from 'ol/geom';
import type { Taxon } from '@/domain/taxon/Taxon';
import Map from 'ol/Map';

const MOBILE_MAX_WIDTH = 650;

@Component
export class LUCAMixin extends Vue {
@Inject()
readonly map!: () => Map;

@Inject()
private lucaLayer!: () => VectorLayer<VectorSource<Point>>;

@Inject()
readonly modalBus!: () => MittModalBus;

@Inject()
private globalWindow!: () => Window;

lucaSelected = false;
lucaSource!: VectorSource<Point>;
lucaSelect!: Select;

lucaSelected = false;

created() {
this.lucaSource = this.lucaLayer().getSource()!;
this.lucaSelect = this.map().getInteractions().item(1) as Select;
this.lucaSelect.on('select', this.onSelectLUCA);
this.lucaSelect.on('unselect', this.onUnselectLUCA);
}

mounted() {
this.map().once('change:target', () => {
this.map()
.getOverlayById('luca-tooltip')
.setElement((this.$refs['luca-tooltip']! as any).$el);
});
}

unmounted() {
this.lucaSelect.un('select', this.onSelectLUCA);
this.lucaSelect.un('unselect', this.onUnselectLUCA);
Expand All @@ -44,6 +66,10 @@ export class LUCAMixin extends Vue {
this.lucaSelected = true;
}

public selectLUCA() {
this.lucaSelect.select(this.lucaSource.getFeatures()[0]);
}

public unselectLUCA() {
this.lucaSelect.unselect();
}
Expand Down
4 changes: 4 additions & 0 deletions src/primary/tree/luca/luca-tooltip/LUCATooltip.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Component, Vue } from 'vue-facing-decorator';

@Component
export default class LUCATooltipComponent extends Vue {}
7 changes: 7 additions & 0 deletions src/primary/tree/luca/luca-tooltip/LUCATooltip.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<template>
<div class="tooltip flex-container -vertical -gap-xs">
<span class="text">Root</span>
</div>
</template>

<script lang="ts" src="./LUCATooltip.component.ts"></script>
4 changes: 4 additions & 0 deletions src/primary/tree/luca/luca-tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import LUCATooltipComponent from './LUCATooltip.component';
import LUCATooltipVue from './LUCATooltip.vue';

export { LUCATooltipComponent, LUCATooltipVue };
9 changes: 9 additions & 0 deletions src/primary/tree/map/overlay/createLUCATooltipOverlay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Overlay } from 'ol';

const OVERLAY_ID = 'luca-tooltip';
const OVERLAY_POSITIONING = 'center-left';
const OVERLAY_OFFSET = [24, 0];

export function createLUCATooltipOverlay(): Overlay {
return new Overlay({ id: OVERLAY_ID, positioning: OVERLAY_POSITIONING, offset: OVERLAY_OFFSET });
}
38 changes: 32 additions & 6 deletions src/primary/tree/search/search-sidebar/SearchSidebar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,46 @@ import { Component, Inject, Vue } from 'vue-facing-decorator';
import { TaxonAutocompleteVue } from '@/primary/tree/taxon/taxon-autcomplete';
import { type TaxonRepository } from '@/domain/taxon/TaxonRepository';
import { type Taxon } from '@/domain/taxon/Taxon';
import type { Logger } from '@/domain/Logger';

const MOBILE_MAX_WIDTH = 650;

@Component({ components: { TaxonAutocompleteVue }, emits: ['select', 'close'] })
export default class SearchSidebarComponent extends Vue {
@Inject()
private taxonRepository!: () => TaxonRepository;

@Inject()
private logger!: () => Logger;

@Inject()
private globalWindow!: () => Window;

private mobile() {
return this.globalWindow().document.body.clientWidth < MOBILE_MAX_WIDTH;
}

private close() {
const routeQuery = { ...this.$router.currentRoute.value.query, tool: undefined };
this.$router.push({ name: this.$router.currentRoute.value.name!, query: routeQuery });
}

private handleTaxon(taxon: Taxon): void {
this.$emit('select', taxon);

if (this.mobile()) {
this.close();
}
}

private handleError(taxonNCBIId: number, error: Error): void {
this.logger().error(`No taxon found for NCBI ID ${taxonNCBIId}`, error);
}

findTaxon(taxonNCBIId: number): void {
this.taxonRepository()
.findByNCBIId(taxonNCBIId)
.then((taxon: Taxon) => {
this.$emit('select', taxon);
})
.catch(error => {
console.error(error);
});
.then(this.handleTaxon)
.catch(error => this.handleError(taxonNCBIId, error));
}
}
29 changes: 19 additions & 10 deletions src/primary/tree/taxon/TaxonMixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ export class TaxonMixin extends Vue {
taxonSelect!: Select;

get defaultTaxonIdToSelect(): number | undefined {
const { tid } = this.$router.currentRoute.value.query as Record<string, string>;
return tid && /^\d+$/.test(tid) ? parseInt(tid) : undefined;
const { subtree, tid } = this.$router.currentRoute.value.query as Record<string, string>;
return tid && !subtree && /^\d+$/.test(tid) ? parseInt(tid) : undefined;
}

created() {
Expand Down Expand Up @@ -116,13 +116,14 @@ export class TaxonMixin extends Vue {
}

private onSelectTaxon(event: any) {
this.addTaxonToRoute(event.feature.get('ncbiId'));

if (this.mobile()) {
this.openTaxonModal(event.feature);
return;
}

this.selectedTaxon = event.feature;
this.addTaxonToRoute(event.feature.get('ncbiId'));
}

private addTaxonToRoute(taxonId: number) {
Expand Down Expand Up @@ -175,23 +176,31 @@ export class TaxonMixin extends Vue {

private searchTaxonIfDefined(taxon: Taxon | undefined) {
if (taxon) {
this.searchTaxon(taxon);
this.searchTaxon(taxon, true, !this.mobile());
}
}

public searchTaxon(searchedTaxon: Taxon): void {
private findTaxonFeature(searchedTaxon: Taxon): TaxonFeature {
const searchTaxonFeature = toTaxonFeature(5)(searchedTaxon);
const taxa = this.taxonSource.getFeatures();
const existingTaxonFeature = taxa.find(feature => feature.getId() === searchTaxonFeature.getId());
const taxonFeature = existingTaxonFeature || searchTaxonFeature;

if (!existingTaxonFeature) {
this.taxonSource.addFeature(searchTaxonFeature);
}

return existingTaxonFeature || searchTaxonFeature;
}

public searchTaxon(searchedTaxon: Taxon, zoom = true, select = true): void {
const taxonFeature = this.findTaxonFeature(searchedTaxon);
const taxonFeatureToSelect = taxonFeature?.getId() === this.selectedTaxon?.getId() ? undefined : taxonFeature;
this.zoomToTaxon(taxonFeature);

if (this.mobile()) {
this.changeTool('search');
if (zoom) {
this.zoomToTaxon(taxonFeature);
}

if (taxonFeatureToSelect && !this.mobile()) {
if (taxonFeatureToSelect && select) {
this.taxonSelect.select(taxonFeatureToSelect);
this.updateSelectedTaxonSequencedGenomes();
}
Expand Down