Skip to content

Commit

Permalink
fix: upload and pin table refactor (#2018)
Browse files Browse the repository at this point in the history
WIP: Refactoring the table renderer for uploads and pins.

TODO: 

- [x] Ensure responsive behaviour on mobile
- [x] Add "edit" upload name functionality
- [x] Add select row functionality
- [x] Add delete Upload functionality
- [x] Add delete many functionality
- [ ] Refactor Pins table

Co-authored-by: Paolo <paolo@potatolondon.com>
  • Loading branch information
joshJarr and flea89 committed Oct 17, 2022
1 parent 20243bb commit cbfe9d7
Show file tree
Hide file tree
Showing 17 changed files with 1,039 additions and 994 deletions.
2 changes: 2 additions & 0 deletions packages/client/src/lib.js
Expand Up @@ -41,6 +41,8 @@ const RATE_LIMIT_PERIOD = 10 * 1000
/** @typedef { import('./lib/interface.js').API } API */
/** @typedef { import('./lib/interface.js').Status} Status */
/** @typedef { import('./lib/interface.js').Upload} Upload */
/** @typedef { import('./lib/interface.js').Deal} Deal */
/** @typedef { import('./lib/interface.js').Pin} Pin */
/** @typedef { import('./lib/interface.js').Service } Service */
/** @typedef { import('./lib/interface.js').Web3File} Web3File */
/** @typedef { import('./lib/interface.js').Filelike } Filelike */
Expand Down
Expand Up @@ -87,7 +87,6 @@ const FileUploader = ({ className = '', content, uploadModalState, background })
};
useEffect(() => {
const acceptedTermsLocalStorage = localStorage.getItem('acceptedTerms');
console.log(acceptedTermsLocalStorage);
if (acceptedTermsLocalStorage) setHasAcceptedTerms(true);
}, []);

Expand Down
@@ -0,0 +1,33 @@
import { useMemo } from 'react';

import CopyIcon from 'assets/icons/copy';
import { addTextToClipboard, truncateString } from 'lib/utils';

/**
* @type {import('react').FC}
* Used to render a checkbox cell within a table component.
* @param {Object} props
* @param {string} props.cid the CID of the upload.
* @param {string} props.gatewayPrefix the gateway prefix used for linking to the file in a gateway.
* @returns
*/
function CidCellRenderer({ cid, gatewayPrefix }) {
const truncatedCID = useMemo(() => truncateString(cid, 5, '...', 'double'), [cid]);
return (
<span className="cid-cell">
<a className="cid-truncate underline" href={`${gatewayPrefix}${cid}`} target="_blank" rel="noreferrer">
{truncatedCID}
</a>
<button
className="copy-icon"
onClick={() => {
addTextToClipboard(cid);
}}
>
<CopyIcon />
</button>
</span>
);
}

export default CidCellRenderer;
@@ -0,0 +1,92 @@
import { useRef, useState } from 'react';
import clsx from 'clsx';

import PencilIcon from 'assets/icons/pencil';
import Loading from 'components/loading/loading';

/**
* @type {import('react').FC}
* @param {Object} props
* @param {string} props.name Name of the upload.
* @param {string} props.cid CID of the upload.
* @param {function} props.onNameEdit On edit callback.
* @param {function} props.renameUploadAction Async method to call to rename the upload.
* @returns
*/
function EditUploadNameRenderer({ name, cid, onNameEdit, renameUploadAction }) {
const [isEditingName, setIsEditingName] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [editError, setEditError] = useState('');

/** @type {import('react').RefObject<HTMLTextAreaElement>} */
const textAreaInput = useRef(null);

const toggleEdit = () => {
setIsEditingName(!isEditingName);
};

const saveNewName = async (oldName, cid) => {
const newName = textAreaInput.current?.value;

setIsLoading(true);

try {
await renameUploadAction(cid, newName);
} catch (error) {
setEditError('Unable to set name.');
}

if (editError) {
setEditError('');
}

setIsLoading(false);
toggleEdit();
onNameEdit();
};

return (
<div className="file-name">
<div className={clsx(isEditingName && 'editing', 'file-name__container')}>
{/* <span className="file-row-label medium-down-only">{'fileRowLabels.name.label'}</span> */}
{!isEditingName ? (
<span>{name}</span>
) : (
<span className="textarea-container">
<textarea
className={clsx(editError && 'file-name__textarea--error', 'file-name__textarea')}
ref={textAreaInput}
disabled={isLoading}
defaultValue={name}
/>
</span>
)}
<div className="file-name__action-button-container">
{isEditingName && (
<button
className="file-name__action-buttons file-name__action-buttons--save"
disabled={isLoading}
onClick={() => {
saveNewName(name, cid);
}}
>
{isLoading ? <Loading size="small" /> : 'Save'}
</button>
)}
<button
className={clsx(isEditingName && 'file-name__action-buttons--cancel', 'file-name__action-buttons')}
disabled={isLoading}
onClick={() => {
toggleEdit();
}}
>
{isEditingName ? 'Cancel' : <PencilIcon className={clsx('pencil-icon')} />}
</button>
</div>
</div>
{editError && isEditingName && <span className="file-name__textarea-hint-text--error">{editError}</span>}
</div>
);
}

export default EditUploadNameRenderer;
@@ -0,0 +1,55 @@
import { renderToString } from 'react-dom/server';

import Tooltip from 'ZeroComponents/tooltip/tooltip';

/**
* @typedef {import('web3.storage').Deal} Deal
*/
/**
* @type {import('react').FC}
* @param {object} props
* @param {Deal[]} props.deals list of deals associated with an upload
* @param {string} props.tooltipText strings
* @returns
*/
function StorageProvidersCellRenderer({ deals, tooltipText }) {
const storageProviders = Array.isArray(deals)
? deals
.filter(deal => !!deal.storageProvider)
.map((deal, indx, deals) => (
<span key={deal.dealId}>
<a
className="underline"
href={`https://filfox.info/en/deal/${deal.dealId}`}
target="_blank"
rel="noreferrer"
>
{`${deal.storageProvider}`}
</a>
{indx !== deals.length - 1 && ', '}
</span>
))
: null;

if (!storageProviders) {
return null;
}

return (
<span className="file-storage-providers">
{!storageProviders.length ? (
<>
Queuing...
<Tooltip position="right" content={tooltipText} />
</>
) : (
<>
Stored ({storageProviders.length})
<Tooltip position="right" content={renderToString(<p>{storageProviders}</p>)} />
</>
)}
</span>
);
}

export default StorageProvidersCellRenderer;
@@ -0,0 +1,48 @@
import { BsFillInfoCircleFill } from 'react-icons/bs';

import Tooltip from 'ZeroComponents/tooltip/tooltip';

export const PinStatus = {
PINNED: 'Pinned',
PINNING: 'Pinning',
PIN_QUEUED: 'PinQueued',
QUEUING: 'Queuing...',
};

/**
* @typedef {import('web3.storage').Pin} Pin
*/

/**
* @type {import('react').FC}
* @param {object} props
* @param {Pin[]} props.pins All pin data for the upload.
* @param {object} props.statusMessages Status message strings for the status tooltip.
* @returns
*/
function UploadStatusTableRenderer({ pins, statusMessages }) {
if (!pins) {
// TODO: Ensure we return something to convey to the user no pins are on this upload.
return <span></span>;
}

const status = Object.values(PinStatus).find(status => pins.some(pin => status === pin.status)) || PinStatus.QUEUING;

const statusTooltips = {
[PinStatus.QUEUING]: statusMessages.queuing,
[PinStatus.PIN_QUEUED]: statusMessages.pin_queued,
[PinStatus.PINNING]: statusMessages.pinning,
[PinStatus.PINNED]: statusMessages.pinned.replace('*numberOfPins*', `${pins.length}`),
};

const statusTooltip = statusTooltips[status];

const tooltip = statusTooltip ? <Tooltip icon={<BsFillInfoCircleFill />} content={statusTooltip} /> : null;
return (
<span>
{status === PinStatus.PINNED ? 'Complete' : status} {tooltip}
</span>
);
}

export default UploadStatusTableRenderer;

0 comments on commit cbfe9d7

Please sign in to comment.