fix: Embed disabled state should persist (#3407)

* Normalize code around localStorage
Persist disabled embed state

* fix: Cannot view more than 10 starred items on load

* More tidying of sidebar state
This commit is contained in:
Tom Moor
2022-04-17 10:24:40 -07:00
committed by GitHub
parent 1e1a57d246
commit e4e98286f4
11 changed files with 267 additions and 192 deletions

View File

@@ -22,7 +22,6 @@ function Collections() {
const [fetchError, setFetchError] = React.useState();
const { documents, collections } = useStores();
const { showToast } = useToasts();
const [expanded, setExpanded] = React.useState(true);
const isPreloaded = !!collections.orderedData.length;
const { t } = useTranslation();
const orderedCollections = collections.orderedData;
@@ -97,20 +96,18 @@ function Collections() {
if (!collections.isLoaded || fetchError) {
return (
<Flex column>
<Header>{t("Collections")}</Header>
<PlaceholderCollections />
<Header id="collections" title={t("Collections")}>
<PlaceholderCollections />
</Header>
</Flex>
);
}
return (
<Flex column>
<Header onClick={() => setExpanded((prev) => !prev)} expanded={expanded}>
{t("Collections")}
</Header>
{expanded && (
<Header id="collections" title={t("Collections")}>
<Relative>{isPreloaded ? content : <Fade>{content}</Fade>}</Relative>
)}
</Header>
</Flex>
);
}

View File

@@ -1,25 +1,65 @@
import { CollapsedIcon } from "outline-icons";
import * as React from "react";
import styled from "styled-components";
import styled, { keyframes } from "styled-components";
import usePersistedState from "~/hooks/usePersistedState";
type Props = {
onClick?: React.MouseEventHandler;
expanded?: boolean;
/** Unique header id if passed the header will become toggleable */
id?: string;
title: React.ReactNode;
};
export const Header: React.FC<Props> = ({ onClick, expanded, children }) => {
/**
* Toggleable sidebar header
*/
export const Header: React.FC<Props> = ({ id, title, children }) => {
const [firstRender, setFirstRender] = React.useState(true);
const [expanded, setExpanded] = usePersistedState(
`sidebar-header-${id}`,
true
);
React.useEffect(() => {
if (!expanded) {
setFirstRender(false);
}
}, [expanded]);
const handleClick = React.useCallback(() => {
setExpanded(!expanded);
}, [expanded, setExpanded]);
return (
<H3>
<Button onClick={onClick} disabled={!onClick}>
{children}
{onClick && (
<Disclosure expanded={expanded} color="currentColor" size={20} />
)}
</Button>
</H3>
<>
<H3>
<Button onClick={handleClick} disabled={!id}>
{title}
{id && (
<Disclosure expanded={expanded} color="currentColor" size={20} />
)}
</Button>
</H3>
{expanded && (firstRender ? children : <Fade>{children}</Fade>)}
</>
);
};
export const fadeAndSlideDown = keyframes`
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0px);
}
`;
const Fade = styled.span`
animation: ${fadeAndSlideDown} 100ms ease-in-out;
`;
const Button = styled.button`
display: inline-flex;
align-items: center;

View File

@@ -24,6 +24,7 @@ type Props = Omit<NavLinkProps, "to"> & {
label?: React.ReactNode;
menu?: React.ReactNode;
showActions?: boolean;
disabled?: boolean;
active?: boolean;
/* If set, a disclosure will be rendered to the left of any icon */
expanded?: boolean;
@@ -55,6 +56,7 @@ function SidebarLink(
className,
expanded,
onDisclosureClick,
disabled,
...rest
}: Props,
ref: React.RefObject<HTMLAnchorElement>
@@ -82,6 +84,7 @@ function SidebarLink(
<Link
$isActiveDrop={isActiveDrop}
$isDraft={isDraft}
$disabled={disabled}
activeStyle={isActiveDrop ? activeDropStyle : activeStyle}
style={active ? activeStyle : style}
onClick={onClick}
@@ -158,7 +161,11 @@ const Actions = styled(EventBoundary)<{ showActions?: boolean }>`
}
`;
const Link = styled(NavLink)<{ $isActiveDrop?: boolean; $isDraft?: boolean }>`
const Link = styled(NavLink)<{
$isActiveDrop?: boolean;
$isDraft?: boolean;
$disabled?: boolean;
}>`
display: flex;
position: relative;
text-overflow: ellipsis;
@@ -174,6 +181,13 @@ const Link = styled(NavLink)<{ $isActiveDrop?: boolean; $isDraft?: boolean }>`
cursor: pointer;
overflow: hidden;
${(props) =>
props.$disabled &&
css`
pointer-events: none;
opacity: 0.75;
`}
${(props) =>
props.$isDraft &&
css`

View File

@@ -16,106 +16,51 @@ import StarredContext from "./StarredContext";
import StarredLink from "./StarredLink";
const STARRED_PAGINATION_LIMIT = 10;
const STARRED = "STARRED";
function Starred() {
const [isFetching, setIsFetching] = React.useState(false);
const [fetchError, setFetchError] = React.useState();
const [expanded, setExpanded] = React.useState(true);
const [show, setShow] = React.useState("Nothing");
const [offset, setOffset] = React.useState(0);
const [upperBound, setUpperBound] = React.useState(STARRED_PAGINATION_LIMIT);
const [displayedStarsCount, setDisplayedStarsCount] = React.useState(
STARRED_PAGINATION_LIMIT
);
const { showToast } = useToasts();
const { stars } = useStores();
const { t } = useTranslation();
const fetchResults = React.useCallback(async () => {
try {
setIsFetching(true);
await stars.fetchPage({
limit: STARRED_PAGINATION_LIMIT,
offset,
});
} catch (error) {
showToast(t("Starred documents could not be loaded"), {
type: "error",
});
setFetchError(error);
} finally {
setIsFetching(false);
}
}, [stars, offset, showToast, t]);
React.useEffect(() => {
let stateInLocal;
try {
stateInLocal = localStorage.getItem(STARRED);
} catch (_) {
// no-op Safari private mode
}
if (!stateInLocal) {
localStorage.setItem(STARRED, expanded ? "true" : "false");
} else {
setExpanded(stateInLocal === "true");
}
}, [expanded]);
React.useEffect(() => {
setOffset(stars.orderedData.length);
if (stars.orderedData.length <= STARRED_PAGINATION_LIMIT) {
setShow("Nothing");
} else if (stars.orderedData.length >= upperBound) {
setShow("More");
} else if (stars.orderedData.length < upperBound) {
setShow("Less");
}
}, [stars.orderedData, upperBound]);
React.useEffect(() => {
if (offset === 0) {
fetchResults();
}
}, [fetchResults, offset]);
const handleShowMore = React.useCallback(async () => {
setUpperBound(
(previousUpperBound) => previousUpperBound + STARRED_PAGINATION_LIMIT
);
await fetchResults();
}, [fetchResults]);
const handleShowLess = React.useCallback(() => {
setUpperBound(STARRED_PAGINATION_LIMIT);
setShow("More");
}, []);
const handleExpandClick = React.useCallback(
(ev) => {
ev.preventDefault();
ev.stopPropagation();
const fetchResults = React.useCallback(
async (offset = 0) => {
try {
localStorage.setItem(STARRED, !expanded ? "true" : "false");
} catch (_) {
// no-op Safari private mode
await stars.fetchPage({
limit: STARRED_PAGINATION_LIMIT + 1,
offset,
});
} catch (error) {
showToast(t("Starred documents could not be loaded"), {
type: "error",
});
setFetchError(error);
}
setExpanded((prev) => !prev);
},
[expanded]
[stars, showToast, t]
);
React.useEffect(() => {
fetchResults();
}, [fetchResults]);
const handleShowMore = async () => {
await fetchResults(displayedStarsCount);
setDisplayedStarsCount((prev) => prev + STARRED_PAGINATION_LIMIT);
};
// Drop to reorder document
const [{ isOverReorder }, dropToReorder] = useDrop({
const [{ isOverReorder, isDraggingAnyStar }, dropToReorder] = useDrop({
accept: "star",
drop: async (item: Star) => {
item?.save({ index: fractionalIndex(null, stars.orderedData[0].index) });
},
collect: (monitor) => ({
isOverReorder: !!monitor.isOver(),
isDraggingAnyStar: monitor.getItemType() === "star",
}),
});
@@ -126,40 +71,33 @@ function Starred() {
return (
<StarredContext.Provider value={true}>
<Flex column>
<Header onClick={handleExpandClick} expanded={expanded}>
{t("Starred")}
</Header>
{expanded && (
<Header id="starred" title={t("Starred")}>
<Relative>
<DropCursor
isActiveDrop={isOverReorder}
innerRef={dropToReorder}
position="top"
/>
{stars.orderedData.slice(0, upperBound).map((star) => (
{isDraggingAnyStar && (
<DropCursor
isActiveDrop={isOverReorder}
innerRef={dropToReorder}
position="top"
/>
)}
{stars.orderedData.slice(0, displayedStarsCount).map((star) => (
<StarredLink key={star.id} star={star} />
))}
{show === "More" && !isFetching && (
{stars.orderedData.length > displayedStarsCount && (
<SidebarLink
onClick={handleShowMore}
label={`${t("Show more")}`}
disabled={stars.isFetching}
depth={0}
/>
)}
{show === "Less" && !isFetching && (
<SidebarLink
onClick={handleShowLess}
label={`${t("Show less")}`}
depth={0}
/>
)}
{(isFetching || fetchError) && !stars.orderedData.length && (
{(stars.isFetching || fetchError) && !stars.orderedData.length && (
<Flex column>
<PlaceholderCollections />
</Flex>
)}
</Relative>
)}
</Header>
</Flex>
</StarredContext.Provider>
);