mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 08:19:52 +03:00
alova upload experiments
This commit is contained in:
@@ -1,15 +1,10 @@
|
||||
// import { useCallback, useEffect } from 'react';
|
||||
import { Navigate, Routes, Route } from 'react-router-dom';
|
||||
import Dashboard from './project/Dashboard';
|
||||
import Help from './project/Help';
|
||||
import Settings from './project/Settings';
|
||||
// import type { AxiosError } from 'axios';
|
||||
import type { FC } from 'react';
|
||||
|
||||
// import * as AuthenticationApi from 'api/authentication';
|
||||
// import { AXIOS } from 'api/endpoints';
|
||||
import { Layout, RequireAdmin } from 'components';
|
||||
|
||||
import AccessPoint from 'framework/ap/AccessPoint';
|
||||
import Mqtt from 'framework/mqtt/Mqtt';
|
||||
import NetworkConnection from 'framework/network/NetworkConnection';
|
||||
@@ -18,10 +13,9 @@ import Security from 'framework/security/Security';
|
||||
import System from 'framework/system/System';
|
||||
|
||||
const AuthenticatedRouting: FC = () => (
|
||||
// TODO not sure if this is needed, to redirect on 401. If so add incerceptor to Alova
|
||||
// const location = useLocation();
|
||||
// const navigate = useNavigate();
|
||||
|
||||
// TODO not sure if this is needed, to redirect on 401. If so add incerceptor to Alova
|
||||
// const handleApiResponseError = useCallback(
|
||||
// (error: AxiosError) => {
|
||||
// if (error.response && error.response.status === 401) {
|
||||
@@ -32,7 +26,6 @@ const AuthenticatedRouting: FC = () => (
|
||||
// },
|
||||
// [location, navigate]
|
||||
// );
|
||||
|
||||
// useEffect(() => {
|
||||
// const axiosHandlerId = AXIOS.interceptors.response.use((response) => response, handleApiResponseError);
|
||||
// return () => AXIOS.interceptors.response.eject(axiosHandlerId);
|
||||
@@ -68,4 +61,5 @@ const AuthenticatedRouting: FC = () => (
|
||||
</Routes>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default AuthenticatedRouting;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { xhrRequestAdapter } from '@alova/adapter-xhr';
|
||||
import { createAlova } from 'alova';
|
||||
import ReactHook from 'alova/react';
|
||||
import axios from 'axios';
|
||||
import { unpack } from '../api/unpack';
|
||||
|
||||
import type { AxiosPromise, CancelToken, AxiosProgressEvent } from 'axios';
|
||||
|
||||
export const ACCESS_TOKEN = 'access_token';
|
||||
|
||||
const host = window.location.host;
|
||||
@@ -14,11 +11,10 @@ export const EVENT_SOURCE_ROOT = 'http://' + host + '/es/';
|
||||
|
||||
export const alovaInstance = createAlova({
|
||||
statesHook: ReactHook,
|
||||
timeout: 3000,
|
||||
// timeout: 3000, // timeout not used because of uploading firmware
|
||||
localCache: {
|
||||
GET: {
|
||||
mode: 'placeholder',
|
||||
// see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode
|
||||
mode: 'placeholder', // see https://alova.js.org/learning/response-cache/#cache-replaceholder-mode
|
||||
expire: 2000
|
||||
}
|
||||
},
|
||||
@@ -61,42 +57,3 @@ export const alovaInstanceGH = createAlova({
|
||||
statesHook: ReactHook,
|
||||
requestAdapter: xhrRequestAdapter()
|
||||
});
|
||||
|
||||
export const AXIOS = axios.create({
|
||||
baseURL: '/rest/',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
transformRequest: [
|
||||
(data, headers) => {
|
||||
if (headers) {
|
||||
if (localStorage.getItem(ACCESS_TOKEN)) {
|
||||
headers.Authorization = 'Bearer ' + localStorage.getItem(ACCESS_TOKEN);
|
||||
}
|
||||
if (headers['Content-Type'] !== 'application/json') {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return JSON.stringify(data);
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// TODO fileupload move to alova
|
||||
// see https://alova.js.org/next-step/download-upload-progress
|
||||
export interface FileUploadConfig {
|
||||
cancelToken?: CancelToken;
|
||||
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
|
||||
}
|
||||
|
||||
export const startUploadFile = (url: string, file: File, config?: FileUploadConfig): AxiosPromise<void> => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return AXIOS.post(url, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
...(config || {})
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { alovaInstance, alovaInstanceGH, startUploadFile } from './endpoints';
|
||||
import type { FileUploadConfig } from './endpoints';
|
||||
import type { AxiosPromise } from 'axios';
|
||||
|
||||
import { alovaInstance, alovaInstanceGH } from './endpoints';
|
||||
import type { OTASettings, SystemStatus, LogSettings, Version } from 'types';
|
||||
|
||||
// SystemStatus - also used to ping in Restart monitor
|
||||
@@ -32,6 +29,7 @@ export const getStableVersion = () =>
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const getDevVersion = () =>
|
||||
alovaInstanceGH.Get<Version>('releases/tags/latest', {
|
||||
transformData(reponse: any) {
|
||||
@@ -43,6 +41,10 @@ export const getDevVersion = () =>
|
||||
}
|
||||
});
|
||||
|
||||
// TODO fileupload move to alova
|
||||
export const uploadFile = (file: File, config?: FileUploadConfig): AxiosPromise<void> =>
|
||||
startUploadFile('/uploadFile', file, config);
|
||||
export const uploadFile = (file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return alovaInstance.Post('/rest/uploadFile', formData, {
|
||||
enableUpload: true
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Box, Button, LinearProgress, Typography, useTheme } from '@mui/material
|
||||
import { Fragment } from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import type { Theme } from '@mui/material';
|
||||
import type { AxiosProgressEvent } from 'axios';
|
||||
import type { Progress } from 'alova';
|
||||
import type { FC } from 'react';
|
||||
import type { DropzoneState } from 'react-dropzone';
|
||||
|
||||
@@ -26,11 +26,13 @@ const getBorderColor = (theme: Theme, props: DropzoneState) => {
|
||||
export interface SingleUploadProps {
|
||||
onDrop: (acceptedFiles: File[]) => void;
|
||||
onCancel: () => void;
|
||||
uploading: boolean;
|
||||
progress?: AxiosProgressEvent;
|
||||
isUploading: boolean;
|
||||
progress?: Progress;
|
||||
// TODO remove
|
||||
// progress?: AxiosProgressEvent;
|
||||
}
|
||||
|
||||
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, progress }) => {
|
||||
const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, isUploading, progress }) => {
|
||||
const dropzoneState = useDropzone({
|
||||
onDrop,
|
||||
accept: {
|
||||
@@ -38,7 +40,7 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
'application/json': ['.json'],
|
||||
'text/plain': ['.md5']
|
||||
},
|
||||
disabled: uploading,
|
||||
disabled: isUploading,
|
||||
multiple: false
|
||||
});
|
||||
const { getRootProps, getInputProps } = dropzoneState;
|
||||
@@ -46,8 +48,11 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
// TODO remove debug
|
||||
console.log('progress', progress?.loaded);
|
||||
|
||||
const progressText = () => {
|
||||
if (uploading) {
|
||||
if (isUploading) {
|
||||
if (progress?.total) {
|
||||
return LL.UPLOADING() + ': ' + Math.round((progress.loaded * 100) / progress.total) + '%';
|
||||
}
|
||||
@@ -68,7 +73,7 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
color: theme.palette.grey[400],
|
||||
transition: 'border .24s ease-in-out',
|
||||
width: '100%',
|
||||
cursor: uploading ? 'default' : 'pointer',
|
||||
cursor: isUploading ? 'default' : 'pointer',
|
||||
borderColor: getBorderColor(theme, dropzoneState)
|
||||
}
|
||||
})}
|
||||
@@ -77,7 +82,7 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
|
||||
<Box flexDirection="column" display="flex" alignItems="center">
|
||||
<CloudUploadIcon fontSize="large" />
|
||||
<Typography variant="h6">{progressText()}</Typography>
|
||||
{uploading && (
|
||||
{isUploading && (
|
||||
<Fragment>
|
||||
<Box width="100%" p={2}>
|
||||
<LinearProgress
|
||||
|
||||
@@ -1,72 +1,94 @@
|
||||
import axios from 'axios';
|
||||
import { useRequest } from 'alova';
|
||||
// import axios from 'axios';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import type { FileUploadConfig } from 'api/endpoints';
|
||||
import type { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
|
||||
// import type { FileUploadConfig } from 'api/endpoints';
|
||||
// import type { AxiosPromise, CancelTokenSource, AxiosProgressEvent } from 'axios';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
|
||||
interface MediaUploadOptions {
|
||||
// TODO fileupload move to alova
|
||||
upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||
// upload: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||
upload: (file: File) => Promise<void>;
|
||||
}
|
||||
|
||||
const useFileUpload = ({ upload }: MediaUploadOptions) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const [uploading, setUploading] = useState<boolean>(false);
|
||||
// const [uploading, setUploading] = useState<boolean>(false);
|
||||
const [md5, setMd5] = useState<string>('');
|
||||
const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>();
|
||||
const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
|
||||
// const [uploadProgress, setUploadProgress] = useState<AxiosProgressEvent>();
|
||||
// const [uploadCancelToken, setUploadCancelToken] = useState<CancelTokenSource>();
|
||||
|
||||
const { uploading, send: sendUpload } = useRequest(SystemApi.uploadFile, {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const resetUploadingStates = () => {
|
||||
setUploading(false);
|
||||
setUploadProgress(undefined);
|
||||
setUploadCancelToken(undefined);
|
||||
// setUploading(false);
|
||||
// setUploadProgress(undefined);
|
||||
// setUploadCancelToken(undefined);
|
||||
setMd5('');
|
||||
};
|
||||
|
||||
const cancelUpload = useCallback(() => {
|
||||
uploadCancelToken?.cancel();
|
||||
resetUploadingStates();
|
||||
}, [uploadCancelToken]);
|
||||
// const cancelUpload = useCallback(() => {
|
||||
// uploadCancelToken?.cancel();
|
||||
// resetUploadingStates();
|
||||
// }, [uploadCancelToken]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
uploadCancelToken?.cancel();
|
||||
},
|
||||
[uploadCancelToken]
|
||||
);
|
||||
// useEffect(
|
||||
// () => () => {
|
||||
// uploadCancelToken?.cancel();
|
||||
// },
|
||||
// [uploadCancelToken]
|
||||
// );
|
||||
|
||||
// TODO fileupload move to alova
|
||||
const uploadFile = async (images: File[]) => {
|
||||
try {
|
||||
const cancelToken = axios.CancelToken.source();
|
||||
setUploadCancelToken(cancelToken);
|
||||
setUploading(true);
|
||||
const response = await upload(images[0], {
|
||||
onUploadProgress: setUploadProgress,
|
||||
cancelToken: cancelToken.token
|
||||
});
|
||||
resetUploadingStates();
|
||||
if (response.status === 200) {
|
||||
toast.success(LL.UPLOAD() + ' ' + LL.SUCCESSFUL());
|
||||
} else if (response.status === 201) {
|
||||
setMd5(String(response.data));
|
||||
toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isCancel(error)) {
|
||||
toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
||||
} else {
|
||||
resetUploadingStates();
|
||||
toast.error(LL.UPLOAD() + ' ' + LL.FAILED(0));
|
||||
}
|
||||
}
|
||||
// TODO make it single file
|
||||
const uploadFile = async (files: File[]) => {
|
||||
// TODO remove debug
|
||||
console.log('useFileUpload.ts:uploadFile:' + files[0].name, files[0].size);
|
||||
|
||||
await sendUpload(files[0]);
|
||||
|
||||
// const response = await SystemApi.startUploadFile(files[0]);
|
||||
// console.log(response.status);
|
||||
|
||||
// const response = await upload(files[0], {
|
||||
// onUploadProgress: setUploadProgress,
|
||||
// cancelToken: cancelToken.token
|
||||
// });
|
||||
|
||||
// try {
|
||||
// const cancelToken = axios.CancelToken.source();
|
||||
// setUploadCancelToken(cancelToken);
|
||||
// setUploading(true);
|
||||
// const response = await upload(images[0], {
|
||||
// onUploadProgress: setUploadProgress,
|
||||
// cancelToken: cancelToken.token
|
||||
// });
|
||||
// resetUploadingStates();
|
||||
// if (response.status === 200) {
|
||||
// toast.success(LL.UPLOAD() + ' ' + LL.SUCCESSFUL());
|
||||
// } else if (response.status === 201) {
|
||||
// setMd5(String(response.data));
|
||||
// toast.success(LL.UPLOAD() + ' MD5 ' + LL.SUCCESSFUL());
|
||||
// }
|
||||
// } catch (error) {
|
||||
// if (axios.isCancel(error)) {
|
||||
// toast.warning(LL.UPLOAD() + ' ' + LL.ABORTED());
|
||||
// } else {
|
||||
// resetUploadingStates();
|
||||
// toast.error(LL.UPLOAD() + ' ' + LL.FAILED(0));
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
return [uploadFile, cancelUpload, uploading, uploadProgress, md5] as const;
|
||||
return [uploadFile, uploading, md5] as const;
|
||||
// return [uploadFile, cancelUpload, uploading, uploadProgress, md5] as const;
|
||||
};
|
||||
|
||||
export default useFileUpload;
|
||||
|
||||
@@ -2,10 +2,9 @@ import DownloadIcon from '@mui/icons-material/GetApp';
|
||||
import { Typography, Button, Box } from '@mui/material';
|
||||
import { useRequest } from 'alova';
|
||||
import { toast } from 'react-toastify';
|
||||
import type { FileUploadConfig } from 'api/endpoints';
|
||||
import type { AxiosPromise } from 'axios';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
import { SingleUpload, useFileUpload } from 'components';
|
||||
|
||||
import { useI18nContext } from 'i18n/i18n-react';
|
||||
@@ -13,11 +12,20 @@ import * as EMSESP from 'project/api';
|
||||
|
||||
interface UploadFileProps {
|
||||
// TODO fileupload move to alova
|
||||
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||
// uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
|
||||
uploadGeneralFile: (file: File) => Promise<void>;
|
||||
}
|
||||
|
||||
const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
const [uploadFile, cancelUpload, uploading, uploadProgress, md5] = useFileUpload({ upload: uploadGeneralFile });
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
// TODO remove these
|
||||
const md5 = '';
|
||||
const cancelUpload = () => {};
|
||||
const uploading = false;
|
||||
|
||||
// const [uploadFile, cancelUpload, uploading, uploadProgress, md5] = useFileUpload({ upload: uploadGeneralFile });
|
||||
// const [uploadFile, cancelUpload, uploading, md5] = useFileUpload({ upload: uploadGeneralFile });
|
||||
|
||||
const { send: getSettings, onSuccess: onSuccessGetSettings } = useRequest(EMSESP.getSettings(), {
|
||||
immediate: false
|
||||
@@ -32,8 +40,23 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
const {
|
||||
loading: isUploading,
|
||||
uploading: progress,
|
||||
send: sendUpload
|
||||
} = useRequest(SystemApi.uploadFile, {
|
||||
immediate: false,
|
||||
force: true
|
||||
});
|
||||
|
||||
const uploadFile = async (files: File[]) => {
|
||||
// TODO remove debug
|
||||
console.log('useFileUpload.ts:uploadFile:' + files[0].name, files[0].size);
|
||||
await sendUpload(files[0]);
|
||||
};
|
||||
|
||||
// TODO see if refactor like https://alova.js.org/extension/alova-adapter-xhr/#download
|
||||
// And use revoke
|
||||
const saveFile = (json: any, endpoint: string) => {
|
||||
const a = document.createElement('a');
|
||||
const filename = 'emsesp_' + endpoint + '.json';
|
||||
@@ -103,9 +126,11 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
|
||||
<Typography variant="body2">{'MD5: ' + md5}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} />
|
||||
|
||||
{!uploading && (
|
||||
{/* TODO fix this hardcoded isUploading */}
|
||||
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} isUploading={isUploading} progress={progress} />
|
||||
{console.log(progress)}
|
||||
{/* <SingleUpload onDrop={uploadFile} onCancel={cancelUpload} isUploading={false} progress={uploading} /> */}
|
||||
{!isUploading && (
|
||||
<>
|
||||
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
|
||||
{LL.DOWNLOAD(0)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRequest } from 'alova';
|
||||
import { useRef, useState } from 'react';
|
||||
import GeneralFileUpload from './GeneralFileUpload';
|
||||
import RestartMonitor from './RestartMonitor';
|
||||
import type { FileUploadConfig } from 'api/endpoints';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
@@ -14,13 +14,25 @@ const UploadFileForm: FC = () => {
|
||||
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
|
||||
const {
|
||||
loading,
|
||||
data,
|
||||
uploading,
|
||||
send: sendUpload
|
||||
} = useRequest(SystemApi.uploadFile, {
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const uploadFile = useRef(async (file: File) => {
|
||||
// TODO fileupload move to alova
|
||||
const response = await SystemApi.uploadFile(file, config);
|
||||
if (response.status === 200) {
|
||||
setRestarting(true);
|
||||
}
|
||||
return response;
|
||||
console.log('UploadFileForm.tsx: uploadFile duplicate!!!'); // TODO do we need this function??? duplicate?
|
||||
await sendUpload(file);
|
||||
|
||||
// const response = await SystemApi.uploadFile(file);
|
||||
// if (response.status === 200) {
|
||||
// setRestarting(true);
|
||||
// }
|
||||
// return response;
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user