feat: 修复Container界面和File App 和 User App
User App 添加permission
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
@import 'tailwindcss';
|
||||
@import '@kevisual/center-components/theme/wind-theme.css';
|
||||
|
||||
@layer components {
|
||||
.test-loading {
|
||||
|
||||
10
packages/resources/src/i-text/index.ts
Normal file
10
packages/resources/src/i-text/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export const iText = {
|
||||
share: {
|
||||
title: '共享设置',
|
||||
tips: `共享设置
|
||||
1. 设置公共可以直接访问
|
||||
2. 设置受保护需要登录后访问
|
||||
3. 设置私有只有自己可以访问。\n
|
||||
受保护可以设置密码,设置访问的用户名。切换共享状态后,需要重新设置密码和用户名。`,
|
||||
},
|
||||
};
|
||||
3
packages/resources/src/index.ts
Normal file
3
packages/resources/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { KeyParse, keysTips } from './pages/file/modules/key-parse';
|
||||
export { iText } from './i-text/index.ts';
|
||||
export * from './pages/upload/app';
|
||||
@@ -1,16 +1,17 @@
|
||||
import ReactDatePicker from 'antd/es/date-picker';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { styled, useTheme } from '@mui/material';
|
||||
import 'antd/es/date-picker/style/index';
|
||||
interface DatePickerProps {
|
||||
value?: Date | null;
|
||||
onChange?: (date: Date | null) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const DatePicker = ({ value, onChange }: DatePickerProps) => {
|
||||
export const DatePickerCom = ({ value, onChange, className }: DatePickerProps) => {
|
||||
const theme = useTheme();
|
||||
const primaryColor = theme.palette.primary.main;
|
||||
return (
|
||||
<div>
|
||||
<div className={className}>
|
||||
<ReactDatePicker
|
||||
placement='topLeft'
|
||||
placeholder='请选择日期'
|
||||
@@ -26,3 +27,12 @@ export const DatePicker = ({ value, onChange }: DatePickerProps) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DatePicker = styled(DatePickerCom)(({ theme }) => ({
|
||||
'& .ant-picker-input': {
|
||||
color: 'var(--color-primary)',
|
||||
'& .ant-picker-suffix, .ant-picker-clear': {
|
||||
color: 'var(--color-primary)',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -9,7 +9,8 @@ import { DatePicker } from './DatePicker';
|
||||
import { SelectPicker } from './SelectPicker';
|
||||
import dayjs from 'dayjs';
|
||||
import { DialogKey } from './DialogKey';
|
||||
|
||||
import { keysTips, KeyParse } from '../../modules/key-parse';
|
||||
import { KeyShareSelect, KeyTextField } from '../../modules/PermissionManager';
|
||||
export const setShareKeysOperate = (value: 'public' | 'protected' | 'private') => {
|
||||
const keys = ['password', 'usernames', 'expiration-time'];
|
||||
const deleteKeys = keys.map((item) => {
|
||||
@@ -28,92 +29,7 @@ export const setShareKeysOperate = (value: 'public' | 'protected' | 'private') =
|
||||
}
|
||||
return deleteKeys;
|
||||
};
|
||||
export const keysTips = [
|
||||
{
|
||||
key: 'share',
|
||||
tips: `共享设置
|
||||
1. 设置公共可以直接访问
|
||||
2. 设置受保护需要登录后访问
|
||||
3. 设置私有只有自己可以访问。\n
|
||||
受保护可以设置密码,设置访问的用户名。切换共享状态后,需要重新设置密码和用户名。`,
|
||||
},
|
||||
{
|
||||
key: 'content-type',
|
||||
tips: `内容类型,设置文件的内容类型。默认不要修改。`,
|
||||
},
|
||||
{
|
||||
key: 'app-source',
|
||||
tips: `应用来源,上传方式。默认不要修改。`,
|
||||
},
|
||||
{
|
||||
key: 'cache-control',
|
||||
tips: `缓存控制,设置文件的缓存控制。默认不要修改。`,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
tips: `密码,设置文件的密码。不设置默认是所有人都可以访问。`,
|
||||
},
|
||||
{
|
||||
key: 'usernames',
|
||||
tips: `用户名,设置文件的用户名。不设置默认是所有人都可以访问。`,
|
||||
parse: (value: string) => {
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
return value.split(',');
|
||||
},
|
||||
stringify: (value: string[]) => {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return value.join(',');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'expiration-time',
|
||||
tips: `过期时间,设置文件的过期时间。不设置默认是永久。`,
|
||||
parse: (value: Date) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return dayjs(value);
|
||||
},
|
||||
stringify: (value?: dayjs.Dayjs) => {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return value.toISOString();
|
||||
},
|
||||
},
|
||||
];
|
||||
export class KeyParse {
|
||||
static parse(metadata: Record<string, any>) {
|
||||
const keys = Object.keys(metadata);
|
||||
const newMetadata = {};
|
||||
keys.forEach((key) => {
|
||||
const tip = keysTips.find((item) => item.key === key);
|
||||
if (tip && tip.parse) {
|
||||
newMetadata[key] = tip.parse(metadata[key]);
|
||||
} else {
|
||||
newMetadata[key] = metadata[key];
|
||||
}
|
||||
});
|
||||
return newMetadata;
|
||||
}
|
||||
static stringify(metadata: Record<string, any>) {
|
||||
const keys = Object.keys(metadata);
|
||||
const newMetadata = {};
|
||||
keys.forEach((key) => {
|
||||
const tip = keysTips.find((item) => item.key === key);
|
||||
if (tip && tip.stringify) {
|
||||
newMetadata[key] = tip.stringify(metadata[key]);
|
||||
} else {
|
||||
newMetadata[key] = metadata[key];
|
||||
}
|
||||
});
|
||||
return newMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
export const useMetaOperate = ({
|
||||
onSave,
|
||||
metaStore,
|
||||
@@ -270,7 +186,7 @@ export const MetaForm = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<FormGroup>
|
||||
<form>
|
||||
{keys.map((key) => {
|
||||
let control: React.ReactNode | null = null;
|
||||
if (key === 'share') {
|
||||
@@ -320,49 +236,10 @@ export const MetaForm = () => {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</FormGroup>
|
||||
</form>
|
||||
<DialogKey onAdd={addMetaKey} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const KeyTextField = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
|
||||
return (
|
||||
<TextField
|
||||
variant='outlined'
|
||||
size='small'
|
||||
name={name}
|
||||
defaultValue={value}
|
||||
// value={formData[key] || ''}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
sx={{
|
||||
width: '100%',
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const KeyShareSelect = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
|
||||
return (
|
||||
<Select
|
||||
variant='outlined'
|
||||
size='small'
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
sx={{
|
||||
width: '100%',
|
||||
marginBottom: '16px',
|
||||
}}>
|
||||
<MenuItem value='public' title='公开'>
|
||||
公开
|
||||
</MenuItem>
|
||||
<MenuItem value='protected' title='受保护'>
|
||||
受保护
|
||||
</MenuItem>
|
||||
<MenuItem value='private' title='私有'>
|
||||
私有
|
||||
</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,14 +5,26 @@ import 'antd/es/select/style/index';
|
||||
interface SelectPickerProps {
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const SelectPickerCom = ({ value, onChange }: SelectPickerProps) => {
|
||||
return <Select style={{ width: '100%' }} showSearch={false} mode='tags' value={value} onChange={onChange} />;
|
||||
export const SelectPickerCom = ({ value, onChange, className }: SelectPickerProps) => {
|
||||
return <Select className={className} style={{ width: '100%' }} popupClassName='hidden' showSearch={false} mode='tags' value={value} onChange={onChange} />;
|
||||
};
|
||||
export const SelectPicker = styled(SelectPickerCom)(({ theme }) => ({
|
||||
|
||||
export const SelectPicker = styled(SelectPickerCom)({
|
||||
'& .ant-select-selector': {
|
||||
color: 'var(--primary-color)',
|
||||
'& .ant-select-selection-item': {
|
||||
backgroundColor: 'var(--color-primary) !important',
|
||||
color: 'white !important',
|
||||
},
|
||||
});
|
||||
'& svg': {
|
||||
color: 'white !important',
|
||||
},
|
||||
'& svg:hover': {
|
||||
color: '#ccc !important',
|
||||
},
|
||||
'& .ant-select-arrow': {
|
||||
// color: 'var(--color-primary) !important',
|
||||
display: 'none !important',
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PrefixRedirect } from './modules/PrefixRedirect';
|
||||
import { UploadButton } from '../upload';
|
||||
import { FileDrawer } from './draw/FileDrawer';
|
||||
import { useResourceFileStore } from '../store/resource-file';
|
||||
import { IconButtonItem } from '@kevisual/center-components/button/index.tsx';
|
||||
export const FileApp = () => {
|
||||
const { getList, prefix, setListType, listType } = useResourceStore();
|
||||
const { getStatFile, prefix: statPrefix, openDrawer } = useResourceFileStore();
|
||||
|
||||
141
packages/resources/src/pages/file/modules/PermissionManager.tsx
Normal file
141
packages/resources/src/pages/file/modules/PermissionManager.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { KeyParse, getTips } from './key-parse';
|
||||
import { FormControlLabel, TextField, Select, MenuItem, FormGroup, Tooltip } from '@mui/material';
|
||||
import { DatePicker } from '../draw/modules/DatePicker';
|
||||
import { SelectPicker } from '../draw/modules/SelectPicker';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
export const KeyShareSelect = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
|
||||
return (
|
||||
<Select
|
||||
variant='outlined'
|
||||
size='small'
|
||||
name={name}
|
||||
value={value || ''}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
sx={{
|
||||
width: '100%',
|
||||
marginBottom: '16px',
|
||||
}}>
|
||||
<MenuItem value='public' title='公开'>
|
||||
公开
|
||||
</MenuItem>
|
||||
<MenuItem value='protected' title='受保护'>
|
||||
受保护
|
||||
</MenuItem>
|
||||
<MenuItem value='private' title='私有'>
|
||||
私有
|
||||
</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
export const KeyTextField = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
|
||||
return (
|
||||
<TextField
|
||||
variant='outlined'
|
||||
size='small'
|
||||
name={name}
|
||||
defaultValue={value}
|
||||
// value={formData[key] || ''}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
sx={{
|
||||
width: '100%',
|
||||
marginBottom: '16px',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type PermissionManagerProps = {
|
||||
value: Record<string, any>;
|
||||
onChange: (value: Record<string, any>) => void;
|
||||
};
|
||||
export const PermissionManager = ({ value, onChange }: PermissionManagerProps) => {
|
||||
const [formData, setFormData] = useState<any>(value);
|
||||
const [keys, setKeys] = useState<any>([]);
|
||||
useEffect(() => {
|
||||
const hasShare = value?.share && value?.share === 'protected';
|
||||
setFormData(KeyParse.parse(value || {}));
|
||||
if (hasShare) {
|
||||
setKeys(['password', 'usernames', 'expiration-time']);
|
||||
} else {
|
||||
setKeys([]);
|
||||
}
|
||||
}, [value]);
|
||||
const onChangeValue = (key: string, newValue: any) => {
|
||||
// setFormData({ ...formData, [key]: newValue });
|
||||
let newFormData = { ...formData, [key]: newValue };
|
||||
if (key === 'share') {
|
||||
if (newValue === 'protected') {
|
||||
newFormData = { ...newFormData, password: '', usernames: [], 'expiration-time': null };
|
||||
onChange(KeyParse.stringify(newFormData));
|
||||
setKeys(['password', 'usernames', 'expiration-time']);
|
||||
} else {
|
||||
delete newFormData.password;
|
||||
delete newFormData.usernames;
|
||||
delete newFormData['expiration-time'];
|
||||
onChange(KeyParse.stringify(newFormData));
|
||||
setKeys([]);
|
||||
}
|
||||
} else {
|
||||
onChange(KeyParse.stringify(newFormData));
|
||||
}
|
||||
};
|
||||
return (
|
||||
<form className='w-[400px] flex flex-col gap-2'>
|
||||
<FormControlLabel
|
||||
labelPlacement='top'
|
||||
control={<KeyShareSelect name='share' value={formData?.share} onChange={(value) => onChangeValue('share', value)} />}
|
||||
label={
|
||||
<div className='flex items-center gap-1'>
|
||||
Share
|
||||
<Tooltip title={getTips('share')}>
|
||||
<HelpCircle size={16} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
'& .MuiFormControlLabel-label': {
|
||||
textAlign: 'left',
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{keys.map((item: any) => {
|
||||
let control: React.ReactNode | null = null;
|
||||
if (item === 'expiration-time') {
|
||||
control = <DatePicker value={formData[item] || ''} onChange={(date) => onChangeValue(item, date)} />;
|
||||
} else if (item === 'usernames') {
|
||||
control = <SelectPicker value={formData[item] || []} onChange={(value) => onChangeValue(item, value)} />;
|
||||
} else {
|
||||
control = <KeyTextField name={item} value={formData[item] || ''} onChange={(value) => onChangeValue(item, value)} />;
|
||||
}
|
||||
const tips = getTips(item);
|
||||
return (
|
||||
<FormControlLabel
|
||||
labelPlacement='top'
|
||||
key={item}
|
||||
control={control}
|
||||
label={
|
||||
<div className='flex items-center gap-1'>
|
||||
{item}
|
||||
{tips && (
|
||||
<Tooltip title={tips}>
|
||||
<HelpCircle size={16} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
'& .MuiFormControlLabel-label': {
|
||||
textAlign: 'left',
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
90
packages/resources/src/pages/file/modules/key-parse.ts
Normal file
90
packages/resources/src/pages/file/modules/key-parse.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import dayjs from 'dayjs';
|
||||
export const getTips = (key: string) => {
|
||||
return keysTips.find((item) => item.key === key)?.tips;
|
||||
};
|
||||
export const keysTips = [
|
||||
{
|
||||
key: 'share',
|
||||
tips: `共享设置
|
||||
1. 设置公共可以直接访问
|
||||
2. 设置受保护需要登录后访问
|
||||
3. 设置私有只有自己可以访问。\n
|
||||
受保护可以设置密码,设置访问的用户名。切换共享状态后,需要重新设置密码和用户名。 不设置,默认是只能自己访问。`,
|
||||
},
|
||||
{
|
||||
key: 'content-type',
|
||||
tips: `内容类型,设置文件的内容类型。默认不要修改。`,
|
||||
},
|
||||
{
|
||||
key: 'app-source',
|
||||
tips: `应用来源,上传方式。默认不要修改。`,
|
||||
},
|
||||
{
|
||||
key: 'cache-control',
|
||||
tips: `缓存控制,设置文件的缓存控制。默认不要修改。`,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
tips: `密码,设置文件的密码。不设置默认是所有人都可以访问。`,
|
||||
},
|
||||
{
|
||||
key: 'usernames',
|
||||
tips: `用户名,设置文件的用户名。不设置默认是所有人都可以访问。`,
|
||||
parse: (value: string) => {
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
return value.split(',');
|
||||
},
|
||||
stringify: (value: string[]) => {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return value.join(',');
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'expiration-time',
|
||||
tips: `过期时间,设置文件的过期时间。不设置默认是永久。`,
|
||||
parse: (value: Date) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return dayjs(value);
|
||||
},
|
||||
stringify: (value?: dayjs.Dayjs) => {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return value.toISOString();
|
||||
},
|
||||
},
|
||||
];
|
||||
export class KeyParse {
|
||||
static parse(metadata: Record<string, any>) {
|
||||
const keys = Object.keys(metadata);
|
||||
const newMetadata = {};
|
||||
keys.forEach((key) => {
|
||||
const tip = keysTips.find((item) => item.key === key);
|
||||
if (tip && tip.parse) {
|
||||
newMetadata[key] = tip.parse(metadata[key]);
|
||||
} else {
|
||||
newMetadata[key] = metadata[key];
|
||||
}
|
||||
});
|
||||
return newMetadata;
|
||||
}
|
||||
static stringify(metadata: Record<string, any>) {
|
||||
const keys = Object.keys(metadata);
|
||||
const newMetadata = {};
|
||||
keys.forEach((key) => {
|
||||
const tip = keysTips.find((item) => item.key === key);
|
||||
if (tip && tip.stringify) {
|
||||
newMetadata[key] = tip.stringify(metadata[key]);
|
||||
} else {
|
||||
newMetadata[key] = metadata[key];
|
||||
}
|
||||
});
|
||||
return newMetadata;
|
||||
}
|
||||
}
|
||||
5
packages/resources/src/pages/upload/app.ts
Normal file
5
packages/resources/src/pages/upload/app.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './tools/to-file';
|
||||
|
||||
export * from './utils/upload';
|
||||
|
||||
export * from './utils/upload-chunk';
|
||||
1
packages/resources/src/pages/upload/tools/test-pnt.ts
Normal file
1
packages/resources/src/pages/upload/tools/test-pnt.ts
Normal file
File diff suppressed because one or more lines are too long
93
packages/resources/src/pages/upload/tools/to-file.ts
Normal file
93
packages/resources/src/pages/upload/tools/to-file.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
const getFileExtension = (filename: string) => {
|
||||
return filename.split('.').pop();
|
||||
};
|
||||
const getFileType = (extension: string) => {
|
||||
switch (extension) {
|
||||
case 'js':
|
||||
return 'text/javascript';
|
||||
case 'css':
|
||||
return 'text/css';
|
||||
case 'html':
|
||||
return 'text/html';
|
||||
case 'json':
|
||||
return 'application/json';
|
||||
case 'png':
|
||||
return 'image/png';
|
||||
case 'jpg':
|
||||
return 'image/jpeg';
|
||||
case 'jpeg':
|
||||
return 'image/jpeg';
|
||||
case 'gif':
|
||||
return 'image/gif';
|
||||
case 'svg':
|
||||
return 'image/svg+xml';
|
||||
case 'ico':
|
||||
return 'image/x-icon';
|
||||
case 'webp':
|
||||
return 'image/webp';
|
||||
case 'gif':
|
||||
return 'image/gif';
|
||||
case 'ico':
|
||||
return 'image/x-icon';
|
||||
default:
|
||||
return 'text/plain';
|
||||
}
|
||||
};
|
||||
const checkIsBase64 = (content: string) => {
|
||||
return content.startsWith('data:');
|
||||
};
|
||||
export const getDirectoryAndName = (filename: string) => {
|
||||
if (!filename) {
|
||||
return null;
|
||||
}
|
||||
if (filename.startsWith('.')) {
|
||||
return null;
|
||||
} else {
|
||||
filename = filename.replace(/^\/+/, ''); // Remove all leading slashes
|
||||
}
|
||||
const hasDirectory = filename.includes('/');
|
||||
if (!hasDirectory) {
|
||||
return { directory: '', name: filename };
|
||||
}
|
||||
const parts = filename.split('/');
|
||||
const name = parts.pop()!; // Get the last part as the file name
|
||||
const directory = parts.join('/'); // Join the remaining parts as the directory
|
||||
return { directory, name };
|
||||
};
|
||||
/**
|
||||
* 把字符串转为文件流,并返回文件流,根据filename的扩展名,自动设置文件类型.
|
||||
* 当不是文本类型,自动需要把base64的字符串转为blob
|
||||
* @param content 字符串
|
||||
* @param filename 文件名
|
||||
* @returns 文件流
|
||||
*/
|
||||
export const toFile = (content: string, filename: string) => {
|
||||
// 如果文件名是 a/d/a.js 格式的,则需要把d作为目录,a.js作为文件名
|
||||
const directoryAndName = getDirectoryAndName(filename);
|
||||
if (!directoryAndName) {
|
||||
throw new Error('Invalid filename');
|
||||
}
|
||||
const { name } = directoryAndName;
|
||||
const extension = getFileExtension(name);
|
||||
if (!extension) {
|
||||
throw new Error('Invalid filename');
|
||||
}
|
||||
const isBase64 = checkIsBase64(content);
|
||||
const type = getFileType(extension);
|
||||
|
||||
if (isBase64) {
|
||||
// Decode base64 string
|
||||
const base64Data = content.split(',')[1]; // Remove the data URL prefix
|
||||
const byteCharacters = atob(base64Data);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type });
|
||||
return new File([blob], filename, { type });
|
||||
} else {
|
||||
const blob = new Blob([content], { type });
|
||||
return new File([blob], filename, { type });
|
||||
}
|
||||
};
|
||||
@@ -12,7 +12,7 @@ type ConvertOpts = {
|
||||
};
|
||||
|
||||
export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
const { directory } = opts;
|
||||
const { directory, appKey, version, username } = opts;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
@@ -66,6 +66,13 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
if (directory) {
|
||||
formData.append('directory', directory);
|
||||
}
|
||||
if (appKey && version) {
|
||||
formData.append('appKey', appKey);
|
||||
formData.append('version', version);
|
||||
}
|
||||
if (username) {
|
||||
formData.append('username', username);
|
||||
}
|
||||
try {
|
||||
const res = await fetch('/api/s1/resources/upload/chunk?taskId=' + taskId, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -11,7 +11,7 @@ type ConvertOpts = {
|
||||
directory?: string;
|
||||
};
|
||||
export const uploadFiles = async (files: File[], opts: ConvertOpts) => {
|
||||
const { directory } = opts;
|
||||
const { directory, appKey, version, username } = opts;
|
||||
return new Promise((resolve, reject) => {
|
||||
const formData = new FormData();
|
||||
const webkitRelativePath = files[0]?.webkitRelativePath;
|
||||
@@ -30,9 +30,13 @@ export const uploadFiles = async (files: File[], opts: ConvertOpts) => {
|
||||
if (directory) {
|
||||
formData.append('directory', directory);
|
||||
}
|
||||
console.log('formData', formData, files);
|
||||
resolve(null);
|
||||
return;
|
||||
if (appKey && version) {
|
||||
formData.append('appKey', appKey);
|
||||
formData.append('version', version);
|
||||
}
|
||||
if (username) {
|
||||
formData.append('username', username);
|
||||
}
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
toastLogin();
|
||||
|
||||
19
packages/resources/src/style.css
Normal file
19
packages/resources/src/style.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-color) #fff;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: var(--scrollbar-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.scrollbar::-webkit-scrollbar-track {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user