- Integrated sync queue mechanisms with `LocalSyncQueueContext` for offline data handling. - Updated key sync-related services (e.g., book, chapter, series) to support offline-first functionality. - Removed redundant database fetch methods to optimize repository logic and improve maintainability. - Enhanced Tauri IPC usage for sync operations and removed legacy methods in Rust services.
127 lines
5.2 KiB
TypeScript
127 lines
5.2 KiB
TypeScript
'use client'
|
|
import React, {ReactNode, useContext, useState} from 'react';
|
|
import {ArrowDown, ArrowUp} from 'lucide-react';
|
|
import {useTranslations} from '@/lib/i18n';
|
|
import {SessionContext, SessionContextProps} from '@/context/SessionContext';
|
|
import {AlertContext, AlertContextProps} from '@/context/AlertContext';
|
|
import {LangContext, LangContextProps} from '@/context/LangContext';
|
|
import {apiPost} from '@/lib/api/client';
|
|
import {isDesktop} from '@/lib/configs';
|
|
import * as tauri from '@/lib/tauri';
|
|
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
|
|
import {BookContext, BookContextProps} from '@/context/BookContext';
|
|
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
|
|
import {SyncedBook} from '@/lib/types/synced-book';
|
|
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
|
|
import IconButton from '@/components/ui/IconButton';
|
|
|
|
export type SyncElementType = 'character' | 'world' | 'location' | 'spell';
|
|
|
|
interface SyncFieldWrapperProps {
|
|
children: ReactNode;
|
|
seriesElementId: string | null | undefined;
|
|
seriesValue: string;
|
|
currentValue: string;
|
|
bookElementId: string;
|
|
field: string;
|
|
elementType: SyncElementType;
|
|
onDownload: () => void;
|
|
onSyncComplete?: () => void;
|
|
}
|
|
|
|
interface SeriesSyncUploadResponse {
|
|
success: boolean;
|
|
updatedCount: number;
|
|
}
|
|
|
|
export default function SyncFieldWrapper({
|
|
children,
|
|
seriesElementId,
|
|
seriesValue,
|
|
currentValue,
|
|
bookElementId,
|
|
field,
|
|
elementType,
|
|
onDownload,
|
|
onSyncComplete
|
|
}: SyncFieldWrapperProps) {
|
|
const t = useTranslations();
|
|
const {session}: SessionContextProps = useContext<SessionContextProps>(SessionContext);
|
|
const {errorMessage, successMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
|
|
const {lang}: LangContextProps = useContext<LangContextProps>(LangContext);
|
|
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
|
|
const {addToQueue}: LocalSyncQueueContextProps = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
|
|
const {localSyncedBooks}: BooksSyncContextProps = useContext<BooksSyncContextProps>(BooksSyncContext);
|
|
const {book}: BookContextProps = useContext<BookContextProps>(BookContext);
|
|
|
|
const [isUploading, setIsUploading] = useState<boolean>(false);
|
|
|
|
const isLinkedToSeries: boolean = !!seriesElementId;
|
|
const hasSeriesDiff: boolean = isLinkedToSeries && seriesValue !== currentValue;
|
|
|
|
async function handleUpload(): Promise<void> {
|
|
if (!seriesElementId || isUploading) return;
|
|
|
|
setIsUploading(true);
|
|
try {
|
|
const requestData = {type: elementType, bookElementId, field, value: currentValue};
|
|
let response: SeriesSyncUploadResponse;
|
|
|
|
if (isDesktop && (isCurrentlyOffline() || book?.localBook)) {
|
|
response = await tauri.invoke<SeriesSyncUploadResponse>('series_sync_upload', {data: requestData});
|
|
} else {
|
|
response = await apiPost<SeriesSyncUploadResponse>('series/propagate', requestData, session.accessToken, lang);
|
|
if (isDesktop && book?.bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book.bookId)) {
|
|
addToQueue('series_sync_upload', requestData);
|
|
}
|
|
}
|
|
if (response.success) {
|
|
successMessage(t('syncField.uploadSuccess', {count: response.updatedCount}));
|
|
if (onSyncComplete) {
|
|
onSyncComplete();
|
|
}
|
|
}
|
|
} catch (e: unknown) {
|
|
if (e instanceof Error) {
|
|
errorMessage(e.message);
|
|
}
|
|
} finally {
|
|
setIsUploading(false);
|
|
}
|
|
}
|
|
|
|
function handleDownload(): void {
|
|
onDownload();
|
|
}
|
|
|
|
if (!isLinkedToSeries) {
|
|
return <>{children}</>;
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-2 w-full">
|
|
<IconButton
|
|
icon={ArrowDown}
|
|
variant={hasSeriesDiff ? 'primary' : 'muted'}
|
|
size="sm"
|
|
onClick={handleDownload}
|
|
disabled={!hasSeriesDiff}
|
|
tooltip={t('syncField.downloadTooltip')}
|
|
/>
|
|
|
|
<div className="flex-grow">
|
|
{children}
|
|
</div>
|
|
|
|
<IconButton
|
|
icon={ArrowUp}
|
|
variant={hasSeriesDiff && !isUploading ? 'primary' : 'muted'}
|
|
size="sm"
|
|
onClick={handleUpload}
|
|
disabled={isUploading || !hasSeriesDiff}
|
|
tooltip={t('syncField.uploadTooltip')}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|