Reduce size of Insights display toggle

This commit is contained in:
Tom Moor
2024-03-08 23:03:06 -05:00
parent 1a454d6dbb
commit f3334cedb2
4 changed files with 173 additions and 120 deletions

View File

@@ -26,6 +26,7 @@ import {
CommentIcon, CommentIcon,
GlobeIcon, GlobeIcon,
CopyIcon, CopyIcon,
EyeIcon,
} from "outline-icons"; } from "outline-icons";
import * as React from "react"; import * as React from "react";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -899,6 +900,37 @@ export const openDocumentInsights = createAction({
}, },
}); });
export const toggleViewerInsights = createAction({
name: ({ t, stores, activeDocumentId }) => {
const document = activeDocumentId
? stores.documents.get(activeDocumentId)
: undefined;
return document?.insightsEnabled
? t("Disable viewer insights")
: t("Enable viewer insights");
},
analyticsName: "Toggle viewer insights",
section: DocumentSection,
icon: <EyeIcon />,
visible: ({ activeDocumentId, stores }) => {
const can = stores.policies.abilities(activeDocumentId ?? "");
return can.updateInsights;
},
perform: async ({ activeDocumentId, stores }) => {
if (!activeDocumentId) {
return;
}
const document = stores.documents.get(activeDocumentId);
if (!document) {
return;
}
await document.save({
insightsEnabled: !document.insightsEnabled,
});
},
});
export const rootDocumentActions = [ export const rootDocumentActions = [
openDocument, openDocument,
archiveDocument, archiveDocument,

View File

@@ -0,0 +1,48 @@
import { t } from "i18next";
import { MoreIcon } from "outline-icons";
import React from "react";
import { MenuButton, useMenuState } from "reakit/Menu";
import styled from "styled-components";
import { s } from "@shared/styles";
import ContextMenu from "~/components/ContextMenu";
import Template from "~/components/ContextMenu/Template";
import NudeButton from "~/components/NudeButton";
import { actionToMenuItem } from "~/actions";
import { toggleViewerInsights } from "~/actions/definitions/documents";
import useActionContext from "~/hooks/useActionContext";
import { hover } from "~/styles";
import { MenuItem } from "~/types";
const InsightsMenu: React.FC = () => {
const menuRef = React.useRef<HTMLDivElement>(null);
const menu = useMenuState();
const context = useActionContext();
const items: MenuItem[] = [actionToMenuItem(toggleViewerInsights, context)];
return (
<>
<MenuButton {...menu}>
{(props) => (
<Button {...props}>
<MoreIcon />
</Button>
)}
</MenuButton>
<ContextMenu {...menu} menuRef={menuRef} aria-label={t("Notification")}>
<Template {...menu} items={items} />
</ContextMenu>
</>
);
};
const Button = styled(NudeButton)`
color: ${s("textSecondary")};
&:${hover},
&:active {
color: ${s("text")};
background: ${s("sidebarControlHoverBackground")};
}
`;
export default InsightsMenu;

View File

@@ -13,21 +13,19 @@ import DocumentViews from "~/components/DocumentViews";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import ListItem from "~/components/List/Item"; import ListItem from "~/components/List/Item";
import PaginatedList from "~/components/PaginatedList"; import PaginatedList from "~/components/PaginatedList";
import Switch from "~/components/Switch";
import Text from "~/components/Text"; import Text from "~/components/Text";
import Time from "~/components/Time"; import Time from "~/components/Time";
import useCurrentUser from "~/hooks/useCurrentUser";
import useKeyDown from "~/hooks/useKeyDown"; import useKeyDown from "~/hooks/useKeyDown";
import usePolicy from "~/hooks/usePolicy"; import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
import useTextSelection from "~/hooks/useTextSelection"; import useTextSelection from "~/hooks/useTextSelection";
import InsightsMenu from "~/menus/InsightsMenu";
import { documentPath } from "~/utils/routeHelpers"; import { documentPath } from "~/utils/routeHelpers";
import Sidebar from "./SidebarLayout"; import Sidebar from "./SidebarLayout";
function Insights() { function Insights() {
const { views, documents } = useStores(); const { views, documents } = useStores();
const { t } = useTranslation(); const { t } = useTranslation();
const user = useCurrentUser();
const match = useRouteMatch<{ documentSlug: string }>(); const match = useRouteMatch<{ documentSlug: string }>();
const history = useHistory(); const history = useHistory();
const selectedText = useTextSelection(); const selectedText = useTextSelection();
@@ -111,114 +109,99 @@ function Insights() {
</List> </List>
</Text> </Text>
</Content> </Content>
{document.insightsEnabled && (
<> <Content column>
<Content column> <Heading>{t("Contributors")}</Heading>
<Heading>{t("Contributors")}</Heading> <Text as="p" type="secondary" size="small">
<Text as="p" type="secondary" size="small"> {t(`Created`)} <Time dateTime={document.createdAt} addSuffix />.
{t(`Created`)}{" "} <br />
<Time dateTime={document.createdAt} addSuffix />. {t(`Last updated`)}{" "}
<br /> <Time dateTime={document.updatedAt} addSuffix />.
{t(`Last updated`)}{" "} </Text>
<Time dateTime={document.updatedAt} addSuffix />. <ListSpacing>
</Text> {document.sourceMetadata?.createdByName && (
<ListSpacing> <ListItem
{document.sourceMetadata?.createdByName && ( title={document.sourceMetadata?.createdByName}
<ListItem image={
title={document.sourceMetadata?.createdByName} <Avatar
image={ model={{
<Avatar color: stringToColor(
model={{ document.sourceMetadata.createdByName
color: stringToColor( ),
document.sourceMetadata.createdByName avatarUrl: null,
), initial: document.sourceMetadata.createdByName[0],
avatarUrl: null, }}
initial: document.sourceMetadata.createdByName[0], size={32}
}}
size={32}
/>
}
subtitle={t("Creator")}
border={false}
small
/> />
)} }
<PaginatedList subtitle={t("Creator")}
aria-label={t("Contributors")} border={false}
items={document.collaborators} small
renderItem={(model: User) => ( />
<ListItem )}
key={model.id} <PaginatedList
title={model.name} aria-label={t("Contributors")}
image={<Avatar model={model} size={32} />} items={document.collaborators}
subtitle={ renderItem={(model: User) => (
model.id === document.createdBy?.id <ListItem
? document.sourceMetadata?.createdByName key={model.id}
? t("Imported") title={model.name}
: t("Creator") image={<Avatar model={model} size={32} />}
: model.id === document.updatedBy?.id subtitle={
? t("Last edited") model.id === document.createdBy?.id
: t("Previously edited") ? document.sourceMetadata?.createdByName
} ? t("Imported")
border={false} : t("Creator")
small : model.id === document.updatedBy?.id
/> ? t("Last edited")
)} : t("Previously edited")
}
border={false}
small
/> />
</ListSpacing>
</Content>
<Content column>
<Heading>{t("Views")}</Heading>
<Text as="p" type="secondary" size="small">
{documentViews.length <= 1
? t("No one else has viewed yet")
: t(
`Viewed {{ count }} times by {{ teamMembers }} people`,
{
count: documentViews.reduce(
(memo, view) => memo + view.count,
0
),
teamMembers: documentViews.length,
}
)}
.
</Text>
{documentViews.length > 1 && (
<ListSpacing>
<DocumentViews document={document} isOpen />
</ListSpacing>
)} )}
</Content> />
</> </ListSpacing>
</Content>
{(document.insightsEnabled || can.updateInsights) && (
<Content column>
<Heading>
<Flex justify="space-between">
{t("Viewed by")}
{can.updateInsights && <InsightsMenu />}
</Flex>
</Heading>
{document.insightsEnabled ? (
<>
<Text as="p" type="secondary" size="small">
{documentViews.length <= 1
? t("No one else has viewed yet")
: t(
`Viewed {{ count }} times by {{ teamMembers }} people`,
{
count: documentViews.reduce(
(memo, view) => memo + view.count,
0
),
teamMembers: documentViews.length,
}
)}
.
</Text>
{documentViews.length > 1 && (
<ListSpacing>
<DocumentViews document={document} isOpen />
</ListSpacing>
)}
</>
) : (
<Text as="p" type="secondary" size="small">
{t("Viewer insights are disabled.")}
</Text>
)}
</Content>
)} )}
</div> </div>
{can.updateInsights && (
<Manage>
<Flex column>
<Text as="p" size="small" weight="bold">
{t("Viewer insights")}
</Text>
<Text as="p" type="secondary" size="small">
{user.isAdmin
? t(
"As an admin you can manage if team members can see who has viewed this document"
)
: t(
"As the doc owner you can manage if team members can see who has viewed this document"
)}
</Text>
</Flex>
<Switch
checked={document.insightsEnabled}
onChange={async (ev) => {
await document.save({
insightsEnabled: ev.currentTarget.checked,
});
}}
/>
</Manage>
)}
</Flex> </Flex>
) : null} ) : null}
</Sidebar> </Sidebar>
@@ -251,16 +234,6 @@ function countWords(text: string): number {
return t ? t.replace(/-/g, " ").split(/\s+/g).length : 0; return t ? t.replace(/-/g, " ").split(/\s+/g).length : 0;
} }
const Manage = styled(Flex)`
background: ${s("background")};
border: 1px solid ${s("inputBorder")};
border-bottom-width: 2px;
border-radius: 8px;
margin: 16px;
padding: 16px 16px 0;
justify-self: flex-end;
`;
const ListSpacing = styled("div")` const ListSpacing = styled("div")`
margin-top: -0.5em; margin-top: -0.5em;
margin-bottom: 0.5em; margin-bottom: 0.5em;

View File

@@ -69,6 +69,8 @@
"Comments": "Comments", "Comments": "Comments",
"History": "History", "History": "History",
"Insights": "Insights", "Insights": "Insights",
"Disable viewer insights": "Disable viewer insights",
"Enable viewer insights": "Enable viewer insights",
"Home": "Home", "Home": "Home",
"Drafts": "Drafts", "Drafts": "Drafts",
"Trash": "Trash", "Trash": "Trash",
@@ -583,13 +585,10 @@
"Creator": "Creator", "Creator": "Creator",
"Last edited": "Last edited", "Last edited": "Last edited",
"Previously edited": "Previously edited", "Previously edited": "Previously edited",
"Views": "Views",
"No one else has viewed yet": "No one else has viewed yet", "No one else has viewed yet": "No one else has viewed yet",
"Viewed {{ count }} times by {{ teamMembers }} people": "Viewed {{ count }} time by {{ teamMembers }} people", "Viewed {{ count }} times by {{ teamMembers }} people": "Viewed {{ count }} time by {{ teamMembers }} people",
"Viewed {{ count }} times by {{ teamMembers }} people_plural": "Viewed {{ count }} times by {{ teamMembers }} people", "Viewed {{ count }} times by {{ teamMembers }} people_plural": "Viewed {{ count }} times by {{ teamMembers }} people",
"Viewer insights": "Viewer insights", "Viewer insights are disabled.": "Viewer insights are disabled.",
"As an admin you can manage if team members can see who has viewed this document": "As an admin you can manage if team members can see who has viewed this document",
"As the doc owner you can manage if team members can see who has viewed this document": "As the doc owner you can manage if team members can see who has viewed this document",
"Sorry, the last change could not be persisted please reload the page": "Sorry, the last change could not be persisted please reload the page", "Sorry, the last change could not be persisted please reload the page": "Sorry, the last change could not be persisted please reload the page",
"{{ count }} days": "{{ count }} day", "{{ count }} days": "{{ count }} day",
"{{ count }} days_plural": "{{ count }} days", "{{ count }} days_plural": "{{ count }} days",
@@ -800,6 +799,7 @@
"Shared nested": "Shared nested", "Shared nested": "Shared nested",
"Nested documents are publicly available": "Nested documents are publicly available", "Nested documents are publicly available": "Nested documents are publicly available",
"Domain": "Domain", "Domain": "Domain",
"Views": "Views",
"Everyone": "Everyone", "Everyone": "Everyone",
"Admins": "Admins", "Admins": "Admins",
"Settings saved": "Settings saved", "Settings saved": "Settings saved",