Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions entrypoints/sidepanel/components/CollectionView.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export const useStyles_CollectionView = makeStyles({
"&:hover":
{
boxShadow: tokens.shadow4
},

"&:not(:focus-within) .compact":
{
display: "none"
}
},
color:
Expand Down
19 changes: 14 additions & 5 deletions entrypoints/sidepanel/components/CollectionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import { useStyles_CollectionView } from "./CollectionView.styles";
import GroupView from "./GroupView";
import TabView from "./TabView";

export default function CollectionView({ collection, index: collectionIndex, dragOverlay }: CollectionViewProps): ReactElement
export default function CollectionView({
collection,
index: collectionIndex,
dragOverlay,
compact
}: CollectionViewProps): ReactElement
{
const { tilesView } = useCollections();
const {
Expand Down Expand Up @@ -53,22 +58,25 @@ export default function CollectionView({ collection, index: collectionIndex, dra
{ (!activeItem || activeItem.item.type !== "collection") && !dragOverlay &&
<>
{ collection.items.length < 1 ?
<div className={ cls.empty }>
<div className={ mergeClasses(cls.empty, compact === true && "compact") }>
<CollectionsRegular fontSize={ 32 } />
<Body1Strong>{ i18n.t("collections.empty") }</Body1Strong>
</div>
:
<div className={ mergeClasses(cls.list, !tilesView && cls.verticalList) }>
<div className={ mergeClasses(cls.list, !tilesView && cls.verticalList, compact === true && "compact") }>
<SortableContext
items={ collection.items.map((_, index) => [collectionIndex, index].join("/")) }
strategy={ tilesView ? horizontalListSortingStrategy : verticalListSortingStrategy }
>
{ collection.items.map((i, index) =>
i.type === "group" ?
<GroupView
key={ index } group={ i } indices={ [collectionIndex, index] } />
key={ index } group={ i } indices={ [collectionIndex, index] }
collectionId={ collection.timestamp } />
:
<TabView key={ index } tab={ i } indices={ [collectionIndex, index] } />
<TabView
key={ index } tab={ i } indices={ [collectionIndex, index] }
collectionId={ collection.timestamp } />
) }
</SortableContext>
</div>
Expand All @@ -85,4 +93,5 @@ export type CollectionViewProps =
collection: CollectionItem;
index: number;
dragOverlay?: boolean;
compact?: boolean | null;
};
2 changes: 1 addition & 1 deletion entrypoints/sidepanel/components/EditDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
value={ color === "pinned" ? i18n.t("groups.pinned") : title }
onChange={ (_, e) => setTitle(e.value) } />
</fui.Field>
<fui.Field label="Color">
<fui.Field label={ i18n.t("dialogs.edit.color") }>
<div className={ cls.colorPicker } { ...horizontalNavigationAttributes }>
{ (props.type === "group" && (!props.hidePinned || props.group?.pinned)) &&
<fui.ToggleButton
Expand Down
7 changes: 5 additions & 2 deletions entrypoints/sidepanel/components/GroupView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import GroupMoreMenu from "./collections/GroupMoreMenu";
import { useStyles_GroupView } from "./GroupView.styles";
import TabView from "./TabView";

export default function GroupView({ group, indices, dragOverlay }: GroupViewProps): ReactElement
export default function GroupView({ group, indices, dragOverlay, collectionId }: GroupViewProps): ReactElement
{
const [alwaysShowToolbars] = useSettings("alwaysShowToolbars");
const { tilesView } = useCollections();
Expand Down Expand Up @@ -101,7 +101,9 @@ export default function GroupView({ group, indices, dragOverlay }: GroupViewProp
strategy={ !tilesView ? verticalListSortingStrategy : horizontalListSortingStrategy }
>
{ group.items.map((i, index) =>
<TabView key={ index } tab={ i } indices={ [...indices, index] } />
<TabView
key={ index } tab={ i } indices={ [...indices, index] }
collectionId={ collectionId } />
) }
</SortableContext>
</div>
Expand All @@ -117,4 +119,5 @@ export type GroupViewProps =
group: GroupItem;
indices: number[];
dragOverlay?: boolean;
collectionId: number;
};
69 changes: 69 additions & 0 deletions entrypoints/sidepanel/components/TabEditDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { track } from "@/features/analytics";
import { TabItem } from "@/models/CollectionModels";
import { Button, DialogActions, DialogBody, DialogContent, DialogSurface, DialogTitle, DialogTrigger, Field, Input, makeStyles, tokens } from "@fluentui/react-components";

export default function TabEditDialog({ tab, onSave }: TabEditDialogProps): React.ReactElement
{
const cls = useStyles();

const [title, setTitle] = useState(tab.title ?? "");
const [url, setUrl] = useState(tab.url);
const isValid = useMemo(() => url.trim().length > 0, [url]);

const onSubmit = (e: React.FormEvent<HTMLFormElement>) =>
{
e.preventDefault();
track("item_edited", { type: "tab" });
onSave({
...tab,
title: title.trim().length > 0 ? title : undefined,
url: url.trim()
});
};

return (
<DialogSurface>
<form onSubmit={ onSubmit }>
<DialogBody>
<DialogTitle>{ i18n.t("dialogs.edit.title.edit_tab") }</DialogTitle>
<DialogContent className={ cls.content }>
<Input
value={ title } onChange={ (_, e) => setTitle(e.value) }
placeholder={ i18n.t("dialogs.edit.collection_title") } />
<Field validationMessage={ isValid ? undefined : i18n.t("dialogs.edit.url_error") }>
<Input
value={ url } onChange={ (_, e) => setUrl(e.value) }
placeholder="URL" />
</Field>
</DialogContent>

<DialogActions>
<DialogTrigger disableButtonEnhancement>
<Button disabled={ !isValid } appearance="primary" as="button" type="submit">
{ i18n.t("common.actions.save") }
</Button>
</DialogTrigger>
<DialogTrigger disableButtonEnhancement>
<Button appearance="subtle">{ i18n.t("common.actions.cancel") }</Button>
</DialogTrigger>
</DialogActions>
</DialogBody>
</form>
</DialogSurface>
);
}

const useStyles = makeStyles({
content:
{
display: "flex",
flexFlow: "column",
gap: tokens.spacingVerticalMNudge
}
});

export type TabEditDialogProps =
{
tab: TabItem;
onSave: (updatedTab: TabItem) => void;
};
42 changes: 42 additions & 0 deletions entrypoints/sidepanel/components/TabMoreButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useDangerStyles } from "@/hooks/useDangerStyles";
import { Button, Menu, MenuItem, MenuList, MenuPopover, MenuTrigger, Tooltip } from "@fluentui/react-components";
import { bundleIcon, Delete20Filled, Delete20Regular, Edit20Filled, Edit20Regular, MoreHorizontal20Regular } from "@fluentui/react-icons";
import { ButtonHTMLAttributes } from "react";

export default function TabMoreButton({ onEdit, onDelete, ...props }: TabMoreButtonProps): React.ReactElement
{
const EditIcon = bundleIcon(Edit20Filled, Edit20Regular);
const DeleteIcon = bundleIcon(Delete20Filled, Delete20Regular);
const dangerCls = useDangerStyles();

return (
<Menu>
<Tooltip relationship="label" content={ i18n.t("common.tooltips.more") }>
<MenuTrigger>
<Button
appearance="subtle" icon={ <MoreHorizontal20Regular /> }
onClick={ ev => ev.stopPropagation() }
{ ...props } />
</MenuTrigger>
</Tooltip>

<MenuPopover onClick={ ev => ev.stopPropagation() }>
<MenuList>
<MenuItem icon={ <EditIcon /> } onClick={ onEdit }>
{ i18n.t("dialogs.edit.title.edit_tab") }
</MenuItem>
<MenuItem icon={ <DeleteIcon /> } className={ dangerCls.menuItem } onClick={ onDelete }>
{ i18n.t("tabs.delete") }
</MenuItem>
</MenuList>
</MenuPopover>
</Menu>
);
}

export type TabMoreButtonProps =
ButtonHTMLAttributes<HTMLButtonElement> &
{
onDelete?: () => void;
onEdit?: () => void;
};
47 changes: 32 additions & 15 deletions entrypoints/sidepanel/components/TabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import { useDialog } from "@/contexts/DialogProvider";
import { useCollections } from "@/entrypoints/sidepanel/contexts/CollectionsProvider";
import useDndItem from "@/entrypoints/sidepanel/hooks/useDndItem";
import useSettings from "@/hooks/useSettings";
import { TabItem } from "@/models/CollectionModels";
import { Button, Caption1, Link, mergeClasses, Tooltip } from "@fluentui/react-components";
import { Dismiss20Regular } from "@fluentui/react-icons";
import { CollectionItem, GroupItem, TabItem } from "@/models/CollectionModels";
import { Caption1, Link, mergeClasses, Tooltip } from "@fluentui/react-components";
import { MouseEventHandler, ReactElement } from "react";
import { useStyles_TabView } from "./TabView.styles";
import CollectionContext, { CollectionContextType } from "../contexts/CollectionContext";
import TabMoreButton from "./TabMoreButton";
import TabEditDialog from "./TabEditDialog";

export default function TabView({ tab, indices, dragOverlay }: TabViewProps): ReactElement
export default function TabView({ tab, indices, dragOverlay, collectionId }: TabViewProps): ReactElement
{
const { removeItem, graphics, tilesView } = useCollections();
const { removeItem, graphics, tilesView, collections, updateCollection } = useCollections();
const { collection } = useContext<CollectionContextType>(CollectionContext);
const {
setNodeRef, setActivatorNodeRef,
Expand All @@ -26,11 +27,8 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re

const cls = useStyles_TabView();

const handleDelete: MouseEventHandler<HTMLButtonElement> = (args) =>
const handleDelete = (): void =>
{
args.preventDefault();
args.stopPropagation();

const removeIndex: number[] = [collection.timestamp, ...indices.slice(1)];

if (deletePrompt)
Expand All @@ -45,6 +43,26 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
removeItem(...removeIndex);
};

const handleEdit = (): void =>
{
if (collectionId < 0)
return;

const updateTab = async (updatedTab: TabItem): Promise<void> =>
{
const collection: CollectionItem = collections!.find(i => i.timestamp === collectionId)!;

if (indices.length > 2)
(collection.items[indices[1]] as GroupItem).items[indices[2]] = updatedTab;
else
collection.items[indices[1]] = updatedTab;

await updateCollection(collection, collection.timestamp);
};

dialog.pushCustom(<TabEditDialog tab={ tab } onSave={ updateTab } />);
};

const handleClick: MouseEventHandler<HTMLAnchorElement> = (args) =>
{
args.preventDefault();
Expand Down Expand Up @@ -91,12 +109,10 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
</Caption1>
</Tooltip>

<Tooltip relationship="label" content={ i18n.t("tabs.delete") }>
<Button
className={ mergeClasses(cls.deleteButton, showToolbar === true && cls.showDeleteButton) }
appearance="subtle" icon={ <Dismiss20Regular /> }
onClick={ handleDelete } />
</Tooltip>
<TabMoreButton
className={ mergeClasses(cls.deleteButton, showToolbar === true && cls.showDeleteButton) }
onEdit={ handleEdit }
onDelete={ handleDelete } />
</div>
</Link>
);
Expand All @@ -107,4 +123,5 @@ export type TabViewProps =
tab: TabItem;
indices: number[];
dragOverlay?: boolean;
collectionId: number;
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getCollectionTitle } from "@/entrypoints/sidepanel/utils/getCollectionT
import useSettings from "@/hooks/useSettings";
import { TabItem } from "@/models/CollectionModels";
import { Button, Caption1, makeStyles, mergeClasses, Subtitle2, tokens, Tooltip } from "@fluentui/react-components";
import { Add20Filled, Add20Regular, bundleIcon } from "@fluentui/react-icons";
import { Add20Filled, Add20Regular, bundleIcon, EyeOff16Regular } from "@fluentui/react-icons";
import CollectionContext, { CollectionContextType } from "../../contexts/CollectionContext";
import { useCollections } from "../../contexts/CollectionsProvider";
import CollectionMoreButton from "./CollectionMoreButton";
Expand All @@ -23,7 +23,7 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col

const handleAddSelected = async () =>
{
const [newTabs, skipCount] = await getTabsToSaveAsync();
const [newTabs, skipCount] = await getTabsToSaveAsync(true);

if (newTabs.length > 0)
await updateCollection({
Expand All @@ -45,9 +45,12 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
content={ getCollectionTitle(collection) }
positioning="above-start"
>
<Subtitle2 truncate wrap={ false } className={ cls.titleText }>
{ getCollectionTitle(collection) }
</Subtitle2>
<div className={ cls.titleContainer }>
{ collection.hidden && <EyeOff16Regular /> }
<Subtitle2 truncate wrap={ false } className={ cls.titleText }>
{ getCollectionTitle(collection) }
</Subtitle2>
</div>
</Tooltip>

<Caption1>
Expand Down Expand Up @@ -112,5 +115,11 @@ const useStyles = makeStyles({
showToolbar:
{
display: "flex"
},
titleContainer:
{
display: "flex",
alignItems: "center",
gap: tokens.spacingHorizontalS
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
const EditIcon = ic.bundleIcon(ic.Edit20Filled, ic.Edit20Regular);
const DeleteIcon = ic.bundleIcon(ic.Delete20Filled, ic.Delete20Regular);
const BookmarkIcon = ic.bundleIcon(ic.BookmarkAdd20Filled, ic.BookmarkAdd20Regular);
const ShowIcon = ic.bundleIcon(ic.Eye20Filled, ic.Eye20Regular);
const HideIcon = ic.bundleIcon(ic.EyeOff20Filled, ic.EyeOff20Regular);

const dangerCls = useDangerStyles();

Expand All @@ -39,6 +41,11 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
removeItem(collection.timestamp);
};

const toggleHidden = () =>
{
updateCollection({ ...collection, hidden: !collection.hidden }, collection.timestamp);
};

const handleEdit = () =>
dialog.pushCustom(
<EditDialog
Expand Down Expand Up @@ -82,6 +89,9 @@ export default function CollectionMoreButton({ onAddSelected, onOpenChange }: Co
<MenuItem icon={ <EditIcon /> } onClick={ handleEdit }>
{ i18n.t("collections.menu.edit") }
</MenuItem>
<MenuItem icon={ collection.hidden ? <ShowIcon /> : <HideIcon /> } onClick={ toggleHidden }>
{ collection.hidden ? i18n.t("collections.menu.unhide") : i18n.t("collections.menu.hide") }
</MenuItem>
<MenuItem icon={ <DeleteIcon /> } className={ dangerCls.menuItem } onClick={ handleDelete }>
{ i18n.t("collections.menu.delete") }
</MenuItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default function GroupMoreMenu(): ReactElement

const handleAddSelected = async () =>
{
const [newTabs, skipCount] = await getTabsToSaveAsync();
const [newTabs, skipCount] = await getTabsToSaveAsync(true);

if (newTabs.length > 0)
await updateGroup({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,9 @@ export const useStyles_CollectionListView = makeStyles({
{
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
}
},
compactList:
{
alignItems: "baseline"
}
});
Loading
Loading