diff --git a/web/src/pages/desktop/menu/settings/offlineupdate/index.tsx b/web/src/pages/desktop/menu/settings/offlineupdate/index.tsx new file mode 100644 index 0000000..45ab004 --- /dev/null +++ b/web/src/pages/desktop/menu/settings/offlineupdate/index.tsx @@ -0,0 +1,211 @@ +import { useEffect, useState } from 'react'; +import { LoadingOutlined, SmileOutlined } from '@ant-design/icons'; +import { Button, Input, Result, Spin } from 'antd'; +import { useTranslation } from 'react-i18next'; +import clsx from 'clsx'; + +import * as api from '@/api/application.ts'; + +type UpdateProps = { + setIsLocked: (isClosable: boolean) => void; +}; + +type Status = '' | 'loading' | 'updating' | 'outdated' | 'latest' | 'latests' | 'failed'; + +export const OfflineUpdate = ({ setIsLocked }: UpdateProps) => { + const { t } = useTranslation(); + + const [status, setStatus] = useState(''); + const [currentVersion, setCurrentVersion] = useState(''); + const [errMsg, setErrMsg] = useState(''); + const [selectedFile, setSelectedFile] = useState(null); + const [isDragging, setIsDragging] = useState(false); + + useEffect(() => { + checkForUpdates(); + }, []); + + function checkForUpdates() { + if (status === 'loading') return; + setStatus('loading'); + + setIsLocked(true); + + api + .getCurrentVersion() + .then((rsp: any) => { + if (rsp.code !== 0 || !rsp.data) { + setStatus('failed'); + setErrMsg(t('settings.offlineupdate.queryFailed')); + return; + } + + setStatus('latest'); + setCurrentVersion(rsp.data.current); + }) + .catch(() => { + setStatus('failed'); + setErrMsg(t('settings.offlineupdate.queryFailed')); + }); + + setIsLocked(false); + } + + function handleFileChange(e: React.ChangeEvent) { + const file = e.target.files?.[0] ?? null; + if (!file || !file.name.toLowerCase().endsWith(".tar.gz")) { + setStatus('failed'); + setErrMsg(t('settings.offlineupdate.noupdatefile')); + return; + } + setSelectedFile(file); + } + + function upload(file: File | null) { + if (!file) return; + + if (!file.name.toLowerCase().endsWith(".tar.gz")) { + setStatus('failed'); + setErrMsg(t('settings.offlineupdate.noupdatefile')); + return; + } + + setStatus('updating'); + setErrMsg(''); + + const formData = new FormData(); + formData.append("file", file); + + fetch("/api/download/file", { + method: "POST", + body: formData, + }) + .then((rsp: Response) => { + // Prüfen ob HTTP OK + if (!rsp.ok) throw new Error(`HTTP error ${rsp.status}`); + return rsp.json(); // JSON-Payload parsen + }) + .then((rspj: any) => { + // Jetzt rspj ist das tatsächliche JSON + if (rspj.code !== 0 || !rspj.data) { + setStatus('failed'); + setErrMsg(rspj.msg || 'Unknown error'); + console.log(rspj); + return; + } + checkForUpdates(); + }) + .catch((err: any) => { + setStatus('failed'); + setErrMsg(err?.message || 'Failed'); + }); +} + + + return ( + <> +
{t('settings.offlineupdate.title')}
+ +
+ +
+ {status === 'loading' && ( +
+ } size="large" /> +
+ )} + + {status === 'latest' && ( +
+
{t('settings.offlineupdate.inputfile')}
+
+
{ + e.preventDefault(); + setIsDragging(false); + const file = e.dataTransfer.files?.[0] ?? null; + if (!file || !file.name.toLowerCase().endsWith(".tar.gz")) { + setStatus('failed'); + setErrMsg(t('settings.offlineupdate.noupdatefile')); + return; + } + setStatus('latest'); + setErrMsg(''); + setSelectedFile(file); + }} + onDragOver={(e) => { + e.preventDefault(); + setIsDragging(true); // Datei wird über den Bereich gezogen + }} + onDragLeave={(e) => { + e.preventDefault(); + setIsDragging(false); // Maus verlässt Bereich + }} + onClick={() => { + document.getElementById("file-upload")?.click() + }} + > + + {selectedFile ? selectedFile.name : t('download.uploadbox')} + + + +
+ +
+
+ )} + + {status === 'updating' && ( +
+ + {t('settings.offlineupdate.updating')} +
+ )} + + {status === 'latests' && ( + } + title={currentVersion} + subTitle={t('settings.offlineupdate.isLatest')} + extra={[ + + ]} + /> + )} + + {status === 'failed' && } + +
+ +
+
+ + ); +};