301 lines
10 KiB
TypeScript
301 lines
10 KiB
TypeScript
import { useNavigation, useParams } from 'react-router';
|
|
import { useAppVersionStore } from '../store';
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
import CloudUploadOutlined from '@ant-design/icons/CloudUploadOutlined';
|
|
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
|
import FileOutlined from '@ant-design/icons/FileOutlined';
|
|
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
|
import LinkOutlined from '@ant-design/icons/LinkOutlined';
|
|
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
|
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
|
import { Tooltip } from '@mui/material';
|
|
import { isObjectNull } from '@/utils/is-null';
|
|
import { FileUpload } from '../modules/FileUpload';
|
|
import clsx from 'clsx';
|
|
import { message } from '@/modules/message';
|
|
import { useNewNavigate } from '@/modules';
|
|
import { Button } from '@mui/material';
|
|
import { Dialog, DialogContent, DialogTitle, ButtonGroup } from '@mui/material';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
|
import { useForm, Controller } from 'react-hook-form';
|
|
import { TextField } from '@mui/material';
|
|
import { pick } from 'lodash-es';
|
|
|
|
const FormModal = () => {
|
|
const { t } = useTranslation();
|
|
const { control, handleSubmit, reset } = useForm();
|
|
const containerStore = useAppVersionStore(
|
|
useShallow((state) => {
|
|
return {
|
|
showEdit: state.showEdit,
|
|
setShowEdit: state.setShowEdit,
|
|
formData: state.formData,
|
|
updateData: state.updateData,
|
|
};
|
|
}),
|
|
);
|
|
|
|
useEffect(() => {
|
|
const open = containerStore.showEdit;
|
|
if (open) {
|
|
const isNull = isObjectNull(containerStore.formData);
|
|
if (isNull) {
|
|
reset({});
|
|
} else {
|
|
reset(containerStore.formData);
|
|
}
|
|
}
|
|
}, [containerStore.showEdit]);
|
|
|
|
const onFinish = async (values: any) => {
|
|
const pickValues = pick(values, ['id', 'key', 'version']);
|
|
containerStore.updateData(pickValues);
|
|
};
|
|
|
|
const onClose = () => {
|
|
containerStore.setShowEdit(false);
|
|
reset();
|
|
};
|
|
|
|
const isEdit = containerStore.formData.id;
|
|
|
|
return (
|
|
<Dialog
|
|
open={containerStore.showEdit}
|
|
onClose={() => containerStore.setShowEdit(false)}
|
|
sx={{
|
|
'& .MuiDialog-paper': {
|
|
width: '800px',
|
|
},
|
|
}}>
|
|
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
|
<DialogContent>
|
|
<form className='flex flex-col gap-6' onSubmit={handleSubmit(onFinish)}>
|
|
<Controller name='key' control={control} defaultValue='' render={({ field }) => <TextField label='key' {...field} disabled />} />
|
|
<Controller name='version' control={control} defaultValue='' render={({ field }) => <TextField label='version' {...field} />} />
|
|
<div>
|
|
<Button type='submit' variant='contained' color='primary'>
|
|
{t('submit')}
|
|
</Button>
|
|
<Button className='ml-2' onClick={onClose}>
|
|
{t('cancel')}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
};
|
|
|
|
export const AppVersionList = () => {
|
|
const params = useParams();
|
|
const appKey = params.appKey;
|
|
const versionStore = useAppVersionStore(
|
|
useShallow((state) => {
|
|
return {
|
|
list: state.list,
|
|
getList: state.getList,
|
|
key: state.key,
|
|
setKey: state.setKey,
|
|
setShowEdit: state.setShowEdit,
|
|
formData: state.formData,
|
|
setFormData: state.setFormData,
|
|
deleteData: state.deleteData,
|
|
publishVersion: state.publishVersion,
|
|
app: state.app,
|
|
};
|
|
}),
|
|
);
|
|
const navigate = useNewNavigate();
|
|
const [modal, contextHolder] = useModal();
|
|
const [isUpload, setIsUpload] = useState(false);
|
|
useEffect(() => {
|
|
// fetch app version list
|
|
if (appKey) {
|
|
versionStore.setKey(appKey);
|
|
versionStore.getList();
|
|
}
|
|
}, []);
|
|
const appVersion = useMemo(() => {
|
|
return versionStore.app?.version || '';
|
|
}, [versionStore.app]);
|
|
return (
|
|
<div className='w-full h-full flex bg-slate-100'>
|
|
<div className='p-2 bg-white'>
|
|
<IconButton
|
|
onClick={() => {
|
|
versionStore.setFormData({ key: appKey });
|
|
versionStore.setShowEdit(true);
|
|
}}>
|
|
<PlusOutlined />
|
|
</IconButton>
|
|
</div>
|
|
|
|
<div className='grow h-full relative'>
|
|
<div className='absolute top-2 left-4'>
|
|
<Tooltip title='返回' placement='bottom'>
|
|
<IconButton
|
|
variant='contained'
|
|
onClick={() => {
|
|
navigate('/app/edit/list');
|
|
}}>
|
|
<LeftOutlined />
|
|
</IconButton>
|
|
</Tooltip>
|
|
</div>
|
|
|
|
<div className='w-full h-full p-4 pt-12'>
|
|
<div className='w-full h-full rounded-lg bg-white'>
|
|
<div className='flex gap-2 flex-wrap p-4'>
|
|
{versionStore.list.map((item, index) => {
|
|
const isPublish = item.version === appVersion;
|
|
const color = isPublish ? 'bg-green-500' : '';
|
|
const isRunning = item.status === 'running';
|
|
return (
|
|
<div className='card w-[300px]' key={index}>
|
|
<div className={'flex items-center justify-between'}>
|
|
{item.version}
|
|
|
|
<Tooltip title={isPublish ? 'published' : ''}>
|
|
<div className={clsx('ml-4 rounded-full w-4 h-4', color)}></div>
|
|
</Tooltip>
|
|
</div>
|
|
<div className='mt-4'>
|
|
<ButtonGroup
|
|
variant='contained'
|
|
color='primary'
|
|
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
|
{/* <Button
|
|
onClick={() => {
|
|
versionStore.setFormData(item);
|
|
versionStore.setShowEdit(true);
|
|
}}
|
|
icon={<EditOutlined />}
|
|
/> */}
|
|
<Tooltip title='Delete'>
|
|
<Button
|
|
onClick={(e) => {
|
|
modal.confirm({
|
|
title: 'Delete',
|
|
content: 'Are you sure delete this data?',
|
|
onOk: () => {
|
|
versionStore.deleteData(item.id);
|
|
},
|
|
});
|
|
e.stopPropagation();
|
|
}}>
|
|
<DeleteOutlined />
|
|
</Button>
|
|
</Tooltip>
|
|
<Tooltip title='使用当前版本,发布为此版本'>
|
|
<Button
|
|
onClick={() => {
|
|
versionStore.publishVersion({ id: item.id });
|
|
}}>
|
|
<CloudUploadOutlined />
|
|
</Button>
|
|
</Tooltip>
|
|
<Tooltip title={'To Test App'}>
|
|
<Button
|
|
onClick={() => {
|
|
if (isRunning) {
|
|
const link = new URL(`/test/${item.id}`, location.origin);
|
|
window.open(link.toString(), '_blank');
|
|
} else {
|
|
message.error('The app is not running');
|
|
}
|
|
}}>
|
|
<LinkOutlined />
|
|
</Button>
|
|
</Tooltip>
|
|
<Tooltip title='文件管理'>
|
|
<Button
|
|
onClick={() => {
|
|
versionStore.setFormData(item);
|
|
setIsUpload(true);
|
|
}}>
|
|
<FileOutlined />
|
|
</Button>
|
|
</Tooltip>
|
|
</ButtonGroup>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{contextHolder}
|
|
<div className='shark h-full'>
|
|
{isUpload && (
|
|
<div className='bg-white p-2 w-[600px] h-full flex flex-col'>
|
|
<div className='header flex items-center gap-2'>
|
|
<Tooltip title='返回'>
|
|
<IconButton
|
|
onClick={() => {
|
|
setIsUpload(false);
|
|
}}>
|
|
<LeftOutlined />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<div className='font-bold'>{versionStore.key}</div>
|
|
</div>
|
|
<AppVersionFile />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<FormModal />
|
|
</div>
|
|
);
|
|
};
|
|
export const AppVersionFile = () => {
|
|
const versionStore = useAppVersionStore(
|
|
useShallow((state) => {
|
|
return {
|
|
formData: state.formData,
|
|
};
|
|
}),
|
|
);
|
|
const versionFiles = useMemo(() => {
|
|
if (!versionStore.formData?.data) return [];
|
|
const files = versionStore.formData.data.files || [];
|
|
return files;
|
|
}, [versionStore.formData]);
|
|
return (
|
|
<>
|
|
<div>version: {versionStore.formData.version}</div>
|
|
<div className='border rounded-md my-2 grow overflow-hidden'>
|
|
<div className='flex gap-2 items-center border-b py-2 px-2'>
|
|
Files
|
|
<FileUpload />
|
|
</div>
|
|
<div
|
|
className='mt-2 '
|
|
style={{
|
|
height: 'calc(100% - 40px)',
|
|
}}>
|
|
<div className='h-full overflow-auto mb-4 pb-8 scrollbar'>
|
|
{versionFiles.map((file, index) => {
|
|
const prefix = versionStore.formData.key + '/' + versionStore.formData.version + '/';
|
|
const _path = file.path || '';
|
|
const path = _path.replace(prefix, '');
|
|
return (
|
|
<div className='flex gap-2 px-4 py-2 border-b' key={index}>
|
|
{/* <div className='w-[100px] truncate'>{file.name}</div> */}
|
|
<div>
|
|
<FileOutlined />
|
|
</div>
|
|
<div>{path}</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|