Skip to content
Open
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
5 changes: 4 additions & 1 deletion frontend/e2e/sectionHierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ import SectionHeirarchy from "@/pages/node/SectionHierarchy.vue"; //
*/
const AppModalStub = defineComponent({
name: "AppModal",
props: { modelValue: { type: Boolean, default: false }, label: String },
props: {
modelValue: { type: Boolean, default: false },
label: { type: String, default: "" },
},
emits: ["update:modelValue"],
template: `<div v-if="modelValue" data-stub="modal"><slot /></div>`,
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/AppAssociationTabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<script setup lang="ts">
import AppButton from "@/components/AppButton.vue";

const props = withDefaults(
withDefaults(
defineProps<{
hasDirectAssociations: boolean;
showAllTab: boolean;
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/AppDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

type Props = {
/** title of info block */
title: string;
title?: string;
/** icon next to title */
icon?: string;
/** number next to title */
Expand All @@ -52,6 +52,7 @@ withDefaults(defineProps<Props>(), {
count: undefined,
blank: false,
full: false,
title: "",
});

type Slots = {
Expand Down
15 changes: 11 additions & 4 deletions frontend/src/components/AppIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@
</template>

<script setup lang="ts">
import { computed, defineAsyncComponent, ref, type VNode } from "vue";
import {
computed,
defineAsyncComponent,
defineComponent,
ref,
type VNode,
} from "vue";
import { kebabCase } from "lodash";
import type { IconName, IconPrefix } from "@fortawesome/fontawesome-svg-core";
import { findIconDefinition } from "@fortawesome/fontawesome-svg-core";
Expand Down Expand Up @@ -74,12 +80,13 @@ const fontAwesome = computed(() => {

const isCustom = ref(true);
const isPng = computed(() => props.icon.endsWith(".png"));
const AppNoop = defineComponent({ name: "AppNoop", setup: () => () => null });

/** look for custom icon with matching name */
const customIcon = defineAsyncComponent(async () => {
if (isPng.value) {
isCustom.value = false;
return;
return AppNoop;
}
try {
return await import(`../assets/icons/${kebabCase(props.icon)}.svg`);
Expand Down Expand Up @@ -108,7 +115,7 @@ const initials = computed(
);

/** when custom icon mounted */
function customMounted(element: VNode["el"], createCircle = false) {
const customMounted = (element: VNode["el"], createCircle = false) => {
/** add child elements to category icon */
if (
element &&
Expand All @@ -131,7 +138,7 @@ function customMounted(element: VNode["el"], createCircle = false) {
element.insertBefore(outline, element.firstChild!);
}
}
}
};
</script>

<style lang="scss" scoped>
Expand Down
32 changes: 2 additions & 30 deletions frontend/src/components/TheHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import TheLogo from "@/assets/TheLogo.vue";
import TabSearch from "@/components/TabSearch.vue";
Expand All @@ -125,7 +125,7 @@ import TheSearchSuggestions from "./TheSearchSuggestions.vue";

/** route info */
const route = useRoute();
const router = useRouter();

/** is nav menu expanded */
const expanded = ref(false);

Expand Down Expand Up @@ -153,14 +153,6 @@ const search = computed(
),
);

function scrollToHashWithOffset(hash: string, offset = 80) {
const el = document.querySelector(hash);
if (el) {
const y = el.getBoundingClientRect().top + window.scrollY - offset;
window.scrollTo({ top: y, behavior: "smooth" });
}
}

/** close nav */
function close() {
expanded.value = false;
Expand Down Expand Up @@ -188,26 +180,6 @@ onUnmounted(() => {
// <script setup>
const MOBILE_BREAKPOINT = 1000;
const isMobile = computed(() => windowWidth.value <= MOBILE_BREAKPOINT);
const isDesktop = computed(() => !isMobile.value);

const isHome = home; // alias for readability
const hasDate = computed(() => !!formattedReleaseDate.value);

const stickyHeader = computed(() => !isHome.value || isMobile.value);
const showHero = computed(() => isHome.value && isDesktop.value);

// If you want loading to be visible before the date arrives:
const showReleaseDesktop = computed(
() => showHero.value && (isLoading.value || hasDate.value),
);
const showReleaseMobile = computed(
() => isMobile.value && (isLoading.value || hasDate.value),
);

// Current search rule, but clearer:
const showSearchBox = computed(
() => search.value && (isMobile.value || !isHome.value),
);

/** close nav when page changes */
watch(() => route.name, close);
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/TheTableOfContents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ import type { Node as ApiNode } from "@/api/model";
import AppBackToTopButton from "@/components/AppBackToTopButton.vue";
import SectionHierarchy from "@/pages/node/SectionHierarchy.vue";
import { firstInView } from "@/util/dom";
import AppCheckbox from "./AppCheckbox.vue";
import type AppFlex from "./AppFlex.vue";

type Entries = {
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/pages/node/AssociationsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@
</template>

<script setup lang="ts">
import { computed, onMounted, ref, watch, watchEffect } from "vue";
import { computed, onMounted, ref, watch } from "vue";
import {
downloadAssociations,
getAssociations,
Expand Down Expand Up @@ -214,7 +214,7 @@ import {
} from "@/pages/node/associationColumns";
import { getBreadcrumbs } from "@/pages/node/AssociationsSummary.vue";
import SectionAssociationDetails from "@/pages/node/SectionAssociationDetails.vue";
import { fieldFor, TYPE_CONFIG } from "@/util/typeConfig";
import { fieldFor } from "@/util/typeConfig";

type Props = {
/** current node */
Expand All @@ -227,7 +227,9 @@ type Props = {
search: string;
};

const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
search: "",
});

const showModal = ref(false);
const selectedAssociation = ref<DirectionalAssociation | null>(null);
Expand All @@ -241,10 +243,10 @@ const emit = defineEmits<{
(e: "inferred-label", payload: { categoryId: string; label: string }): void;
}>();

function openModal(association: DirectionalAssociation) {
const openModal = (association: DirectionalAssociation) => {
selectedAssociation.value = association;
showModal.value = true;
}
};

watch(showModal, (newValue) => {
if (!newValue) {
Expand Down
1 change: 0 additions & 1 deletion frontend/src/pages/node/PageNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import { addEntry } from "@/global/history";
import { appDescription, appTitle } from "@/global/meta";
import SectionBreadcrumbs from "@/pages/node/SectionBreadcrumbs.vue";
import SectionAssociations from "./SectionAssociations.vue";
import SectionHierarchy from "./SectionHierarchy.vue";
import SectionOverview from "./SectionOverview.vue";
import SectionTitle from "./SectionTitle.vue";
import SectionVisualization from "./SectionVisualization.vue";
Expand Down
28 changes: 16 additions & 12 deletions frontend/src/pages/node/SectionAssociations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<!--tabs omly if its disease node-->
<AppAssociationTabs
v-if="isDiseaseNode"
v-if="isDiseaseNode || isPhenotypeNode"
:has-direct-associations="hasDirectAssociationsForCategory(category.id)"
:show-all-tab="showAllTab(category.count ?? 0, category.id)"
:direct-active="
Expand Down Expand Up @@ -106,7 +106,7 @@ type Totals = { direct: number; all: number };
type TotalsMap = Record<string, Totals>;
const totalsByCategory = ref<TotalsMap>({});

const { category: nodeCategory } = props.node;
const nodeCategory: string = props.node?.category ?? "";

const selectedTabs = ref<Record<string, "all" | "direct">>({});
const searchValues = ref<Record<string, string>>({});
Expand All @@ -129,6 +129,9 @@ const onTotals = ({
};

const isDiseaseNode = computed(() => nodeCategory === "biolink:Disease");
const isPhenotypeNode = computed(
() => nodeCategory === "biolink:PhenotypicFeature",
);

const directFor = (id: string) => totalsByCategory.value[id]?.direct ?? 0;
const allFor = (id: string) => totalsByCategory.value[id]?.all ?? 0;
Expand All @@ -155,21 +158,21 @@ const setDirect = (categoryId: string, directId: "true" | "false") => {
selectedTabs.value[categoryId] = directId === "true" ? "direct" : "all";
};
const directTooltip = (categoryId: string): string | undefined => {
return formatDirectTooltip(categoryId, {
return formatDirectTooltip(categoryId, nodeCategory, {
node: props.node.name,
label: labelFor(categoryId),
n: directFor(categoryId),
});
};

const inferredTooltip = (categoryId: string): string | undefined => {
return formatInferredTooltip(categoryId, {
return formatInferredTooltip(categoryId, nodeCategory, {
node: props.node.name,
label: labelFor(categoryId),
all: allFor(categoryId),
n: directFor(categoryId),
diff: diffFor(categoryId),
example: inferredByCategory.value[categoryId], // optional subclass example text
example: inferredByCategory.value[categoryId],
});
};

Expand All @@ -191,16 +194,17 @@ const showAllTab = computed(() => {
* Non-disease nodes always default to "all".
*/

function defaultTab(categoryId: string): "direct" | "all" {
const defaultTab = (categoryId: string): "direct" | "all" => {
const directCount = directFor(categoryId);
const isDisease = isDiseaseNode.value;

if (!isDisease) {
return "all";
// Disease or Phenotype: default to Direct if any direct rows exist, else Inferred (all)
if (isDiseaseNode.value || isPhenotypeNode.value) {
return directCount > 0 ? "direct" : "all";
}
// disease node: no direct → all, else → direct
return directCount > 0 ? "direct" : "all";
}

// Other node types: default to Inferred (all)
return "all";
};

/**
* - For non-disease pages(temperory, will change once we get the new layout):
Expand Down
65 changes: 39 additions & 26 deletions frontend/src/pages/node/SectionClinicalReources.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,20 @@
>
<div class="clinical-resources">
<div class="custom-grid">
<div v-for="(res, id) in clinicalResources" :key="id" class="linkout">
<AppLink
v-tooltip="res.tooltip"
:to="res.url || ''"
class="brand-chip"
:style="chipStyle(res)"
:aria-label="res.label || res.id"
>
<span>
{{ brandText(res.id, res.label) }}
</span>
<small class="brand-id">{{ res.id }}</small>
</AppLink>

<!-- removed the bottom ID display -->
</div>
<AppLink
v-for="(res, id) in clinicalResources"
:key="id"
:to="res.url || ''"
class="brand-chip"
v-tooltip="res.tooltip"
:style="chipStyle(res)"
:aria-label="res.label || res.id"
>
<span>
{{ brandText(res.id, res.label) }}
</span>
<small class="brand-id">{{ res.id }}</small>
</AppLink>
</div>

<div class="sub-items">
Expand Down Expand Up @@ -52,13 +50,36 @@
</div>
</div>
</AppDetail>
<AppDetails v-else>
<AppDetail v-if="node?.inheritance" title="Heritability" :full="true">
<AppLink
v-tooltip="node?.inheritance?.name"
:to="node?.inheritance?.id || ''"
>{{ node?.inheritance?.name }}</AppLink
>
</AppDetail>
<AppDetail
v-if="node?.causal_gene?.length"
title="Casual Genes"
:full="true"
>
<AppFlex align-h="left" gap="small">
<AppNodeBadge
v-for="(gene, index) in node?.causal_gene"
:key="index"
:node="omit(gene, 'in_taxon_label')"
/>
</AppFlex>
</AppDetail>
</AppDetails>
</template>

<script setup lang="ts">
import { computed, type ComputedRef } from "vue";
import { type ComputedRef } from "vue";
import omit from "lodash/omit";
import type { Entity, Node as ModelNode } from "@/api/model";
import type { Node as ModelNode } from "@/api/model";
import AppDetail from "@/components/AppDetail.vue";
import AppDetails from "@/components/AppDetails.vue";
import AppLink from "@/components/AppLink.vue";
import AppNodeBadge from "@/components/AppNodeBadge.vue";
import { useClinicalResources } from "@/composables/use-clinical-resources";
Expand All @@ -73,7 +94,6 @@ type Props = {
};

const { node, frequencyLabel } = defineProps<Props>();
console.log("node in clinical resources", node);
const clinicalResources = useClinicalResources(node)
.clinicalResources as ComputedRef<ClinicalResourceEntry[]>;
const chipStyle = (res: ClinicalResourceEntry) => {
Expand Down Expand Up @@ -117,13 +137,6 @@ const brandText = (id: string, fallback?: string) => {
gap: 0.4em;
}

.linkout {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.35em;
}

.brand-chip {
display: flex;
flex-direction: column;
Expand Down
Loading