diff --git a/src/views/Charts/components/MutualFriends.vue b/src/views/Charts/components/MutualFriends.vue index 8598ea67..8355b72a 100644 --- a/src/views/Charts/components/MutualFriends.vue +++ b/src/views/Charts/components/MutualFriends.vue @@ -111,11 +111,13 @@ import { useI18n } from 'vue-i18n'; import { useAppearanceSettingsStore, useChartsStore, useFriendStore, useUserStore } from '../../../stores'; - import { computeForceOptions, useMutualGraphChart } from '../composables/useMutualGraphChart'; + import { applyForceOverrides, computeForceOptions, useMutualGraphChart } from '../composables/useMutualGraphChart'; import { createRateLimiter, executeWithBackoff } from '../../../shared/utils'; import { database } from '../../../service/database'; import { userRequest } from '../../../api'; + import configRepository from '../../../service/config'; + import * as echarts from 'echarts'; const { t } = useI18n(); @@ -179,12 +181,14 @@ const isForceDialogVisible = ref(false); const forceOverrides = ref(null); + const persistedForce = ref(null); const forceForm = reactive({ repulsion: null, edgeLengthMin: null, edgeLengthMax: null, gravity: null }); + const forceConfigKey = 'VRCX_MutualGraphForce'; const parseForceField = (value, { min = 0, max = Infinity, decimals = 0 } = {}) => { if (value === '' || value === null || value === undefined) { @@ -250,11 +254,22 @@ (tab) => { if (tab === 'mutual') { loadGraphFromDatabase(); + loadForceOverridesFromConfig(); } }, { immediate: true } ); + watch( + graphReady, + (ready) => { + if (ready && forceOverrides.value) { + updateChart(graphPayload.value); + } + }, + { immediate: false } + ); + function showStatusMessage(message, type = 'info') { if (!message) { return; @@ -530,7 +545,10 @@ if (!chartInstance) { return; } - chartInstance.setOption(createChartOption(payload, forceOverrides.value)); + const forceOption = + persistedForce.value || + applyForceOverrides(computeForceOptions(nodes, payload?.links ?? []), forceOverrides.value); + chartInstance.setOption(createChartOption(payload, forceOption)); nextTick(() => chartInstance?.resize()); } @@ -590,16 +608,59 @@ gravity: gravity.value === null ? defaults.gravity : gravity.value, layoutAnimation: defaults.layoutAnimation }; + persistedForce.value = applyForceOverrides(defaults, forceOverrides.value); + persistForceOverrides(); updateChart(graphPayload.value); isForceDialogVisible.value = false; } function resetForceSettings() { forceOverrides.value = null; + persistedForce.value = null; syncForceForm(forceDefaults.value); if (hasGraphData.value) { updateChart(graphPayload.value); } + clearForceOverrides(); + } + + async function loadForceOverridesFromConfig() { + try { + const saved = await configRepository.getObject(forceConfigKey, null); + if (!saved || typeof saved !== 'object') { + return; + } + forceOverrides.value = saved.overrides || null; + persistedForce.value = saved.force || null; + if (forceOverrides.value) { + syncForceForm(forceOverrides.value); + } + if (graphReady.value) { + updateChart(graphPayload.value); + } + } catch (err) { + console.warn('[MutualNetworkGraph] Failed to load force settings', err); + } + } + + function persistForceOverrides() { + if (!forceOverrides.value) { + clearForceOverrides(); + return; + } + const payload = { + overrides: forceOverrides.value, + force: persistedForce.value + }; + configRepository.setObject(forceConfigKey, payload).catch((err) => { + console.warn('[MutualNetworkGraph] Failed to save force settings', err); + }); + } + + function clearForceOverrides() { + configRepository.remove(forceConfigKey).catch((err) => { + console.warn('[MutualNetworkGraph] Failed to clear force settings', err); + }); } diff --git a/src/views/Charts/composables/useMutualGraphChart.js b/src/views/Charts/composables/useMutualGraphChart.js index 96dd7c90..97333dd0 100644 --- a/src/views/Charts/composables/useMutualGraphChart.js +++ b/src/views/Charts/composables/useMutualGraphChart.js @@ -55,7 +55,7 @@ export function computeForceOptions(nodes, links) { }; } -function applyForceOverrides(force, forceOverrides) { +export function applyForceOverrides(force, forceOverrides) { if (!forceOverrides) { return force; } @@ -181,13 +181,10 @@ export function useMutualGraphChart({ cachedUsers, graphPayload }) { updateChart?.(graphPayload.value); } - function createChartOption(payload, forceOverrides) { + function createChartOption(payload, force) { const nodes = payload?.nodes ?? []; const links = payload?.links ?? []; - const force = applyForceOverrides( - computeForceOptions(nodes, links), - forceOverrides - ); + const resolvedForce = force || computeForceOptions(nodes, links); const labelMap = Object.create(null); nodes.forEach((node) => { if (node?.id) {