diff --git a/entrypoints/sidepanel/components/CollectionView.styles.ts b/entrypoints/sidepanel/components/CollectionView.styles.ts
index c214b74..a684b11 100644
--- a/entrypoints/sidepanel/components/CollectionView.styles.ts
+++ b/entrypoints/sidepanel/components/CollectionView.styles.ts
@@ -19,6 +19,11 @@ export const useStyles_CollectionView = makeStyles({
"&:hover":
{
boxShadow: tokens.shadow4
+ },
+
+ "&:not(:focus-within) .compact":
+ {
+ display: "none"
}
},
color:
diff --git a/entrypoints/sidepanel/components/CollectionView.tsx b/entrypoints/sidepanel/components/CollectionView.tsx
index 40a5b69..d7ee416 100644
--- a/entrypoints/sidepanel/components/CollectionView.tsx
+++ b/entrypoints/sidepanel/components/CollectionView.tsx
@@ -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 {
@@ -53,12 +58,12 @@ export default function CollectionView({ collection, index: collectionIndex, dra
{ (!activeItem || activeItem.item.type !== "collection") && !dragOverlay &&
<>
{ collection.items.length < 1 ?
-
+
{ i18n.t("collections.empty") }
:
-
+
[collectionIndex, index].join("/")) }
strategy={ tilesView ? horizontalListSortingStrategy : verticalListSortingStrategy }
@@ -66,9 +71,12 @@ export default function CollectionView({ collection, index: collectionIndex, dra
{ collection.items.map((i, index) =>
i.type === "group" ?
+ key={ index } group={ i } indices={ [collectionIndex, index] }
+ collectionId={ collection.timestamp } />
:
-
+
) }
@@ -85,4 +93,5 @@ export type CollectionViewProps =
collection: CollectionItem;
index: number;
dragOverlay?: boolean;
+ compact?: boolean | null;
};
diff --git a/entrypoints/sidepanel/components/EditDialog.tsx b/entrypoints/sidepanel/components/EditDialog.tsx
index cc3c80a..1eb0594 100644
--- a/entrypoints/sidepanel/components/EditDialog.tsx
+++ b/entrypoints/sidepanel/components/EditDialog.tsx
@@ -87,7 +87,7 @@ export default function EditDialog(props: GroupEditDialogProps): ReactElement
value={ color === "pinned" ? i18n.t("groups.pinned") : title }
onChange={ (_, e) => setTitle(e.value) } />
-
+
{ (props.type === "group" && (!props.hidePinned || props.group?.pinned)) &&
{ group.items.map((i, index) =>
-
+
) }
@@ -117,4 +119,5 @@ export type GroupViewProps =
group: GroupItem;
indices: number[];
dragOverlay?: boolean;
+ collectionId: number;
};
diff --git a/entrypoints/sidepanel/components/TabEditDialog.tsx b/entrypoints/sidepanel/components/TabEditDialog.tsx
new file mode 100644
index 0000000..9a32192
--- /dev/null
+++ b/entrypoints/sidepanel/components/TabEditDialog.tsx
@@ -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) =>
+ {
+ e.preventDefault();
+ track("item_edited", { type: "tab" });
+ onSave({
+ ...tab,
+ title: title.trim().length > 0 ? title : undefined,
+ url: url.trim()
+ });
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useStyles = makeStyles({
+ content:
+ {
+ display: "flex",
+ flexFlow: "column",
+ gap: tokens.spacingVerticalMNudge
+ }
+});
+
+export type TabEditDialogProps =
+{
+ tab: TabItem;
+ onSave: (updatedTab: TabItem) => void;
+};
diff --git a/entrypoints/sidepanel/components/TabMoreButton.tsx b/entrypoints/sidepanel/components/TabMoreButton.tsx
new file mode 100644
index 0000000..decd49f
--- /dev/null
+++ b/entrypoints/sidepanel/components/TabMoreButton.tsx
@@ -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 (
+
+ );
+}
+
+export type TabMoreButtonProps =
+ ButtonHTMLAttributes &
+ {
+ onDelete?: () => void;
+ onEdit?: () => void;
+ };
diff --git a/entrypoints/sidepanel/components/TabView.tsx b/entrypoints/sidepanel/components/TabView.tsx
index 9937b63..15b87b0 100644
--- a/entrypoints/sidepanel/components/TabView.tsx
+++ b/entrypoints/sidepanel/components/TabView.tsx
@@ -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(CollectionContext);
const {
setNodeRef, setActivatorNodeRef,
@@ -26,11 +27,8 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
const cls = useStyles_TabView();
- const handleDelete: MouseEventHandler = (args) =>
+ const handleDelete = (): void =>
{
- args.preventDefault();
- args.stopPropagation();
-
const removeIndex: number[] = [collection.timestamp, ...indices.slice(1)];
if (deletePrompt)
@@ -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 =>
+ {
+ 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();
+ };
+
const handleClick: MouseEventHandler = (args) =>
{
args.preventDefault();
@@ -91,12 +109,10 @@ export default function TabView({ tab, indices, dragOverlay }: TabViewProps): Re
-
- }
- onClick={ handleDelete } />
-
+
);
@@ -107,4 +123,5 @@ export type TabViewProps =
tab: TabItem;
indices: number[];
dragOverlay?: boolean;
+ collectionId: number;
};
diff --git a/entrypoints/sidepanel/components/collections/CollectionHeader.tsx b/entrypoints/sidepanel/components/collections/CollectionHeader.tsx
index c4a1efa..62a0a96 100644
--- a/entrypoints/sidepanel/components/collections/CollectionHeader.tsx
+++ b/entrypoints/sidepanel/components/collections/CollectionHeader.tsx
@@ -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";
@@ -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({
@@ -45,9 +45,12 @@ export default function CollectionHeader({ dragHandleRef, dragHandleProps }: Col
content={ getCollectionTitle(collection) }
positioning="above-start"
>
-
- { getCollectionTitle(collection) }
-
+
+ { collection.hidden && }
+
+ { getCollectionTitle(collection) }
+
+
@@ -112,5 +115,11 @@ const useStyles = makeStyles({
showToolbar:
{
display: "flex"
+ },
+ titleContainer:
+ {
+ display: "flex",
+ alignItems: "center",
+ gap: tokens.spacingHorizontalS
}
});
diff --git a/entrypoints/sidepanel/components/collections/CollectionMoreButton.tsx b/entrypoints/sidepanel/components/collections/CollectionMoreButton.tsx
index db0fc24..1dba8cc 100644
--- a/entrypoints/sidepanel/components/collections/CollectionMoreButton.tsx
+++ b/entrypoints/sidepanel/components/collections/CollectionMoreButton.tsx
@@ -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();
@@ -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(
} onClick={ handleEdit }>
{ i18n.t("collections.menu.edit") }
+ : } onClick={ toggleHidden }>
+ { collection.hidden ? i18n.t("collections.menu.unhide") : i18n.t("collections.menu.hide") }
+
} className={ dangerCls.menuItem } onClick={ handleDelete }>
{ i18n.t("collections.menu.delete") }
diff --git a/entrypoints/sidepanel/components/collections/GroupMoreMenu.tsx b/entrypoints/sidepanel/components/collections/GroupMoreMenu.tsx
index fd5fbf0..55d9755 100644
--- a/entrypoints/sidepanel/components/collections/GroupMoreMenu.tsx
+++ b/entrypoints/sidepanel/components/collections/GroupMoreMenu.tsx
@@ -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({
diff --git a/entrypoints/sidepanel/layouts/collections/CollectionListView.styles.ts b/entrypoints/sidepanel/layouts/collections/CollectionListView.styles.ts
index 4faa635..5e3d55f 100644
--- a/entrypoints/sidepanel/layouts/collections/CollectionListView.styles.ts
+++ b/entrypoints/sidepanel/layouts/collections/CollectionListView.styles.ts
@@ -51,5 +51,9 @@ export const useStyles_CollectionListView = makeStyles({
{
gridTemplateColumns: "repeat(auto-fit, minmax(360px, 1fr))"
}
+ },
+ compactList:
+ {
+ alignItems: "baseline"
}
});
diff --git a/entrypoints/sidepanel/layouts/collections/CollectionListView.tsx b/entrypoints/sidepanel/layouts/collections/CollectionListView.tsx
index 7b2f9e1..d84c361 100644
--- a/entrypoints/sidepanel/layouts/collections/CollectionListView.tsx
+++ b/entrypoints/sidepanel/layouts/collections/CollectionListView.tsx
@@ -18,10 +18,10 @@ import CollectionContext from "../../contexts/CollectionContext";
import { useCollections } from "../../contexts/CollectionsProvider";
import applyReorder from "../../utils/dnd/applyReorder";
import { collisionDetector } from "../../utils/dnd/collisionDetector";
+import { snapHandleToCursor } from "../../utils/dnd/snapHandleToCursor";
import { useStyles_CollectionListView } from "./CollectionListView.styles";
import SearchBar from "./SearchBar";
import StorageCapacityIssueMessage from "./messages/StorageCapacityIssueMessage";
-import { snapHandleToCursor } from "../../utils/dnd/snapHandleToCursor";
export default function CollectionListView(): ReactElement
{
@@ -30,17 +30,19 @@ export default function CollectionListView(): ReactElement
const [sortMode, setSortMode] = useSettings("sortMode");
const [query, setQuery] = useState("");
const [colors, setColors] = useState([]);
+ const [showHidden, setShowHidden] = useState(false);
+ const [compactView] = useSettings("compactView");
const [active, setActive] = useState(null);
const sensors = useSensors(
- useSensor(MouseSensor, { activationConstraint: { delay: 10, tolerance: 20 } }),
+ useSensor(MouseSensor, { activationConstraint: { delay: 150, tolerance: 20 } }),
useSensor(TouchSensor, { activationConstraint: { delay: 300, tolerance: 20 } })
);
const resultList = useMemo(
- () => sortCollections(filterCollections(collections, { query, colors }), sortMode),
- [query, colors, sortMode, collections]
+ () => sortCollections(filterCollections(collections, { query, colors, showHidden }), sortMode),
+ [query, colors, sortMode, collections, showHidden]
);
const cls = useStyles_CollectionListView();
@@ -49,6 +51,13 @@ export default function CollectionListView(): ReactElement
{
setQuery("");
setColors([]);
+ setShowHidden(false);
+ }, []);
+
+ const updateFilter = useCallback((newColors: CollectionFilterType["colors"], newShowHidden: boolean) =>
+ {
+ setColors(newColors);
+ setShowHidden(newShowHidden);
}, []);
const handleDragStart = (event: DragStartEvent): void =>
@@ -87,8 +96,9 @@ export default function CollectionListView(): ReactElement
@@ -105,7 +115,7 @@ export default function CollectionListView(): ReactElement
:
-