/** ****************************************************************************** * Copyright (c) 2022 Precies. Software and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 * ****************************************************************************** */ import { FunctionComponent, useContext, useEffect, useState, useRef } from 'react'; import { Button, Dialog, DialogTitle, DialogContent, DialogActions, Typography, Box, Paper } from '@mui/material'; import { CheckCircleOutline } from '@mui/icons-material'; import Dropzone, { FileRejection } from 'react-dropzone'; import { ButtonWithProgress } from '../../components/button-with-progress'; import { ErrorResult, isError } from '../../extension-registry-types'; import { MainContext } from '../../context'; import { styled, Theme } from '@mui/material/styles'; const getColor = (isFocused: boolean, isDragAccept: boolean, isDragReject: boolean) => { if (isDragAccept) { return 'success.main'; } else if (isDragReject) { return 'error.main'; } else if (isFocused) { return 'secondary.main'; } else { return 'text.primary'; } }; const DropzoneDiv = styled('div')(({ theme }: { theme: Theme }) => ({ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', padding: theme.spacing(3), borderWidth: 2, borderRadius: 2, borderStyle: 'dashed', backgroundColor: theme.palette.background.default, color: theme.palette.text.primary, outline: 'none', transition: 'border .24s ease-in-out' })); export const PublishExtensionDialog: FunctionComponent = props => { const [open, setOpen] = useState(false); const [publishing, setPublishing] = useState(false); const [fileToPublish, setFileToPublish] = useState(); const [oldFileToPublish, setOldFileToPublish] = useState(); const [rejectedFile, setRejectedFile] = useState(); const context = useContext(MainContext); const effectiveMaxSize = context.version?.maxExtensionSize ?? 512 * 1024 * 1024; const abortController = useRef(new AbortController()); useEffect(() => { document.addEventListener('keydown', handleEnter); return () => { abortController.current.abort(); document.removeEventListener('keydown', handleEnter); }; }, []); const formatSize = (bytes: number): string => { const units = ['B', 'kB', 'MB', 'GB', 'TB']; let value = bytes; let unitIndex = 0; while (value >= 1024 && unitIndex < units.length - 1) { value /= 1024; unitIndex++; } const formattedValue = unitIndex === 0 ? value.toString() : value.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return `${formattedValue} ${units[unitIndex]}`; }; const handleOpenDialog = () => setOpen(true); const handleCancel = () => { if (publishing) { abortController.current.abort(); abortController.current = new AbortController(); } setOpen(false); setPublishing(false); setFileToPublish(undefined); setOldFileToPublish(undefined); setRejectedFile(undefined); }; const handleUndo = () => { setFileToPublish(oldFileToPublish); setOldFileToPublish(undefined); setRejectedFile(undefined); }; const handleDrop = (acceptedFiles: T[], fileRejections: FileRejection[]) => { const tooLarge = fileRejections.find(r => r.errors.some(e => e.code === 'file-too-large')); if (tooLarge) { if (fileToPublish) { setOldFileToPublish(fileToPublish); setFileToPublish(undefined); } setRejectedFile(tooLarge.file); return; } setRejectedFile(undefined); if (fileToPublish) { setOldFileToPublish(fileToPublish); } setFileToPublish(acceptedFiles[0]); }; const handleFileDialogOpen = () => setOldFileToPublish(undefined); const handlePublish = async () => { if (!context.user || !fileToPublish) { return; } setPublishing(true); let published = false; let retryPublish = false; try { published = await tryPublishExtension(fileToPublish); } catch (err) { try { await tryResolveNamespaceError(err); retryPublish = true; } catch (namespaceError) { context.handleError(namespaceError); } } if (retryPublish) { try { published = await tryPublishExtension(fileToPublish); } catch (err) { context.handleError(err); } } if (published) { props.extensionPublished(); setOpen(false); setFileToPublish(undefined); setOldFileToPublish(undefined); } setPublishing(false); }; const handleEnter = (e: KeyboardEvent) => { if (e.code === 'Enter') { handlePublish(); } }; const tryPublishExtension = async (fileToPublish: File): Promise => { let published = false; const publishResponse = await context.service.publishExtension(abortController.current, fileToPublish); if (isError(publishResponse)) { throw publishResponse; } published = true; return published; }; const tryResolveNamespaceError = async (publishResponse: Readonly) => { const namespaceError = 'Unknown publisher: '; if (!isError(publishResponse) || !publishResponse.error.startsWith(namespaceError)) { throw publishResponse; } const namespace = publishResponse.error.substring( namespaceError.length, publishResponse.error.indexOf('\n', namespaceError.length) ); if (!namespace || namespace === 'undefined') { const result: Readonly = { error: `Invalid namespace: ${namespace}` }; throw result; } const namespaceResponse = await context.service.createNamespace(abortController.current, namespace); if (isError(namespaceResponse)) { throw namespaceResponse; } }; const successColor = context.pageSettings.themeType === 'dark' ? '#fff' : '#000'; return ( <> Publish extension {oldFileToPublish ? ( Changed extension package. ) : null} {({ getRootProps, getInputProps, isFocused, isDragAccept, isDragReject }) => (

Drag & drop an extension here, or click to select an extension

(Only 1 *.vsix package at a time is accepted)

{effectiveMaxSize ? ( Max size: {formatSize(effectiveMaxSize)} ) : null}
{fileToPublish ? ( {fileToPublish.name} ({formatSize(fileToPublish.size)}) ) : null} {rejectedFile ? ( {rejectedFile.name} ({formatSize(rejectedFile.size)}) — exceeds the{' '} {effectiveMaxSize ? formatSize(effectiveMaxSize) : '?'} limit ) : null}
)}
Publish
); }; export interface PublishExtensionDialogProps { extensionPublished: () => void; }