Persist layout settings (#1532)

This commit is contained in:
pa
2025-12-12 07:05:28 +09:00
committed by Natsumi
parent b830311772
commit 43c3ba7be4
2 changed files with 66 additions and 8 deletions
+63 -2
View File
@@ -111,11 +111,13 @@
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAppearanceSettingsStore, useChartsStore, useFriendStore, useUserStore } from '../../../stores'; 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 { createRateLimiter, executeWithBackoff } from '../../../shared/utils';
import { database } from '../../../service/database'; import { database } from '../../../service/database';
import { userRequest } from '../../../api'; import { userRequest } from '../../../api';
import configRepository from '../../../service/config';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
const { t } = useI18n(); const { t } = useI18n();
@@ -179,12 +181,14 @@
const isForceDialogVisible = ref(false); const isForceDialogVisible = ref(false);
const forceOverrides = ref(null); const forceOverrides = ref(null);
const persistedForce = ref(null);
const forceForm = reactive({ const forceForm = reactive({
repulsion: null, repulsion: null,
edgeLengthMin: null, edgeLengthMin: null,
edgeLengthMax: null, edgeLengthMax: null,
gravity: null gravity: null
}); });
const forceConfigKey = 'VRCX_MutualGraphForce';
const parseForceField = (value, { min = 0, max = Infinity, decimals = 0 } = {}) => { const parseForceField = (value, { min = 0, max = Infinity, decimals = 0 } = {}) => {
if (value === '' || value === null || value === undefined) { if (value === '' || value === null || value === undefined) {
@@ -250,11 +254,22 @@
(tab) => { (tab) => {
if (tab === 'mutual') { if (tab === 'mutual') {
loadGraphFromDatabase(); loadGraphFromDatabase();
loadForceOverridesFromConfig();
} }
}, },
{ immediate: true } { immediate: true }
); );
watch(
graphReady,
(ready) => {
if (ready && forceOverrides.value) {
updateChart(graphPayload.value);
}
},
{ immediate: false }
);
function showStatusMessage(message, type = 'info') { function showStatusMessage(message, type = 'info') {
if (!message) { if (!message) {
return; return;
@@ -530,7 +545,10 @@
if (!chartInstance) { if (!chartInstance) {
return; 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()); nextTick(() => chartInstance?.resize());
} }
@@ -590,16 +608,59 @@
gravity: gravity.value === null ? defaults.gravity : gravity.value, gravity: gravity.value === null ? defaults.gravity : gravity.value,
layoutAnimation: defaults.layoutAnimation layoutAnimation: defaults.layoutAnimation
}; };
persistedForce.value = applyForceOverrides(defaults, forceOverrides.value);
persistForceOverrides();
updateChart(graphPayload.value); updateChart(graphPayload.value);
isForceDialogVisible.value = false; isForceDialogVisible.value = false;
} }
function resetForceSettings() { function resetForceSettings() {
forceOverrides.value = null; forceOverrides.value = null;
persistedForce.value = null;
syncForceForm(forceDefaults.value); syncForceForm(forceDefaults.value);
if (hasGraphData.value) { if (hasGraphData.value) {
updateChart(graphPayload.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);
});
} }
</script> </script>
@@ -55,7 +55,7 @@ export function computeForceOptions(nodes, links) {
}; };
} }
function applyForceOverrides(force, forceOverrides) { export function applyForceOverrides(force, forceOverrides) {
if (!forceOverrides) { if (!forceOverrides) {
return force; return force;
} }
@@ -181,13 +181,10 @@ export function useMutualGraphChart({ cachedUsers, graphPayload }) {
updateChart?.(graphPayload.value); updateChart?.(graphPayload.value);
} }
function createChartOption(payload, forceOverrides) { function createChartOption(payload, force) {
const nodes = payload?.nodes ?? []; const nodes = payload?.nodes ?? [];
const links = payload?.links ?? []; const links = payload?.links ?? [];
const force = applyForceOverrides( const resolvedForce = force || computeForceOptions(nodes, links);
computeForceOptions(nodes, links),
forceOverrides
);
const labelMap = Object.create(null); const labelMap = Object.create(null);
nodes.forEach((node) => { nodes.forEach((node) => {
if (node?.id) { if (node?.id) {