diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index ee8b94e5..3bab35c7 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -15,8 +15,28 @@ on:
- main
jobs:
+ changes:
+ name: Check changed files
+ runs-on: ubuntu-latest
+
+ outputs:
+ src: ${{ steps.filter.outputs.src }}
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - uses: dorny/paths-filter@v3
+ id: filter
+ with:
+ filters: |
+ src:
+ - '!content/**'
+
validate:
name: Validate
+ needs: [changes]
+ if: ${{ needs.changes.outputs.src == 'true' }}
runs-on: ${{ matrix.os }}
timeout-minutes: 60
@@ -99,7 +119,8 @@ jobs:
retention-days: 30
build-deploy:
- if: ${{ github.event_name == 'push' }}
+ name: Build and deploy
+ if: ${{ always() && github.event_name == 'push' }}
needs: [validate]
uses: ./.github/workflows/build-deploy.yml
secrets: inherit
diff --git a/components/data-graph.vue b/components/data-graph.vue
index b07a35b3..7681e291 100644
--- a/components/data-graph.vue
+++ b/components/data-graph.vue
@@ -8,6 +8,7 @@ import { colors } from "../project.config.json";
const props = defineProps<{
networkData: NetworkEntity;
searchNode: string;
+ detailNode?: string;
}>();
const graph = new Graph();
@@ -62,5 +63,10 @@ function getNodeColor(nodeClass: string) {
-
+
diff --git a/components/data-map-view.vue b/components/data-map-view.vue
index 2cab6c7e..8bd21b68 100644
--- a/components/data-map-view.vue
+++ b/components/data-map-view.vue
@@ -16,12 +16,17 @@ const route = useRoute();
const t = useTranslations();
const currentView = useGetCurrentView();
+const { getUnprefixedId } = useIdPrefix();
const searchFiltersSchema = v.object({
category: v.fallback(v.picklist(categories), "entityName"),
search: v.fallback(v.string(), ""),
});
+const detailEntityId = computed(() => {
+ return route.params.id as string;
+});
+
const searchFilters = computed(() => {
return v.parse(searchFiltersSchema, route.query);
});
@@ -48,7 +53,7 @@ const { data, isPending, isPlaceholderData } = useGetSearchResults(
: [],
show: ["geometry", "when"],
centroid: true,
- system_classes: ["place", "object_location"],
+ system_classes: ["place"],
limit: 0,
};
}),
@@ -141,6 +146,34 @@ watch(data, () => {
*/
popover.value = null;
});
+
+watchEffect(() => {
+ const entity = entities.value.find((feature) => {
+ const id = getUnprefixedId(feature["@id"]);
+ return id === detailEntityId.value;
+ });
+
+ if (entity) {
+ let coordinates = null;
+
+ if (entity.geometry.type === "GeometryCollection") {
+ coordinates = entity.geometry.geometries.find((g) => {
+ return g.type === "Point";
+ })?.coordinates as [number, number] | undefined;
+ }
+
+ if (entity.geometry.type === "Point") {
+ coordinates = entity.geometry.coordinates as unknown as [number, number];
+ }
+
+ popover.value = {
+ coordinates:
+ coordinates ??
+ (turf.center(createFeatureCollection([entity])).geometry.coordinates as [number, number]),
+ entities: [entity],
+ };
+ }
+});
diff --git a/components/data-network-view.vue b/components/data-network-view.vue
index 27a3194c..a61386a8 100644
--- a/components/data-network-view.vue
+++ b/components/data-network-view.vue
@@ -9,6 +9,10 @@ import { project } from "../config/project.config";
const router = useRouter();
const route = useRoute();
+const detailEntityId = computed(() => {
+ return route.params.id as string;
+});
+
const searchFiltersSchema = z.object({
search: z.string().catch(""),
});
@@ -104,7 +108,11 @@ const systemClasses = computed(() => {
:system-classes="systemClasses"
@submit="onChangeCategory"
/>
-
+
diff --git a/components/geo-map-popup.client.vue b/components/geo-map-popup.client.vue
index fee65fdb..4010b71f 100644
--- a/components/geo-map-popup.client.vue
+++ b/components/geo-map-popup.client.vue
@@ -10,7 +10,7 @@ const emit = defineEmits<{
(event: "close"): void;
}>();
-const { map } = useGeoMap();
+const geoMapContext = useGeoMap();
const elementRef = ref(null);
@@ -24,6 +24,7 @@ const context: Context = {
onMounted(async () => {
await nextTick();
+ const { map } = geoMapContext;
assert(elementRef.value != null);
assert(map != null);
diff --git a/components/network.client.vue b/components/network.client.vue
index 93542741..b14032d4 100644
--- a/components/network.client.vue
+++ b/components/network.client.vue
@@ -23,6 +23,7 @@ interface State {
const props = defineProps<{
graph: Graph;
searchNode?: string;
+ detailNode?: string;
}>();
interface NetworkContext {
@@ -41,6 +42,7 @@ circular.assign(context.graph);
const locale = useLocale();
const router = useRouter();
+
let hoverTimeOut: ReturnType;
const state = ref({});
@@ -91,6 +93,48 @@ watch(
{ immediate: true },
);
+watch(
+ () => {
+ return props.detailNode;
+ },
+ (detailNode) => {
+ context.graph.nodes().forEach((el) => {
+ context.graph.removeNodeAttribute(el, "highlighted");
+ });
+
+ if (detailNode) {
+ const results = context.graph
+ .nodes()
+ .map((n) => {
+ return { id: n, label: context.graph.getNodeAttribute(n, "id") as string };
+ })
+ .filter(({ id }) => {
+ return id === detailNode;
+ });
+
+ if (results.length === 1) {
+ state.value.selectedNodes = results;
+ state.value.selectedNodes.forEach((el) => {
+ context.graph.setNodeAttribute(el.id, "highlighted", true);
+ });
+ }
+ }
+ // If the query is empty, then we reset the selectedNode
+ else {
+ state.value.selectedNodes = undefined;
+ }
+
+ // Refresh rendering
+ // You can directly call `renderer.refresh()`, but if you need performances
+ // you can provide some options to the refresh method.
+ // In this case, we don't touch the graph data so we can skip its reindexation
+ context.renderer?.refresh({
+ skipIndexation: true,
+ });
+ },
+ { immediate: true },
+);
+
const currentView = useGetCurrentView();
onMounted(async () => {
diff --git a/layouts/visualization.vue b/layouts/visualization.vue
index 5b027ecf..f2f35cfd 100644
--- a/layouts/visualization.vue
+++ b/layouts/visualization.vue
@@ -51,7 +51,7 @@ const currentView = useGetCurrentView();
-
+