Allow direct editing of collection icon (#7120)
* Allow direct editing of collection icon * feedback
This commit is contained in:
@@ -2,7 +2,6 @@ import { observer } from "mobx-react";
|
||||
import { CollectionIcon } from "outline-icons";
|
||||
import { getLuminance } from "polished";
|
||||
import * as React from "react";
|
||||
import { randomElement } from "@shared/random";
|
||||
import { colorPalette } from "@shared/utils/collections";
|
||||
import Collection from "~/models/Collection";
|
||||
import Icon from "~/components/Icon";
|
||||
@@ -32,7 +31,7 @@ function ResolvedCollectionIcon({
|
||||
if (!collection.icon || collection.icon === "collection") {
|
||||
// If the chosen icon color is very dark then we invert it in dark mode
|
||||
// otherwise it will be impossible to see against the dark background.
|
||||
const collectionColor = collection.color ?? randomElement(colorPalette);
|
||||
const collectionColor = collection.color ?? colorPalette[0];
|
||||
const color =
|
||||
inputColor ||
|
||||
(ui.resolvedTheme === "dark" && collectionColor !== "currentColor"
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import { s } from "@shared/styles";
|
||||
import { colorPalette } from "@shared/utils/collections";
|
||||
import Collection from "~/models/Collection";
|
||||
import Search from "~/scenes/Search";
|
||||
import { Action } from "~/components/Actions";
|
||||
@@ -43,6 +44,8 @@ import Empty from "./components/Empty";
|
||||
import MembershipPreview from "./components/MembershipPreview";
|
||||
import ShareButton from "./components/ShareButton";
|
||||
|
||||
const IconPicker = React.lazy(() => import("~/components/IconPicker"));
|
||||
|
||||
function CollectionScene() {
|
||||
const params = useParams<{ id?: string }>();
|
||||
const history = useHistory();
|
||||
@@ -60,6 +63,13 @@ function CollectionScene() {
|
||||
collections.getByUrl(id) || collections.get(id);
|
||||
const can = usePolicy(collection);
|
||||
|
||||
const handleIconChange = React.useCallback(
|
||||
async (icon: string | null, color: string | null) => {
|
||||
await collection?.save({ icon, color });
|
||||
},
|
||||
[collection]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
setLastVisitedPath(currentPath);
|
||||
}, [currentPath, setLastVisitedPath]);
|
||||
@@ -119,6 +129,10 @@ function CollectionScene() {
|
||||
return <Search notFound />;
|
||||
}
|
||||
|
||||
const fallbackIcon = collection ? (
|
||||
<Icon as={CollectionIcon} collection={collection} size={40} expanded />
|
||||
) : null;
|
||||
|
||||
return collection ? (
|
||||
<Scene
|
||||
// Forced mount prevents animation of pinned documents when navigating
|
||||
@@ -164,7 +178,21 @@ function CollectionScene() {
|
||||
) : (
|
||||
<>
|
||||
<HeadingWithIcon>
|
||||
<HeadingIcon collection={collection} size={40} expanded />
|
||||
{can.update ? (
|
||||
<React.Suspense fallback={fallbackIcon}>
|
||||
<Icon
|
||||
icon={collection.icon ?? "collection"}
|
||||
color={collection.color ?? colorPalette[0]}
|
||||
initial={collection.name[0]}
|
||||
size={40}
|
||||
popoverPosition="bottom-start"
|
||||
onChange={handleIconChange}
|
||||
borderOnHover
|
||||
/>
|
||||
</React.Suspense>
|
||||
) : (
|
||||
fallbackIcon
|
||||
)}
|
||||
{collection.name}
|
||||
{collection.isPrivate &&
|
||||
!FeatureFlags.isEnabled(Feature.newCollectionSharing) && (
|
||||
@@ -305,7 +333,7 @@ const HeadingWithIcon = styled(Heading)`
|
||||
`};
|
||||
`;
|
||||
|
||||
const HeadingIcon = styled(CollectionIcon)`
|
||||
const Icon = styled(IconPicker)`
|
||||
flex-shrink: 0;
|
||||
margin-left: -8px;
|
||||
margin-right: 8px;
|
||||
|
||||
@@ -659,7 +659,7 @@ type EditorContainerProps = {
|
||||
|
||||
const EditorContainer = styled.div<EditorContainerProps>`
|
||||
// Adds space to the gutter to make room for icon & heading annotations
|
||||
padding: 0 44px;
|
||||
padding: 0 40px;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
grid-row: 1;
|
||||
@@ -686,7 +686,7 @@ type RevisionContainerProps = {
|
||||
|
||||
const RevisionContainer = styled.div<RevisionContainerProps>`
|
||||
// Adds space to the gutter to make room for icon
|
||||
padding: 0 44px;
|
||||
padding: 0 40px;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
grid-row: 1;
|
||||
|
||||
@@ -8,7 +8,7 @@ import styled from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import isMarkdown from "@shared/editor/lib/isMarkdown";
|
||||
import normalizePastedMarkdown from "@shared/editor/lib/markdown/normalize";
|
||||
import { extraArea, s } from "@shared/styles";
|
||||
import { s } from "@shared/styles";
|
||||
import { light } from "@shared/styles/theme";
|
||||
import {
|
||||
getCurrentDateAsString,
|
||||
@@ -138,21 +138,21 @@ const DocumentTitle = React.forwardRef(function _DocumentTitle(
|
||||
);
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
(value: string) => {
|
||||
let title = value;
|
||||
(input: string) => {
|
||||
let value = input;
|
||||
|
||||
if (/\/date\s$/.test(value)) {
|
||||
title = getCurrentDateAsString();
|
||||
if (/\/date\s$/.test(input)) {
|
||||
value = getCurrentDateAsString();
|
||||
ref?.current?.focusAtEnd();
|
||||
} else if (/\/time$/.test(value)) {
|
||||
title = getCurrentTimeAsString();
|
||||
} else if (/\/time$/.test(input)) {
|
||||
value = getCurrentTimeAsString();
|
||||
ref?.current?.focusAtEnd();
|
||||
} else if (/\/datetime$/.test(value)) {
|
||||
title = getCurrentDateTimeAsString();
|
||||
} else if (/\/datetime$/.test(input)) {
|
||||
value = getCurrentDateTimeAsString();
|
||||
ref?.current?.focusAtEnd();
|
||||
}
|
||||
|
||||
onChangeTitle?.(title);
|
||||
onChangeTitle?.(value);
|
||||
},
|
||||
[ref, onChangeTitle]
|
||||
);
|
||||
@@ -257,16 +257,16 @@ const DocumentTitle = React.forwardRef(function _DocumentTitle(
|
||||
{can.update && !readOnly ? (
|
||||
<IconWrapper align="center" justify="center" dir={dir}>
|
||||
<React.Suspense fallback={fallbackIcon}>
|
||||
<StyledIconPicker
|
||||
<IconPicker
|
||||
icon={icon ?? null}
|
||||
color={color}
|
||||
size={40}
|
||||
popoverPosition="bottom-start"
|
||||
allowDelete={true}
|
||||
borderOnHover={true}
|
||||
onChange={handleIconChange}
|
||||
onOpen={handleOpen}
|
||||
onClose={handleClose}
|
||||
allowDelete
|
||||
borderOnHover
|
||||
/>
|
||||
</React.Suspense>
|
||||
</IconWrapper>
|
||||
@@ -346,10 +346,6 @@ const Title = styled(ContentEditable)<TitleProps>`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledIconPicker = styled(IconPicker)`
|
||||
${extraArea(8)}
|
||||
`;
|
||||
|
||||
const IconWrapper = styled(Flex)<{ dir?: string }>`
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
|
||||
@@ -244,7 +244,7 @@ export class IconLibrary {
|
||||
keywords: "browser web app",
|
||||
},
|
||||
collection: {
|
||||
component: CollectionIcon,
|
||||
component: (props) => <CollectionIcon expanded {...props} />,
|
||||
keywords: "collection",
|
||||
},
|
||||
coins: {
|
||||
|
||||
Reference in New Issue
Block a user