fix: spdv-914 - Modals are partially cut off in File Manager on Windows (Chrome) (#219)
* fix: spdv-914 * refactor: spdv-914-fix * refactor: spdv-914-fix
This commit is contained in:
@@ -20,7 +20,9 @@ const useStyles = makeStyles()(theme => ({
|
||||
},
|
||||
|
||||
fileManagerOn: {
|
||||
padding: '0px',
|
||||
padding: '0px !important',
|
||||
margin: '0px !important',
|
||||
maxWidth: '100% !important',
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
}
|
||||
|
||||
.fm-modal-container .fm-modal-window-body .fm-modal-white-section {
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
word-break: break-word;
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -41,19 +41,20 @@ export function ConfirmModal({
|
||||
<div className={`fm-modal-container fm-confirm-modal ${background ? '' : 'fm-modal-no-background'}`}>
|
||||
<div className="fm-modal-window">
|
||||
<div className="fm-modal-window-header">{title}</div>
|
||||
|
||||
<div className="fm-modal-window-body">
|
||||
{isProgress ? (
|
||||
<div className="fm-spinner-center">
|
||||
<div className="fm-spinner-message">
|
||||
<div>{spinnerMessage || 'Working…'}</div>
|
||||
<div className="fm-mini-spinner" />
|
||||
<div className="fm-modal-window-scrollable">
|
||||
<div className="fm-modal-window-body">
|
||||
{isProgress ? (
|
||||
<div className="fm-spinner-center">
|
||||
<div className="fm-spinner-message">
|
||||
<div>{spinnerMessage || 'Working…'}</div>
|
||||
<div className="fm-mini-spinner" />
|
||||
</div>
|
||||
{showMinimize && <Button label="Minimize" variant="secondary" onClick={onMinimize} />}
|
||||
</div>
|
||||
{showMinimize && <Button label="Minimize" variant="secondary" onClick={onMinimize} />}
|
||||
</div>
|
||||
) : (
|
||||
<div className="fm-modal-white-section">{message}</div>
|
||||
)}
|
||||
) : (
|
||||
<div className="fm-modal-white-section">{message}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showFooter && (onCancel || onConfirm) && (
|
||||
|
||||
@@ -139,87 +139,89 @@ export function CreateDriveModal({
|
||||
<div className="fm-modal-container">
|
||||
<div className="fm-modal-window">
|
||||
<div className="fm-modal-window-header">Create new drive</div>
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="drive-name" className="fm-input-label">
|
||||
Drive name: <Tooltip label={TOOLTIPS.DRIVE_NAME} />
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="drive-name"
|
||||
placeholder="My important files"
|
||||
value={driveName}
|
||||
onChange={e => setDriveName(e.target.value)}
|
||||
onBlur={() => setDuplicate(true)}
|
||||
maxLength={maxDriveNameLength}
|
||||
/>
|
||||
{validationError && <div className="fm-error-text">{validationError}</div>}
|
||||
</div>
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="drive-initial-capacity" className="fm-input-label">
|
||||
Initial capacity: <Tooltip label={TOOLTIPS.DRIVE_INITIAL_CAPACITY} />
|
||||
</label>
|
||||
<CustomDropdown
|
||||
id="drive-initial-capacity"
|
||||
options={sizeMarks}
|
||||
value={capacity}
|
||||
onChange={handleCapacityChange}
|
||||
placeholder="Select a value"
|
||||
/>
|
||||
</div>
|
||||
<div className="fm-modal-info-warning">
|
||||
Drive sizes are calculated automatically from your current stamp configuration.
|
||||
</div>
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="drive-desired-lifetime" className="fm-input-label">
|
||||
Desired lifetime: <Tooltip label={TOOLTIPS.DRIVE_DESIRED_LIFETIME} />
|
||||
</label>
|
||||
<CustomDropdown
|
||||
id="drive-desired-lifetime"
|
||||
options={desiredLifetimeOptions}
|
||||
value={lifetimeIndex}
|
||||
onChange={setLifetimeIndex}
|
||||
placeholder="Select a value"
|
||||
/>
|
||||
</div>
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="drive-security-level" className="fm-input-label">
|
||||
Security Level <Tooltip label={TOOLTIPS.DRIVE_SECURITY_LEVEL} />
|
||||
</label>
|
||||
<FMSlider
|
||||
id="drive-security-level"
|
||||
defaultValue={0}
|
||||
marks={erasureCodeMarks}
|
||||
onChange={value => setErasureCodeLevel(value)}
|
||||
minValue={minMarkValue}
|
||||
maxValue={maxMarkValue}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="fm-modal-estimated-cost-container">
|
||||
<div className="fm-emphasized-text">Estimated Cost:</div>
|
||||
<div>
|
||||
{cost} BZZ {isBalanceSufficient ? '' : '(Insufficient balance)'}
|
||||
{isxDaiBalanceSufficient ? '' : ' (Insufficient xDAI balance)'}
|
||||
</div>
|
||||
<Tooltip label={TOOLTIPS.DRIVE_ESTIMATED_COST} bottomTooltip={true} />
|
||||
<div className="fm-modal-window-scrollable">
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="drive-name" className="fm-input-label">
|
||||
Drive name: <Tooltip label={TOOLTIPS.DRIVE_NAME} />
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="drive-name"
|
||||
placeholder="My important files"
|
||||
value={driveName}
|
||||
onChange={e => setDriveName(e.target.value)}
|
||||
onBlur={() => setDuplicate(true)}
|
||||
maxLength={maxDriveNameLength}
|
||||
/>
|
||||
{validationError && <div className="fm-error-text">{validationError}</div>}
|
||||
</div>
|
||||
<div>(Based on current network conditions)</div>
|
||||
{isUltraLightNode && (
|
||||
<div>
|
||||
Creating a drive requires running a light node. Please{' '}
|
||||
<a
|
||||
href="https://docs.ethswarm.org/docs/desktop/configuration/#upgrading-from-an-ultra-light-to-a-light-node"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
upgrade
|
||||
</a>{' '}
|
||||
to continue.
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="drive-initial-capacity" className="fm-input-label">
|
||||
Initial capacity: <Tooltip label={TOOLTIPS.DRIVE_INITIAL_CAPACITY} />
|
||||
</label>
|
||||
<CustomDropdown
|
||||
id="drive-initial-capacity"
|
||||
options={sizeMarks}
|
||||
value={capacity}
|
||||
onChange={handleCapacityChange}
|
||||
placeholder="Select a value"
|
||||
/>
|
||||
</div>
|
||||
<div className="fm-modal-info-warning">
|
||||
Drive sizes are calculated automatically from your current stamp configuration.
|
||||
</div>
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="drive-desired-lifetime" className="fm-input-label">
|
||||
Desired lifetime: <Tooltip label={TOOLTIPS.DRIVE_DESIRED_LIFETIME} />
|
||||
</label>
|
||||
<CustomDropdown
|
||||
id="drive-desired-lifetime"
|
||||
options={desiredLifetimeOptions}
|
||||
value={lifetimeIndex}
|
||||
onChange={setLifetimeIndex}
|
||||
placeholder="Select a value"
|
||||
/>
|
||||
</div>
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="drive-security-level" className="fm-input-label">
|
||||
Security Level <Tooltip label={TOOLTIPS.DRIVE_SECURITY_LEVEL} />
|
||||
</label>
|
||||
<FMSlider
|
||||
id="drive-security-level"
|
||||
defaultValue={0}
|
||||
marks={erasureCodeMarks}
|
||||
onChange={value => setErasureCodeLevel(value)}
|
||||
minValue={minMarkValue}
|
||||
maxValue={maxMarkValue}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="fm-modal-estimated-cost-container">
|
||||
<div className="fm-emphasized-text">Estimated Cost:</div>
|
||||
<div>
|
||||
{cost} BZZ {isBalanceSufficient ? '' : '(Insufficient balance)'}
|
||||
{isxDaiBalanceSufficient ? '' : ' (Insufficient xDAI balance)'}
|
||||
</div>
|
||||
<Tooltip label={TOOLTIPS.DRIVE_ESTIMATED_COST} bottomTooltip={true} />
|
||||
</div>
|
||||
)}
|
||||
<div>(Based on current network conditions)</div>
|
||||
{isUltraLightNode && (
|
||||
<div>
|
||||
Creating a drive requires running a light node. Please{' '}
|
||||
<a
|
||||
href="https://docs.ethswarm.org/docs/desktop/configuration/#upgrading-from-an-ultra-light-to-a-light-node"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
upgrade
|
||||
</a>{' '}
|
||||
to continue.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fm-modal-window-footer">
|
||||
|
||||
@@ -42,79 +42,82 @@ export function DeleteFileModal({
|
||||
<div className="fm-modal-window-header">
|
||||
<TrashIcon /> <span className="fm-main-font-color">{headerText}</span>
|
||||
</div>
|
||||
<div className="fm-modal-window-scrollable">
|
||||
<div className="fm-modal-window-body">
|
||||
{isBulk && (
|
||||
<ul className="fm-delete-file-modal-list">
|
||||
{names.map(n => (
|
||||
<li key={n} className="fm-delete-file-modal-list-item" title={n}>
|
||||
{n}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<FormControl component="fieldset">
|
||||
<div className="fm-radio-group">
|
||||
<div className="fm-form-control-label">
|
||||
<FormControlLabel
|
||||
value={FileAction.Trash}
|
||||
control={<Radio checked={value === FileAction.Trash} onChange={() => setValue(FileAction.Trash)} />}
|
||||
label={
|
||||
<div className="fm-radio-label">
|
||||
<div className="fm-radio-label-header fm-main-font-color fm-line-height-fit">
|
||||
Move to Trash
|
||||
<Tooltip label={TOOLTIPS.FILE_OPERATION_TRASH} iconSize="14px" />
|
||||
</div>
|
||||
<div onClick={e => e.preventDefault()}>
|
||||
Moves {subjectNoun} to the trash. It will still take up space on{' '}
|
||||
{currentDriveName ?? 'this drive'} and expire along with it. You can restore it later.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="fm-modal-window-body">
|
||||
{isBulk && (
|
||||
<ul className="fm-delete-file-modal-list">
|
||||
{names.map(n => (
|
||||
<li key={n} className="fm-delete-file-modal-list-item" title={n}>
|
||||
{n}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<FormControl component="fieldset">
|
||||
<div className="fm-radio-group">
|
||||
<div className="fm-form-control-label">
|
||||
<FormControlLabel
|
||||
value={FileAction.Trash}
|
||||
control={<Radio checked={value === FileAction.Trash} onChange={() => setValue(FileAction.Trash)} />}
|
||||
label={
|
||||
<div className="fm-radio-label">
|
||||
<div className="fm-radio-label-header fm-main-font-color fm-line-height-fit">
|
||||
Move to Trash
|
||||
<Tooltip label={TOOLTIPS.FILE_OPERATION_TRASH} iconSize="14px" />
|
||||
<div className="fm-form-control-label">
|
||||
<FormControlLabel
|
||||
value={FileAction.Forget}
|
||||
control={
|
||||
<Radio checked={value === FileAction.Forget} onChange={() => setValue(FileAction.Forget)} />
|
||||
}
|
||||
label={
|
||||
<div className="fm-radio-label">
|
||||
<div className="fm-radio-label-header fm-main-font-color fm-line-height-fit">
|
||||
Forget
|
||||
<Tooltip label={TOOLTIPS.FILE_OPERATION_FORGET} iconSize="14px" />
|
||||
</div>
|
||||
<div onClick={e => e.preventDefault()}>
|
||||
Removes {subjectNoun} from your view. The data will remain on Swarm until{' '}
|
||||
{currentDriveName ?? 'the drive'} expires. This action cannot be easily undone.
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={e => e.preventDefault()}>
|
||||
Moves {subjectNoun} to the trash. It will still take up space on{' '}
|
||||
{currentDriveName ?? 'this drive'} and expire along with it. You can restore it later.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="fm-form-control-label">
|
||||
<FormControlLabel
|
||||
value={FileAction.Forget}
|
||||
control={<Radio checked={value === FileAction.Forget} onChange={() => setValue(FileAction.Forget)} />}
|
||||
label={
|
||||
<div className="fm-radio-label">
|
||||
<div className="fm-radio-label-header fm-main-font-color fm-line-height-fit">
|
||||
Forget
|
||||
<Tooltip label={TOOLTIPS.FILE_OPERATION_FORGET} iconSize="14px" />
|
||||
<div className="fm-form-control-label">
|
||||
<FormControlLabel
|
||||
value={FileAction.Destroy}
|
||||
control={
|
||||
<Radio checked={value === FileAction.Destroy} onChange={() => setValue(FileAction.Destroy)} />
|
||||
}
|
||||
label={
|
||||
<div className="fm-radio-label">
|
||||
<div className="fm-radio-label-header fm-main-font-color fm-line-height-fit">
|
||||
Destroy entire drive {currentDriveName ? `‘${currentDriveName}’` : ''} to delete this{' '}
|
||||
{subjectNoun}
|
||||
</div>
|
||||
<div className="fm-red-font" onClick={e => e.preventDefault()}>
|
||||
<AlertIcon size="14px" className="fm-alert-icon-inline" />
|
||||
Warning: This will make all files on this drive inaccessible. This action is irreversible.
|
||||
</div>
|
||||
</div>
|
||||
<div onClick={e => e.preventDefault()}>
|
||||
Removes {subjectNoun} from your view. The data will remain on Swarm until{' '}
|
||||
{currentDriveName ?? 'the drive'} expires. This action cannot be easily undone.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="fm-form-control-label">
|
||||
<FormControlLabel
|
||||
value={FileAction.Destroy}
|
||||
control={
|
||||
<Radio checked={value === FileAction.Destroy} onChange={() => setValue(FileAction.Destroy)} />
|
||||
}
|
||||
label={
|
||||
<div className="fm-radio-label">
|
||||
<div className="fm-radio-label-header fm-main-font-color fm-line-height-fit">
|
||||
Destroy entire drive {currentDriveName ? `‘${currentDriveName}’` : ''} to delete this{' '}
|
||||
{subjectNoun}
|
||||
</div>
|
||||
<div className="fm-red-font" onClick={e => e.preventDefault()}>
|
||||
<AlertIcon size="14px" className="fm-alert-icon-inline" />
|
||||
Warning: This will make all files on this drive inaccessible. This action is irreversible.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="fm-modal-window-footer">
|
||||
|
||||
@@ -23,20 +23,22 @@ export function ProgressDestroyModal({ drive, onMinimize }: ProgressDestroyModal
|
||||
|
||||
return createPortal(
|
||||
<div className="fm-modal-container">
|
||||
<div className="fm-modal-window">
|
||||
<div className="fm-modal-window-header fm-red-font">Destroying Drive</div>
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-body-destroy">
|
||||
<div className="fm-emphasized-text">Drive "{drive.name}" is being destroyed</div>
|
||||
<div>Please wait while the operation completes...</div>
|
||||
<div style={{ marginTop: '20px', textAlign: 'center' }}>
|
||||
<div className="fm-mini-spinner" style={{ display: 'inline-block', marginRight: '10px' }} />
|
||||
<span>Destroying drive...</span>
|
||||
<div className="fm-modal-window-scrollable">
|
||||
<div className="fm-modal-window">
|
||||
<div className="fm-modal-window-header fm-red-font">Destroying Drive</div>
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-body-destroy">
|
||||
<div className="fm-emphasized-text">Drive "{drive.name}" is being destroyed</div>
|
||||
<div>Please wait while the operation completes...</div>
|
||||
<div style={{ marginTop: '20px', textAlign: 'center' }}>
|
||||
<div className="fm-mini-spinner" style={{ display: 'inline-block', marginRight: '10px' }} />
|
||||
<span>Destroying drive...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fm-modal-window-footer">
|
||||
<Button label="Minimize" variant="secondary" onClick={onMinimize} />
|
||||
<div className="fm-modal-window-footer">
|
||||
<Button label="Minimize" variant="secondary" onClick={onMinimize} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
@@ -57,28 +59,32 @@ export function DestroyDriveModal({ drive, onCancelClick, doDestroy }: DestroyDr
|
||||
<div className="fm-modal-container">
|
||||
<div className="fm-modal-window">
|
||||
<div className="fm-modal-window-header fm-red-font">Destroy entire drive</div>
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-body-destroy">
|
||||
<div className="fm-emphasized-text">Destroy Drive? This Action Is Permanent</div>
|
||||
<div>All files stored only on this drive will become inaccessible.</div>
|
||||
<div>
|
||||
While the data may still temporarily persist on Swarm, it will be permanently removed once the storage
|
||||
expires and the data is garbage collected by the network. The File Manager will no longer recognise or
|
||||
recover these files.
|
||||
</div>
|
||||
<div>Confirmation:</div>
|
||||
<div>Requires typing a fixed expression to prevent accidental deletion. This action cannot be undone.</div>
|
||||
<div>
|
||||
Type: <span className="fm-emphasized-text">{destroyDriveText}</span>
|
||||
</div>
|
||||
<div className="fm-modal-window-input-container">
|
||||
<input
|
||||
type="text"
|
||||
id="drive-name"
|
||||
placeholder={destroyDriveText}
|
||||
value={driveNameInput}
|
||||
onChange={e => setDriveNameInput(e.target.value)}
|
||||
/>
|
||||
<div className="fm-modal-window-scrollable">
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-body-destroy">
|
||||
<div className="fm-emphasized-text">Destroy Drive? This Action Is Permanent</div>
|
||||
<div>All files stored only on this drive will become inaccessible.</div>
|
||||
<div>
|
||||
While the data may still temporarily persist on Swarm, it will be permanently removed once the storage
|
||||
expires and the data is garbage collected by the network. The File Manager will no longer recognise or
|
||||
recover these files.
|
||||
</div>
|
||||
<div>Confirmation:</div>
|
||||
<div>
|
||||
Requires typing a fixed expression to prevent accidental deletion. This action cannot be undone.
|
||||
</div>
|
||||
<div>
|
||||
Type: <span className="fm-emphasized-text">{destroyDriveText}</span>
|
||||
</div>
|
||||
<div className="fm-modal-window-input-container">
|
||||
<input
|
||||
type="text"
|
||||
id="drive-name"
|
||||
placeholder={destroyDriveText}
|
||||
value={driveNameInput}
|
||||
onChange={e => setDriveNameInput(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+19
-59
@@ -1,17 +1,15 @@
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { Warning } from '@mui/icons-material'
|
||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement, useEffect, useMemo, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import AlertIcon from 'remixicon-react/AlertLineIcon'
|
||||
import CalendarIcon from 'remixicon-react/CalendarLineIcon'
|
||||
import DriveIcon from 'remixicon-react/HardDrive2LineIcon'
|
||||
|
||||
import { calculateStampCapacityMetrics } from '../../utils/bee'
|
||||
import { getDaysLeft } from '../../utils/common'
|
||||
import { Button } from '../Button/Button'
|
||||
import { UpgradeDriveModal } from '../UpgradeDriveModal/UpgradeDriveModal'
|
||||
|
||||
import { ExpiringNotificationModalItem } from './ExpiringNotificationModalItem/ExpiringNotificationModalItem'
|
||||
|
||||
import './ExpiringNotificationModal.scss'
|
||||
import '../../styles/global.scss'
|
||||
|
||||
@@ -67,62 +65,24 @@ export function ExpiringNotificationModal({
|
||||
<AlertIcon size="21px" /> Drives Expiring soon
|
||||
</div>
|
||||
<div>The following drives will expire soon. Extend them to keep your data accessible.</div>
|
||||
|
||||
<div className="fm-modal-window-body fm-expiring-notification-modal-body">
|
||||
{paginatedStamps.map((stamp, index) => {
|
||||
const daysLeft = getDaysLeft(stamp.duration.toEndDate())
|
||||
let daysClass = ''
|
||||
|
||||
const drive = drives.find(d => d.batchId.toString() === stamp.batchID.toString())
|
||||
|
||||
if (!drive) return null
|
||||
|
||||
const filesPerDrive = files.filter(fi => fi.driveId === drive.id.toString())
|
||||
|
||||
const { usedSize, stampSize } = calculateStampCapacityMetrics(stamp, filesPerDrive, drive.redundancyLevel)
|
||||
|
||||
if (daysLeft < 10) {
|
||||
daysClass = 'fm-red-font'
|
||||
} else if (daysLeft < 30) {
|
||||
daysClass = 'fm-swarm-orange-font'
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
<div className="fm-modal-window-scrollable">
|
||||
<div className="fm-modal-window-body fm-expiring-notification-modal-body">
|
||||
{paginatedStamps.map((stamp, index) => (
|
||||
<ExpiringNotificationModalItem
|
||||
key={`${stamp.batchID.toString()}-${currentPage}-${index}`}
|
||||
className="fm-modal-white-section fm-space-between"
|
||||
>
|
||||
<div className="fm-expiring-notification-modal-section-left fm-space-between">
|
||||
<DriveIcon size="20" color="rgb(237, 129, 49)" />
|
||||
<div>
|
||||
<div className="fm-expiring-notification-modal-section-left-header fm-emphasized-text">
|
||||
{stamp.label} {drive.isAdmin && <Warning style={{ fontSize: '16px' }} />}
|
||||
</div>
|
||||
<div className="fm-expiring-notification-modal-section-left-value">
|
||||
{usedSize} / {stampSize}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fm-expiring-notification-modal-section-right">
|
||||
<div className="fm-expiring-notification-modal-section-right-header">
|
||||
<CalendarIcon size="14" /> Expiry date: {stamp.duration.toEndDate().toLocaleDateString()}
|
||||
</div>
|
||||
<div className={daysClass}>{daysLeft} days left</div>
|
||||
<div className="fm-expiring-notification-modal-section-right-button">
|
||||
<Button
|
||||
label="Upgrade"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
setActualStamp(stamp)
|
||||
setActualDrive(drive)
|
||||
setShowUpgradeDriveModal(true)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
stamp={stamp}
|
||||
drives={drives}
|
||||
files={files}
|
||||
currentPage={currentPage}
|
||||
index={index}
|
||||
onUpgradeClick={(stamp, drive) => {
|
||||
setActualStamp(stamp)
|
||||
setActualDrive(drive)
|
||||
setShowUpgradeDriveModal(true)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="fm-modal-window-footer">
|
||||
<div className="fm-expiring-notification-modal-footer-one-button">
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { PostageBatch } from '@ethersphere/bee-js'
|
||||
import { Warning } from '@mui/icons-material'
|
||||
import { DriveInfo, FileInfo } from '@solarpunkltd/file-manager-lib'
|
||||
import { ReactElement } from 'react'
|
||||
import CalendarIcon from 'remixicon-react/CalendarLineIcon'
|
||||
import DriveIcon from 'remixicon-react/HardDrive2LineIcon'
|
||||
|
||||
import { calculateStampCapacityMetrics } from '../../../utils/bee'
|
||||
import { getDaysLeft } from '../../../utils/common'
|
||||
import { Button } from '../../Button/Button'
|
||||
|
||||
import '../../../styles/global.scss'
|
||||
|
||||
interface ExpiringNotificationModalItemProps {
|
||||
stamp: PostageBatch
|
||||
drives: DriveInfo[]
|
||||
files: FileInfo[]
|
||||
currentPage: number
|
||||
index: number
|
||||
onUpgradeClick: (stamp: PostageBatch, drive: DriveInfo) => void
|
||||
}
|
||||
|
||||
export function ExpiringNotificationModalItem({
|
||||
stamp,
|
||||
drives,
|
||||
files,
|
||||
currentPage,
|
||||
index,
|
||||
onUpgradeClick,
|
||||
}: ExpiringNotificationModalItemProps): ReactElement | null {
|
||||
const daysLeft = getDaysLeft(stamp.duration.toEndDate())
|
||||
let daysClass = ''
|
||||
|
||||
const drive = drives.find(d => d.batchId.toString() === stamp.batchID.toString())
|
||||
|
||||
if (!drive) return null
|
||||
|
||||
const filesPerDrive = files.filter(fi => fi.driveId === drive.id.toString())
|
||||
|
||||
const { usedSize, stampSize } = calculateStampCapacityMetrics(stamp, filesPerDrive, drive.redundancyLevel)
|
||||
|
||||
if (daysLeft < 10) {
|
||||
daysClass = 'fm-red-font'
|
||||
} else if (daysLeft < 30) {
|
||||
daysClass = 'fm-swarm-orange-font'
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`${stamp.batchID.toString()}-${currentPage}-${index}`}
|
||||
className="fm-modal-white-section fm-space-between"
|
||||
>
|
||||
<div className="fm-expiring-notification-modal-section-left fm-space-between">
|
||||
<DriveIcon size="20" color="rgb(237, 129, 49)" />
|
||||
<div>
|
||||
<div className="fm-expiring-notification-modal-section-left-header fm-emphasized-text">
|
||||
{stamp.label} {drive.isAdmin && <Warning style={{ fontSize: '16px' }} />}
|
||||
</div>
|
||||
<div className="fm-expiring-notification-modal-section-left-value">
|
||||
{usedSize} / {stampSize}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fm-expiring-notification-modal-section-right">
|
||||
<div className="fm-expiring-notification-modal-section-right-header">
|
||||
<CalendarIcon size="14" /> Expiry date: {stamp.duration.toEndDate().toLocaleDateString()}
|
||||
</div>
|
||||
<div className={daysClass}>{daysLeft} days left</div>
|
||||
<div className="fm-expiring-notification-modal-section-right-button">
|
||||
<Button label="Upgrade" variant="primary" onClick={() => onUpgradeClick(stamp, drive)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,11 @@
|
||||
bottom: 45px;
|
||||
background-color: white;
|
||||
z-index: 1200;
|
||||
max-height: calc(100vh - 275px);
|
||||
min-height: 170px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fm-file-progress-window-header {
|
||||
@@ -15,65 +20,143 @@
|
||||
border-bottom: 1px solid rgb(209, 213, 219);
|
||||
}
|
||||
|
||||
.fm-file-progress-window-header-actions { display: inline-flex; gap: 6px; }
|
||||
.fm-file-progress-window-header-actions {
|
||||
display: inline-flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.fm-file-progress-window-header-btn {
|
||||
width: 22px; height: 22px; display: inline-grid; place-items: center;
|
||||
padding: 0; margin: 0; background: #f0f0f0; color: #4b5563;
|
||||
border: none; border-radius: 4px; cursor: pointer;
|
||||
&:hover { background: #e5e7eb; } &:active { background: #d1d5db; }
|
||||
&:disabled { cursor: default; opacity: .6; filter: grayscale(.3); }
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: #f0f0f0;
|
||||
color: #4b5563;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
&:active {
|
||||
background: #d1d5db;
|
||||
}
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
filter: grayscale(0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.fm-file-progress-window-file-item {
|
||||
display: flex; align-items: flex-start; gap: 8px;
|
||||
padding: 12px; border-bottom: 1px solid rgb(243, 244, 246);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid rgb(243, 244, 246);
|
||||
}
|
||||
|
||||
.fm-file-progress-window-file-type-icon { margin-top: 4px; }
|
||||
.fm-file-progress-window-file-type-icon {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.fm-file-progress-window-file-datas {
|
||||
display: flex; flex-direction: column; gap: 8px; width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fm-file-progress-window-file-item-header {
|
||||
display: grid; grid-template-columns: 1fr auto auto;
|
||||
align-items: center; gap: 8px; min-width: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fm-file-progress-window-name { min-width: 0; }
|
||||
.fm-file-progress-window-name {
|
||||
min-width: 0;
|
||||
}
|
||||
.fm-file-progress-window-name-text {
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fm-drive-line {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.fm-drive-line { margin-top: 2px; }
|
||||
|
||||
.fm-file-progress-window-percent { white-space: nowrap; }
|
||||
.fm-file-progress-window-percent {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fm-file-progress-window-file-item-footer {
|
||||
display: grid; grid-template-columns: auto 1fr auto;
|
||||
align-items: center; column-gap: 8px; font-size: 11px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.fm-file-progress-window-size {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fm-file-progress-window-center {
|
||||
justify-self: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fm-file-progress-window-status {
|
||||
justify-self: end;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fm-file-progress-window-size { white-space: nowrap; }
|
||||
.fm-file-progress-window-center { justify-self: center; white-space: nowrap; }
|
||||
.fm-file-progress-window-status { justify-self: end; white-space: nowrap; }
|
||||
|
||||
.fm-file-progress-window-row-close {
|
||||
width: 20px; height: 20px; display: inline-grid; place-items: center;
|
||||
padding: 0; margin: 0; background: #f0f0f0; color: #4b5563;
|
||||
border: none; border-radius: 4px; cursor: pointer;
|
||||
&:hover { background: #e5e7eb; } &:active { background: #d1d5db; }
|
||||
&:disabled { cursor: default; opacity: .6; filter: grayscale(.3); }
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: #f0f0f0;
|
||||
color: #4b5563;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
&:active {
|
||||
background: #d1d5db;
|
||||
}
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
filter: grayscale(0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.fm-drive-chip {
|
||||
display: inline-block; margin-left: 0; padding: 2px 6px;
|
||||
border-radius: 999px; font-size: 11px; line-height: 1;
|
||||
background: rgba(0,0,0,.06); color: #333; vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin-left: 0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
color: #333;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.fm-eta {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.fm-file-subtext {
|
||||
line-height: 1.2;
|
||||
}
|
||||
.fm-eta { font-size: 12px; opacity: .8; }
|
||||
.fm-file-subtext { line-height: 1.2; }
|
||||
|
||||
.fm-file-progress-window-list {
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReactElement, useLayoutEffect, useRef } from 'react'
|
||||
import { ReactElement } from 'react'
|
||||
import ArrowDownIcon from 'remixicon-react/ArrowDownSLineIcon'
|
||||
import CloseIcon from 'remixicon-react/CloseLineIcon'
|
||||
|
||||
@@ -44,8 +44,6 @@ export function FileProgressWindow({
|
||||
onRowClose,
|
||||
onCloseAll,
|
||||
}: FileProgressWindowProps): ReactElement | null {
|
||||
const listRef = useRef<HTMLDivElement | null>(null)
|
||||
const firstRowRef = useRef<HTMLDivElement | null>(null)
|
||||
const count = items?.length ?? 0
|
||||
const rows: ProgressItem[] = items ?? []
|
||||
|
||||
@@ -73,17 +71,6 @@ export function FileProgressWindow({
|
||||
)
|
||||
})
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const rowEl = firstRowRef.current
|
||||
const listEl = listRef.current
|
||||
|
||||
if (!rowEl || !listEl) return
|
||||
|
||||
const rowH = rowEl.getBoundingClientRect().height
|
||||
const safeRowH = rowH > 0 ? rowH : 72
|
||||
listEl.style.maxHeight = `${safeRowH * 5}px`
|
||||
}, [rows.length])
|
||||
|
||||
return (
|
||||
<div className="fm-file-progress-window">
|
||||
<div className="fm-file-progress-window-header">
|
||||
@@ -113,7 +100,7 @@ export function FileProgressWindow({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fm-file-progress-window-list" ref={listRef}>
|
||||
<div className="fm-file-progress-window-list">
|
||||
{rows.map((item, idx) => {
|
||||
const pctNum = Number.isFinite(item.percent)
|
||||
? Math.max(0, Math.min(100, Math.round(item.percent as number)))
|
||||
@@ -140,11 +127,7 @@ export function FileProgressWindow({
|
||||
const centerDisplay = getCenterText() || '\u00A0'
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fm-file-progress-window-file-item"
|
||||
key={item.uuid || `${item.name}-${idx}`}
|
||||
ref={idx === 0 ? firstRowRef : undefined}
|
||||
>
|
||||
<div className="fm-file-progress-window-file-item" key={item.uuid || `${item.name}-${idx}`}>
|
||||
<div className="fm-file-progress-window-file-type-icon">
|
||||
<GetIconElement size="14" name={item.name} color="black" />
|
||||
</div>
|
||||
|
||||
@@ -294,71 +294,73 @@ export function InitialModal({
|
||||
<div className="fm-modal-window">
|
||||
<div className="fm-modal-window-header">Welcome to your Swarm File Manager</div>
|
||||
<div>{initText} the File Manager</div>
|
||||
{nonFullStamps.length > 0 && (
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-window-input-container">
|
||||
<CustomDropdown
|
||||
id="batch-id-selector"
|
||||
options={createBatchIdOptions(nonFullStamps)}
|
||||
value={selectedBatchIndex}
|
||||
label="Link an existing Admin Drive (optional)"
|
||||
onChange={(index: number) => {
|
||||
setSelectedBatchIndex(index)
|
||||
<div className="fm-modal-window-scrollable">
|
||||
{nonFullStamps.length > 0 && (
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-window-input-container">
|
||||
<CustomDropdown
|
||||
id="batch-id-selector"
|
||||
options={createBatchIdOptions(nonFullStamps)}
|
||||
value={selectedBatchIndex}
|
||||
label="Link an existing Admin Drive (optional)"
|
||||
onChange={(index: number) => {
|
||||
setSelectedBatchIndex(index)
|
||||
|
||||
if (index === -1) {
|
||||
setSelectedBatch(null)
|
||||
}
|
||||
}}
|
||||
placeholder={BATCH_ID_PLACEHOLDER}
|
||||
/>
|
||||
{selectedBatch && (
|
||||
<div className="fm-drive-item-content">
|
||||
<div className="fm-drive-item-capacity">
|
||||
Capacity <ProgressBar value={capacityPct} width="64px" /> {usedSize} / {stampSize}
|
||||
if (index === -1) {
|
||||
setSelectedBatch(null)
|
||||
}
|
||||
}}
|
||||
placeholder={BATCH_ID_PLACEHOLDER}
|
||||
/>
|
||||
{selectedBatch && (
|
||||
<div className="fm-drive-item-content">
|
||||
<div className="fm-drive-item-capacity">
|
||||
Capacity <ProgressBar value={capacityPct} width="64px" /> {usedSize} / {stampSize}
|
||||
</div>
|
||||
<div className="fm-drive-item-capacity">
|
||||
Expiry date: {selectedBatch.duration.toEndDate().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="fm-drive-item-capacity">
|
||||
Expiry date: {selectedBatch.duration.toEndDate().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{selectedBatch && setSecurityLevel(setErasureCodeLevel)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!selectedBatch && (
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="admin-desired-lifetime" className="fm-input-label">
|
||||
Create a new Admin Drive with desired lifetime: <Tooltip label={TOOLTIPS.ADMIN_DESIRED_LIFETIME} />
|
||||
</label>
|
||||
<CustomDropdown
|
||||
id="admin-desired-lifetime"
|
||||
options={desiredLifetimeOptions}
|
||||
value={lifetimeIndex}
|
||||
onChange={setLifetimeIndex}
|
||||
placeholder="Select a value"
|
||||
/>
|
||||
</div>
|
||||
{setSecurityLevel(setErasureCodeLevel)}
|
||||
<div className="fm-modal-window-input-container">
|
||||
<div className="fm-modal-estimated-cost-container">
|
||||
<div className="fm-emphasized-text">Estimated Cost:</div>
|
||||
<div>
|
||||
{cost} BZZ {isBalanceSufficient ? '' : '(Insufficient balance)'}
|
||||
{isxDaiBalanceSufficient ? '' : ' (Insufficient xDAI balance)'}
|
||||
</div>
|
||||
<Tooltip label={TOOLTIPS.ADMIN_ESTIMATED_COST} />
|
||||
)}
|
||||
{selectedBatch && setSecurityLevel(setErasureCodeLevel)}
|
||||
</div>
|
||||
<div>(Based on current network conditions)</div>
|
||||
{renderUltraLightNodeWarning()}
|
||||
{isNodeSyncing && !selectedBatch && (
|
||||
<div className="fm-modal-info-warning" style={{ marginBottom: '16px' }}>
|
||||
Node is syncing. Please wait until sync completes before purchasing a stamp.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{!selectedBatch && (
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-window-input-container">
|
||||
<label htmlFor="admin-desired-lifetime" className="fm-input-label">
|
||||
Create a new Admin Drive with desired lifetime: <Tooltip label={TOOLTIPS.ADMIN_DESIRED_LIFETIME} />
|
||||
</label>
|
||||
<CustomDropdown
|
||||
id="admin-desired-lifetime"
|
||||
options={desiredLifetimeOptions}
|
||||
value={lifetimeIndex}
|
||||
onChange={setLifetimeIndex}
|
||||
placeholder="Select a value"
|
||||
/>
|
||||
</div>
|
||||
{setSecurityLevel(setErasureCodeLevel)}
|
||||
<div className="fm-modal-window-input-container">
|
||||
<div className="fm-modal-estimated-cost-container">
|
||||
<div className="fm-emphasized-text">Estimated Cost:</div>
|
||||
<div>
|
||||
{cost} BZZ {isBalanceSufficient ? '' : '(Insufficient balance)'}
|
||||
{isxDaiBalanceSufficient ? '' : ' (Insufficient xDAI balance)'}
|
||||
</div>
|
||||
<Tooltip label={TOOLTIPS.ADMIN_ESTIMATED_COST} />
|
||||
</div>
|
||||
<div>(Based on current network conditions)</div>
|
||||
{renderUltraLightNodeWarning()}
|
||||
{isNodeSyncing && !selectedBatch && (
|
||||
<div className="fm-modal-info-warning" style={{ marginBottom: '16px' }}>
|
||||
Node is syncing. Please wait until sync completes before purchasing a stamp.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="fm-modal-window-footer">
|
||||
<Button
|
||||
label={selectedBatch ? `${createText} Drive` : `Purchase Stamp & ${createText} Drive`}
|
||||
|
||||
@@ -116,16 +116,6 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fm-initialization-modal-container .fm-modal-window-scrollable {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.fm-main:has(.fm-initialization-modal-container) {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
@@ -9,16 +9,6 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fm-modal-window-scrollable {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.fm-upgrade-drive-modal-wallet {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -74,4 +64,4 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,8 +228,8 @@ export function UpgradeDriveModal({
|
||||
<div className="fm-modal-window-header">
|
||||
<DriveIcon size="18px" /> Upgrade {truncateNameMiddle(drive.name || stamp.label || shortBatchId, 35)}
|
||||
</div>
|
||||
<div>Choose extension period and additional storage for your drive.</div>
|
||||
<div className="fm-modal-window-scrollable">
|
||||
<div>Choose extension period and additional storage for your drive.</div>
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-upgrade-drive-modal-wallet">
|
||||
<div className="fm-upgrade-drive-modal-wallet-header fm-emphasized-text">
|
||||
|
||||
@@ -39,63 +39,64 @@ export function UploadConflictModal({
|
||||
<WarningIcon size="18px" />
|
||||
<span className="fm-main-font-color">File already exists</span>
|
||||
</div>
|
||||
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-white-section">
|
||||
<div className="fm-conflict-row">
|
||||
<div className="fm-emphasized-text">A file named “{filename}” already exists in this drive.</div>
|
||||
<div className="fm-soft-text">What would you like to do?</div>
|
||||
</div>
|
||||
|
||||
<div className="fm-conflict-option">
|
||||
<div className="fm-conflict-option-title">Keep both</div>
|
||||
<div className="fm-conflict-option-sub">
|
||||
Upload the new file as a separate item with a different name.
|
||||
<div className="fm-modal-window-scrollable">
|
||||
<div className="fm-modal-window-body">
|
||||
<div className="fm-modal-white-section">
|
||||
<div className="fm-conflict-row">
|
||||
<div className="fm-emphasized-text">A file named “{filename}” already exists in this drive.</div>
|
||||
<div className="fm-soft-text">What would you like to do?</div>
|
||||
</div>
|
||||
<div className="fm-conflict-rename-row">
|
||||
<label htmlFor="conflict-newname">New name</label>
|
||||
<input
|
||||
id="conflict-newname"
|
||||
type="text"
|
||||
value={customName}
|
||||
onChange={e => setCustomName(e.target.value)}
|
||||
className="fm-input"
|
||||
placeholder={suggestedName}
|
||||
|
||||
<div className="fm-conflict-option">
|
||||
<div className="fm-conflict-option-title">Keep both</div>
|
||||
<div className="fm-conflict-option-sub">
|
||||
Upload the new file as a separate item with a different name.
|
||||
</div>
|
||||
<div className="fm-conflict-rename-row">
|
||||
<label htmlFor="conflict-newname">New name</label>
|
||||
<input
|
||||
id="conflict-newname"
|
||||
type="text"
|
||||
value={customName}
|
||||
onChange={e => setCustomName(e.target.value)}
|
||||
className="fm-input"
|
||||
placeholder={suggestedName}
|
||||
/>
|
||||
{!isNameValid && customName.trim().length > 0 && existingNames.has(customName.trim()) && (
|
||||
<div className="fm-soft-text" style={{ marginTop: 6 }}>
|
||||
That name already exists.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
label="Keep both"
|
||||
variant="secondary"
|
||||
onClick={() => isNameValid && onKeepBoth(customName.trim())}
|
||||
disabled={!isNameValid}
|
||||
/>
|
||||
{!isNameValid && customName.trim().length > 0 && existingNames.has(customName.trim()) && (
|
||||
<div className="fm-soft-text" style={{ marginTop: 6 }}>
|
||||
That name already exists.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
label="Keep both"
|
||||
variant="secondary"
|
||||
onClick={() => isNameValid && onKeepBoth(customName.trim())}
|
||||
disabled={!isNameValid}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="fm-conflict-sep" />
|
||||
<div className="fm-conflict-sep" />
|
||||
|
||||
<div className="fm-conflict-option">
|
||||
<div className="fm-conflict-option-title">Replace</div>
|
||||
<div className="fm-conflict-option-sub">
|
||||
Replace the existing file by uploading this as a new version of “{filename}”.
|
||||
<div className="fm-conflict-option">
|
||||
<div className="fm-conflict-option-title">Replace</div>
|
||||
<div className="fm-conflict-option-sub">
|
||||
Replace the existing file by uploading this as a new version of “{filename}”.
|
||||
</div>
|
||||
<Button label="Replace" variant="primary" onClick={onReplace} />
|
||||
</div>
|
||||
<Button label="Replace" variant="primary" onClick={onReplace} />
|
||||
</div>
|
||||
{isTrashedExisting && (
|
||||
<div className="fm-callout fm-callout--warning" role="note" aria-live="polite" style={{ marginTop: 12 }}>
|
||||
<span className="fm-callout__icon" aria-hidden>
|
||||
<WarningIcon size="16px" />
|
||||
</span>
|
||||
<span className="fm-callout__text">
|
||||
<b>Heads up:</b> The existing '{filename}' is currently in <b>Trash</b>.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isTrashedExisting && (
|
||||
<div className="fm-callout fm-callout--warning" role="note" aria-live="polite" style={{ marginTop: 12 }}>
|
||||
<span className="fm-callout__icon" aria-hidden>
|
||||
<WarningIcon size="16px" />
|
||||
</span>
|
||||
<span className="fm-callout__text">
|
||||
<b>Heads up:</b> The existing '{filename}' is currently in <b>Trash</b>.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="fm-modal-window-footer">
|
||||
|
||||
@@ -293,60 +293,61 @@ export function VersionHistoryModal({ fileInfo, onCancelClick, onDownload }: Ver
|
||||
</>
|
||||
</span>
|
||||
</div>
|
||||
<div className="fm-modal-window-scrollable">
|
||||
<div className="fm-modal-window-body fm-expiring-notification-modal-body">
|
||||
{error && <div className="fm-modal-white-section fm-soft-text">{error}</div>}
|
||||
|
||||
<div className="fm-modal-window-body fm-expiring-notification-modal-body">
|
||||
{error && <div className="fm-modal-white-section fm-soft-text">{error}</div>}
|
||||
{loading && <div className="fm-loading">Loading…</div>}
|
||||
{!error && !loading && pageVersions.length === 0 && (
|
||||
<div className="fm-empty">No versions found for this file.</div>
|
||||
)}
|
||||
{conflictWarning && (
|
||||
<div
|
||||
className="fm-modal-white-section fm-soft-text"
|
||||
style={{ borderLeft: '3px solid var(--fm-accent, #6aa7ff)' }}
|
||||
>
|
||||
{conflictWarning}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loading && <div className="fm-loading">Loading…</div>}
|
||||
{!error && !loading && pageVersions.length === 0 && (
|
||||
<div className="fm-empty">No versions found for this file.</div>
|
||||
)}
|
||||
{conflictWarning && (
|
||||
<div
|
||||
className="fm-modal-white-section fm-soft-text"
|
||||
style={{ borderLeft: '3px solid var(--fm-accent, #6aa7ff)' }}
|
||||
>
|
||||
{conflictWarning}
|
||||
</div>
|
||||
)}
|
||||
{renameConfirm && (
|
||||
<ConfirmModal
|
||||
title={
|
||||
<>
|
||||
Restore this version?
|
||||
<Tooltip label={TOOLTIPS.FILE_OPERATION_RESTORE_VERSION} />
|
||||
</>
|
||||
}
|
||||
message={
|
||||
<>
|
||||
Restoring will rename:
|
||||
<b className="vh-name" title={renameConfirm.headName}>
|
||||
{truncateNameMiddle(renameConfirm.headName)}
|
||||
</b>{' '}
|
||||
→{' '}
|
||||
<b className="vh-name" title={renameConfirm.targetName}>
|
||||
{truncateNameMiddle(renameConfirm.targetName)}
|
||||
</b>
|
||||
.
|
||||
</>
|
||||
}
|
||||
confirmLabel="Restore"
|
||||
cancelLabel="Cancel"
|
||||
onConfirm={async () => {
|
||||
await doRestore(renameConfirm.version)
|
||||
setRenameConfirm(null)
|
||||
}}
|
||||
onCancel={() => setRenameConfirm(null)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{renameConfirm && (
|
||||
<ConfirmModal
|
||||
title={
|
||||
<>
|
||||
Restore this version?
|
||||
<Tooltip label={TOOLTIPS.FILE_OPERATION_RESTORE_VERSION} />
|
||||
</>
|
||||
}
|
||||
message={
|
||||
<>
|
||||
Restoring will rename:
|
||||
<b className="vh-name" title={renameConfirm.headName}>
|
||||
{truncateNameMiddle(renameConfirm.headName)}
|
||||
</b>{' '}
|
||||
→{' '}
|
||||
<b className="vh-name" title={renameConfirm.targetName}>
|
||||
{truncateNameMiddle(renameConfirm.targetName)}
|
||||
</b>
|
||||
.
|
||||
</>
|
||||
}
|
||||
confirmLabel="Restore"
|
||||
cancelLabel="Cancel"
|
||||
onConfirm={async () => {
|
||||
await doRestore(renameConfirm.version)
|
||||
setRenameConfirm(null)
|
||||
}}
|
||||
onCancel={() => setRenameConfirm(null)}
|
||||
<VersionsList
|
||||
versions={!error && !loading ? pageVersions : []}
|
||||
headFi={fileInfo}
|
||||
restoreVersion={restoreVersion}
|
||||
onDownload={trackDownload}
|
||||
/>
|
||||
)}
|
||||
|
||||
<VersionsList
|
||||
versions={!error && !loading ? pageVersions : []}
|
||||
headFi={fileInfo}
|
||||
restoreVersion={restoreVersion}
|
||||
onDownload={trackDownload}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="fm-modal-window-footer vh-footer">
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
$fm-modal-vertical-offset: 48px;
|
||||
|
||||
.fm-modal-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -25,6 +27,7 @@
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
justify-content: center;
|
||||
max-height: calc(100vh - #{$fm-modal-vertical-offset});
|
||||
}
|
||||
|
||||
.fm-modal-window-header {
|
||||
@@ -38,6 +41,15 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fm-modal-window-scrollable {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.fm-modal-window-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user