-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6f65b1c
commit 8ff8314
Showing
28 changed files
with
2,153 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
weave-js/src/components/PagePanelComponents/Home/Browse3/compare/Badge.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import * as DropdownMenu from '@wandb/weave/components/DropdownMenu'; | ||
import classNames from 'classnames'; | ||
import React, {useState} from 'react'; | ||
import {SortableElement, SortableHandle} from 'react-sortable-hoc'; | ||
|
||
import {Button} from '../../../../Button'; | ||
import {Icon} from '../../../../Icon'; | ||
import {BadgeDef} from './types'; | ||
|
||
// TODO: Change cursor to grabbing when dragging | ||
const DragHandle = SortableHandle(() => ( | ||
<div className="cursor-grab"> | ||
<Icon name="drag-grip" /> | ||
</div> | ||
)); | ||
|
||
type BadgeProps = { | ||
numBadges: number; | ||
idx: number; | ||
badge: BadgeDef; | ||
useBaseline: boolean; | ||
isSelected: boolean; | ||
onClickBadgeLabel: (value: string) => void; | ||
onSetBaseline: (value: string | null) => void; | ||
onRemoveBadge: (value: string) => void; | ||
}; | ||
|
||
export const Badge = SortableElement( | ||
({ | ||
numBadges, | ||
idx, | ||
badge, | ||
useBaseline, | ||
isSelected, | ||
onClickBadgeLabel, | ||
onSetBaseline, | ||
onRemoveBadge, | ||
}: BadgeProps) => { | ||
const [isOpen, setIsOpen] = useState(false); | ||
|
||
const onClickLabel = | ||
idx === 0 | ||
? undefined | ||
: () => { | ||
onClickBadgeLabel(badge.value); | ||
}; | ||
|
||
const onMakeBaseline = () => { | ||
onSetBaseline(badge.value); | ||
}; | ||
|
||
const onRemoveItem = () => { | ||
onRemoveBadge(badge.value); | ||
}; | ||
|
||
return ( | ||
<div className="tw-style"> | ||
<div | ||
className={classNames( | ||
'flex select-none items-center gap-4 whitespace-nowrap rounded-[4px] border-[1px] border-moon-250 bg-white px-4 py-8 text-sm font-semibold', | ||
{ | ||
'border-teal-350 text-teal-500 outline outline-1 outline-teal-350': | ||
isSelected, | ||
} | ||
)}> | ||
<DragHandle /> | ||
<div | ||
className={classNames( | ||
{ | ||
'underline decoration-teal-450': useBaseline && idx === 0, | ||
}, | ||
{ | ||
'cursor-pointer': idx !== 0, | ||
} | ||
)} | ||
onClick={onClickLabel}> | ||
{badge.label ?? badge.value} | ||
</div> | ||
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}> | ||
<DropdownMenu.Trigger> | ||
<Button | ||
variant="ghost" | ||
icon="overflow-vertical" | ||
active={isOpen} | ||
size="small" | ||
/> | ||
</DropdownMenu.Trigger> | ||
<DropdownMenu.Portal> | ||
<DropdownMenu.Content align="start"> | ||
{useBaseline && idx === 0 && ( | ||
<DropdownMenu.Item | ||
onClick={() => { | ||
onSetBaseline(null); | ||
}}> | ||
<Icon name="baseline-alt" /> | ||
Remove baseline | ||
</DropdownMenu.Item> | ||
)} | ||
{(!useBaseline || idx !== 0) && ( | ||
<DropdownMenu.Item onClick={onMakeBaseline}> | ||
<Icon name="baseline-alt" /> | ||
Make baseline | ||
</DropdownMenu.Item> | ||
)} | ||
{numBadges > 2 && ( | ||
<> | ||
<DropdownMenu.Separator /> | ||
<DropdownMenu.Item onClick={onRemoveItem}> | ||
<Icon name="delete" /> | ||
Remove from comparison | ||
</DropdownMenu.Item> | ||
</> | ||
)} | ||
</DropdownMenu.Content> | ||
</DropdownMenu.Portal> | ||
</DropdownMenu.Root> | ||
</div> | ||
</div> | ||
); | ||
} | ||
); |
144 changes: 144 additions & 0 deletions
144
weave-js/src/components/PagePanelComponents/Home/Browse3/compare/Badges.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import React from 'react'; | ||
import {useHistory} from 'react-router-dom'; | ||
import {SortableContainer} from 'react-sortable-hoc'; | ||
|
||
import {queryToggleString, searchParamsSetArray} from '../urlQueryUtil'; | ||
import {Badge} from './Badge'; | ||
import {BadgeDefs} from './types'; | ||
|
||
type BadgesProps = { | ||
badges: BadgeDefs; | ||
useBaseline: boolean; | ||
selected: string | null; | ||
}; | ||
|
||
type SortableBadgesProps = BadgesProps & { | ||
onClickBadgeLabel: (value: string) => void; | ||
onSetBaseline: (value: string | null) => void; | ||
onRemoveBadge: (value: string) => void; | ||
}; | ||
|
||
const SortableList = SortableContainer( | ||
({ | ||
badges, | ||
useBaseline, | ||
selected, | ||
onClickBadgeLabel, | ||
onSetBaseline, | ||
onRemoveBadge, | ||
}: SortableBadgesProps) => { | ||
return ( | ||
<div className="flex flex-wrap items-center gap-8"> | ||
{badges.map((badge, index) => ( | ||
<Badge | ||
key={`item-${badge.value}`} | ||
index={index} | ||
idx={index} | ||
badge={badge} | ||
useBaseline={useBaseline} | ||
onClickBadgeLabel={onClickBadgeLabel} | ||
onSetBaseline={onSetBaseline} | ||
onRemoveBadge={onRemoveBadge} | ||
numBadges={badges.length} | ||
isSelected={badge.value === selected} | ||
/> | ||
))} | ||
</div> | ||
); | ||
} | ||
); | ||
|
||
// Create a copy of the specified array, moving an item from one index to another. | ||
function arrayMove<T>(array: readonly T[], from: number, to: number) { | ||
const slicedArray = array.slice(); | ||
slicedArray.splice( | ||
to < 0 ? array.length + to : to, | ||
0, | ||
slicedArray.splice(from, 1)[0] | ||
); | ||
return slicedArray; | ||
} | ||
|
||
export const Badges = ({badges, useBaseline, selected}: BadgesProps) => { | ||
const history = useHistory(); | ||
const onSortEnd = ({ | ||
oldIndex, | ||
newIndex, | ||
}: { | ||
oldIndex: number; | ||
newIndex: number; | ||
}) => { | ||
if (oldIndex === newIndex) { | ||
return; | ||
} | ||
const {search} = history.location; | ||
const params = new URLSearchParams(search); | ||
params.delete('baseline'); | ||
const newBadges = arrayMove(badges, oldIndex, newIndex); | ||
const values = newBadges.map(b => b.value); | ||
searchParamsSetArray(params, badges[0].key, values); | ||
history.replace({ | ||
search: params.toString(), | ||
}); | ||
}; | ||
|
||
function moveToFront<T>(list: T[], item: T): T[] { | ||
const index = list.indexOf(item); | ||
if (index !== -1) { | ||
list.splice(index, 1); // Remove the item from its current position | ||
list.unshift(item); // Add the item to the front | ||
} | ||
return list; | ||
} | ||
|
||
const onClickBadgeLabel = (value: string) => { | ||
queryToggleString(history, 'sel', value); | ||
}; | ||
|
||
const onSetBaseline = (value: string | null) => { | ||
const {search} = history.location; | ||
const params = new URLSearchParams(search); | ||
if (value === null) { | ||
params.delete('baseline'); | ||
} else { | ||
let values = badges.map(b => b.value); | ||
values = moveToFront(values, value); | ||
searchParamsSetArray(params, badges[0].key, values); | ||
params.set('baseline', '1'); | ||
} | ||
history.replace({ | ||
search: params.toString(), | ||
}); | ||
}; | ||
|
||
const onRemoveBadge = (value: string) => { | ||
const newBadges = badges.filter(b => b.value !== value); | ||
const {search} = history.location; | ||
const params = new URLSearchParams(search); | ||
searchParamsSetArray( | ||
params, | ||
newBadges[0].key, | ||
newBadges.map(b => b.value) | ||
); | ||
if (selected === value || selected === newBadges[0].value) { | ||
params.delete('sel'); | ||
} | ||
history.replace({ | ||
search: params.toString(), | ||
}); | ||
}; | ||
|
||
return ( | ||
<SortableList | ||
useDragHandle | ||
axis="xy" | ||
badges={badges} | ||
useBaseline={useBaseline} | ||
selected={selected} | ||
onSortEnd={onSortEnd} | ||
onClickBadgeLabel={onClickBadgeLabel} | ||
onSetBaseline={onSetBaseline} | ||
onRemoveBadge={onRemoveBadge} | ||
/> | ||
); | ||
}; |
Oops, something went wrong.