axios#CancelTokenSource TypeScript Examples
The following examples show how to use
axios#CancelTokenSource.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: customHooks.ts From frames with Mozilla Public License 2.0 | 6 votes |
fetcher = <S>(url: string, cancel?: CancelTokenSource) => {
return new Promise<S>(resolve => {
axios({
method: 'GET',
url,
cancelToken: cancel?.token
}).then(res => resolve(res.data))
.catch(e => {
if (axios.isCancel(e)) return
console.log(e)
})
})
}
Example #2
Source File: update_lock.ts From SpaceEye with MIT License | 6 votes |
/**
* Generate a new Axios download cancel token.
*
* @throws {LockNotHeldError} if lock is no longer held
* @returns New cancel token
*/
public generateCancelToken(): CancelTokenSource {
if (!this.isStillHeld()) {
throw new LockNotHeldError()
}
const token = Axios.CancelToken.source()
this.downloadCancelTokens.add(token)
return token
}
Example #3
Source File: image_downloader.ts From SpaceEye with MIT License | 5 votes |
// eslint-disable-next-line jsdoc/require-returns
/**
* Download a file as a cancellable stream.
*
* @param url - URL to download
* @param destPath - Destination file path to save to
* @param cancelToken - Cancel token
* @param onProgress - Called whenever the percentage downloaded value is updated
*/
async function downloadStream(
url: string,
destPath: string,
cancelToken: CancelTokenSource,
onProgress: (percentage?: number) => void
): Promise<void> {
return new Promise<void>((resolve, reject) => {
Axios.get(url, {
responseType: 'stream',
cancelToken: cancelToken.token
})
.then(({ data, headers }) => {
const writer = Fs.createWriteStream(destPath)
const dataStream = data as Readable
// Get the content size (if available)
const contentLength = parseInt((headers ?? {})['content-length'] ?? '-1', 10)
// If none found, signal that the download has begun indeterminately
if (contentLength === -1) {
onProgress(-1)
} else {
// If length found, set up to send percentage updates
let lengthDownloaded = 0
let previousPercentage = -1
// TODO: NOT TYPESAFE!!!!!!!!!
dataStream.on('data', chunk => {
lengthDownloaded += chunk.length
const percentage = Math.round((lengthDownloaded / contentLength) * 100)
// Only send an update if a percentage point changed
if (percentage !== previousPercentage) {
previousPercentage = percentage
onProgress(percentage)
}
})
}
dataStream.pipe(writer)
let isCancelled = false
cancelToken.token.promise.then(async cancellation => {
log.debug('Download canceled for:', url)
isCancelled = true
writer.destroy()
// Delete partially downloaded file
if (await existsAsync(destPath)) {
log.debug('Deleting partially downloaded file:', destPath)
await unlinkAsync(destPath)
reject(cancellation)
}
})
writer.on('close', () => {
// Signal that we are done downloading
onProgress(undefined)
if (!isCancelled) {
resolve()
}
})
})
.catch(error => {
reject(error)
})
})
}
Example #4
Source File: image_downloader.ts From SpaceEye with MIT License | 5 votes |
/**
* Download an image, canceling any other downloads with the same lock key
* already in progress.
*
* If 2 downloads have the same key and one is already in progress when the
* other is called, the first will be canceled so the second can begin.
*
* If 2+ downloads all have different keys, they will be allowed to download
* in parallel.
*
* @param image - Image to download
* @param cancelToken - Cancel token for this download
* @param lock - Active lock on the download pipeline
* @param onProgress - Called whenever the percentage downloaded value is updated
* @throws {RequestCancelledError} If request is cancelled
* @returns Downloaded image information
*/
export async function downloadImage(
image: ImageSource,
cancelToken: CancelTokenSource,
lock: UpdateLock,
onProgress: (percentage?: number) => void
): Promise<DownloadedImage> {
// FIXME: Don't leave as hardcoded jpg
const downloadedImage = new DownloadedImage(image.id, moment.utc(), 'jpg')
log.debug('Downloading image to:', downloadedImage.getDownloadPath())
try {
await downloadStream(image.url, downloadedImage.getDownloadPath(), cancelToken, onProgress)
lock.destroyCancelToken(cancelToken)
} catch (error) {
lock.destroyCancelToken(cancelToken)
// Throw special error if request is cancelled
if (Axios.isCancel(error)) {
log.debug('Download cancelled for image:', image.id)
throw new RequestCancelledError()
}
log.debug('Unknown error while downloading image:', image.id)
// Rethrow if not
throw error
}
// Sanity check to make sure the image actually exists
if (await existsAsync(downloadedImage.getDownloadPath())) {
log.debug('Successfully downloaded image:', image.id)
await fse.rename(downloadedImage.getDownloadPath(), downloadedImage.getPath())
return downloadedImage
}
// Else, throw an error
log.debug("Image file doesn't exist at:", downloadedImage.getPath())
throw new FileDoesNotExistError(
`Downloaded image "${downloadedImage.getPath()}" does not exist`
)
}
Example #5
Source File: update_lock.ts From SpaceEye with MIT License | 5 votes |
/**
* Remove reference to a token.
*
* @param token - Token to remove
*/
public destroyCancelToken(token: CancelTokenSource): void {
this.downloadCancelTokens.delete(token)
}
Example #6
Source File: update_lock.ts From SpaceEye with MIT License | 5 votes |
// Key-mapped download cancel tokens for a lock
private downloadCancelTokens: Set<CancelTokenSource>
Example #7
Source File: MatrixHttpClient.ts From beacon-sdk with MIT License | 5 votes |
private readonly cancelTokenSource: CancelTokenSource
Example #8
Source File: DiscoveryImageForm.tsx From assisted-ui-lib with Apache License 2.0 | 5 votes |
DiscoveryImageForm: React.FC<DiscoveryImageFormProps> = ({
cluster,
onCancel,
onSuccess,
}) => {
const { infraEnv, error: infraEnvError, isLoading } = useInfraEnv(cluster.id);
const cancelSourceRef = React.useRef<CancelTokenSource>();
const dispatch = useDispatch();
const ocmPullSecret = usePullSecret();
React.useEffect(() => {
cancelSourceRef.current = Axios.CancelToken.source();
return () => cancelSourceRef.current?.cancel('Image generation cancelled by user.');
}, []);
const handleCancel = () => {
dispatch(forceReload());
onCancel();
};
const handleSubmit = async (
formValues: DiscoveryImageFormValues,
formikActions: FormikHelpers<DiscoveryImageFormValues>,
) => {
if (cluster.id && infraEnv?.id) {
try {
const { updatedCluster } = await DiscoveryImageFormService.update(
cluster.id,
infraEnv.id,
cluster.kind,
formValues,
ocmPullSecret,
);
onSuccess();
dispatch(updateCluster(updatedCluster));
} catch (error) {
handleApiError(error, () => {
formikActions.setStatus({
error: {
title: 'Failed to download the discovery Image',
message: getErrorMessage(error),
},
});
});
}
}
};
if (isLoading) {
return <LoadingState></LoadingState>;
} else if (infraEnvError) {
return <ErrorState></ErrorState>;
}
return (
<DiscoveryImageConfigForm
onCancel={handleCancel}
handleSubmit={handleSubmit}
sshPublicKey={infraEnv?.sshAuthorizedKey}
httpProxy={infraEnv?.proxy?.httpProxy}
httpsProxy={infraEnv?.proxy?.httpsProxy}
noProxy={infraEnv?.proxy?.noProxy}
imageType={infraEnv?.type}
/>
);
}
Example #9
Source File: CrustPinner.tsx From crust-apps with Apache License 2.0 | 4 votes |
function CrustPinner ({ className, user }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { queueAction } = useContext<QueueProps>(StatusContext);
const [isBusy, setBusy] = useState(false);
const [password, setPassword] = useState('');
const [cidObject, setCidObject] = useState({ cid: '', prefetchedSize: 0 });
const [validatingCID, setValidatingCID] = useState(false);
const [isValidCID, setValidCID] = useState(false);
const [CIDTips, setCIDTips] = useState({ tips: '', level: 'info' });
const ipfsApiEndpoint = createIpfsApiEndpoints(t)[0];
useEffect(() => {
let cancelTokenSource: CancelTokenSource | null;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
(async function () {
const cid = cidObject.cid;
if (_.isEmpty(cid)) {
setValidCID(false);
setCIDTips({ tips: '', level: 'info' });
return;
}
setCIDTips(t('Checking CID...'));
let isValid = false;
try {
const cidObj = CID.parse(cid);
isValid = CID.asCID(cidObj) != null;
} catch (error) {
// eslint-disable-next-line
console.log(`Invalid CID: ${error.message}`);
}
if (!isValid) {
setValidCID(false);
setCIDTips({ tips: t('Invalid CID'), level: 'warn' });
return;
}
setCIDTips({ tips: t('Retrieving file size...'), level: 'info' });
let fileSize = 0;
try {
setValidatingCID(true);
cancelTokenSource = axios.CancelToken.source();
const res: AxiosResponse = await axios.request({
cancelToken: cancelTokenSource.token,
method: 'POST',
url: `${ipfsApiEndpoint.baseUrl}/api/v0/files/stat?arg=/ipfs/${cid}`,
timeout: 30000
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
fileSize = _.get(res, 'data.CumulativeSize', 0);
if (fileSize > 5 * 1024 * 1024 * 1024) {
setValidCID(false);
setCIDTips({ tips: t('File size exceeds 5GB'), level: 'warn' });
} else if (fileSize > 2 * 1024 * 1024 * 1024) {
setValidCID(true);
setCIDTips({ tips: t('Note: File may be oversize for full network capability and performance'), level: 'warn' });
} else {
setValidCID(true);
setCIDTips({ tips: `${t('File Size')}: ${fileSize} Bytes`, level: 'info' });
}
setCidObject({ cid, prefetchedSize: fileSize });
setValidatingCID(false);
cancelTokenSource = null;
} catch (error) {
console.error(error);
if (axios.isCancel(error)) {
return;
}
fileSize = 2 * 1024 * 1024 * 1024;
setValidCID(true);
setCIDTips({ tips: t('Unknown File'), level: 'warn' });
setValidatingCID(false);
cancelTokenSource = null;
}
})();
return () => {
if (cancelTokenSource) {
cancelTokenSource.cancel('Ipfs api request cancelled');
}
cancelTokenSource = null;
setValidatingCID(false);
setBusy(false);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cidObject.cid]);
const onChangeCID = useCallback<OnInputChange>((e) => {
const cid = (e.target.value ?? '').trim();
setCidObject({ cid, prefetchedSize: 0 });
}, []);
const wFiles = useFiles('pins:files');
const { onChangePinner, pinner, pins } = useAuthPinner();
const onClickPin = useCallback(async () => {
try {
if (!isValidCID || !user.sign || (user.isLocked && !password)) return;
setBusy(true);
const prefix = getPerfix(user);
const msg = user.wallet === 'near' ? user.pubKey || '' : user.account;
const signature = await user.sign(msg, password);
const perSignData = user.wallet === 'elrond' ? signature : `${prefix}-${msg}:${signature}`;
const base64Signature = window.btoa(perSignData);
const AuthBearer = `Bearer ${base64Signature}`;
await axios.request({
data: {
cid: cidObject.cid
},
headers: { Authorization: AuthBearer },
method: 'POST',
url: `${pinner.value}/psa/pins`
});
const filter = wFiles.files.filter((item) => item.Hash !== cidObject.cid);
wFiles.setFiles([{
Hash: cidObject.cid,
Name: '',
Size: cidObject.prefetchedSize.toString(),
UpEndpoint: '',
PinEndpoint: pinner.value
}, ...filter]);
setCidObject({ cid: '', prefetchedSize: 0 });
setCIDTips({ tips: '', level: 'info' });
setBusy(false);
} catch (e) {
setBusy(false);
queueAction({
status: 'error',
message: t('Error'),
action: t('Pin')
});
}
}, [isValidCID, pinner, cidObject, queueAction, user, password, wFiles, t]);
const _onImportResult = useCallback<(m: string, s?: ActionStatusBase['status']) => void>(
(message, status = 'queued') => {
queueAction && queueAction({
action: t('Import files'),
message,
status
});
},
[queueAction, t]
);
const importInputRef = useRef<HTMLInputElement>(null);
const _clickImport = useCallback(() => {
if (!importInputRef.current) return;
importInputRef.current.click();
}, [importInputRef]);
const _onInputImportFile = useCallback<FunInputFile>((e) => {
try {
_onImportResult(t('Importing'));
const fileReader = new FileReader();
const files = e.target.files;
if (!files) return;
fileReader.readAsText(files[0], 'UTF-8');
if (!(/(.json)$/i.test(e.target.value))) {
return _onImportResult(t('File error'), 'error');
}
fileReader.onload = (e) => {
const _list = JSON.parse(e.target?.result as string) as SaveFile[];
if (!Array.isArray(_list)) {
return _onImportResult(t('File content error'), 'error');
}
const fitter: SaveFile[] = [];
const mapImport: { [key: string]: boolean } = {};
for (const item of _list) {
if (item.Hash && item.PinEndpoint) {
fitter.push(item);
mapImport[item.Hash] = true;
}
}
const filterOld = wFiles.files.filter((item) => !mapImport[item.Hash]);
wFiles.setFiles([...fitter, ...filterOld]);
_onImportResult(t('Import Success'), 'success');
};
} catch (e) {
_onImportResult(t('File content error'), 'error');
}
}, [wFiles, _onImportResult, t]);
const _export = useCallback(() => {
const blob = new Blob([JSON.stringify(wFiles.files)], { type: 'application/json; charset=utf-8' });
FileSaver.saveAs(blob, 'pins.json');
}, [wFiles]);
return <main className={className}>
<header>
<div className='inputPanel'>
<div className='inputCIDWithTips'>
<input
className={'inputCID'}
onChange={onChangeCID}
placeholder={t('Enter CID')}
value={cidObject.cid}
/>
{CIDTips.tips && <div className='inputCIDTipsContainer'>
{validatingCID && <MSpinner noLabel />}
<div className={`inputCIDTips ${CIDTips.level !== 'info' ? 'inputCIDTipsWarn' : ''}`}>
{CIDTips.tips}
</div>
</div>}
</div>
<Dropdown
help={t<string>('Your file will be pinned to IPFS for long-term storage.')}
isDisabled={true}
label={t<string>('Select a Web3 IPFS Pinner')}
onChange={onChangePinner}
options={pins}
value={pinner.value}
/>
{
user.isLocked &&
<Password
help={t<string>('The account\'s password specified at the creation of this account.')}
isError={false}
label={t<string>('Password')}
onChange={setPassword}
value={password}
/>
}
<Button
className={'btnPin'}
disable={isBusy || !isValidCID || (user.isLocked && !password)}
isBuzy={isBusy}
label={t('Pin')}
onClick={onClickPin}/>
</div>
</header>
<div className={'importExportPanel'}>
<input
onChange={_onInputImportFile}
ref={importInputRef}
style={{ display: 'none' }}
type={'file'}
/>
<RCButton
icon={'file-import'}
label={t('Import')}
onClick={_clickImport}
/>
<RCButton
icon={'file-export'}
label={t('Export')}
onClick={_export}
/>
</div>
<Table
empty={t<string>('empty')}
emptySpinner={t<string>('Loading')}
header={[
[t('Pins'), 'start', 2],
[t('File Size'), 'expand', 2],
[t('Status'), 'expand'],
[t('Action'), 'expand'],
[]
]}
>
{wFiles.files.map((f, index) =>
<ItemFile key={`files_item-${index}`}>
<td
className='start'
colSpan={2}
>
{shortStr(f.Hash)}
<MCopyButton value={f.Hash}>
<Badge
color='highlight'
hover={t<string>('Copy File CID')}
icon='copy'
/>
</MCopyButton>
</td>
<td
className='end'
colSpan={2}
>{(_.isEmpty(f.Size) || Number(f.Size) === 0) ? '-' : filesize(Number(f.Size), { round: 2 })}</td>
<td
className='end'
colSpan={1}
>
<a
href={`${window.location.origin}/#/storage_files/status/${f.Hash}`}
rel='noreferrer'
target='_blank'
>{t('View status in Crust')}</a>
</td>
<td
className='end'
colSpan={1}
>
<div className='actions'>
<Badge
color='highlight'
hover={t<string>('Open File')}
icon='external-link-square-alt'
onClick={createOnDown(f)}
/>
<MCopyButton value={createUrl(f)}>
<Badge
color='highlight'
hover={t<string>('Copy Download Link')}
icon='copy'
/>
</MCopyButton>
</div>
</td>
<td colSpan={1}/>
</ItemFile>
)}
</Table>
<div>
{t('Note: The file list is cached locally, switching browsers or devices will not keep displaying the original browser information.')}
</div>
</main>;
}
Example #10
Source File: UploadModal.tsx From crust-apps with Apache License 2.0 | 4 votes |
function UploadModal (p: Props): React.ReactElement<Props> {
const { file, onClose = NOOP, onSuccess = NOOP, user } = p;
const { t } = useTranslation();
const { endpoint, endpoints, onChangeEndpoint } = useAuthGateway();
const { onChangePinner, pinner, pins } = useAuthPinner();
const [password, setPassword] = useState('');
const [isBusy, setBusy] = useState(false);
const fileSizeError = useMemo(() => {
const MAX = 100 * 1024 * 1024;
if (file.file) {
return file.file.size > MAX;
} else if (file.files) {
let sum = 0;
for (const f of file.files) {
sum += f.size;
}
return sum > MAX;
}
return false;
}, [file]);
// const fileSizeError = file.size > 100 * 1024 * 1024;
const [error, setError] = useState('');
const errorText = fileSizeError ? t<string>('Do not upload files larger than 100MB!') : error;
const [upState, setUpState] = useState({ progress: 0, up: false });
const [cancelUp, setCancelUp] = useState<CancelTokenSource | null>(null);
const _onClose = useCallback(() => {
if (cancelUp) cancelUp.cancel();
onClose();
}, [cancelUp, onClose]);
const _onClickUp = useCallback(async () => {
setError('');
if (!user.account || !user.sign) {
return;
}
try {
// 1: sign
setBusy(true);
const prefix = getPerfix(user);
const msg = user.wallet === 'near' ? user.pubKey || '' : user.account;
const signature = await user.sign(msg, password);
const perSignData = user.wallet === 'elrond' ? signature : `${prefix}-${msg}:${signature}`;
const base64Signature = window.btoa(perSignData);
const AuthBasic = `Basic ${base64Signature}`;
const AuthBearer = `Bearer ${base64Signature}`;
// 2: up file
const cancel = axios.CancelToken.source();
setCancelUp(cancel);
setUpState({ progress: 0, up: true });
const form = new FormData();
if (file.file) {
form.append('file', file.file, file.file.name);
} else if (file.files) {
for (const f of file.files) {
form.append('file', f, f.webkitRelativePath);
}
}
const UpEndpoint = endpoint.value;
const upResult = await axios.request<UploadRes | string>({
cancelToken: cancel.token,
data: form,
headers: { Authorization: AuthBasic },
maxContentLength: 100 * 1024 * 1024,
method: 'POST',
onUploadProgress: (p: { loaded: number, total: number }) => {
const percent = p.loaded / p.total;
setUpState({ progress: Math.round(percent * 99), up: true });
},
params: { pin: true },
url: `${UpEndpoint}/api/v0/add`
});
let upRes: UploadRes;
if (typeof upResult.data === 'string') {
const jsonStr = upResult.data.replaceAll('}\n{', '},{');
const items = JSON.parse(`[${jsonStr}]`) as UploadRes[];
const folder = items.length - 1;
upRes = items[folder];
delete items[folder];
upRes.items = items;
} else {
upRes = upResult.data;
}
console.info('upResult:', upResult);
setCancelUp(null);
setUpState({ progress: 100, up: false });
// remote pin order
const PinEndpoint = pinner.value;
await axios.request({
data: {
cid: upRes.Hash,
name: upRes.Name
},
headers: { Authorization: AuthBearer },
method: 'POST',
url: `${PinEndpoint}/psa/pins`
});
onSuccess({
...upRes,
PinEndpoint,
UpEndpoint
});
} catch (e) {
setUpState({ progress: 0, up: false });
setBusy(false);
console.error(e);
setError(t('Network Error,Please try to switch a Gateway.'));
// setError((e as Error).message);
}
}, [user, file, password, pinner, endpoint, onSuccess, t]);
return (
<Modal
header={t<string>(file.dir ? 'Upload Folder' : 'Upload File')}
onClose={_onClose}
open={true}
size={'large'}
>
<Modal.Content>
<Modal.Columns>
<div style={{ paddingLeft: '2rem', width: '100%', maxHeight: 300, overflow: 'auto' }}>
{
file.file && <ShowFile file={file.file}/>
}
{file.files && file.files.map((f, i) =>
<ShowFile
file={f}
key={`file_item:${i}`}/>
)}
</div>
</Modal.Columns>
<Modal.Columns>
<Dropdown
help={t<string>('File streaming and wallet authentication will be processed by the chosen gateway.')}
isDisabled={isBusy}
label={t<string>('Select a Web3 IPFS Gateway')}
onChange={onChangeEndpoint}
options={endpoints}
value={endpoint.value}
/>
</Modal.Columns>
<Modal.Columns>
<Dropdown
help={t<string>('Your file will be pinned to IPFS for long-term storage.')}
isDisabled={true}
label={t<string>('Select a Web3 IPFS Pinner')}
onChange={onChangePinner}
options={pins}
value={pinner.value}
/>
</Modal.Columns>
<Modal.Columns>
{
!upState.up && user.isLocked &&
<Password
help={t<string>('The account\'s password specified at the creation of this account.')}
isError={false}
label={t<string>('password')}
onChange={setPassword}
value={password}
/>
}
<Progress
progress={upState.progress}
style={{ marginLeft: '2rem', marginTop: '2rem', width: 'calc(100% - 2rem)' }}
/>
{
errorText &&
<div
style={{
color: 'orangered',
padding: '1rem',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all'
}}
>
{errorText}
</div>
}
</Modal.Columns>
</Modal.Content>
<Modal.Actions onCancel={_onClose}>
<Button
icon={'arrow-circle-up'}
isBusy={isBusy}
isDisabled={fileSizeError}
label={t<string>('Sign and Upload')}
onClick={_onClickUp}
/>
</Modal.Actions>
</Modal>
);
}
Example #11
Source File: TorrentMediainfo.tsx From flood with GNU General Public License v3.0 | 4 votes |
TorrentMediainfo: FC = () => {
const {i18n} = useLingui();
const cancelToken = useRef<CancelTokenSource>(axios.CancelToken.source());
const clipboardRef = useRef<HTMLInputElement>(null);
const [mediainfo, setMediainfo] = useState<string | null>(null);
const [fetchMediainfoError, setFetchMediainfoError] = useState<Error | null>(null);
const [isFetchingMediainfo, setIsFetchingMediainfo] = useState<boolean>(true);
const [isCopiedToClipboard, setIsCopiedToClipboard] = useState<boolean>(false);
useEffect(() => {
const {current: currentCancelToken} = cancelToken;
if (UIStore.activeModal?.id === 'torrent-details') {
TorrentActions.fetchMediainfo(UIStore.activeModal?.hash, cancelToken.current.token).then(
(fetchedMediainfo) => {
setMediainfo(fetchedMediainfo.output);
setIsFetchingMediainfo(false);
},
(error) => {
if (!axios.isCancel(error)) {
setFetchMediainfoError(error.response.data);
setIsFetchingMediainfo(false);
}
},
);
}
return () => {
currentCancelToken.cancel();
};
}, []);
let headingMessageId = 'mediainfo.heading';
if (isFetchingMediainfo) {
headingMessageId = 'mediainfo.fetching';
} else if (fetchMediainfoError) {
headingMessageId = 'mediainfo.execError';
}
return (
<div className="torrent-details__section mediainfo modal__content--nested-scroll__content">
<div className="mediainfo__toolbar">
<div className="mediainfo__toolbar__item">
<span className="torrent-details__table__heading--tertiary">
<Trans id={headingMessageId} />
</span>
</div>
{mediainfo && (
<Tooltip
content={i18n._(isCopiedToClipboard ? 'general.clipboard.copied' : 'general.clipboard.copy')}
wrapperClassName="tooltip__wrapper mediainfo__toolbar__item"
>
<Button
priority="tertiary"
onClick={() => {
if (mediainfo != null) {
if (typeof navigator.clipboard?.writeText === 'function') {
navigator.clipboard.writeText(mediainfo).then(() => {
setIsCopiedToClipboard(true);
});
} else if (clipboardRef.current != null) {
clipboardRef.current.value = mediainfo;
clipboardRef.current.select();
document.execCommand('copy');
setIsCopiedToClipboard(true);
}
}
}}
>
{isCopiedToClipboard ? <Checkmark /> : <Clipboard />}
</Button>
</Tooltip>
)}
</div>
<input ref={clipboardRef} style={{width: '0.1px', height: '1px', position: 'absolute', right: 0}} />
{fetchMediainfoError ? (
<pre className="mediainfo__output mediainfo__output--error">{fetchMediainfoError.message}</pre>
) : (
<pre className="mediainfo__output">{mediainfo}</pre>
)}
</div>
);
}