patch oko teksta PDFa i UI tablica
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'preact/hooks';
|
||||
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
|
||||
import { animated, useTransition } from '@react-spring/web';
|
||||
import { api } from '../../services/apiClient';
|
||||
|
||||
function getBrowserApiBase() {
|
||||
const apiUrl = import.meta.env.PUBLIC_API_URL || '';
|
||||
@@ -44,6 +45,109 @@ export function resolveMediaUrl(pathOrUrl) {
|
||||
return new URL(finalSrc, apiUrl).toString();
|
||||
}
|
||||
|
||||
function isProtectedMediaUrl(url) {
|
||||
return typeof url === 'string' && /\/api\/fleet\//i.test(url);
|
||||
}
|
||||
|
||||
function revokeObjectUrls(entries = {}) {
|
||||
Object.values(entries).forEach((url) => {
|
||||
if (typeof url === 'string' && url.startsWith('blob:')) {
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useAuthenticatedMediaSources(mediaPaths = []) {
|
||||
const [authenticatedSources, setAuthenticatedSources] = useState({});
|
||||
const authenticatedSourcesRef = useRef({});
|
||||
|
||||
const resolvedMediaPaths = useMemo(() => {
|
||||
const unique = new Set();
|
||||
if (!Array.isArray(mediaPaths)) {
|
||||
return [];
|
||||
}
|
||||
mediaPaths.forEach((item) => {
|
||||
const resolved = resolveMediaUrl(item);
|
||||
if (resolved) {
|
||||
unique.add(resolved);
|
||||
}
|
||||
});
|
||||
return Array.from(unique);
|
||||
}, [mediaPaths]);
|
||||
const resolvedMediaPathsKey = useMemo(() => resolvedMediaPaths.join('|'), [resolvedMediaPaths]);
|
||||
|
||||
useEffect(() => {
|
||||
authenticatedSourcesRef.current = authenticatedSources;
|
||||
}, [authenticatedSources]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const protectedPaths = resolvedMediaPaths.filter((url) => isProtectedMediaUrl(url));
|
||||
|
||||
if (protectedPaths.length === 0) {
|
||||
setAuthenticatedSources((previous) => {
|
||||
revokeObjectUrls(previous);
|
||||
return {};
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const nextSources = {};
|
||||
const createdObjectUrls = [];
|
||||
|
||||
const fetched = await Promise.all(
|
||||
protectedPaths.map(async (url) => {
|
||||
try {
|
||||
const blob = await api.get(url, { responseType: 'blob' });
|
||||
const blobUrl = window.URL.createObjectURL(blob);
|
||||
createdObjectUrls.push(blobUrl);
|
||||
return [url, blobUrl];
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
fetched.forEach((entry) => {
|
||||
if (!entry) return;
|
||||
const [url, blobUrl] = entry;
|
||||
nextSources[url] = blobUrl;
|
||||
});
|
||||
|
||||
if (cancelled) {
|
||||
createdObjectUrls.forEach((blobUrl) => window.URL.revokeObjectURL(blobUrl));
|
||||
return;
|
||||
}
|
||||
|
||||
setAuthenticatedSources((previous) => {
|
||||
revokeObjectUrls(previous);
|
||||
return nextSources;
|
||||
});
|
||||
})();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [resolvedMediaPathsKey]);
|
||||
|
||||
useEffect(() => () => {
|
||||
revokeObjectUrls(authenticatedSourcesRef.current);
|
||||
}, []);
|
||||
|
||||
const getAuthenticatedMediaSrc = (pathOrUrl) => {
|
||||
const resolved = resolveMediaUrl(pathOrUrl);
|
||||
if (!resolved) {
|
||||
return '';
|
||||
}
|
||||
return authenticatedSources[resolved] || resolved;
|
||||
};
|
||||
|
||||
return {
|
||||
getAuthenticatedMediaSrc,
|
||||
};
|
||||
}
|
||||
|
||||
export default function WorkOrderImageCarousel({ images = [], editMode = false, emptyLabel = 'Nema priložene tehničke dokumentacije' }) {
|
||||
const normalizedImages = useMemo(() => (
|
||||
Array.isArray(images)
|
||||
@@ -51,6 +155,7 @@ export default function WorkOrderImageCarousel({ images = [], editMode = false,
|
||||
: (typeof images === 'string' && images ? [images] : [])
|
||||
), [images]);
|
||||
const [currentImgIdx, setCurrentImgIdx] = useState(0);
|
||||
const { getAuthenticatedMediaSrc } = useAuthenticatedMediaSources(normalizedImages);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentImgIdx(0);
|
||||
@@ -87,7 +192,7 @@ export default function WorkOrderImageCarousel({ images = [], editMode = false,
|
||||
setCurrentImgIdx((prev) => (prev + 1) % normalizedImages.length);
|
||||
};
|
||||
|
||||
const finalSrc = resolveMediaUrl(normalizedImages[currentImgIdx] || '');
|
||||
const finalSrc = getAuthenticatedMediaSrc(normalizedImages[currentImgIdx] || '');
|
||||
const imageTransitions = useTransition(currentImgIdx, {
|
||||
from: { opacity: 0, transform: 'translate3d(8%,0,0) scale(1.01)' },
|
||||
enter: { opacity: 1, transform: 'translate3d(0%,0,0) scale(1)' },
|
||||
@@ -101,7 +206,7 @@ export default function WorkOrderImageCarousel({ images = [], editMode = false,
|
||||
{imageTransitions((style, idx) => (
|
||||
<animated.img
|
||||
key={idx}
|
||||
src={resolveMediaUrl(normalizedImages[idx] || finalSrc)}
|
||||
src={getAuthenticatedMediaSrc(normalizedImages[idx] || finalSrc)}
|
||||
alt={`Dokumentacija s terena ${idx + 1}`}
|
||||
style={style}
|
||||
className="absolute inset-0 h-full w-full object-cover will-change-transform"
|
||||
|
||||
Reference in New Issue
Block a user