mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: enhance icon error handling and loading state management in TotpCodesPage and VaultListIcon components
This commit is contained in:
@@ -126,7 +126,12 @@ function buildConfigResponse(origin: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeIconHost(rawHost: string): string | null {
|
function normalizeIconHost(rawHost: string): string | null {
|
||||||
const decoded = decodeURIComponent(String(rawHost || '').trim()).toLowerCase().replace(/\.+$/, '');
|
let decoded: string;
|
||||||
|
try {
|
||||||
|
decoded = decodeURIComponent(String(rawHost || '').trim()).toLowerCase().replace(/\.+$/, '');
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (!decoded || decoded.includes('/') || decoded.includes('\\')) return null;
|
if (!decoded || decoded.includes('/') || decoded.includes('\\')) return null;
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(`https://${decoded}`);
|
const parsed = new URL(`https://${decoded}`);
|
||||||
|
|||||||
@@ -74,6 +74,18 @@ function TotpListIcon({ cipher }: { cipher: Cipher }) {
|
|||||||
const host = hostFromUri(uri);
|
const host = hostFromUri(uri);
|
||||||
const [errored, setErrored] = useState(() => (host ? failedIconHosts.has(host) : false));
|
const [errored, setErrored] = useState(() => (host ? failedIconHosts.has(host) : false));
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
const markIconError = () => {
|
||||||
|
if (host) failedIconHosts.add(host);
|
||||||
|
setErrored(true);
|
||||||
|
};
|
||||||
|
const syncCachedIconState = (img: HTMLImageElement | null) => {
|
||||||
|
if (!img || !img.complete) return;
|
||||||
|
if (img.naturalWidth > 0) {
|
||||||
|
setLoaded(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
markIconError();
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setErrored(host ? failedIconHosts.has(host) : false);
|
setErrored(host ? failedIconHosts.has(host) : false);
|
||||||
setLoaded(false);
|
setLoaded(false);
|
||||||
@@ -91,11 +103,9 @@ function TotpListIcon({ cipher }: { cipher: Cipher }) {
|
|||||||
alt=""
|
alt=""
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
|
ref={syncCachedIconState}
|
||||||
onLoad={() => setLoaded(true)}
|
onLoad={() => setLoaded(true)}
|
||||||
onError={() => {
|
onError={markIconError}
|
||||||
failedIconHosts.add(host);
|
|
||||||
setErrored(true);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -434,6 +434,18 @@ export function VaultListIcon({ cipher }: { cipher: Cipher }) {
|
|||||||
const host = hostFromUri(uri);
|
const host = hostFromUri(uri);
|
||||||
const [errored, setErrored] = useState(() => (host ? failedIconHosts.has(host) : false));
|
const [errored, setErrored] = useState(() => (host ? failedIconHosts.has(host) : false));
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
const markIconError = () => {
|
||||||
|
if (host) failedIconHosts.add(host);
|
||||||
|
setErrored(true);
|
||||||
|
};
|
||||||
|
const syncCachedIconState = (img: HTMLImageElement | null) => {
|
||||||
|
if (!img || !img.complete) return;
|
||||||
|
if (img.naturalWidth > 0) {
|
||||||
|
setLoaded(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
markIconError();
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setErrored(host ? failedIconHosts.has(host) : false);
|
setErrored(host ? failedIconHosts.has(host) : false);
|
||||||
setLoaded(false);
|
setLoaded(false);
|
||||||
@@ -443,7 +455,7 @@ export function VaultListIcon({ cipher }: { cipher: Cipher }) {
|
|||||||
return (
|
return (
|
||||||
<span className="list-icon-stack">
|
<span className="list-icon-stack">
|
||||||
<span className={`list-icon-fallback ${loaded ? 'hidden' : ''}`}>
|
<span className={`list-icon-fallback ${loaded ? 'hidden' : ''}`}>
|
||||||
<TypeIcon type={Number(cipher.type || 1)} />
|
<Globe size={18} />
|
||||||
</span>
|
</span>
|
||||||
<img
|
<img
|
||||||
className={`list-icon ${loaded ? 'loaded' : ''}`}
|
className={`list-icon ${loaded ? 'loaded' : ''}`}
|
||||||
@@ -451,11 +463,9 @@ export function VaultListIcon({ cipher }: { cipher: Cipher }) {
|
|||||||
alt=""
|
alt=""
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
|
ref={syncCachedIconState}
|
||||||
onLoad={() => setLoaded(true)}
|
onLoad={() => setLoaded(true)}
|
||||||
onError={() => {
|
onError={markIconError}
|
||||||
failedIconHosts.add(host);
|
|
||||||
setErrored(true);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -376,13 +376,13 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-icon-fallback.hidden {
|
.list-icon-stack > .list-icon-fallback {
|
||||||
opacity: 0;
|
opacity: 1;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-icon-stack > .list-icon-fallback {
|
.list-icon-stack > .list-icon-fallback.hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
z-index: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-icon-fallback {
|
.list-icon-fallback {
|
||||||
|
|||||||
Reference in New Issue
Block a user