- Replaced `window.electron.invoke` calls with equivalent `tauri` function calls for all IPC interactions. - Removed `electron.d.ts` TypeScript definitions as they are no longer needed. - Updated related logic for offline/online state synchronization. - Added `types.rs` and `shared/mod.rs` modules to support Tauri IPC integration with Rust enums and shared logic. - Refactored IPC request queues to use updated handler names for consistency with Tauri.
151 lines
5.6 KiB
TypeScript
151 lines
5.6 KiB
TypeScript
'use client'
|
|
import React, {ReactNode, useContext, useState} from 'react';
|
|
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
|
import {faArrowDown, faArrowUp, faSpinner} from '@fortawesome/free-solid-svg-icons';
|
|
import {useTranslations} from 'next-intl';
|
|
import {SessionContext} from '@/context/SessionContext';
|
|
import {AlertContext} from '@/context/AlertContext';
|
|
import {LangContext, LangContextProps} from '@/context/LangContext';
|
|
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
|
|
import {LocalSyncQueueContext, LocalSyncQueueContextProps} from '@/context/SyncQueueContext';
|
|
import {BooksSyncContext, BooksSyncContextProps} from '@/context/BooksSyncContext';
|
|
import {BookContext} from '@/context/BookContext';
|
|
import {SyncedBook} from '@/lib/models/SyncedBook';
|
|
import System from '@/lib/models/System';
|
|
import * as tauri from '@/lib/tauri';
|
|
|
|
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} = useContext(SessionContext);
|
|
const {errorMessage, successMessage} = useContext(AlertContext);
|
|
const {lang} = useContext<LangContextProps>(LangContext);
|
|
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
|
|
const {addToQueue} = useContext<LocalSyncQueueContextProps>(LocalSyncQueueContext);
|
|
const {localSyncedBooks} = useContext<BooksSyncContextProps>(BooksSyncContext);
|
|
const {book} = useContext(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: bookElementId,
|
|
field: field,
|
|
value: currentValue
|
|
};
|
|
|
|
let response: SeriesSyncUploadResponse;
|
|
|
|
if (isCurrentlyOffline() || book?.localBook) {
|
|
response = await tauri.seriesSyncUpload(requestData) as SeriesSyncUploadResponse;
|
|
} else {
|
|
response = await System.authPostToServer<SeriesSyncUploadResponse>(
|
|
'series/propagate',
|
|
requestData,
|
|
session.accessToken,
|
|
lang
|
|
);
|
|
|
|
if (book?.bookId && localSyncedBooks.find((sb: SyncedBook): boolean => sb.id === book.bookId)) {
|
|
addToQueue('series_sync_upload', {data: 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-start gap-2 w-full">
|
|
<button
|
|
onClick={handleDownload}
|
|
disabled={!hasSeriesDiff}
|
|
title={t('syncField.downloadTooltip')}
|
|
className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center transition-all duration-200 ${
|
|
hasSeriesDiff
|
|
? 'bg-blue-500/20 text-blue-400 hover:bg-blue-500/40 hover:scale-110 cursor-pointer'
|
|
: 'bg-secondary/30 text-muted cursor-not-allowed opacity-50'
|
|
}`}
|
|
>
|
|
<FontAwesomeIcon icon={faArrowDown} className="w-3.5 h-3.5"/>
|
|
</button>
|
|
|
|
<div className="flex-grow">
|
|
{children}
|
|
</div>
|
|
|
|
<button
|
|
onClick={handleUpload}
|
|
disabled={isUploading || !hasSeriesDiff}
|
|
title={t('syncField.uploadTooltip')}
|
|
className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center transition-all duration-200 ${
|
|
hasSeriesDiff && !isUploading
|
|
? 'bg-primary/20 text-primary hover:bg-primary/40 hover:scale-110 cursor-pointer'
|
|
: 'bg-secondary/30 text-muted cursor-not-allowed opacity-50'
|
|
}`}
|
|
>
|
|
{isUploading ? (
|
|
<FontAwesomeIcon icon={faSpinner} className="w-3.5 h-3.5 animate-spin"/>
|
|
) : (
|
|
<FontAwesomeIcon icon={faArrowUp} className="w-3.5 h-3.5"/>
|
|
)}
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|