diff --git a/xp-archive/client/content/Content.tsx b/xp-archive/client/content/Content.tsx index 2702491..390c67f 100644 --- a/xp-archive/client/content/Content.tsx +++ b/xp-archive/client/content/Content.tsx @@ -9,6 +9,11 @@ import { VersionSelector } from 'client/versionSelector/VersionSelector'; import { ContentView } from '../contentView/ContentView'; import { formatTimestamp } from '@common/shared/timestamp'; import { EmptyState } from '@common/shared/EmptyState/EmptyState'; +import { + setCachedVersionSelector, + getCachedVersionSelector, + clearCachedVersionSelector, +} from 'client/versionSelector/VersionSelectorCache'; import style from './Content.module.css'; @@ -18,15 +23,12 @@ const getDefaultView = (isWebpage: boolean, hasAttachment: boolean): ViewVariant return undefined; }; -const updateContentUrl = (nodeId: string, locale: string, versionId?: string) => { - const newUrl = `${xpArchiveConfig.basePath}/${nodeId}/${locale}/${versionId ?? ''}`; - window.history.pushState({}, '', newUrl); -}; - export const Content = () => { const { selectedContentId, selectedLocale, selectedVersion, setSelectedVersion } = useAppState(); + const prevContentIdRef = React.useRef(selectedContentId); + useEffect(() => { const pathSegments = window.location.pathname.split('/'); if (pathSegments.length >= 5) { @@ -43,21 +45,58 @@ export const Content = () => { }); useEffect(() => { - if (selectedVersion) { - updateContentUrl(selectedContentId ?? '', selectedLocale, selectedVersion); - } else if (data?.versions?.[0]) { - const latestVersionId = data.versions[0].versionId; - setSelectedVersion(latestVersionId); - updateContentUrl(selectedContentId ?? '', selectedLocale, latestVersionId); + const versionId = selectedVersion ?? data?.versions?.[0]?.versionId; + if (versionId) { + if (!selectedVersion) { + setSelectedVersion(versionId); + } + const newUrl = `${xpArchiveConfig.basePath}/${selectedContentId}/${selectedLocale}/${versionId}`; + window.history.pushState({}, '', newUrl); } }, [data, selectedContentId, selectedLocale, selectedVersion]); - const isWebpage = !!data?.html && !data.json.attachment; - const hasAttachment = !!data?.json.attachment; + const isWebpage = !!data?.html && !data?.json?.attachment; + const hasAttachment = !!data?.json?.attachment; const [selectedView, setSelectedView] = useState( getDefaultView(isWebpage, hasAttachment) ); - const [isVersionPanelOpen, setIsVersionPanelOpen] = useState(false); + + const [versionSelectorCache, setVersionSelectorCache] = useState(() => { + const cache = getCachedVersionSelector(selectedContentId ?? ''); + return { + component: cache.component, + versions: cache.versions, + isOpen: cache.isOpen, + }; + }); + + const [cachedDisplayData, setCachedDisplayData] = useState({ + displayName: '', + path: '', + }); + + useEffect(() => { + if (prevContentIdRef.current && prevContentIdRef.current !== selectedContentId) { + clearCachedVersionSelector(prevContentIdRef.current); + } + + if (data?.versions && selectedContentId) { + setVersionSelectorCache((prev) => ({ + component: null, + versions: data.versions, + isOpen: prev.isOpen, + })); + + if (data.json?.displayName || data.json?._path) { + setCachedDisplayData({ + displayName: data.json.displayName || '', + path: data.json._path || '', + }); + } + } + + prevContentIdRef.current = selectedContentId; + }, [selectedContentId, data?.versions, data?.json]); useEffect(() => { setSelectedView(getDefaultView(isWebpage, hasAttachment)); @@ -68,6 +107,15 @@ export const Content = () => { }`; const getVersionDisplay = () => { + if (selectedVersion && versionSelectorCache.versions.length > 0) { + const cachedVersion = versionSelectorCache.versions.find( + (v) => v.versionId === selectedVersion + ); + if (cachedVersion?.timestamp) { + return formatTimestamp(cachedVersion.timestamp); + } + } + if (selectedVersion && data?.versions) { return formatTimestamp( data.versions.find((v) => v.versionId === selectedVersion)?.timestamp ?? '' @@ -91,15 +139,49 @@ export const Content = () => { variant={'secondary'} icon={} iconPosition={'right'} - onClick={() => setIsVersionPanelOpen(true)} + onClick={() => { + setVersionSelectorCache((prev) => ({ + ...prev, + isOpen: true, + })); + }} > {getVersionDisplay()} - setIsVersionPanelOpen(false)} - /> + + {versionSelectorCache.component + ? versionSelectorCache.component + : (() => { + const versionSelectorVersions = + versionSelectorCache.versions.length > 0 + ? versionSelectorCache.versions + : data?.versions || []; + + const handleClose = () => { + setVersionSelectorCache((prev) => ({ + ...prev, + isOpen: false, + })); + }; + + const handleMount = (component: React.ReactNode) => { + setCachedVersionSelector( + selectedContentId ?? '', + component, + versionSelectorVersions, + versionSelectorCache.isOpen + ); + }; + + return ( + + ); + })()}
@@ -128,10 +210,10 @@ export const Content = () => {
- {data?.json.displayName ?? ''} + {data?.json?.displayName || cachedDisplayData.displayName || 'Laster...'}
- {data?.json._path ?? ''} + {data?.json?._path || cachedDisplayData.path || ''}
diff --git a/xp-archive/client/versionSelector/SlidePanel/SlidePanel.module.css b/xp-archive/client/versionSelector/SlidePanel/SlidePanel.module.css index d1298b5..00005a7 100644 --- a/xp-archive/client/versionSelector/SlidePanel/SlidePanel.module.css +++ b/xp-archive/client/versionSelector/SlidePanel/SlidePanel.module.css @@ -17,9 +17,11 @@ } .panel { + display: flex; + flex-direction: column; position: relative; z-index: 1; - width: 400px; + width: 460px; height: 100vh; background: var(--a-surface-default); box-shadow: 4px 0 8px rgba(0, 0, 0, 0.1); diff --git a/xp-archive/client/versionSelector/VersionSelector.module.css b/xp-archive/client/versionSelector/VersionSelector.module.css index 04a8387..13d858b 100644 --- a/xp-archive/client/versionSelector/VersionSelector.module.css +++ b/xp-archive/client/versionSelector/VersionSelector.module.css @@ -40,3 +40,14 @@ font-weight: var(--a-font-weight-bold); } } + +.closeButton { + position: sticky; + bottom: 1rem; + margin-top: 1.75rem; + + width: fit-content; + border-radius: 99px; + background-color: var(--a-surface-inverted); + align-self: center; +} diff --git a/xp-archive/client/versionSelector/VersionSelector.tsx b/xp-archive/client/versionSelector/VersionSelector.tsx index 5a77e4f..e6d9d00 100644 --- a/xp-archive/client/versionSelector/VersionSelector.tsx +++ b/xp-archive/client/versionSelector/VersionSelector.tsx @@ -1,4 +1,5 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; +import { CheckmarkIcon, XMarkIcon } from '@navikt/aksel-icons'; import { Heading, Button, Search } from '@navikt/ds-react'; import { VersionReference } from 'shared/types'; import { formatTimestamp } from '@common/shared/timestamp'; @@ -6,12 +7,12 @@ import { useAppState } from 'client/context/appState/useAppState'; import { SlidePanel } from './SlidePanel/SlidePanel'; import { classNames } from '@common/client/utils/classNames'; import style from './VersionSelector.module.css'; -import { CheckmarkIcon } from '@navikt/aksel-icons'; type Props = { versions: VersionReference[]; isOpen: boolean; onClose: () => void; + onMount?: (component: React.ReactNode) => void; }; type VersionButtonProps = { @@ -32,7 +33,7 @@ const VersionButton = ({ isSelected, onClick, children }: VersionButtonProps) => ); -export const VersionSelector = ({ versions, isOpen, onClose }: Props) => { +export const VersionSelector = ({ versions, isOpen, onClose, onMount }: Props) => { const [searchQuery, setSearchQuery] = useState(''); const { setSelectedContentId, selectedVersion, setSelectedVersion } = useAppState(); @@ -45,14 +46,13 @@ export const VersionSelector = ({ versions, isOpen, onClose }: Props) => { const nodeId = versions.find((v) => v.versionId === versionId)?.nodeId; if (nodeId) setSelectedContentId(nodeId); setSelectedVersion(versionId); - handleClose(); }; const filteredVersions = versions.filter((version) => formatTimestamp(version.timestamp).toLowerCase().includes(searchQuery.toLowerCase()) ); - return ( + const component = ( Versjoner @@ -78,6 +78,22 @@ export const VersionSelector = ({ versions, isOpen, onClose }: Props) => { ))} + ); + + useEffect(() => { + if (onMount) { + onMount(component); + } + }, [versions, isOpen, searchQuery, selectedVersion]); + + return component; }; diff --git a/xp-archive/client/versionSelector/VersionSelectorCache.tsx b/xp-archive/client/versionSelector/VersionSelectorCache.tsx new file mode 100644 index 0000000..4b083cd --- /dev/null +++ b/xp-archive/client/versionSelector/VersionSelectorCache.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { VersionReference } from 'shared/types'; + +type VersionSelectorCacheItem = { + component: React.ReactNode | null; + versions: VersionReference[]; + isOpen: boolean; +}; + +const DEFAULT_CACHE: VersionSelectorCacheItem = { + component: null, + versions: [], + isOpen: false, +}; + +const contentCache: Record = {}; + +export const setCachedVersionSelector = ( + contentId: string, + component: React.ReactNode, + versions: VersionReference[], + isOpen: boolean +) => { + Object.keys(contentCache).forEach((key) => { + if (key !== contentId) { + delete contentCache[key]; + } + }); + + contentCache[contentId] = { + component, + versions, + isOpen, + }; +}; + +export const getCachedVersionSelector = (contentId: string): VersionSelectorCacheItem => { + return contentCache[contentId] || DEFAULT_CACHE; +}; + +export const clearCachedVersionSelector = (contentId: string): void => { + if (contentId in contentCache) { + delete contentCache[contentId]; + } +};