Merge pull request #1047 from filecoin-project/@aminejv/updates

Improvements and bug fixes
This commit is contained in:
martinalong 2022-01-06 14:42:19 -08:00 committed by GitHub
commit 568d8bc4f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 135 additions and 140 deletions

View File

@ -450,7 +450,8 @@ export const useImage = ({ src, maxWidth }) => {
if (!src) setImgState({ error: true, loaded: true }); if (!src) setImgState({ error: true, loaded: true });
const img = new Image(); const img = new Image();
img.src = src; // NOTE(amine): setting img.src to null will redirect the app to /_/null
img.src = src || "";
img.onload = () => { img.onload = () => {
if (maxWidth && img.naturalWidth < maxWidth) { if (maxWidth && img.naturalWidth < maxWidth) {

View File

@ -15,8 +15,8 @@ import { css } from "@emotion/react";
import { useFilterContext } from "~/components/core/Filter/Provider"; import { useFilterContext } from "~/components/core/Filter/Provider";
import { Link } from "~/components/core/Link"; import { Link } from "~/components/core/Link";
import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons"; import { ButtonPrimary, ButtonSecondary } from "~/components/system/components/Buttons";
import { motion } from "framer-motion";
import { FocusRing } from "../FocusRing"; import { FocusRing } from "../FocusRing";
import { Show } from "~/components/utility/Show";
/* ------------------------------------------------------------------------------------------------- /* -------------------------------------------------------------------------------------------------
* Shared components between filters * Shared components between filters
@ -77,7 +77,7 @@ const FilterButton = React.forwardRef(({ children, Icon, image, isSelected, ...p
</Link> </Link>
)); ));
const FilterSection = React.forwardRef(({ title, children, ...props }, ref) => { const FilterSection = React.forwardRef(({ title, children, emptyState, ...props }, ref) => {
const [isExpanded, setExpanded] = React.useState(true); const [isExpanded, setExpanded] = React.useState(true);
const toggleExpandState = () => setExpanded((prev) => !prev); const toggleExpandState = () => setExpanded((prev) => !prev);
@ -89,7 +89,7 @@ const FilterSection = React.forwardRef(({ title, children, ...props }, ref) => {
<Tooltip.Trigger aria-describedby={titleButtonId} aria-expanded={isExpanded}> <Tooltip.Trigger aria-describedby={titleButtonId} aria-expanded={isExpanded}>
<FocusRing> <FocusRing>
<Typography.H6 <Typography.H6
as={motion.button} as="button"
layoutId={title + "title"} layoutId={title + "title"}
css={STYLES_FILTER_TITLE_BUTTON} css={STYLES_FILTER_TITLE_BUTTON}
style={{ paddingLeft: 8, marginBottom: 4 }} style={{ paddingLeft: 8, marginBottom: 4 }}
@ -108,25 +108,26 @@ const FilterSection = React.forwardRef(({ title, children, ...props }, ref) => {
</Tooltip.Root> </Tooltip.Root>
)} )}
{isExpanded ? ( {isExpanded ? (
<RovingTabIndex.Provider axis="vertical"> Array.isArray(children) && children?.length === 0 ? (
<RovingTabIndex.List> <Show when={emptyState}>
<motion.ul <System.P2 color="textGrayDark" style={{ paddingLeft: "8px" }}>
layoutId={title + "section"} {emptyState}
initial={{ opacity: 0 }} </System.P2>
animate={{ opacity: 1 }} </Show>
css={STYLES_FILTERS_GROUP} ) : (
> <RovingTabIndex.Provider axis="vertical">
{children} <RovingTabIndex.List>
</motion.ul> <ul css={STYLES_FILTERS_GROUP}>{children}</ul>
</RovingTabIndex.List> </RovingTabIndex.List>
</RovingTabIndex.Provider> </RovingTabIndex.Provider>
)
) : null} ) : null}
</div> </div>
); );
}); });
/* ------------------------------------------------------------------------------------------------- /* -------------------------------------------------------------------------------------------------
* InitialFilters * Library
* -----------------------------------------------------------------------------------------------*/ * -----------------------------------------------------------------------------------------------*/
function Library({ page, onAction }) { function Library({ page, onAction }) {
@ -151,11 +152,19 @@ function Library({ page, onAction }) {
); );
} }
/* -------------------------------------------------------------------------------------------------
* Tags
* -----------------------------------------------------------------------------------------------*/
function Tags({ viewer, data, onAction, ...props }) { function Tags({ viewer, data, onAction, ...props }) {
const [, { hidePopup }] = useFilterContext(); const [, { hidePopup }] = useFilterContext();
return ( return (
<FilterSection title="Tags" {...props}> <FilterSection
title="Tags"
emptyState="when you tag a file, that tag will automatically show up here"
{...props}
>
{viewer.slates.map((slate, index) => ( {viewer.slates.map((slate, index) => (
<li key={slate.id}> <li key={slate.id}>
<RovingTabIndex.Item index={index}> <RovingTabIndex.Item index={index}>
@ -175,35 +184,39 @@ function Tags({ viewer, data, onAction, ...props }) {
); );
} }
/* -------------------------------------------------------------------------------------------------
* Following
* -----------------------------------------------------------------------------------------------*/
function Following({ viewer, onAction, ...props }) { function Following({ viewer, onAction, ...props }) {
const [, { hidePopup }] = useFilterContext(); const [, { hidePopup }] = useFilterContext();
return ( return (
<RovingTabIndex.Provider axis="vertical"> <FilterSection title="Following" emptyState="follow users to see them here" {...props}>
<RovingTabIndex.List> {viewer.following.map((user, index) => (
<FilterSection title="Following" {...props}> <li key={user.id}>
{viewer.following.map((user, index) => ( <RovingTabIndex.Item index={index}>
<li key={user.id}> <FilterButton
<RovingTabIndex.Item index={index}> href={`/${user.username}`}
<FilterButton isSelected={false}
href={`/${user.username}`} onAction={onAction}
isSelected={false} // Icon={SVG.ProfileUser}
onAction={onAction} image={<ProfilePhoto user={user} style={{ borderRadius: "8px" }} size={20} />}
// Icon={SVG.ProfileUser} onClick={hidePopup}
image={<ProfilePhoto user={user} style={{ borderRadius: "8px" }} size={20} />} >
onClick={hidePopup} {user.username}
> </FilterButton>
{user.username} </RovingTabIndex.Item>
</FilterButton> </li>
</RovingTabIndex.Item> ))}
</li> </FilterSection>
))}
</FilterSection>
</RovingTabIndex.List>
</RovingTabIndex.Provider>
); );
} }
/* -------------------------------------------------------------------------------------------------
* Profile
* -----------------------------------------------------------------------------------------------*/
function Profile({ viewer, data, page, ...props }) { function Profile({ viewer, data, page, ...props }) {
if (page.id === "NAV_SLATE") { if (page.id === "NAV_SLATE") {
data = data.owner; data = data.owner;
@ -288,6 +301,10 @@ function Profile({ viewer, data, page, ...props }) {
); );
} }
/* -------------------------------------------------------------------------------------------------
* ProfileTags
* -----------------------------------------------------------------------------------------------*/
function ProfileTags({ data, page, onAction, ...props }) { function ProfileTags({ data, page, onAction, ...props }) {
const [, { hidePopup }] = useFilterContext(); const [, { hidePopup }] = useFilterContext();
@ -297,27 +314,23 @@ function ProfileTags({ data, page, onAction, ...props }) {
} }
return ( return (
<RovingTabIndex.Provider axis="vertical"> <FilterSection {...props}>
<RovingTabIndex.List> {user?.slates?.map((slate, index) => (
<FilterSection {...props}> <li key={slate.id}>
{user?.slates?.map((slate, index) => ( <RovingTabIndex.Item index={index}>
<li key={slate.id}> <FilterButton
<RovingTabIndex.Item index={index}> href={`/$/slate/${slate.id}`}
<FilterButton isSelected={slate.id === data?.id}
href={`/$/slate/${slate.id}`} onAction={onAction}
isSelected={slate.id === data?.id} Icon={slate.isPublic ? SVG.Hash : SVG.SecurityLock}
onAction={onAction} onClick={hidePopup}
Icon={slate.isPublic ? SVG.Hash : SVG.SecurityLock} >
onClick={hidePopup} {slate.slatename}
> </FilterButton>
{slate.slatename} </RovingTabIndex.Item>
</FilterButton> </li>
</RovingTabIndex.Item> ))}
</li> </FilterSection>
))}
</FilterSection>
</RovingTabIndex.List>
</RovingTabIndex.Provider>
); );
} }

View File

@ -12,6 +12,7 @@ import { Show } from "~/components/utility/Show";
import { useHover } from "~/common/hooks"; import { useHover } from "~/common/hooks";
import { clamp } from "lodash"; import { clamp } from "lodash";
import { useUploadStore } from "~/components/core/Upload/store"; import { useUploadStore } from "~/components/core/Upload/store";
import { LoaderSpinner } from "~/components/system/components/Loaders";
import DataMeter from "~/components/core/DataMeter"; import DataMeter from "~/components/core/DataMeter";
import BlobObjectPreview from "~/components/core/BlobObjectPreview"; import BlobObjectPreview from "~/components/core/BlobObjectPreview";
@ -27,7 +28,6 @@ const STYLES_POPUP_WRAPPER = (theme) => css`
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
z-index: ${theme.zindex.tooltip}; z-index: ${theme.zindex.tooltip};
box-shadow: ${theme.shadow.lightLarge};
`; `;
const STYLES_DISMISS_BUTTON = (theme) => css` const STYLES_DISMISS_BUTTON = (theme) => css`
@ -391,11 +391,15 @@ function Summary({ uploadSummary }) {
} }
> >
<Match when={file.status === "uploading"}> <Match when={file.status === "uploading"}>
<DataMeter {file.isLink ? (
bytes={file.loaded} <LoaderSpinner style={{ height: 12, width: 12 }} />
maximumBytes={file.total} ) : (
style={{ maxWidth: 84, marginTop: 2 }} <DataMeter
/> bytes={file.loaded}
maximumBytes={file.total}
style={{ maxWidth: 84, marginTop: 2 }}
/>
)}
</Match> </Match>
<Match when={file.status === "failed"}> <Match when={file.status === "failed"}>
<System.P3 color="red">failed</System.P3> <System.P3 color="red">failed</System.P3>

View File

@ -161,7 +161,9 @@ export const useUploadStore = create((setUploadState) => {
onError: handleError, onError: handleError,
}); });
const resetUploadState = () => (uploadProvider.clearUploadCache(), setUploadState(DEFAULT_STATE)); const resetUploadState = () => (
uploadProvider.clearUploadCache(), setUploadState((prev) => ({ ...prev, state: DEFAULT_STATE }))
);
return { return {
state: DEFAULT_STATE, state: DEFAULT_STATE,

View File

@ -17,7 +17,6 @@ import {
useDetectTextOverflow, useDetectTextOverflow,
useEscapeKey, useEscapeKey,
useEventListener, useEventListener,
useIsomorphicLayoutEffect,
useLockScroll, useLockScroll,
} from "~/common/hooks"; } from "~/common/hooks";
import { Show } from "~/components/utility/Show"; import { Show } from "~/components/utility/Show";
@ -50,7 +49,7 @@ const VisitLinkButton = ({ file }) => {
rel="noreferrer" rel="noreferrer"
type="link" type="link"
> >
<LinkIcon file={file} width={16} height={16} style={{ marginRight: 4 }} /> <LinkIcon file={file} width={16} height={16} style={{ marginRight: 4 }} key={file.id} />
<span style={{ whiteSpace: "nowrap" }}>Visit site</span> <span style={{ whiteSpace: "nowrap" }}>Visit site</span>
</System.ButtonTertiary> </System.ButtonTertiary>
); );
@ -142,7 +141,6 @@ const useCarouselJumperControls = () => {
const STYLES_HEADER_WRAPPER = (theme) => css` const STYLES_HEADER_WRAPPER = (theme) => css`
${Styles.HORIZONTAL_CONTAINER_CENTERED}; ${Styles.HORIZONTAL_CONTAINER_CENTERED};
position: absolute;
width: 100%; width: 100%;
min-height: 64px; min-height: 64px;
padding: 13px 24px 10px; padding: 13px 24px 10px;
@ -243,42 +241,6 @@ function CarouselHeader({
}; };
useEventListener({ type: "keyup", handler: handleKeyDown }); useEventListener({ type: "keyup", handler: handleKeyDown });
const isJumperOpen =
isFileDescriptionVisible ||
isMoreInfoVisible ||
isEditInfoVisible ||
isShareFileVisible ||
isEditChannelsVisible;
const [isHeaderVisible, setHeaderVisibility] = React.useState(true);
const timeoutRef = React.useRef();
const showHeader = () => {
clearTimeout(timeoutRef.current);
setHeaderVisibility(true);
};
const hideHeader = (ms = 1000) => {
timeoutRef.current = setTimeout(() => {
if (isJumperOpen) return;
setHeaderVisibility(false);
}, ms);
};
React.useEffect(() => {
hideHeader(3000);
return () => clearTimeout(timeoutRef.current);
}, []);
useIsomorphicLayoutEffect(() => {
if (isJumperOpen) {
showHeader();
return;
}
hideHeader();
}, [isJumperOpen]);
const headerRef = React.useRef(); const headerRef = React.useRef();
React.useEffect(() => { React.useEffect(() => {
if (headerRef.current) headerRef.current.focus(); if (headerRef.current) headerRef.current.focus();
@ -320,18 +282,7 @@ function CarouselHeader({
/> />
</ModalPortal> </ModalPortal>
<motion.nav <nav css={STYLES_HEADER_WRAPPER} {...props}>
css={STYLES_HEADER_WRAPPER}
initial={{ opacity: 0 }}
animate={{ opacity: isHeaderVisible ? 1 : 0 }}
transition={{ ease: "easeInOut", duration: 0.25 }}
onMouseEnter={showHeader}
onMouseOver={showHeader}
onMouseLeave={hideHeader}
onFocus={showHeader}
onBlur={hideHeader}
{...props}
>
<div> <div>
<div css={Styles.HORIZONTAL_CONTAINER}> <div css={Styles.HORIZONTAL_CONTAINER}>
<System.H5 <System.H5
@ -483,20 +434,14 @@ function CarouselHeader({
</div> </div>
)} )}
</div> </div>
</motion.nav> </nav>
<CarouselControls <CarouselControls
enableNextSlide={enableNextSlide} enableNextSlide={enableNextSlide}
enablePreviousSlide={enablePreviousSlide} enablePreviousSlide={enablePreviousSlide}
onNextSlide={onNextSlide} onNextSlide={onNextSlide}
onPreviousSlide={onPreviousSlide} onPreviousSlide={onPreviousSlide}
showControls={isHeaderVisible}
onClose={onClose} onClose={onClose}
onMouseEnter={showHeader}
onMouseOver={showHeader}
onMouseLeave={hideHeader}
onFocus={showHeader}
onBlur={hideHeader}
/> />
</> </>
); );
@ -783,10 +728,7 @@ function CarouselControls({
enablePreviousSlide, enablePreviousSlide,
onNextSlide, onNextSlide,
onPreviousSlide, onPreviousSlide,
showControls,
onClose, onClose,
...props
}) { }) {
useCarouselKeyCommands({ useCarouselKeyCommands({
handleNext: onNextSlide, handleNext: onNextSlide,
@ -794,19 +736,41 @@ function CarouselControls({
handleClose: onClose, handleClose: onClose,
}); });
const [areControlsVisible, setCarouselVisibility] = React.useState(true);
const timeoutRef = React.useRef();
const showControls = () => {
clearTimeout(timeoutRef.current);
setCarouselVisibility(true);
};
const hideControls = (ms = 1000) => {
timeoutRef.current = setTimeout(() => {
setCarouselVisibility(false);
}, ms);
};
React.useEffect(() => {
hideControls(3000);
return () => clearTimeout(timeoutRef.current);
}, []);
return ( return (
<> <>
<div <div
css={STYLES_CONTROLS_WRAPPER} css={STYLES_CONTROLS_WRAPPER}
style={{ left: 0, justifyContent: "flex-start" }} style={{ left: 0, justifyContent: "flex-start" }}
{...props} onMouseEnter={showControls}
onMouseLeave={hideControls}
onFocus={showControls}
onBlur={hideControls}
> >
{enablePreviousSlide ? ( {enablePreviousSlide ? (
<System.ButtonPrimitive <System.ButtonPrimitive
as={motion.button} as={motion.button}
onClick={onPreviousSlide} onClick={onPreviousSlide}
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: showControls ? 1 : 0 }} animate={{ opacity: areControlsVisible ? 1 : 0 }}
transition={{ ease: "easeInOut", duration: 0.25 }} transition={{ ease: "easeInOut", duration: 0.25 }}
css={STYLES_CONTROLS_BUTTON} css={STYLES_CONTROLS_BUTTON}
aria-label="previous slide" aria-label="previous slide"
@ -818,14 +782,17 @@ function CarouselControls({
<div <div
css={STYLES_CONTROLS_WRAPPER} css={STYLES_CONTROLS_WRAPPER}
style={{ right: 0, justifyContent: "flex-end" }} style={{ right: 0, justifyContent: "flex-end" }}
{...props} onMouseEnter={showControls}
onMouseLeave={hideControls}
onFocus={showControls}
onBlur={hideControls}
> >
{enableNextSlide ? ( {enableNextSlide ? (
<System.ButtonPrimitive <System.ButtonPrimitive
as={motion.button} as={motion.button}
onClick={onNextSlide} onClick={onNextSlide}
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: showControls ? 1 : 0 }} animate={{ opacity: areControlsVisible ? 1 : 0 }}
css={STYLES_CONTROLS_BUTTON} css={STYLES_CONTROLS_BUTTON}
aria-label="next slide" aria-label="next slide"
> >

View File

@ -76,7 +76,7 @@ function ChannelKeyboardShortcut({ searchResults, searchQuery, onAddFileToChanne
}); });
// NOTE(amine): don't show the 'select channel ⏎' hint when the channel is created optimistically // NOTE(amine): don't show the 'select channel ⏎' hint when the channel is created optimistically
if (isFileAdded || !selectedChannel.ownerId) return null; if (isFileAdded || !selectedChannel?.ownerId) return null;
return ( return (
<div css={Styles.HORIZONTAL_CONTAINER_CENTERED}> <div css={Styles.HORIZONTAL_CONTAINER_CENTERED}>
@ -113,7 +113,15 @@ const STYLES_SEARCH_TAGS_INPUT_WRAPPER = (theme) => css`
function ChannelInput({ value, searchResults, onChange, onAddFileToChannel, ...props }) { function ChannelInput({ value, searchResults, onChange, onAddFileToChannel, ...props }) {
const { publicChannels, privateChannels } = searchResults; const { publicChannels, privateChannels } = searchResults;
const showShortcut = publicChannels.length + privateChannels.length === 1; const [isShortcutVisible, setShortcutVisibility] = React.useState();
React.useEffect(() => {
if (value && publicChannels.length + privateChannels.length === 1) {
setShortcutVisibility(true);
} else {
setShortcutVisibility(false);
}
}, [value]);
return ( return (
<div css={[STYLES_SEARCH_TAGS_INPUT_WRAPPER, Styles.CONTAINER_CENTERED]}> <div css={[STYLES_SEARCH_TAGS_INPUT_WRAPPER, Styles.CONTAINER_CENTERED]}>
@ -129,7 +137,7 @@ function ChannelInput({ value, searchResults, onChange, onAddFileToChannel, ...p
{...props} {...props}
/> />
<div style={{ position: "absolute", top: "50%", transform: "translateY(-50%)", right: 20 }}> <div style={{ position: "absolute", top: "50%", transform: "translateY(-50%)", right: 20 }}>
{showShortcut ? ( {isShortcutVisible ? (
<ChannelKeyboardShortcut <ChannelKeyboardShortcut
searchQuery={value} searchQuery={value}
searchResults={searchResults} searchResults={searchResults}
@ -425,7 +433,7 @@ export function EditChannels({ file, viewer, isOpen, onClose, ...props }) {
channels={isSearching ? searchResults.privateChannels : privateChannels} channels={isSearching ? searchResults.privateChannels : privateChannels}
searchQuery={searchQuery} searchQuery={searchQuery}
onAddFileToChannel={handleAddFileToChannel} onAddFileToChannel={handleAddFileToChannel}
onCreateChannel={handleCreateChannel(false)} onCreateChannel={(query) => (handleCreateChannel(false)(query), clearQuery())}
file={file} file={file}
viewer={viewer} viewer={viewer}
/> />
@ -437,7 +445,7 @@ export function EditChannels({ file, viewer, isOpen, onClose, ...props }) {
isCreatingChannel={isSearching && !channelAlreadyExists} isCreatingChannel={isSearching && !channelAlreadyExists}
channels={isSearching ? searchResults.publicChannels : publicChannels} channels={isSearching ? searchResults.publicChannels : publicChannels}
onAddFileToChannel={handleAddFileToChannel} onAddFileToChannel={handleAddFileToChannel}
onCreateChannel={handleCreateChannel(true)} onCreateChannel={(query) => (handleCreateChannel(true)(query), clearQuery())}
/> />
</div> </div>
</Jumper.Item> </Jumper.Item>
@ -493,7 +501,7 @@ export function EditChannelsMobile({ file, viewer, isOpen, onClose }) {
channels={isSearching ? searchResults.privateChannels : privateChannels} channels={isSearching ? searchResults.privateChannels : privateChannels}
searchQuery={searchQuery} searchQuery={searchQuery}
onAddFileToChannel={handleAddFileToChannel} onAddFileToChannel={handleAddFileToChannel}
onCreateChannel={handleCreateChannel(false)} onCreateChannel={(query) => (handleCreateChannel(false)(query), clearQuery())}
/> />
<div style={{ marginTop: 20 }}> <div style={{ marginTop: 20 }}>
<Channels <Channels
@ -503,7 +511,7 @@ export function EditChannelsMobile({ file, viewer, isOpen, onClose }) {
isCreatingChannel={isSearching && !channelAlreadyExists} isCreatingChannel={isSearching && !channelAlreadyExists}
channels={isSearching ? searchResults.publicChannels : publicChannels} channels={isSearching ? searchResults.publicChannels : publicChannels}
onAddFileToChannel={handleAddFileToChannel} onAddFileToChannel={handleAddFileToChannel}
onCreateChannel={handleCreateChannel(true)} onCreateChannel={(query) => (handleCreateChannel(true)(query), clearQuery())}
/> />
</div> </div>
</MobileJumper.Content> </MobileJumper.Content>