Skip to content

Commit

Permalink
display draggable thumbs when the <pinboard-article-button> has a `…
Browse files Browse the repository at this point in the history
…data-with-draggable-thumbs-of-ratio` attribute
  • Loading branch information
twrichards committed Oct 14, 2024
1 parent ec7a2f5 commit afbff5a
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 92 deletions.
26 changes: 23 additions & 3 deletions bootstrapping-lambda/local/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,32 @@ <h3>Override unread notifications bubble</h3>
</div>

<h3>Fronts Integration</h3>
<pinboard-article-button
data-url-path="football/2024/oct/08/tottenham-ryan-mason-anderlecht-manager-job"
></pinboard-article-button>

Composer Buttons etc. (under trail image in furniture)<br />
<pinboard-suggest-alternate-crops
data-media-id="d6518ba44eb272830b779e4ed6356482007d4536"
></pinboard-suggest-alternate-crops>

<div style="max-width: 180px">
Fronts Clipboard Card:<br />
<pinboard-article-button
data-url-path="football/2024/oct/08/tottenham-ryan-mason-anderlecht-manager-job"
></pinboard-article-button>
</div>
Fronts Article Cards [Collapsed]:<br />
<pinboard-article-button
data-url-path="football/2024/oct/08/tottenham-ryan-mason-anderlecht-manager-job"
></pinboard-article-button>
<br />
Fronts Article Cards [Expanded]<br />
<pinboard-article-button
data-url-path="football/2024/oct/08/tottenham-ryan-mason-anderlecht-manager-job"
data-with-draggable-thumbs-of-ratio="5:4"
></pinboard-article-button>
<pinboard-article-button
data-url-path="football/2024/oct/08/tottenham-ryan-mason-anderlecht-manager-job"
data-with-draggable-thumbs-of-ratio="4:5"
></pinboard-article-button>
<h3><code>Add to 📌</code> button</h3>
<ul id="button_section">
<h4>present initially</h4>
Expand Down
2 changes: 1 addition & 1 deletion client/src/buttonInOtherTools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const ButtonInOtherTools = ({
border: none;
border-radius: 100px;
padding: 0 ${iconAtEnd ? 6 : 10}px 0 ${iconAtEnd ? 10 : 6}px;
line-height: 2;
min-height: 24px;
cursor: pointer;
color: ${pinMetal};
${extraCss};
Expand Down
115 changes: 27 additions & 88 deletions client/src/fronts/frontsIntegration.tsx
Original file line number Diff line number Diff line change
@@ -1,101 +1,34 @@
import React, { useEffect, useMemo, useState } from "react";
import { useGlobalStateContext } from "../globalState";
import { PinboardData } from "shared/graphql/extraTypes";
import { PinboardIdWithItemCounts } from "shared/graphql/graphql";
import ReactDOM from "react-dom";
import root from "react-shadow/emotion";
import { css } from "@emotion/react";
import { useApolloClient } from "@apollo/client";
import { gqlGetItemCounts, gqlGetPinboardsByPaths } from "../../gql";
import { pinboard } from "../../colours";
import { neutral } from "@guardian/source-foundations";
import { ButtonInOtherTools } from "../buttonInOtherTools";
import { FrontsPinboardArticleButton } from "./frontsPinboardArticleButton";

export const FRONTS_PINBOARD_ELEMENTS_QUERY_SELECTOR =
"pinboard-article-button";

interface PinboardArticleButtonProps {
maybePinboardData: PinboardData | undefined;
maybeItemCounts: PinboardIdWithItemCounts | undefined;
}
const PinboardArticleButton = ({
maybePinboardData,
maybeItemCounts,
}: PinboardArticleButtonProps) => {
const { setIsExpanded, openPinboard } = useGlobalStateContext();

return (
<root.span>
<ButtonInOtherTools
extraCss={css`
display: inline-flex;
background-color: ${maybePinboardData
? pinboard[500]
: neutral["60"]};
`}
onClick={(event) => {
event.stopPropagation();
if (maybePinboardData) {
setIsExpanded(true);
openPinboard(false)(maybePinboardData, false); // TODO probably should be 'peek at pinboard' from panel.tsx
} else {
alert(
"This piece is not tracked in workflow, this is needed to chat and share assets such as crops via Pinboard."
);
// TODO consider offering an option to create workflow tracking
}
}}
>
{!maybePinboardData && "not tracked in workflow"}
{maybeItemCounts && (
<>
{
maybeItemCounts.unreadCount ||
null /*TODO display number in red blob*/
}
{maybeItemCounts.unreadCount > 0 && " unread of "}
{maybeItemCounts.totalCount}
{" items "}
{maybeItemCounts.totalCropCount > 0 && (
<>
{" with "}
{maybeItemCounts.totalCropCount}
{" crops "}
{(maybeItemCounts.fiveByFourCount > 0 ||
maybeItemCounts.fourByFiveCount > 0) &&
" (incl."}
{maybeItemCounts.fiveByFourCount > 0 &&
`${maybeItemCounts.fiveByFourCount} at 5:4`}
{maybeItemCounts.fourByFiveCount > 0 &&
`${maybeItemCounts.fourByFiveCount} at 4:5`}
{(maybeItemCounts.fiveByFourCount > 0 ||
maybeItemCounts.fourByFiveCount > 0) &&
")"}
</>
)}
</>
)}
</ButtonInOtherTools>
</root.span>
);
};

export const FrontsIntegration = ({
frontsPinboardElements,
}: {
frontsPinboardElements: HTMLElement[];
}) => {
const pathToElementMap: { [path: string]: HTMLElement } = useMemo(
const pathToElementsMap: { [path: string]: HTMLElement[] } = useMemo(
() =>
frontsPinboardElements.reduce(
// TODO could be replaced with groupBy if we upgrade to latest ES in tsconfig
(acc, htmlElement) =>
htmlElement.dataset.urlPath
? {
...acc,
[htmlElement.dataset.urlPath]: htmlElement,
[htmlElement.dataset.urlPath]: [
...(acc[htmlElement.dataset.urlPath] || []),
htmlElement,
],
}
: acc,
{} as Record<string, HTMLElement>
{} as Record<string, HTMLElement[]>
),
[frontsPinboardElements]
);
Expand All @@ -106,7 +39,7 @@ export const FrontsIntegration = ({
{} as { [path: string]: PinboardData }
);
useEffect(() => {
const paths = Object.keys(pathToElementMap);
const paths = Object.keys(pathToElementsMap);
paths.length > 0 &&
apolloClient
.query({
Expand All @@ -126,7 +59,7 @@ export const FrontsIntegration = ({
);
});
//TODO handle errors
}, [pathToElementMap]);
}, [pathToElementsMap]);

interface ItemCountsLookup {
[pinboardId: string]: PinboardIdWithItemCounts;
Expand Down Expand Up @@ -166,17 +99,23 @@ export const FrontsIntegration = ({

return (
<>
{Object.entries(pathToElementMap).map(([path, htmlElementToMountInto]) =>
ReactDOM.createPortal(
<PinboardArticleButton
maybePinboardData={pathToPinboardDataMap[path]}
maybeItemCounts={
pathToPinboardDataMap[path] &&
itemCountsLookup[pathToPinboardDataMap[path].id]
}
/>,
htmlElementToMountInto
)
{Object.entries(pathToElementsMap).map(
([path, htmlElementsToMountInto]) =>
htmlElementsToMountInto.map((htmlElementToMountInto) =>
ReactDOM.createPortal(
<FrontsPinboardArticleButton
maybePinboardData={pathToPinboardDataMap[path]}
maybeItemCounts={
pathToPinboardDataMap[path] &&
itemCountsLookup[pathToPinboardDataMap[path].id]
}
withDraggableThumbsOfRatio={
htmlElementToMountInto.dataset.withDraggableThumbsOfRatio
}
/>,
htmlElementToMountInto
)
)
)}
</>
);
Expand Down
161 changes: 161 additions & 0 deletions client/src/fronts/frontsPinboardArticleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { isPinboardData, PinboardData } from "shared/graphql/extraTypes";
import { Item, PinboardIdWithItemCounts } from "shared/graphql/graphql";
import { useGlobalStateContext } from "../globalState";
import { useApolloClient } from "@apollo/client";
import React, { useEffect, useState } from "react";
import { PayloadWithThumbnail } from "../types/PayloadAndType";
import { gqlGetInitialItems } from "../../gql";
import root from "react-shadow/emotion";
import { ButtonInOtherTools } from "../buttonInOtherTools";
import { css } from "@emotion/react";
import { pinboard } from "../../colours";
import { neutral } from "@guardian/source-foundations";
import { agateSans } from "../../fontNormaliser";

interface FrontsPinboardArticleButtonProps {
maybePinboardData: PinboardData | undefined;
maybeItemCounts: PinboardIdWithItemCounts | undefined;
withDraggableThumbsOfRatio: string | undefined;
}

export const FrontsPinboardArticleButton = ({
maybePinboardData,
maybeItemCounts,
withDraggableThumbsOfRatio,
}: FrontsPinboardArticleButtonProps) => {
const { setIsExpanded, openPinboard } = useGlobalStateContext();

const apolloClient = useApolloClient();

const [cropsAtRequiredRatio, setCropsAtRequiredRatio] = useState<
Array<[PayloadWithThumbnail, Item]>
>([]);

useEffect(() => {
if (isPinboardData(maybePinboardData) && withDraggableThumbsOfRatio) {
apolloClient
.query({
query: gqlGetInitialItems(maybePinboardData.id), //TODO consider creating new query
})
.then(({ data }) => {
data?.listItems &&
setCropsAtRequiredRatio(
data.listItems.reduce(
(acc: Array<[PayloadWithThumbnail, Item]>, item: Item) => {
const parsedPayload =
item.payload &&
(JSON.parse(item.payload) as PayloadWithThumbnail);
if (
item.type === "grid-crop" &&
parsedPayload &&
parsedPayload.aspectRatio === withDraggableThumbsOfRatio
) {
return [...acc, [parsedPayload, item]];
} else {
return acc;
}
},
[]
)
);
});
}
}, [
maybePinboardData,
withDraggableThumbsOfRatio,
maybeItemCounts?.totalCropCount,
]);

return (
<root.span>
<ButtonInOtherTools
extraCss={css`
display: inline-flex;
background-color: ${maybePinboardData
? pinboard[500]
: neutral["60"]};
// TODO fix line-height when button text wraps over multiple lines
`}
onClick={(event) => {
event.stopPropagation();
if (maybePinboardData) {
setIsExpanded(true);
openPinboard(false)(maybePinboardData, false); // TODO probably should be 'peek at pinboard' from panel.tsx
} else {
alert(
"This piece is not tracked in workflow, this is needed to chat and share assets (such as crops) via Pinboard."
);
}
}}
>
{!maybePinboardData && "Not tracked in workflow"}
{maybePinboardData && !maybeItemCounts && <em>loading...</em>}
{maybeItemCounts && (
<>
{
maybeItemCounts.unreadCount ||
null /*TODO move to absolute position top right of button in red blob*/
}
{maybeItemCounts.unreadCount > 0 && " unread of "}
{maybeItemCounts.totalCount}
&nbsp;items
{maybeItemCounts.totalCropCount > 0 && (
<>
&nbsp;with&nbsp;
{maybeItemCounts.totalCropCount}
&nbsp;crops
<wbr /> ({maybeItemCounts.fiveByFourCount}
&nbsp;at&nbsp;5:4&nbsp;and&nbsp;
{maybeItemCounts.fourByFiveCount}&nbsp;at&nbsp;4:5)
</>
)}
</>
)}
</ButtonInOtherTools>
{cropsAtRequiredRatio && cropsAtRequiredRatio.length > 0 && (
<div
css={css`
margin-left: 22px;
${agateSans.xxsmall()};
overflow: auto;
margin-bottom: 3px;
`}
>
<strong>
The {withDraggableThumbsOfRatio} crops suggested via Pinboard:
</strong>
<div
css={css`
display: flex;
gap: 3px;
`}
>
{cropsAtRequiredRatio.map(([payload, item], index) => (
<img
key={index}
css={css`
max-width: 100px;
max-height: 100px;
cursor: grab;
&:active {
cursor: grabbing;
}
`}
src={payload.thumbnail}
draggable
onDragStart={(event) => {
event.dataTransfer.setData("URL", payload.embeddableUrl);
}}
onClick={
() => console.log(item.id) //TODO open pinboard and scroll to selected item to see context
}
></img>
))}
</div>
<em>These can be dragged onto the trail image to replace it.</em>
</div>
)}
</root.span>
);
};

0 comments on commit afbff5a

Please sign in to comment.