preview stack, refresh token, design

This commit is contained in:
2025-10-11 13:12:05 +02:00
parent 6850918e3d
commit a21853c874
9 changed files with 320 additions and 82 deletions
+17 -61
View File
@@ -21,6 +21,7 @@ const API_ROOT = (process.env.API_BASE_URL || '').replace(/\/$/, '');
const api = axios.create({
baseURL: API_ROOT ? `${API_ROOT}/api` : '/api',
withCredentials: true,
});
const DEFAULT_FOLDER_NAME = 'All Documents';
@@ -562,7 +563,7 @@ const computeStackAngle = (docId, index) => {
return magnitude * sign;
};
const MAX_PREVIEW_STACK_ITEMS = 4;
const MAX_PREVIEW_STACK_ITEMS = 15;
const PreviewStack = ({
items = [],
@@ -572,75 +573,27 @@ const PreviewStack = ({
onOpenPreview,
activeItemId = null,
}) => {
const angleMapRef = useRef(new Map());
const previousOrderRef = useRef([]);
if (!items.length) {
return <span className="meta">{emptyMessage}</span>;
}
const limited = items.slice(0, maxItems);
const hasMultiple = limited.length > 1;
const preparedItems = useMemo(() => {
const angleMap = angleMapRef.current;
const previousOrder = previousOrderRef.current;
const prevFrontId = previousOrder.length ? previousOrder[0] : null;
const currentFrontId = limited.length ? limited[0].id : null;
const currentIds = new Set();
limited.forEach((entry, index) => {
const id = entry.id;
currentIds.add(id);
if (!angleMap.has(id)) {
const initialAngle = index === 0 ? 0 : computeStackAngle(id, index);
angleMap.set(id, initialAngle);
}
});
if (
onItemActivate &&
activeItemId &&
currentFrontId === activeItemId &&
prevFrontId &&
prevFrontId !== activeItemId
) {
const frontAngle = angleMap.get(prevFrontId);
const activeAngle = angleMap.get(activeItemId);
angleMap.set(
prevFrontId,
typeof activeAngle === 'number' ? activeAngle : computeStackAngle(prevFrontId, 1),
);
angleMap.set(activeItemId, typeof frontAngle === 'number' ? frontAngle : 0);
} else if (currentFrontId && (!angleMap.has(currentFrontId) || angleMap.get(currentFrontId) !== 0)) {
angleMap.set(currentFrontId, 0);
}
angleMap.forEach((_, id) => {
if (!currentIds.has(id)) {
angleMap.delete(id);
}
});
const result = limited.map((entry, index) => {
const angle = angleMap.get(entry.id);
return {
const preparedItems = useMemo(
() =>
limited.map((entry, index) => ({
entry,
angle: typeof angle === 'number' ? angle : computeStackAngle(entry.id, index),
offset: hasMultiple ? index * 8 : 0,
};
});
previousOrderRef.current = limited.map((entry) => entry.id);
return result;
}, [limited, hasMultiple, activeItemId, onItemActivate]);
angle: index === 0 ? 0 : computeStackAngle(entry.id, index),
offset: 0,
})),
[limited],
);
return (
<div className="preview-stack preview-stack--stacked">
{preparedItems.map(({ entry, angle, offset }, index) => {
const transform = hasMultiple
? `translate(-50%, -50%) rotate(${angle}deg) translateY(${offset}px)`
? `translate(-50%, -50%) rotate(${angle}deg)`
: 'translate(-50%, -50%)';
const isFront = index === 0;
return (
@@ -659,8 +612,6 @@ const PreviewStack = ({
src={entry.url}
alt={entry.alt || ''}
className="preview-stack__image"
role={onItemActivate || onOpenPreview ? 'button' : undefined}
tabIndex={onItemActivate || onOpenPreview ? 0 : -1}
onClick={(event) => {
event.stopPropagation();
if (isFront && onOpenPreview) {
@@ -1195,7 +1146,7 @@ const Sidebar = ({
);
};
function App({ routeFolderId = 'root', routeDocumentId = null, navigate }) {
function App({ routeFolderId = null, routeDocumentId = null, navigate }) {
const [token, setToken] = useState(() => window.localStorage.getItem('paperless_token') || '');
const [status, setStatus] = useState(null);
const [loading, setLoading] = useState(false);
@@ -1292,6 +1243,7 @@ function App({ routeFolderId = 'root', routeDocumentId = null, navigate }) {
if (!selectedDocumentIds.includes(activePreviewId)) {
setActivePreviewId(selectedDocumentIds[selectedDocumentIds.length - 1]);
}
initializedRef.current = true;
}, [selectedDocumentIds, activePreviewId]);
const currentFolderName = useMemo(() => {
@@ -1318,6 +1270,10 @@ function App({ routeFolderId = 'root', routeDocumentId = null, navigate }) {
let nextSelection = [];
setSelectedDocumentIds((previous) => {
if (initializedRef.current) {
nextSelection = previous.filter((id) => availableIds.has(id));
return nextSelection;
}
const filtered = previous.filter((id) => availableIds.has(id));
if (filtered.length) {
nextSelection = filtered;
+1 -4
View File
@@ -198,7 +198,6 @@ button.icon-button.ghost:hover:not([disabled]) {
flex: 1;
display: flex;
flex-direction: column;
padding: 0.75rem 1.5rem 1.25rem;
min-height: 0;
}
@@ -215,6 +214,7 @@ button.icon-button.ghost:hover:not([disabled]) {
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.75rem 1.5rem 0;
}
.preview-workspace__meta {
@@ -242,9 +242,6 @@ button.icon-button.ghost:hover:not([disabled]) {
.preview-workspace__body {
flex: 1;
background: var(--surface-subtle);
border: 1px solid var(--border);
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;