Skip to content

Commit

Permalink
Use Item Tooltip everywhere item cannot be popped
Browse files Browse the repository at this point in the history
  • Loading branch information
robojumper committed Jan 20, 2022
1 parent baf0217 commit 8c2104c
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 34 deletions.
34 changes: 23 additions & 11 deletions src/app/dim-ui/PressTip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ interface Props {
* constructing the tree until the tooltip is shown.
*/
tooltip: React.ReactNode | (() => React.ReactNode);
/**
* Whether the presstip should be shown or not.
*/
disabled?: boolean;
/**
* The children of this component define the content that will trigger the tooltip.
*/
Expand All @@ -41,6 +45,7 @@ interface Props {

type ControlProps = Props &
React.HTMLAttributes<HTMLDivElement> & {
events: React.HTMLAttributes<HTMLDivElement>;
open: boolean;
triggerRef: React.RefObject<HTMLDivElement>;
};
Expand All @@ -65,10 +70,12 @@ type ControlProps = Props &
function Control({
tooltip,
open,
disabled,
triggerRef,
children,
elementType: Component = 'div',
className,
events,
...rest
}: ControlProps) {
const tooltipContents = useRef<HTMLDivElement>(null);
Expand All @@ -94,8 +101,10 @@ function Control({
// TODO: or use framer motion layout animations?
return (
<Component ref={triggerRef} className={clsx(styles.control, className)} {...rest}>
{children}
<div {...events}>{children}</div>
{open &&
!disabled &&
tooltip &&
ReactDOM.createPortal(
<div className={styles.tooltip} ref={tooltipContents}>
<div className={styles.content}>{_.isFunction(tooltip) ? tooltip() : tooltip}</div>
Expand Down Expand Up @@ -142,14 +151,17 @@ function PressTip(props: Props) {
timer.current = 0;
}, []);

const hover = useCallback((e: React.MouseEvent | React.TouchEvent | TouchEvent) => {
e.preventDefault();
clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
setOpen(true);
}, hoverDelay);
touchStartTime.current = performance.now();
}, []);
const hover = useCallback(
(e: React.MouseEvent | React.TouchEvent | TouchEvent) => {
e.preventDefault();
clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
setOpen(!props.disabled);
}, hoverDelay);
touchStartTime.current = performance.now();
},
[props.disabled]
);

// Stop the hover timer when the component unmounts
useEffect(() => () => clearTimeout(timer.current), []);
Expand Down Expand Up @@ -181,12 +193,12 @@ function PressTip(props: Props) {
onClick: absorbClick,
}
: {
onMouseEnter: hover,
onMouseOver: hover,
onMouseUp: closeToolTip,
onMouseLeave: closeToolTip,
};

return <Control open={open} triggerRef={ref} {...events} {...props} />;
return <Control open={open} triggerRef={ref} events={events} {...props} />;
}

export default PressTip;
16 changes: 12 additions & 4 deletions src/app/infuse/InfusionFinder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,23 @@ export default function InfusionFinder() {
<div className="infusionControls">
<div className="infuseTopRow">
<div className="infusionEquation">
{effectiveTarget ? <ConnectedInventoryItem item={effectiveTarget} /> : missingItem}
{effectiveTarget ? (
<ConnectedInventoryItem item={effectiveTarget} includeTooltip />
) : (
missingItem
)}
<div className="icon">
<AppIcon icon={plusIcon} />
</div>
{effectiveSource ? <ConnectedInventoryItem item={effectiveSource} /> : missingItem}
{effectiveSource ? (
<ConnectedInventoryItem item={effectiveSource} includeTooltip />
) : (
missingItem
)}
<div className="icon">
<AppIcon icon={faEquals} />
</div>
{result ? <ConnectedInventoryItem item={result} /> : missingItem}
{result ? <ConnectedInventoryItem item={result} includeTooltip /> : missingItem}
</div>
<div className="infuseActions">
<button type="button" className="dim-button" onClick={switchDirection}>
Expand Down Expand Up @@ -269,7 +277,7 @@ export default function InfusionFinder() {
className={clsx({ 'infuse-selected': item === target })}
onClick={() => selectItem(item)}
>
<ConnectedInventoryItem item={item} />
<ConnectedInventoryItem includeTooltip item={item} />
</div>
);

Expand Down
10 changes: 5 additions & 5 deletions src/app/inventory/InventoryItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ export default function InventoryItem({
</div>
);

if (tooltip) {
return <PressTip tooltip={() => <DimItemTooltip item={item} />}>{inner}</PressTip>;
} else {
return inner;
}
return (
<PressTip disabled={!tooltip} tooltip={() => <DimItemTooltip item={item} />}>
{inner}
</PressTip>
);
}
22 changes: 21 additions & 1 deletion src/app/inventory/ItemTooltip.m.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
flex-flow: column;
margin: 4px 0;

border-left: 2px solid #888;
padding-left: 3px;

> div {
display: flex;
flex-flow: row;
Expand All @@ -17,9 +20,26 @@
}
}

.perkSelected {
font-weight: bold;
}

.notes {
margin-left: 4px;
}

.note {
margin-left: -4px;
}

.noteIcon {
height: 24px;
width: 24px;
}

.stats {
margin: 4px 0 0 0;
:global(.stat) {
line-height: 8px;
line-height: 12px;
}
}
4 changes: 4 additions & 0 deletions src/app/inventory/ItemTooltip.m.scss.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 34 additions & 13 deletions src/app/inventory/ItemTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,43 @@ import BungieImage from 'app/dim-ui/BungieImage';
import { DimItem, DimStat } from 'app/inventory/item-types';
import { DefItemIcon } from 'app/inventory/ItemIcon';
import { useD2Definitions } from 'app/manifest/selectors';
import { AppIcon, stickyNoteIcon } from 'app/shell/icons';
import { isKillTrackerSocket } from 'app/utils/item-utils';
import { DestinyInventoryItemDefinition } from 'bungie-api-ts/destiny2';
import clsx from 'clsx';
import _ from 'lodash';
import React from 'react';
import { useSelector } from 'react-redux';
import { itemNoteSelector } from './dim-item-info';
import styles from './ItemTooltip.m.scss';

export function DimItemTooltip({ item }: { item: DimItem }) {
const defs = useD2Definitions()!;
const itemDef = defs.InventoryItem.get(item.hash);
const savedNotes = useSelector(itemNoteSelector(item));

if (item.bucket.sort === 'Weapons' && item.sockets) {
const perkSockets = item.sockets?.allSockets.filter((s) => s.isPerk && !isKillTrackerSocket(s));
const perks = _.takeRight(perkSockets, 2).flatMap((s) => {
const perk = s.plugged?.plugDef;
return perk ? [perk] : [];
});
const contents = perks.length ? (
<div className={clsx(styles.perks)}>
{perks.map((perk, index) => (
<div key={index}>
<DefItemIcon itemDef={perk} borderless={true} /> {perk.displayProperties.name}
const sockets = _.takeRight(perkSockets, 2);

const contents = sockets.map((socket) => (
<div key={socket.socketIndex} className={clsx(styles.perks)}>
{socket.plugOptions.map((p) => (
<div
key={p.plugDef.hash}
className={clsx(undefined, {
[styles.perkSelected]:
socket.isPerk && socket.plugOptions.length > 1 && p === socket.plugged,
})}
data-perk-name={p.plugDef.displayProperties.name}
>
<DefItemIcon itemDef={p.plugDef} borderless={true} /> {p.plugDef.displayProperties.name}
</div>
))}
</div>
) : undefined;
return <Tooltip def={itemDef} contents={contents} />;
));

return <Tooltip def={itemDef} notes={savedNotes} contents={contents} />;
} else if (item.bucket.sort === 'Armor' && item.stats?.length) {
const renderStat = (stat: DimStat) => (
<div key={stat.statHash} className="stat">
Expand All @@ -41,29 +52,39 @@ export function DimItemTooltip({ item }: { item: DimItem }) {
{stat.base}
</div>
);

const contents = (
<div className={clsx(styles.stats, 'stat-bars', 'destiny2')}>
<div className="stat-row">{item.stats?.filter((s) => s.statHash > 0).map(renderStat)}</div>
<div className="stat-row">{item.stats?.filter((s) => s.statHash < 0).map(renderStat)}</div>
</div>
);
return <Tooltip def={itemDef} contents={contents} />;

return <Tooltip def={itemDef} notes={savedNotes} contents={contents} />;
} else {
return <Tooltip def={itemDef} contents={undefined} />;
return <Tooltip def={itemDef} notes={savedNotes} contents={undefined} />;
}
}

function Tooltip({
def,
notes,
contents,
}: {
def: DestinyInventoryItemDefinition;
notes?: string;
contents?: React.ReactNode;
}) {
return (
<>
<h2>{def.displayProperties.name}</h2>
{def.itemTypeDisplayName && <h3>{def.itemTypeDisplayName}</h3>}
{notes && (
<div className={clsx(styles.notes)}>
<AppIcon className={styles.noteIcon} icon={stickyNoteIcon} />
<span className={clsx(styles.note)}>{notes}</span>
</div>
)}
{contents}
</>
);
Expand Down
4 changes: 4 additions & 0 deletions src/app/inventory/StoreInventoryItem.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { compareOpenSelector } from 'app/compare/selectors';
import { useThunkDispatch } from 'app/store/thunk-dispatch';
import React from 'react';
import { useSelector } from 'react-redux';
import ConnectedInventoryItem from './ConnectedInventoryItem';
import DraggableInventoryItem from './DraggableInventoryItem';
import { DimItem } from './item-types';
Expand All @@ -15,6 +17,7 @@ interface Props {
*/
export default function StoreInventoryItem({ item }: Props) {
const dispatch = useThunkDispatch();
const compareOpen = useSelector(compareOpenSelector);
const doubleClicked = (e: React.MouseEvent) => {
dispatch(moveItemToCurrentStore(item, e));
};
Expand All @@ -27,6 +30,7 @@ export default function StoreInventoryItem({ item }: Props) {
item={item}
allowFilter={true}
innerRef={ref}
includeTooltip={compareOpen}
onClick={onClick}
onDoubleClick={doubleClicked}
// for only StoreInventoryItems (the main inventory page)
Expand Down

0 comments on commit 8c2104c

Please sign in to comment.