Upload details (#368)
This commit is contained in:
parent
43255fa40f
commit
15c058d624
37
src/App.less
37
src/App.less
|
@ -141,20 +141,6 @@ select:-webkit-autofill:focus {
|
|||
transition: background-color 5000s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.CodeMirror, .editor-preview {
|
||||
color: @text !important;
|
||||
background-color: @main !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.cm-s-easymde .CodeMirror-cursor {
|
||||
border: 1px solid @text !important;
|
||||
}
|
||||
|
||||
.CodeMirror-wrap {
|
||||
background-color: @main-light !important;
|
||||
}
|
||||
|
||||
.markdown-preview {
|
||||
background-color: @main-light;
|
||||
padding: 5px 11px;
|
||||
|
@ -162,4 +148,27 @@ select:-webkit-autofill:focus {
|
|||
border-width: 1px;
|
||||
border-radius: @border-radius-base;
|
||||
border-color: @main-dark;
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
background-color: @main;
|
||||
border-radius: @border-radius-base;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
p code {
|
||||
padding: 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-readme {
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,13 @@ import {
|
|||
} from 'react';
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Divider,
|
||||
Input,
|
||||
InputNumber,
|
||||
notification,
|
||||
Row,
|
||||
Select,
|
||||
Skeleton,
|
||||
Space,
|
||||
Switch,
|
||||
|
@ -64,6 +67,22 @@ enum MaxFiles {
|
|||
CUSTOM_INI_FILES = 1,
|
||||
}
|
||||
|
||||
interface TuneDataDetails {
|
||||
readme?: string;
|
||||
make?: string;
|
||||
model?: string;
|
||||
displacement?: string;
|
||||
year?: number;
|
||||
hp?: number;
|
||||
stockHp?: number;
|
||||
engineCode?: string;
|
||||
cylinders?: number;
|
||||
aspiration?: string;
|
||||
fuel?: string;
|
||||
injectors?: string;
|
||||
coils?: string;
|
||||
}
|
||||
|
||||
interface TuneDbData {
|
||||
userUid?: string;
|
||||
createdAt?: Date;
|
||||
|
@ -75,7 +94,7 @@ interface TuneDbData {
|
|||
logFiles?: string[];
|
||||
toothLogFiles?: string[];
|
||||
customIniFile?: string | null;
|
||||
description?: string;
|
||||
details?: TuneDataDetails;
|
||||
}
|
||||
|
||||
type Path = string;
|
||||
|
@ -101,8 +120,10 @@ const containerStyle = {
|
|||
margin: '0 auto',
|
||||
};
|
||||
|
||||
const NEW_TUNE_ID_KEY = 'newTuneId';
|
||||
const MAX_FILE_SIZE_MB = 10;
|
||||
const newTuneIdKey = 'newTuneId';
|
||||
const maxFileSizeMB = 10;
|
||||
const descriptionEditorHeight = 260;
|
||||
const rowProps = { gutter: 10, style: { marginBottom: 10 } };
|
||||
|
||||
const tuneIcon = () => <ToolOutlined />;
|
||||
const logIcon = () => <FundOutlined />;
|
||||
|
@ -117,12 +138,11 @@ const UploadPage = () => {
|
|||
const [isUserAuthorized, setIsUserAuthorized] = useState(false);
|
||||
const [shareUrl, setShareUrl] = useState<string>();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [showOptions, setShowOptions] = useState(false);
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isPublished, setIsPublished] = useState(false);
|
||||
const [isPublic, setIsPublic] = useState(true);
|
||||
const [isListed, setIsListed] = useState(true);
|
||||
const [description, setDescription] = useState('# My Tune\n\ndescription');
|
||||
const [tuneFile, setTuneFile] = useState<UploadedFile | null | false>(null);
|
||||
const [logFiles, setLogFiles] = useState<UploadedFile>({});
|
||||
const [toothLogFiles, setToothLogFiles] = useState<UploadedFile>({});
|
||||
|
@ -132,6 +152,21 @@ const UploadPage = () => {
|
|||
const history = useHistory();
|
||||
const { storageSet, storageGet, storageDelete } = useStorage();
|
||||
|
||||
// details
|
||||
const [readme, setReadme] = useState('# My Tune\n\ndescription');
|
||||
const [make, setMake] = useState<string>();
|
||||
const [model, setModel] = useState<string>();
|
||||
const [displacement, setDisplacement] = useState<string>();
|
||||
const [year, setYear] = useState<number>();
|
||||
const [hp, setHp] = useState<number>();
|
||||
const [stockHp, setStockHp] = useState<number>();
|
||||
const [engineCode, setEngineCode] = useState<string>();
|
||||
const [cylinders, setCylinders] = useState<number>();
|
||||
const [aspiration, setAspiration] = useState<string>();
|
||||
const [fuel, setFuel] = useState<string>();
|
||||
const [injectors, setInjectors] = useState<string>();
|
||||
const [coils, setCoils] = useState<string>();
|
||||
|
||||
const noop = () => { };
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
|
@ -179,16 +214,30 @@ const UploadPage = () => {
|
|||
isPublished: true,
|
||||
isPublic,
|
||||
isListed,
|
||||
description,
|
||||
details: {
|
||||
readme,
|
||||
make,
|
||||
model,
|
||||
displacement,
|
||||
year,
|
||||
hp,
|
||||
stockHp,
|
||||
engineCode,
|
||||
cylinders,
|
||||
aspiration,
|
||||
fuel,
|
||||
injectors,
|
||||
coils,
|
||||
},
|
||||
});
|
||||
setIsPublished(true);
|
||||
setIsLoading(false);
|
||||
storageDelete(NEW_TUNE_ID_KEY);
|
||||
storageDelete(newTuneIdKey);
|
||||
};
|
||||
|
||||
const validateSize = (file: File) => Promise.resolve({
|
||||
result: (file.size / 1024 / 1024) <= MAX_FILE_SIZE_MB,
|
||||
message: `File should not be larger than ${MAX_FILE_SIZE_MB}MB!`,
|
||||
result: (file.size / 1024 / 1024) <= maxFileSizeMB,
|
||||
message: `File should not be larger than ${maxFileSizeMB}MB!`,
|
||||
});
|
||||
|
||||
const upload = async (path: string, options: UploadRequestOption, done: Function, validate: ValidateFile) => {
|
||||
|
@ -265,7 +314,7 @@ const UploadPage = () => {
|
|||
logFiles: [],
|
||||
toothLogFiles: [],
|
||||
customIniFile: null,
|
||||
description: '',
|
||||
details: {},
|
||||
};
|
||||
await updateDbData(newTuneId!, tuneData);
|
||||
}
|
||||
|
@ -449,15 +498,15 @@ const UploadPage = () => {
|
|||
}
|
||||
setIsUserAuthorized(true);
|
||||
} catch (error) {
|
||||
storageDelete(NEW_TUNE_ID_KEY);
|
||||
storageDelete(newTuneIdKey);
|
||||
console.error(error);
|
||||
genericError(error as Error);
|
||||
}
|
||||
|
||||
let newTuneIdTemp = await storageGet(NEW_TUNE_ID_KEY);
|
||||
let newTuneIdTemp = await storageGet(newTuneIdKey);
|
||||
if (!newTuneIdTemp) {
|
||||
newTuneIdTemp = nanoidCustom();
|
||||
await storageSet(NEW_TUNE_ID_KEY, newTuneIdTemp);
|
||||
await storageSet(newTuneIdKey, newTuneIdTemp);
|
||||
}
|
||||
setNewTuneId(newTuneIdTemp);
|
||||
}, [currentUser, history, refreshToken, storageDelete, storageGet, storageSet]);
|
||||
|
@ -505,6 +554,116 @@ const UploadPage = () => {
|
|||
</>
|
||||
);
|
||||
|
||||
const detailsSection = (
|
||||
<>
|
||||
<Divider>
|
||||
<Space>
|
||||
Upload Custom INI
|
||||
<Typography.Text type="secondary">(.ini)</Typography.Text>
|
||||
</Space>
|
||||
</Divider>
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
customRequest={uploadCustomIni}
|
||||
data={customIniFileData}
|
||||
onRemove={removeCustomIniFile}
|
||||
iconRender={iniIcon}
|
||||
disabled={isPublished}
|
||||
onPreview={noop}
|
||||
accept=".ini"
|
||||
>
|
||||
{!customIniFile && uploadButton}
|
||||
</Upload>
|
||||
<Divider>
|
||||
<Space>
|
||||
README
|
||||
<Typography.Text type="secondary">(markdown)</Typography.Text>
|
||||
</Space>
|
||||
</Divider>
|
||||
<Tabs defaultActiveKey="source" className="upload-readme">
|
||||
<Tabs.TabPane tab="Edit" key="source" style={{ height: descriptionEditorHeight }}>
|
||||
<Input.TextArea
|
||||
rows={10}
|
||||
showCount
|
||||
value={readme}
|
||||
onChange={(e) => setReadme(e.target.value)}
|
||||
maxLength={3_000}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Preview" key="preview" style={{ height: descriptionEditorHeight }}>
|
||||
<div className="markdown-preview" style={{ height: '100%' }}>
|
||||
<ReactMarkdown>
|
||||
{readme}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Input addonBefore="Make" value={make} onChange={(e) => setMake(e.target.value)} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Input addonBefore="Model" value={model} onChange={(e) => setModel(e.target.value)} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<InputNumber addonBefore="Year" value={year} onChange={setYear} style={{ width: '100%' }} min={1886} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Input addonBefore="Displacement" addonAfter="l" value={displacement} onChange={(e) => setDisplacement(e.target.value)} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<InputNumber addonBefore="HP" value={hp} onChange={setHp} style={{ width: '100%' }} min={0} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber addonBefore="Stock HP" value={stockHp} onChange={setStockHp} style={{ width: '100%' }} min={0} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Input addonBefore="Engine code" value={engineCode} onChange={(e) => setEngineCode(e.target.value)} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber addonBefore="No of cylinders" value={cylinders} onChange={setCylinders} style={{ width: '100%' }} min={0} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Select placeholder="Aspiration" value={aspiration} onChange={setAspiration} style={{ width: '100%' }}>
|
||||
<Select.Option value="NA">Naturally aspirated</Select.Option>
|
||||
<Select.Option value="turbo">Turbo</Select.Option>
|
||||
<Select.Option value="compressor">Compressor</Select.Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Input addonBefore="Fuel type" value={fuel} onChange={(e) => setFuel(e.target.value)} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Input addonBefore="Injectors" value={injectors} onChange={(e) => setInjectors(e.target.value)} />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Input addonBefore="Coils" value={coils} onChange={(e) => setCoils(e.target.value)} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider>
|
||||
Visibility
|
||||
</Divider>
|
||||
<Space direction="vertical" size="large">
|
||||
<Space>
|
||||
Public:<Switch disabled checked={isPublic} onChange={setIsPublic} />
|
||||
</Space>
|
||||
<Space>
|
||||
Listed:<Switch checked={isListed} onChange={setIsListed} />
|
||||
</Space>
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
|
||||
const optionalSection = (
|
||||
<>
|
||||
<Divider>
|
||||
|
@ -546,65 +705,11 @@ const UploadPage = () => {
|
|||
>
|
||||
{Object.keys(toothLogFiles).length < MaxFiles.TOOTH_LOG_FILES && uploadButton}
|
||||
</Upload>
|
||||
<Divider>
|
||||
<Space>
|
||||
Description
|
||||
<Typography.Text type="secondary">(markdown)</Typography.Text>
|
||||
</Space>
|
||||
</Divider>
|
||||
<Tabs defaultActiveKey="source">
|
||||
<Tabs.TabPane tab="Edit" key="source">
|
||||
<Input.TextArea
|
||||
rows={10}
|
||||
showCount
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
maxLength={3_000}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="Preview" key="preview">
|
||||
<div className="markdown-preview" style={{ minHeight: 230 }}>
|
||||
<ReactMarkdown>
|
||||
{description}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<Space style={{ marginTop: 30 }}>
|
||||
Show more:
|
||||
<Switch checked={showOptions} onChange={setShowOptions} />
|
||||
Show details:
|
||||
<Switch checked={showDetails} onChange={setShowDetails} />
|
||||
</Space>
|
||||
{showOptions && <>
|
||||
<Divider>
|
||||
<Space>
|
||||
Upload Custom INI
|
||||
<Typography.Text type="secondary">(.ini)</Typography.Text>
|
||||
</Space>
|
||||
</Divider>
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
customRequest={uploadCustomIni}
|
||||
data={customIniFileData}
|
||||
onRemove={removeCustomIniFile}
|
||||
iconRender={iniIcon}
|
||||
disabled={isPublished}
|
||||
onPreview={noop}
|
||||
accept=".ini"
|
||||
>
|
||||
{!customIniFile && uploadButton}
|
||||
</Upload>
|
||||
<Divider>
|
||||
Visibility
|
||||
</Divider>
|
||||
<Space direction="vertical" size="large">
|
||||
<Space>
|
||||
Public:<Switch disabled checked={isPublic} onChange={setIsPublic} />
|
||||
</Space>
|
||||
<Space>
|
||||
Listed:<Switch checked={isListed} onChange={setIsListed} />
|
||||
</Space>
|
||||
</Space>
|
||||
</>}
|
||||
{showDetails && detailsSection}
|
||||
{shareUrl && tuneFile && shareSection}
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue