refactor: Remove unused APIs and data structures, optimize loading state component styles

This commit is contained in:
shuaiplus
2026-04-28 23:01:23 +08:00
parent 1b0386bf78
commit 69b98f9e67
18 changed files with 221 additions and 258 deletions
+33 -31
View File
@@ -3,13 +3,13 @@ import { useEffect } from 'preact/hooks';
import { Link, Route, Switch } from 'wouter';
import { ArrowUpDown, Cloud, LogOut, Settings as SettingsIcon, Shield, ShieldUser } from 'lucide-preact';
import type { ImportAttachmentFile, ImportResultSummary } from '@/components/ImportPage';
import VaultPage from '@/components/VaultPage';
import type { AdminBackupImportResponse, AdminBackupRunResponse, AdminBackupSettings, RemoteBackupBrowserResponse } from '@/lib/api/backup';
import type { CiphersImportPayload } from '@/lib/api/vault';
import { t } from '@/lib/i18n';
import type { AdminInvite, AdminUser, AuthorizedDevice, Cipher, Folder as VaultFolder, Profile, Send, SendDraft, SessionState, VaultDraft } from '@/lib/types';
import type { ExportRequest } from '@/lib/export-formats';
const VaultPage = lazy(() => import('@/components/VaultPage'));
const SendsPage = lazy(() => import('@/components/SendsPage'));
const TotpCodesPage = lazy(() => import('@/components/TotpCodesPage'));
const SettingsPage = lazy(() => import('@/components/SettingsPage'));
@@ -181,36 +181,38 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
</Suspense>
</Route>
<Route path="/vault">
<VaultPage
ciphers={props.decryptedCiphers}
folders={props.decryptedFolders}
loading={props.ciphersLoading || props.foldersLoading}
emailForReprompt={props.profile?.email || props.session?.email || ''}
onRefresh={props.onRefreshVault}
onCreate={props.onCreateVaultItem}
onUpdate={props.onUpdateVaultItem}
onDelete={props.onDeleteVaultItem}
onArchive={props.onArchiveVaultItem}
onUnarchive={props.onUnarchiveVaultItem}
onBulkDelete={props.onBulkDeleteVaultItems}
onBulkPermanentDelete={props.onBulkPermanentDeleteVaultItems}
onBulkRestore={props.onBulkRestoreVaultItems}
onBulkArchive={props.onBulkArchiveVaultItems}
onBulkUnarchive={props.onBulkUnarchiveVaultItems}
onBulkMove={props.onBulkMoveVaultItems}
onVerifyMasterPassword={props.onVerifyMasterPassword}
onNotify={props.onNotify}
onCreateFolder={props.onCreateFolder}
onRenameFolder={props.onRenameFolder}
onDeleteFolder={props.onDeleteFolder}
onBulkDeleteFolders={props.onBulkDeleteFolders}
onDownloadAttachment={props.onDownloadVaultAttachment}
downloadingAttachmentKey={props.downloadingAttachmentKey}
attachmentDownloadPercent={props.attachmentDownloadPercent}
uploadingAttachmentName={props.uploadingAttachmentName}
attachmentUploadPercent={props.attachmentUploadPercent}
mobileSidebarToggleKey={props.mobileSidebarToggleKey}
/>
<Suspense fallback={<RouteContentFallback />}>
<VaultPage
ciphers={props.decryptedCiphers}
folders={props.decryptedFolders}
loading={props.ciphersLoading || props.foldersLoading}
emailForReprompt={props.profile?.email || props.session?.email || ''}
onRefresh={props.onRefreshVault}
onCreate={props.onCreateVaultItem}
onUpdate={props.onUpdateVaultItem}
onDelete={props.onDeleteVaultItem}
onArchive={props.onArchiveVaultItem}
onUnarchive={props.onUnarchiveVaultItem}
onBulkDelete={props.onBulkDeleteVaultItems}
onBulkPermanentDelete={props.onBulkPermanentDeleteVaultItems}
onBulkRestore={props.onBulkRestoreVaultItems}
onBulkArchive={props.onBulkArchiveVaultItems}
onBulkUnarchive={props.onBulkUnarchiveVaultItems}
onBulkMove={props.onBulkMoveVaultItems}
onVerifyMasterPassword={props.onVerifyMasterPassword}
onNotify={props.onNotify}
onCreateFolder={props.onCreateFolder}
onRenameFolder={props.onRenameFolder}
onDeleteFolder={props.onDeleteFolder}
onBulkDeleteFolders={props.onBulkDeleteFolders}
onDownloadAttachment={props.onDownloadVaultAttachment}
downloadingAttachmentKey={props.downloadingAttachmentKey}
attachmentDownloadPercent={props.attachmentDownloadPercent}
uploadingAttachmentName={props.uploadingAttachmentName}
attachmentUploadPercent={props.attachmentUploadPercent}
mobileSidebarToggleKey={props.mobileSidebarToggleKey}
/>
</Suspense>
</Route>
<Route path={props.settingsAccountRoute}>
{props.profile && (
+23
View File
@@ -0,0 +1,23 @@
interface LoadingStateProps {
lines?: number;
compact?: boolean;
card?: boolean;
className?: string;
}
export default function LoadingState(props: LoadingStateProps) {
const lines = Math.max(1, props.lines || 4);
return (
<div className={`${props.card ? 'loading-state-card card' : 'loading-state'}${props.compact ? ' compact' : ''}${props.className ? ` ${props.className}` : ''}`} aria-hidden="true">
{Array.from({ length: lines }, (_, index) => (
<div key={index} className="loading-state-row">
<div className="loading-state-icon shimmer" />
<div className="loading-state-text">
<div className="loading-state-line shimmer" />
<div className="loading-state-line short shimmer" />
</div>
</div>
))}
</div>
);
}
+4 -1
View File
@@ -1,6 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { CheckCheck, ChevronLeft, Copy, Eye, EyeOff, File, FileText, LayoutGrid, Pencil, Plus, RefreshCw, Save, Send as SendIcon, Trash2, X } from 'lucide-preact';
import { copyTextToClipboard } from '@/lib/clipboard';
import LoadingState from '@/components/LoadingState';
import type { Send, SendDraft } from '@/lib/types';
import { t } from '@/lib/i18n';
@@ -322,6 +323,7 @@ export default function SendsPage(props: SendsPageProps) {
</button>
</div>
<div className="list-panel">
{props.loading && !filteredSends.length && <LoadingState lines={6} compact />}
{filteredSends.map((send, index) => (
<div
key={send.id}
@@ -375,7 +377,7 @@ export default function SendsPage(props: SendsPageProps) {
</button>
</div>
))}
{!filteredSends.length && <div className="empty">{t('txt_no_sends')}</div>}
{!props.loading && !filteredSends.length && <div className="empty">{t('txt_no_sends')}</div>}
</div>
</section>
@@ -553,6 +555,7 @@ export default function SendsPage(props: SendsPageProps) {
</div>
</div>
)}
{!isEditing && !selectedSend && props.loading && <LoadingState card lines={4} />}
</section>
</div>
);
+3 -11
View File
@@ -21,7 +21,8 @@ import { copyTextToClipboard as copyTextWithFeedback } from '@/lib/clipboard';
import { calcTotpNow } from '@/lib/crypto';
import { t } from '@/lib/i18n';
import type { Cipher } from '@/lib/types';
import { isCipherVisibleInNormalVault, websiteIconUrl } from '@/components/vault/vault-page-helpers';
import LoadingState from '@/components/LoadingState';
import { hostFromUri, isCipherVisibleInNormalVault, websiteIconUrl } from '@/components/vault/vault-page-helpers';
interface TotpCodesPageProps {
ciphers: Cipher[];
@@ -62,16 +63,6 @@ function firstCipherUri(cipher: Cipher): string {
return '';
}
function hostFromUri(uri: string): string {
if (!uri.trim()) return '';
try {
const normalized = /^https?:\/\//i.test(uri) ? uri : `https://${uri}`;
return new URL(normalized).hostname || '';
} catch {
return '';
}
}
function TotpListIcon({ cipher }: { cipher: Cipher }) {
const host = useMemo(() => hostFromUri(firstCipherUri(cipher)), [cipher]);
const iconStackRef = useRef<HTMLSpanElement | null>(null);
@@ -447,6 +438,7 @@ export default function TotpCodesPage(props: TotpCodesPageProps) {
className="totp-codes-list"
style={{ '--totp-columns': String(columnCount) } as Record<string, string>}
>
{!totpItems.length && props.loading && <LoadingState lines={6} />}
{!totpItems.length && !props.loading && <div className="empty">{t('txt_no_verification_codes')}</div>}
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={sortableTotpItems} strategy={rectSortingStrategy}>
+2 -1
View File
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import LoadingState from '@/components/LoadingState';
import VaultDialogs from '@/components/vault/VaultDialogs';
import VaultDetailView from '@/components/vault/VaultDetailView';
import VaultEditor from '@/components/vault/VaultEditor';
@@ -1139,7 +1140,7 @@ const folderName = useCallback((id: string | null | undefined): string => {
</div>
)}
{!isEditing && !selectedCipher && <div className="empty card">{t('txt_select_an_item')}</div>}
{!isEditing && !selectedCipher && (props.loading ? <LoadingState card lines={5} /> : <div className="empty card">{t('txt_select_an_item')}</div>)}
</section>
</div>
@@ -2,6 +2,7 @@ import type { RefObject } from 'preact';
import { memo } from 'preact/compat';
import { createPortal } from 'preact/compat';
import { Archive, ArrowUpDown, Check, CheckCheck, FolderInput, Plus, RefreshCw, RotateCcw, Trash2, X } from 'lucide-preact';
import LoadingState from '@/components/LoadingState';
import type { Cipher } from '@/lib/types';
import { t } from '@/lib/i18n';
import {
@@ -234,6 +235,7 @@ export default function VaultListPanel(props: VaultListPanelProps) {
</div>
<div className="list-panel" ref={props.listPanelRef} onScroll={(event) => props.onScroll((event.currentTarget as HTMLDivElement).scrollTop)}>
{props.loading && !props.filteredCiphers.length && <LoadingState lines={7} compact />}
{!!props.filteredCiphers.length && (
<div style={{ paddingTop: `${props.virtualRange.padTop}px`, paddingBottom: `${props.virtualRange.padBottom}px` }}>
{props.visibleCiphers.map((cipher) => (
@@ -249,7 +251,7 @@ export default function VaultListPanel(props: VaultListPanelProps) {
))}
</div>
)}
{!props.filteredCiphers.length && <div className="empty">{t('txt_no_items')}</div>}
{!props.loading && !props.filteredCiphers.length && <div className="empty">{t('txt_no_items')}</div>}
</div>
</section>
);