Fix inline editor persistence during websocket updates
Critical fixes to prevent the inline URL editor from closing during API updates: 1. Implement stable guest store with reconcile: - Use createStore with reconcile() to maintain stable object references - Key function ensures each guest keeps same proxy instance across updates - Prevents <For> loop from remounting rows during websocket updates 2. Allow switching between guest editors: - Mark guest name spans with data-guest-name-editable attribute - Click handler allows clicking another guest name to switch editors - Prevents click consumption when opening a different guest's editor This ensures the inline editor stays open and preserves user input even when websocket updates arrive, while still allowing seamless switching between editing different guests.
This commit is contained in:
parent
15252c95ce
commit
32cd925e1f
2 changed files with 22 additions and 9 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { createSignal, createMemo, createEffect, For, Show, onMount } from 'solid-js';
|
||||
import { createStore, reconcile } from 'solid-js/store';
|
||||
import { useNavigate } from '@solidjs/router';
|
||||
import type { VM, Container, Node } from '@/types/api';
|
||||
import { GuestRow } from './GuestRow';
|
||||
|
|
@ -41,6 +42,16 @@ export function Dashboard(props: DashboardProps) {
|
|||
const [selectedNode, setSelectedNode] = createSignal<string | null>(null);
|
||||
const [guestMetadata, setGuestMetadata] = createSignal<Record<string, GuestMetadata>>({});
|
||||
|
||||
// Stable guest store using reconcile to prevent row remounting during websocket updates
|
||||
const guestKey = (g: VM | Container) =>
|
||||
g.id ?? (g.instance === g.node ? `${g.node}-${g.vmid}` : `${g.instance}-${g.node}-${g.vmid}`);
|
||||
const [guestStore, setGuestStore] = createStore<(VM | Container)[]>([]);
|
||||
|
||||
// Reconcile guests whenever props change
|
||||
createEffect(() => {
|
||||
setGuestStore(reconcile([...props.vms, ...props.containers], { key: guestKey, merge: true }));
|
||||
});
|
||||
|
||||
// Initialize from localStorage with proper type checking
|
||||
const [viewMode, setViewMode] = usePersistentSignal<ViewMode>('dashboardViewMode', 'all', {
|
||||
deserialize: (raw) => (raw === 'all' || raw === 'vm' || raw === 'lxc' ? raw : 'all'),
|
||||
|
|
@ -237,12 +248,8 @@ export function Dashboard(props: DashboardProps) {
|
|||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
});
|
||||
|
||||
// Combine VMs and containers into a single list
|
||||
const allGuests = createMemo(() => {
|
||||
const vms = props.vms || [];
|
||||
const containers = props.containers || [];
|
||||
return [...vms, ...containers];
|
||||
});
|
||||
// Use the stable guest store
|
||||
const allGuests = createMemo(() => guestStore);
|
||||
|
||||
// Filter guests based on current settings
|
||||
const filteredGuests = createMemo(() => {
|
||||
|
|
|
|||
|
|
@ -192,8 +192,11 @@ export function GuestRow(props: GuestRowProps) {
|
|||
if (isEditingUrl()) {
|
||||
const handleGlobalClick = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
// If clicking outside the editor, close it and prevent the click
|
||||
if (!target.closest('[data-url-editor]') && currentlyEditingGuestId() === guestId()) {
|
||||
// Allow clicking another guest name to switch editing
|
||||
const isClickingGuestName = target.closest('[data-guest-name-editable]');
|
||||
|
||||
// If clicking outside the editor (and not another guest name), close it and prevent the click
|
||||
if (!target.closest('[data-url-editor]') && !isClickingGuestName && currentlyEditingGuestId() === guestId()) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
|
|
@ -203,7 +206,9 @@ export function GuestRow(props: GuestRowProps) {
|
|||
|
||||
const handleGlobalMouseDown = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target.closest('[data-url-editor]') && currentlyEditingGuestId() === guestId()) {
|
||||
const isClickingGuestName = target.closest('[data-guest-name-editable]');
|
||||
|
||||
if (!target.closest('[data-url-editor]') && !isClickingGuestName && currentlyEditingGuestId() === guestId()) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
|
|
@ -407,6 +412,7 @@ export function GuestRow(props: GuestRowProps) {
|
|||
style="cursor: text;"
|
||||
title={`${props.guest.name}${customUrl() ? ' - Click to edit URL' : ' - Click to add URL'}`}
|
||||
onClick={startEditingUrl}
|
||||
data-guest-name-editable
|
||||
>
|
||||
{props.guest.name}
|
||||
</span>
|
||||
|
|
|
|||
Loading…
Reference in a new issue