fix: make upload dialog work as intended

This commit is contained in:
en0tuk
2025-12-01 02:41:47 +05:00
parent 23c0d64575
commit 247d5066c6
6 changed files with 141 additions and 134 deletions

View File

@@ -1,8 +1,6 @@
// FIXME: replace with radix tooltip
// import Tooltip from '@/components/elements/tooltip/Tooltip';
// FIXME: add icons back
import { useSignal } from '@preact/signals-react';
import { useContext, useEffect } from 'react';
import { Xmark } from '@gravity-ui/icons';
import * as Tooltip from '@radix-ui/react-tooltip';
import { useContext, useEffect, useState } from 'react';
import ActionButton from '@/components/elements/ActionButton';
import Code from '@/components/elements/Code';
@@ -12,27 +10,39 @@ import asDialog from '@/hoc/asDialog';
import { ServerContext } from '@/state/server';
const svgProps = {
cx: 16,
cy: 16,
r: 14,
strokeWidth: 3,
fill: 'none',
stroke: 'currentColor',
};
// TODO: Make it more pretty
const CircleProgress = ({ progress, className }: { progress: number; className?: string }) => {
const radius = 12;
const circumference = 2 * Math.PI * radius;
const offset = circumference - (progress / 100) * circumference;
const Spinner = ({ progress, className }: { progress: number; className?: string }) => (
<svg viewBox={'0 0 32 32'} className={className}>
<circle {...svgProps} className={'opacity-25'} />
<circle
{...svgProps}
stroke={'white'}
strokeDasharray={28 * Math.PI}
className={'rotate-[-90deg] origin-[50%_50%] transition-[stroke-dashoffset] duration-300'}
style={{ strokeDashoffset: ((100 - progress) / 100) * 28 * Math.PI }}
/>
</svg>
);
return (
<svg className={className} viewBox='0 0 32 32'>
<circle
stroke='currentColor'
strokeWidth='4'
fill='transparent'
r={radius}
cx='16'
cy='16'
className='opacity-25'
/>
<circle
className='transition-all duration-300'
stroke='currentColor'
strokeWidth='4'
strokeLinecap='round'
fill='transparent'
r={radius}
cx='16'
cy='16'
strokeDasharray={circumference}
strokeDashoffset={offset}
transform='rotate(-90 16 16)'
/>
</svg>
);
};
const FileUploadList = () => {
const { close } = useContext(DialogWrapperContext);
@@ -43,34 +53,62 @@ const FileUploadList = () => {
);
return (
<div className={'space-y-2 mt-6'}>
{uploads.map(([name, file]) => (
<div key={name} className={'flex items-center space-x-3 bg-zinc-700 p-3 rounded-sm'}>
{/* <Tooltip content={`${Math.floor((file.loaded / file.total) * 100)}%`} placement={'left'}> */}
<div className={'shrink-0'}>
<Spinner progress={(file.loaded / file.total) * 100} className={'w-6 h-6'} />
<Tooltip.Provider>
<div className={'space-y-2 mt-6'}>
{uploads.map(([name, file]) => (
<div key={name} className={'flex items-center space-x-3 bg-zinc-700 p-3 rounded-sm'}>
<Tooltip.Root delayDuration={200}>
<Tooltip.Trigger asChild>
<div className={'shrink-0'}>
<CircleProgress progress={(file.loaded / file.total) * 100} className={'w-6 h-6'} />
</div>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
side='left'
className='px-2 py-1 text-sm bg-gray-800 text-gray-100 rounded shadow-lg z-9999'
sideOffset={5}
>
{`${Math.floor((file.loaded / file.total) * 100)}%`}
<Tooltip.Arrow className='fill-gray-800' />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
<Code className={'flex-1 truncate'}>{name}</Code>
<Tooltip.Root delayDuration={200}>
<Tooltip.Trigger asChild>
<ActionButton
variant='secondary'
size='sm'
onClick={cancelFileUpload.bind(this, name)}
className='hover:!text-red-400'
>
<Xmark />
</ActionButton>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
side='right'
className='px-2 py-1 text-sm bg-gray-800 text-red-400 rounded shadow-lg z-9999'
sideOffset={5}
>
Cancel
<Tooltip.Arrow className='fill-gray-800' />
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
{/* </Tooltip> */}
<Code className={'flex-1 truncate'}>{name}</Code>
<ActionButton
variant='secondary'
size='sm'
onClick={cancelFileUpload.bind(this, name)}
className='hover:!text-red-400'
>
Cancel
))}
<Dialog.Footer>
<ActionButton variant='danger' onClick={() => clearFileUploads()}>
Cancel Uploads
</ActionButton>
</div>
))}
<Dialog.Footer>
<ActionButton variant='danger' onClick={() => clearFileUploads()}>
Cancel Uploads
</ActionButton>
<ActionButton variant='secondary' onClick={close}>
Close
</ActionButton>
</Dialog.Footer>
</div>
<ActionButton variant='secondary' onClick={close}>
Close
</ActionButton>
</Dialog.Footer>
</div>
</Tooltip.Provider>
);
};
@@ -80,50 +118,65 @@ const FileUploadListDialog = asDialog({
})(FileUploadList);
const FileManagerStatus = () => {
const open = useSignal(false);
const [open, setOpen] = useState(false);
const count = ServerContext.useStoreState((state) => Object.keys(state.files.uploads).length);
useEffect(() => {
if (count === 0) {
open.value = false;
setOpen(false);
}
}, [count]);
return (
<>
{count > 0 && (
// <Tooltip content={`${count} files are uploading, click to view`}>
<ActionButton
variant='secondary'
size='sm'
className='w-10 h-10 p-0'
onClick={() => (open.value = true)}
>
<svg
className='animate-spin h-5 w-5 text-white'
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'
>
<circle
className='opacity-25'
cx='12'
cy='12'
r='10'
stroke='currentColor'
strokeWidth='4'
></circle>
<path
className='opacity-75'
fill='currentColor'
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'
></path>
</svg>
</ActionButton>
// </Tooltip>
)}
<FileUploadListDialog open={open.value} onClose={() => (open.value = false)} />
<Tooltip.Provider>
{count > 0 && (
<Tooltip.Root delayDuration={200}>
<Tooltip.Trigger asChild>
<ActionButton
variant='secondary'
size='sm'
className='w-10 h-10 p-0'
onClick={() => {
setOpen(true);
}}
>
<svg
className='animate-spin h-5 w-5 text-white'
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'
>
<circle
className='opacity-25'
cx='12'
cy='12'
r='10'
stroke='currentColor'
strokeWidth='4'
></circle>
<path
className='opacity-75'
fill='currentColor'
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'
></path>
</svg>
</ActionButton>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
side='top'
className='px-2 py-1 text-sm bg-gray-800 text-gray-100 rounded shadow-lg'
sideOffset={5}
>
{`${count} files are uploading, click to view`}
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
<FileUploadListDialog open={open} onClose={() => setOpen(false)} />
</Tooltip.Provider>
</>
);
};

View File

@@ -1,46 +0,0 @@
import { Progress } from '@radix-ui/react-progress';
import { X } from 'lucide-react';
import Code from '@/components/elements/Code';
import { cn } from '@/lib/utils';
import { useStoreActions } from '@/state/hooks';
import { ServerContext } from '@/state/server';
// Assuming you use a utility like this for conditional classnames
interface FileUploadRowProps {
name: string;
loaded: number;
total: number;
}
export default function FileUploadRow({ name, loaded, total }: FileUploadRowProps) {
const cancel = ServerContext.useStoreActions((actions) => actions.files.cancelFileUpload);
const percent = Math.floor((loaded / total) * 100);
return (
<div className='flex items-center px-4 py-3 bg-zinc-800 border-b border-zinc-700 rounded-md space-x-4'>
<div className='flex-1 truncate'>
<Code>{name}</Code>
</div>
<div className='flex flex-col w-1/3'>
<Progress value={percent} className='h-2 rounded bg-zinc-700 overflow-hidden'>
<div className='h-full bg-blue-500 transition-all' style={{ width: `${percent}%` }} />
</Progress>
<div className='text-xs text-zinc-400 mt-1'>{percent}%</div>
</div>
<button
onClick={() => cancelFileUpload(name)}
className={cn('text-red-400 hover:text-red-200 transition-colors', 'p-1')}
title='Cancel upload'
>
<X size={16} />
</button>
</div>
);
}

View File

@@ -133,7 +133,7 @@ const OperationProgressModal: React.FC<Props> = ({
return (
<Dialog
open={visible}
onClose={canClose ? handleClose : () => { }}
onClose={canClose ? handleClose : () => {}}
preventExternalClose={!canClose}
hideCloseIcon={!canClose}
title={operationType}

View File

@@ -101,8 +101,8 @@ const VariableBox = ({ variable }: Props) => {
? 'Enabled'
: 'Disabled'
: variable.serverValue === '1'
? 'On'
: 'Off'}
? 'On'
: 'Off'}
</span>
<Switch
disabled={!canEdit || !variable.isEditable}

0
storage/app/private/.gitignore vendored Normal file → Executable file
View File

0
storage/app/public/.gitignore vendored Normal file → Executable file
View File