preview stack, refresh token, design
This commit is contained in:
+17
-61
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user