mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
107 lines
3.3 KiB
TypeScript
107 lines
3.3 KiB
TypeScript
import {useState} from 'react';
|
|
import styled from '@emotion/styled';
|
|
import omit from 'lodash/omit';
|
|
|
|
import FormField from 'sentry/components/forms/formField';
|
|
import FormFieldControlState from 'sentry/components/forms/formField/controlState';
|
|
import type FormModel from 'sentry/components/forms/model';
|
|
import {InputGroup} from 'sentry/components/inputGroup';
|
|
import {t} from 'sentry/locale';
|
|
|
|
// XXX(epurkhiser): This is wrong, it should not be inheriting these props
|
|
import type {InputFieldProps} from './inputField';
|
|
|
|
export interface FileFieldProps extends Omit<InputFieldProps, 'type' | 'accept'> {
|
|
accept?: string[];
|
|
// TODO(dcramer): multiple is native to the file input type, but not yet supported
|
|
// multiple?: boolean;
|
|
}
|
|
|
|
// XXX(dcramer): This stores files in memory - serialized into the forms values
|
|
// which is not ideal, but is compatible with the API-side SerializedFileField.
|
|
|
|
// TODO(dcramer): it'd be nice if the form api supported a validation state, so
|
|
// async controls like this would could finish their work and disable submission.
|
|
// Until that is done though if you try to submit the form while this is uploading
|
|
// you will just submit the form without the field.
|
|
|
|
export default function FileField({accept, hideControlState, ...props}: FileFieldProps) {
|
|
const [fileName, setFileName] = useState('');
|
|
const handleFile = (model, name, onChange, e) => {
|
|
const file = e.target.files[0];
|
|
|
|
const reader = new FileReader();
|
|
model.setSaving(name, true);
|
|
reader.addEventListener(
|
|
'load',
|
|
() => {
|
|
setFileName(file.name);
|
|
onChange([file.name, (reader.result as string).split(',')[1]], e);
|
|
model.setSaving(name, false);
|
|
},
|
|
false
|
|
);
|
|
reader.readAsDataURL(file);
|
|
};
|
|
|
|
return (
|
|
<FormField {...props} hideControlState>
|
|
{({
|
|
children: _children,
|
|
onChange,
|
|
name,
|
|
model,
|
|
...fieldProps
|
|
}: {
|
|
children: React.ReactNode;
|
|
model: FormModel;
|
|
name: string;
|
|
onChange: (value, event?: React.FormEvent<HTMLInputElement>) => void;
|
|
}) => {
|
|
return (
|
|
<InputGroup>
|
|
<InputGroup.LeadingItems disablePointerEvents>
|
|
<FileName hasFile={!!fileName}>
|
|
{fileName || t('No file selected')}
|
|
</FileName>
|
|
</InputGroup.LeadingItems>
|
|
<FileInput
|
|
{...omit(fieldProps, 'value', 'onBlur', 'onKeyDown')}
|
|
type="file"
|
|
name={name}
|
|
accept={accept?.join(', ')}
|
|
onChange={e => handleFile(model, name, onChange, e)}
|
|
/>
|
|
{!hideControlState && (
|
|
<InputGroup.TrailingItems disablePointerEvents>
|
|
<FormFieldControlState name={name} model={model} />
|
|
<BrowseIndicator>Browse</BrowseIndicator>
|
|
</InputGroup.TrailingItems>
|
|
)}
|
|
</InputGroup>
|
|
);
|
|
}}
|
|
</FormField>
|
|
);
|
|
}
|
|
|
|
const FileName = styled('span')<{hasFile: boolean}>`
|
|
color: ${p => (p.hasFile ? p.theme.textColor : p.theme.subText)};
|
|
`;
|
|
|
|
const BrowseIndicator = styled('span')`
|
|
color: ${p => p.theme.activeText};
|
|
`;
|
|
|
|
const FileInput = styled(InputGroup.Input)`
|
|
cursor: pointer;
|
|
color: transparent;
|
|
|
|
::file-selector-button {
|
|
display: none;
|
|
}
|
|
::-webkit-file-upload-button {
|
|
display: none;
|
|
}
|
|
`;
|