Navbar customization followup (#17848)

Addresses review comments from the [first navbar customization
PR](https://github.com/twentyhq/twenty/pull/17728)

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
This commit is contained in:
Abdul Rahman
2026-02-12 08:05:43 +05:30
committed by GitHub
parent 91bfd45dc5
commit 3fb2352c78
67 changed files with 993 additions and 686 deletions
@@ -1,30 +1,21 @@
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { IconFolder, IconLink } from 'twenty-ui/display';
import { IconFolder } from 'twenty-ui/display';
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
import { useUpdateFolderNameInDraft } from '@/navigation-menu-item/hooks/useUpdateFolderNameInDraft';
import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
const ICON_CONFIG = {
folder: { Icon: IconFolder, colorKey: 'folder' },
link: { Icon: IconLink, colorKey: 'link' },
} as const;
export const CommandMenuFolderLinkInfo = ({
type,
}: {
type: 'folder' | 'link';
}) => {
export const CommandMenuFolderInfo = () => {
const theme = useTheme();
const { t } = useLingui();
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
@@ -38,15 +29,14 @@ export const CommandMenuFolderLinkInfo = ({
);
const items = useWorkspaceSectionItems();
const { updateFolderNameInDraft } = useUpdateFolderNameInDraft();
const { updateLinkInDraft } = useUpdateLinkInDraft();
const defaultLabel = type === 'folder' ? t`New folder` : t`Link label`;
const placeholder = type === 'folder' ? t`Folder name` : t`Link label`;
const defaultLabel = t`New folder`;
const placeholder = t`Folder name`;
const selectedItem = selectedNavigationMenuItemInEditMode
? items.find(
(item) =>
item.itemType === type &&
item.itemType === NavigationMenuItemType.FOLDER &&
item.id === selectedNavigationMenuItemInEditMode,
)
: undefined;
@@ -57,11 +47,7 @@ export const CommandMenuFolderLinkInfo = ({
const itemName = selectedItem.name ?? defaultLabel;
const handleChange = (text: string) => {
if (type === 'folder') {
updateFolderNameInDraft(itemId, text);
} else {
updateLinkInDraft(itemId, { name: text });
}
updateFolderNameInDraft(itemId, text);
};
const handleSave = () => {
@@ -69,23 +55,17 @@ export const CommandMenuFolderLinkInfo = ({
const finalName = trimmed.length > 0 ? trimmed : defaultLabel;
if (finalName !== itemName) {
if (type === 'folder') {
updateFolderNameInDraft(itemId, finalName);
} else {
updateLinkInDraft(itemId, { name: finalName });
}
updateFolderNameInDraft(itemId, finalName);
}
};
const { Icon, colorKey } = ICON_CONFIG[type];
return (
<CommandMenuPageInfoLayout
icon={
<StyledNavigationMenuItemIconContainer
$backgroundColor={getNavigationMenuItemIconColors(theme)[colorKey]}
$backgroundColor={getNavigationMenuItemIconColors(theme).folder}
>
<Icon
<IconFolder
size={theme.spacing(3.5)}
color={theme.grayScale.gray1}
stroke={theme.icon.stroke.md}
@@ -94,9 +74,7 @@ export const CommandMenuFolderLinkInfo = ({
}
title={
<TitleInput
instanceId={
type === 'folder' ? `folder-name-${itemId}` : `link-label-${itemId}`
}
instanceId={`folder-name-${itemId}`}
sizeVariant="sm"
value={itemName}
onChange={handleChange}
@@ -110,7 +88,6 @@ export const CommandMenuFolderLinkInfo = ({
onFocus={() => setShouldFocusTitleInput(false)}
/>
}
label={type === 'link' ? t`link` : undefined}
/>
);
};
@@ -0,0 +1,94 @@
import { useTheme } from '@emotion/react';
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { IconLink } from 'twenty-ui/display';
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuShouldFocusTitleInputComponentState } from '@/command-menu/states/commandMenuShouldFocusTitleInputComponentState';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
export const CommandMenuLinkInfo = () => {
const theme = useTheme();
const { t } = useLingui();
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
const [shouldFocusTitleInput, setShouldFocusTitleInput] =
useRecoilComponentState(
commandMenuShouldFocusTitleInputComponentState,
commandMenuPageInfo.instanceId,
);
const selectedNavigationMenuItemInEditMode = useRecoilValue(
selectedNavigationMenuItemInEditModeState,
);
const items = useWorkspaceSectionItems();
const { updateLinkInDraft } = useUpdateLinkInDraft();
const defaultLabel = t`Link label`;
const placeholder = t`Link label`;
const selectedItem = selectedNavigationMenuItemInEditMode
? items.find(
(item) =>
item.itemType === NavigationMenuItemType.LINK &&
item.id === selectedNavigationMenuItemInEditMode,
)
: undefined;
if (!selectedItem) return null;
const itemId = selectedItem.id;
const itemName = selectedItem.name ?? defaultLabel;
const handleChange = (text: string) => {
updateLinkInDraft(itemId, { name: text });
};
const handleSave = () => {
const trimmed = itemName.trim();
const finalName = trimmed.length > 0 ? trimmed : defaultLabel;
if (finalName !== itemName) {
updateLinkInDraft(itemId, { name: finalName });
}
};
return (
<CommandMenuPageInfoLayout
icon={
<StyledNavigationMenuItemIconContainer
$backgroundColor={getNavigationMenuItemIconColors(theme).link}
>
<IconLink
size={theme.spacing(3.5)}
color={theme.grayScale.gray1}
stroke={theme.icon.stroke.md}
/>
</StyledNavigationMenuItemIconContainer>
}
title={
<TitleInput
instanceId={`link-label-${itemId}`}
sizeVariant="sm"
value={itemName}
onChange={handleChange}
placeholder={placeholder}
onEnter={handleSave}
onEscape={handleSave}
onClickOutside={handleSave}
onTab={handleSave}
onShiftTab={handleSave}
shouldFocus={shouldFocusTitleInput}
onFocus={() => setShouldFocusTitleInput(false)}
/>
}
label={t`link`}
/>
);
};
@@ -2,27 +2,35 @@ import { useLingui } from '@lingui/react/macro';
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData';
import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon';
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { ViewKey } from '@/views/types/ViewKey';
export const CommandMenuObjectViewRecordInfo = () => {
const { t } = useLingui();
const { processedItem, selectedItemLabel } =
useSelectedNavigationMenuItemEditData();
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const { selectedItemLabel } = useSelectedNavigationMenuItemEditItemLabel();
const processedItem =
selectedItem && selectedItem.itemType !== NavigationMenuItemType.FOLDER
? selectedItem
: undefined;
if (!processedItem || !selectedItemLabel) {
return null;
}
const isViewOrRecord =
processedItem.itemType === 'view' || processedItem.itemType === 'record';
processedItem.itemType === NavigationMenuItemType.VIEW ||
processedItem.itemType === NavigationMenuItemType.RECORD;
if (!isViewOrRecord) {
return null;
}
const label =
processedItem.itemType === 'record'
processedItem.itemType === NavigationMenuItemType.RECORD
? t`record`
: processedItem.viewKey === ViewKey.Index
? t`object`
@@ -3,13 +3,15 @@ import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
import { CommandMenuFolderLinkInfo } from '@/command-menu/components/CommandMenuFolderLinkInfo';
import { CommandMenuFolderInfo } from '@/command-menu/components/CommandMenuFolderInfo';
import { CommandMenuLinkInfo } from '@/command-menu/components/CommandMenuLinkInfo';
import { CommandMenuMultipleRecordsInfo } from '@/command-menu/components/CommandMenuMultipleRecordsInfo';
import { CommandMenuObjectViewRecordInfo } from '@/command-menu/components/CommandMenuObjectViewRecordInfo';
import { CommandMenuPageLayoutInfo } from '@/command-menu/components/CommandMenuPageLayoutInfo';
import { CommandMenuRecordInfo } from '@/command-menu/components/CommandMenuRecordInfo';
import { CommandMenuWorkflowStepInfo } from '@/command-menu/components/CommandMenuWorkflowStepInfo';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
@@ -44,11 +46,18 @@ export const CommandMenuPageInfo = ({ pageChip }: CommandMenuPageInfoProps) => {
if (isNavigationMenuItemEditPage && isDefined(selectedNavItem)) {
const itemType = selectedNavItem.itemType;
if (itemType === 'folder' || itemType === 'link') {
return <CommandMenuFolderLinkInfo type={itemType} />;
if (itemType === NavigationMenuItemType.FOLDER) {
return <CommandMenuFolderInfo />;
}
if (itemType === 'view' || itemType === 'record') {
if (itemType === NavigationMenuItemType.LINK) {
return <CommandMenuLinkInfo />;
}
if (
itemType === NavigationMenuItemType.VIEW ||
itemType === NavigationMenuItemType.RECORD
) {
return <CommandMenuObjectViewRecordInfo />;
}
}
@@ -0,0 +1,8 @@
export enum CommandMenuNavigationItemActions {
MOVE_UP = 'move-up',
MOVE_DOWN = 'move-down',
MOVE_TO_FOLDER = 'move-to-folder',
ADD_BEFORE = 'add-before',
ADD_AFTER = 'add-after',
REMOVE = 'remove',
}
@@ -20,7 +20,7 @@ export const CommandMenuEditFolderPickerSubView = ({
const { t } = useLingui();
const [searchValue, setSearchValue] = useState('');
const { foldersToShow, includeNoFolderOption, handleSelectFolder } =
useFolderPickerSelectionData();
useFolderPickerSelectionData({ onCloseSubView: onBack });
const filteredFolders = filterBySearchQuery({
items: foldersToShow,
@@ -10,6 +10,7 @@ import {
import { CommandGroup } from '@/command-menu/components/CommandGroup';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { CommandMenuNavigationItemActions } from '@/command-menu/pages/navigation-menu-item/command-menu-navigation-item-actions';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
export type OrganizeActionsProps = {
@@ -45,65 +46,77 @@ export const CommandMenuEditOrganizeActions = ({
return (
<CommandGroup heading={t`Organize`}>
<SelectableListItem
itemId="move-up"
itemId={CommandMenuNavigationItemActions.MOVE_UP}
onEnter={canMoveUp ? onMoveUp : undefined}
>
<CommandMenuItem
Icon={IconChevronUp}
label={t`Move up`}
id="move-up"
id={CommandMenuNavigationItemActions.MOVE_UP}
onClick={onMoveUp}
disabled={!canMoveUp}
/>
</SelectableListItem>
<SelectableListItem
itemId="move-down"
itemId={CommandMenuNavigationItemActions.MOVE_DOWN}
onEnter={canMoveDown ? onMoveDown : undefined}
>
<CommandMenuItem
Icon={IconChevronDown}
label={t`Move down`}
id="move-down"
id={CommandMenuNavigationItemActions.MOVE_DOWN}
onClick={onMoveDown}
disabled={!canMoveDown}
/>
</SelectableListItem>
{showMoveToFolder && onMoveToFolder && (
<SelectableListItem itemId="move-to-folder" onEnter={onMoveToFolder}>
<SelectableListItem
itemId={CommandMenuNavigationItemActions.MOVE_TO_FOLDER}
onEnter={onMoveToFolder}
>
<CommandMenuItem
Icon={IconFolderSymlink}
label={t`Move to folder`}
id="move-to-folder"
id={CommandMenuNavigationItemActions.MOVE_TO_FOLDER}
hasSubMenu={moveToFolderHasSubMenu}
onClick={onMoveToFolder}
/>
</SelectableListItem>
)}
{onAddBefore && (
<SelectableListItem itemId="add-before" onEnter={onAddBefore}>
<SelectableListItem
itemId={CommandMenuNavigationItemActions.ADD_BEFORE}
onEnter={onAddBefore}
>
<CommandMenuItem
Icon={IconRowInsertTop}
label={t`Add menu item before`}
id="add-before"
id={CommandMenuNavigationItemActions.ADD_BEFORE}
onClick={onAddBefore}
/>
</SelectableListItem>
)}
{onAddAfter && (
<SelectableListItem itemId="add-after" onEnter={onAddAfter}>
<SelectableListItem
itemId={CommandMenuNavigationItemActions.ADD_AFTER}
onEnter={onAddAfter}
>
<CommandMenuItem
Icon={IconRowInsertBottom}
label={t`Add menu item after`}
id="add-after"
id={CommandMenuNavigationItemActions.ADD_AFTER}
onClick={onAddAfter}
/>
</SelectableListItem>
)}
<SelectableListItem itemId="remove" onEnter={onRemove}>
<SelectableListItem
itemId={CommandMenuNavigationItemActions.REMOVE}
onEnter={onRemove}
>
<CommandMenuItem
Icon={IconTrash}
label={t`Remove from sidebar`}
id="remove"
id={CommandMenuNavigationItemActions.REMOVE}
onClick={onRemove}
/>
</SelectableListItem>
@@ -4,8 +4,8 @@ import { IconApps } from 'twenty-ui/display';
import { CommandGroup } from '@/command-menu/components/CommandGroup';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { useFindOneApplicationQuery } from '~/generated-metadata/graphql';
@@ -18,8 +18,8 @@ export const CommandMenuEditOwnerSection = ({
}: CommandMenuEditOwnerSectionProps) => {
const { t } = useLingui();
const { selectedItem } = useSelectedNavigationMenuItemEditData();
const { currentDraft } = useNavigationMenuItemEditFolderData();
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const { currentDraft } = useDraftNavigationMenuItems();
const applicationIdFromDraft =
selectedItem && currentDraft
@@ -5,14 +5,16 @@ import { CommandMenuEditObjectViewBase } from '@/command-menu/pages/navigation-m
import { CommandMenuEditOrganizeActions } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuEditOrganizeActions';
import { CommandMenuEditOwnerSection } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuEditOwnerSection';
import { useNavigationMenuItemEditOrganizeActions } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditOrganizeActions';
import { useNavigationMenuItemEditSubView } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditSubView';
import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData';
import { getOrganizeActionsSelectableItemIds } from '@/command-menu/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { useSelectedNavigationMenuItemEditItemLabel } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemLabel';
import { useSelectedNavigationMenuItemEditItemObjectMetadata } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata';
import { useUpdateLinkInDraft } from '@/navigation-menu-item/hooks/useUpdateLinkInDraft';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -31,15 +33,14 @@ export const CommandMenuNavigationMenuItemEditPage = () => {
const selectedNavigationMenuItemInEditMode = useRecoilValue(
selectedNavigationMenuItemInEditModeState,
);
const {
selectedItemLabel,
selectedItem,
selectedItemObjectMetadata,
selectedItemType,
} = useSelectedNavigationMenuItemEditData();
const { selectedItemLabel } = useSelectedNavigationMenuItemEditItemLabel();
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const { selectedItemObjectMetadata } =
useSelectedNavigationMenuItemEditItemObjectMetadata();
const selectedItemType = selectedItem?.itemType ?? null;
const { editSubView, setFolderPicker, clearSubView } =
useNavigationMenuItemEditSubView();
const [isFolderPickerOpen, setIsFolderPickerOpen] = useState(false);
const setFolderPicker = () => setIsFolderPickerOpen(true);
const {
canMoveUp,
@@ -63,64 +64,20 @@ export const CommandMenuNavigationMenuItemEditPage = () => {
);
}
if (editSubView === 'folder-picker') {
return <CommandMenuEditFolderPickerSubView onBack={clearSubView} />;
}
if (
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.VIEW &&
!selectedItemObjectMetadata
) {
return null;
}
if (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.VIEW) {
if (isFolderPickerOpen) {
return (
<CommandMenuEditObjectViewBase
onOpenFolderPicker={setFolderPicker}
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
<CommandMenuEditFolderPickerSubView
onBack={() => setIsFolderPickerOpen(false)}
/>
);
}
if (
isDefined(selectedItem) &&
selectedItem.itemType === NAVIGATION_MENU_ITEM_TYPE.LINK
) {
return (
<CommandMenuEditLinkItemView
key={selectedItem.id}
selectedItem={selectedItem}
onUpdateLink={(linkId, link) => updateLinkInDraft(linkId, { link })}
onOpenFolderPicker={setFolderPicker}
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
/>
);
}
if (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.LINK) {
return null;
}
if (selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER) {
return (
<CommandMenuList
commandGroups={[]}
selectableItemIds={getOrganizeActionsSelectableItemIds(false)}
>
<CommandMenuEditOrganizeActions
switch (selectedItemType) {
case NavigationMenuItemType.VIEW:
if (!selectedItemObjectMetadata) return null;
return (
<CommandMenuEditObjectViewBase
onOpenFolderPicker={setFolderPicker}
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
@@ -129,27 +86,65 @@ export const CommandMenuNavigationMenuItemEditPage = () => {
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
/>
<CommandMenuEditOwnerSection />
</CommandMenuList>
);
);
case NavigationMenuItemType.LINK:
if (
isDefined(selectedItem) &&
selectedItem.itemType === NavigationMenuItemType.LINK
) {
return (
<CommandMenuEditLinkItemView
key={selectedItem.id}
selectedItem={selectedItem}
onUpdateLink={(linkId, link) => updateLinkInDraft(linkId, { link })}
onOpenFolderPicker={setFolderPicker}
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
/>
);
}
return null;
case NavigationMenuItemType.FOLDER:
return (
<CommandMenuList
commandGroups={[]}
selectableItemIds={getOrganizeActionsSelectableItemIds(false)}
>
<CommandMenuEditOrganizeActions
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
/>
<CommandMenuEditOwnerSection />
</CommandMenuList>
);
default:
return (
<CommandMenuList
commandGroups={[]}
selectableItemIds={getOrganizeActionsSelectableItemIds(true)}
>
<CommandMenuEditOrganizeActions
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
showMoveToFolder
onMoveToFolder={setFolderPicker}
/>
</CommandMenuList>
);
}
return (
<CommandMenuList
commandGroups={[]}
selectableItemIds={getOrganizeActionsSelectableItemIds(true)}
>
<CommandMenuEditOrganizeActions
canMoveUp={canMoveUp}
canMoveDown={canMoveDown}
onMoveUp={onMoveUp}
onMoveDown={onMoveDown}
onRemove={onRemove}
onAddBefore={onAddBefore}
onAddAfter={onAddAfter}
showMoveToFolder
onMoveToFolder={setFolderPicker}
/>
</CommandMenuList>
);
};
@@ -13,24 +13,25 @@ import { CommandMenuAddToNavDroppable } from '@/command-menu/components/CommandM
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { CommandMenuItemWithAddToNavigationDrag } from '@/command-menu/components/CommandMenuItemWithAddToNavigationDrag';
import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
import { useAddFolderToNavigationMenu } from '@/command-menu/pages/navigation-menu-item/hooks/useAddFolderToNavigationMenu';
import { useAddLinkToNavigationMenu } from '@/command-menu/pages/navigation-menu-item/hooks/useAddLinkToNavigationMenu';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
type CommandMenuNewSidebarItemMainMenuProps = {
onSelectObject: () => void;
onSelectView: () => void;
onSelectRecord: () => void;
onAddFolder: () => void;
onAddLink: () => void;
};
export const CommandMenuNewSidebarItemMainMenu = ({
onSelectObject,
onSelectView,
onSelectRecord,
onAddFolder,
onAddLink,
}: CommandMenuNewSidebarItemMainMenuProps) => {
const { t } = useLingui();
const { handleAddFolder } = useAddFolderToNavigationMenu();
const { handleAddLink } = useAddLinkToNavigationMenu();
return (
<CommandMenuAddToNavDroppable>
@@ -77,29 +78,29 @@ export const CommandMenuNewSidebarItemMainMenu = ({
</CommandMenuAddToNavDraggablePlaceholder>
</CommandGroup>
<CommandGroup heading={t`Other`}>
<SelectableListItem itemId="folder" onEnter={onAddFolder}>
<SelectableListItem itemId="folder" onEnter={handleAddFolder}>
<CommandMenuItemWithAddToNavigationDrag
icon={IconFolder}
label={t`Folder`}
id="folder"
onClick={onAddFolder}
onClick={handleAddFolder}
dragIndex={3}
payload={{
type: 'folder',
type: NavigationMenuItemType.FOLDER,
folderId: 'new',
name: t`New folder`,
}}
/>
</SelectableListItem>
<SelectableListItem itemId="link" onEnter={onAddLink}>
<SelectableListItem itemId="link" onEnter={handleAddLink}>
<CommandMenuItemWithAddToNavigationDrag
icon={IconLink}
label={t`Link`}
id="link"
onClick={onAddLink}
onClick={handleAddLink}
dragIndex={4}
payload={{
type: 'link',
type: NavigationMenuItemType.LINK,
linkId: 'new',
name: t`Link label`,
link: 'https://www.example.com',
@@ -0,0 +1,106 @@
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuObjectPickerSubView';
import { CommandMenuSystemObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuSystemObjectPickerSubView';
import { getAvailableObjectMetadataForNewSidebarItem } from '@/command-menu/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem';
import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ViewKey } from '@/views/types/ViewKey';
type CommandMenuNewSidebarItemObjectFlowProps = {
onBack: () => void;
};
export const CommandMenuNewSidebarItemObjectFlow = ({
onBack,
}: CommandMenuNewSidebarItemObjectFlowProps) => {
const { t } = useLingui();
const { closeCommandMenu } = useCommandMenu();
const [objectSearchInput, setObjectSearchInput] = useState('');
const [systemObjectSearchInput, setSystemObjectSearchInput] = useState('');
const [isInSystemPicker, setIsInSystemPicker] = useState(false);
const { currentDraft } = useDraftNavigationMenuItems();
const { objectMetadataItems } = useObjectMetadataItems();
const { addObjectToDraft } = useAddObjectToNavigationMenuDraft();
const { activeNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
const setAddMenuItemInsertionContext = useSetRecoilState(
addMenuItemInsertionContextState,
);
const { views, objectMetadataIdsWithIndexView } =
useNavigationMenuObjectMetadataFromDraft(currentDraft);
const objectMetadataIdsWithDisplayableViews = new Set(
views
.filter((view) => view.key !== ViewKey.Index)
.map((view) => view.objectMetadataId),
);
const { availableObjectMetadataItems, availableSystemObjectMetadataItems } =
getAvailableObjectMetadataForNewSidebarItem({
objectMetadataItems,
activeNonSystemObjectMetadataItems,
objectMetadataIdsWithIndexView,
objectMetadataIdsWithDisplayableViews,
});
const handleSelectObject = (
objectMetadataItem: ObjectMetadataItem,
defaultViewId: string,
) => {
addObjectToDraft(
objectMetadataItem.id,
defaultViewId,
currentDraft,
addMenuItemInsertionContext?.targetFolderId,
addMenuItemInsertionContext?.targetIndex,
);
setAddMenuItemInsertionContext(null);
closeCommandMenu();
};
const handleBackToObjectList = () => {
setIsInSystemPicker(false);
setSystemObjectSearchInput('');
};
if (isInSystemPicker) {
return (
<CommandMenuSystemObjectPickerSubView
systemObjects={availableSystemObjectMetadataItems}
searchValue={systemObjectSearchInput}
onSearchChange={setSystemObjectSearchInput}
onBack={handleBackToObjectList}
isViewItem={false}
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
emptyNoResultsText={t`All system objects are already in the sidebar`}
/>
);
}
return (
<CommandMenuObjectPickerSubView
objects={availableObjectMetadataItems}
searchValue={objectSearchInput}
onSearchChange={setObjectSearchInput}
onBack={onBack}
onOpenSystemPicker={() => setIsInSystemPicker(true)}
isViewItem={false}
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
/>
);
};
@@ -1,252 +1,24 @@
import { useLingui } from '@lingui/react/macro';
import { useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconFolder, IconLink } from 'twenty-ui/display';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { CommandMenuNewSidebarItemMainMenu } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu';
import { CommandMenuNewSidebarItemObjectFlow } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemObjectFlow';
import { CommandMenuNewSidebarItemRecordSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordSubView';
import { CommandMenuNewSidebarItemViewObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewObjectPickerSubView';
import { CommandMenuNewSidebarItemViewPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView';
import { CommandMenuNewSidebarItemViewSystemSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewSystemSubView';
import { CommandMenuObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuObjectPickerSubView';
import { CommandMenuSystemObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuSystemObjectPickerSubView';
import { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft';
import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft';
import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ViewKey } from '@/views/types/ViewKey';
import { CommandMenuNewSidebarItemViewFlow } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewFlow';
type SelectedOption =
| 'object'
| 'record'
| 'system'
| 'view'
| 'view-system'
| null;
type SelectedOption = 'object' | 'record' | 'view' | null;
export const CommandMenuNewSidebarItemPage = () => {
const { t } = useLingui();
const { closeCommandMenu } = useCommandMenu();
const [selectedOption, setSelectedOption] = useState<SelectedOption>(null);
const [selectedObjectMetadataIdForView, setSelectedObjectMetadataIdForView] =
useState<string | null>(null);
const [objectSearchInput, setObjectSearchInput] = useState('');
const [systemObjectSearchInput, setSystemObjectSearchInput] = useState('');
const { objectMetadataItems } = useObjectMetadataItems();
const { addObjectToDraft } = useAddObjectToNavigationMenuDraft();
const { addFolderToDraft } = useAddFolderToNavigationMenuDraft();
const { addLinkToDraft } = useAddLinkToNavigationMenuDraft();
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const navigationMenuItemsDraft = useRecoilValue(
navigationMenuItemsDraftState,
);
const setSelectedNavigationMenuItemInEditMode = useSetRecoilState(
selectedNavigationMenuItemInEditModeState,
);
const { openNavigationMenuItemInCommandMenu } =
useOpenNavigationMenuItemInCommandMenu();
const { activeNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
const currentDraft = isDefined(navigationMenuItemsDraft)
? navigationMenuItemsDraft
: workspaceNavigationMenuItems;
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
const setAddMenuItemInsertionContext = useSetRecoilState(
addMenuItemInsertionContextState,
);
const { views, objectMetadataIdsWithIndexView } =
useNavigationMenuObjectMetadataFromDraft(currentDraft);
const objectMetadataIdsWithDisplayableViews = new Set(
views
.filter((view) => view.key !== ViewKey.Index)
.map((view) => view.objectMetadataId),
);
const availableObjectMetadataItems = [
...activeNonSystemObjectMetadataItems,
].sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const activeSystemObjectMetadataItems = objectMetadataItems
.filter((item) => item.isActive && item.isSystem)
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const availableSystemObjectMetadataItems =
activeSystemObjectMetadataItems.filter((item) =>
objectMetadataIdsWithIndexView.has(item.id),
);
const objectMetadataItemsWithViews = objectMetadataItems
.filter(
(item) =>
item.isActive && objectMetadataIdsWithDisplayableViews.has(item.id),
)
.filter((item) => !item.isSystem)
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const availableSystemObjectMetadataItemsForView =
activeSystemObjectMetadataItems.filter((item) =>
objectMetadataIdsWithDisplayableViews.has(item.id),
);
const handleSelectObject = (
objectMetadataItem: ObjectMetadataItem,
defaultViewId: string,
) => {
addObjectToDraft(
objectMetadataItem.id,
defaultViewId,
currentDraft,
addMenuItemInsertionContext?.targetFolderId,
addMenuItemInsertionContext?.targetIndex,
);
setAddMenuItemInsertionContext(null);
closeCommandMenu();
};
const handleBackToMain = () => {
setSelectedOption(null);
setSelectedObjectMetadataIdForView(null);
setObjectSearchInput('');
setSystemObjectSearchInput('');
};
const handleBackToObjectList = () => {
setSelectedOption('object');
setSystemObjectSearchInput('');
};
const handleBackToViewObjectList = () => {
const selectedObjectMetadataItem = isDefined(
selectedObjectMetadataIdForView,
)
? objectMetadataItems.find(
(item) => item.id === selectedObjectMetadataIdForView,
)
: undefined;
const cameFromSystemObjects = selectedObjectMetadataItem?.isSystem ?? false;
setSelectedObjectMetadataIdForView(null);
if (cameFromSystemObjects) {
setSelectedOption('view-system');
}
};
const handleBackToViewObjectListFromSystem = () => {
setSelectedOption('view');
setSystemObjectSearchInput('');
};
const handleAddFolderAndOpenEdit = () => {
const newFolderId = addFolderToDraft(
t`New folder`,
currentDraft,
addMenuItemInsertionContext?.targetFolderId ?? null,
addMenuItemInsertionContext?.targetIndex,
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(newFolderId);
openNavigationMenuItemInCommandMenu({
pageTitle: t`Edit folder`,
pageIcon: IconFolder,
focusTitleInput: true,
});
};
const handleAddLinkAndOpenEdit = () => {
const newLinkId = addLinkToDraft(
t`Link label`,
'www.example.com',
currentDraft,
addMenuItemInsertionContext?.targetFolderId ?? null,
addMenuItemInsertionContext?.targetIndex,
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(newLinkId);
openNavigationMenuItemInCommandMenu({
pageTitle: t`Edit link`,
pageIcon: IconLink,
focusTitleInput: true,
});
};
switch (selectedOption) {
case 'view':
if (isDefined(selectedObjectMetadataIdForView)) {
return (
<CommandMenuNewSidebarItemViewPickerSubView
selectedObjectMetadataIdForView={selectedObjectMetadataIdForView}
onBack={handleBackToViewObjectList}
/>
);
}
return (
<CommandMenuNewSidebarItemViewObjectPickerSubView
objects={objectMetadataItemsWithViews}
searchValue={objectSearchInput}
onSearchChange={setObjectSearchInput}
onBack={handleBackToMain}
onOpenSystemPicker={() => setSelectedOption('view-system')}
onSelectObject={(item) => setSelectedObjectMetadataIdForView(item.id)}
showSystemObjectsOption={
availableSystemObjectMetadataItemsForView.length > 0
}
/>
);
case 'view-system':
return (
<CommandMenuNewSidebarItemViewSystemSubView
systemObjects={availableSystemObjectMetadataItemsForView}
searchValue={systemObjectSearchInput}
onSearchChange={setSystemObjectSearchInput}
onBack={handleBackToViewObjectListFromSystem}
onSelectObject={(item) => {
setSelectedObjectMetadataIdForView(item.id);
setSelectedOption('view');
}}
/>
);
return <CommandMenuNewSidebarItemViewFlow onBack={handleBackToMain} />;
case 'object':
return (
<CommandMenuObjectPickerSubView
objects={availableObjectMetadataItems}
searchValue={objectSearchInput}
onSearchChange={setObjectSearchInput}
onBack={handleBackToMain}
onOpenSystemPicker={() => setSelectedOption('system')}
isViewItem={false}
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
/>
);
case 'system':
return (
<CommandMenuSystemObjectPickerSubView
systemObjects={availableSystemObjectMetadataItems}
searchValue={systemObjectSearchInput}
onSearchChange={setSystemObjectSearchInput}
onBack={handleBackToObjectList}
isViewItem={false}
onChangeObject={handleSelectObject}
objectMenuItemVariant="add"
emptyNoResultsText={t`All system objects are already in the sidebar`}
/>
);
return <CommandMenuNewSidebarItemObjectFlow onBack={handleBackToMain} />;
case 'record':
return (
<CommandMenuNewSidebarItemRecordSubView onBack={handleBackToMain} />
@@ -254,11 +26,9 @@ export const CommandMenuNewSidebarItemPage = () => {
default:
return (
<CommandMenuNewSidebarItemMainMenu
onSelectRecord={() => setSelectedOption('record')}
onSelectObject={() => setSelectedOption('object')}
onSelectView={() => setSelectedOption('view')}
onSelectRecord={() => setSelectedOption('record')}
onAddFolder={handleAddFolderAndOpenEdit}
onAddLink={handleAddLinkAndOpenEdit}
/>
);
}
@@ -3,7 +3,8 @@ import { Avatar } from 'twenty-ui/display';
import { CommandMenuItemWithAddToNavigationDrag } from '@/command-menu/components/CommandMenuItemWithAddToNavigationDrag';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useAddRecordToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddRecordToNavigationMenuDraft';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload';
@@ -29,7 +30,7 @@ export const CommandMenuNewSidebarItemRecordItem = ({
}: CommandMenuNewSidebarItemRecordItemProps) => {
const { closeCommandMenu } = useCommandMenu();
const { addRecordToDraft } = useAddRecordToNavigationMenuDraft();
const { currentDraft } = useNavigationMenuItemEditFolderData();
const { currentDraft } = useDraftNavigationMenuItems();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
@@ -41,7 +42,7 @@ export const CommandMenuNewSidebarItemRecordItem = ({
(item) => item.nameSingular === record.objectNameSingular,
);
const recordPayload: AddToNavigationDragPayload = {
type: 'record',
type: NavigationMenuItemType.RECORD,
recordId: record.recordId,
objectMetadataId: objectMetadataItem?.id ?? '',
objectNameSingular: record.objectNameSingular,
@@ -9,7 +9,7 @@ import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
import { CommandMenuSubViewWithSearch } from '@/command-menu/components/CommandMenuSubViewWithSearch';
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
import { CommandMenuNewSidebarItemRecordItem } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemRecordItem';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
@@ -31,7 +31,7 @@ export const CommandMenuNewSidebarItemRecordSubView = ({
onBack,
}: CommandMenuNewSidebarItemRecordSubViewProps) => {
const { t } = useLingui();
const { currentDraft } = useNavigationMenuItemEditFolderData();
const { currentDraft } = useDraftNavigationMenuItems();
const { objectMetadataItems } = useObjectMetadataItems();
const [recordSearchInput, setRecordSearchInput] = useState('');
const [deferredRecordSearchInput] = useDebounce(recordSearchInput, 300);
@@ -0,0 +1,111 @@
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { CommandMenuNewSidebarItemViewObjectPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewObjectPickerSubView';
import { CommandMenuNewSidebarItemViewPickerSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewPickerSubView';
import { CommandMenuNewSidebarItemViewSystemSubView } from '@/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemViewSystemSubView';
import { getAvailableObjectMetadataForNewSidebarItem } from '@/command-menu/pages/navigation-menu-item/utils/getAvailableObjectMetadataForNewSidebarItem';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { ViewKey } from '@/views/types/ViewKey';
type CommandMenuNewSidebarItemViewFlowProps = {
onBack: () => void;
};
export const CommandMenuNewSidebarItemViewFlow = ({
onBack,
}: CommandMenuNewSidebarItemViewFlowProps) => {
const [selectedObjectMetadataIdForView, setSelectedObjectMetadataIdForView] =
useState<string | null>(null);
const [objectSearchInput, setObjectSearchInput] = useState('');
const [systemObjectSearchInput, setSystemObjectSearchInput] = useState('');
const [isInSystemPicker, setIsInSystemPicker] = useState(false);
const { currentDraft } = useDraftNavigationMenuItems();
const { objectMetadataItems } = useObjectMetadataItems();
const { activeNonSystemObjectMetadataItems } =
useFilteredObjectMetadataItems();
const { views, objectMetadataIdsWithIndexView } =
useNavigationMenuObjectMetadataFromDraft(currentDraft);
const objectMetadataIdsWithDisplayableViews = new Set(
views
.filter((view) => view.key !== ViewKey.Index)
.map((view) => view.objectMetadataId),
);
const {
objectMetadataItemsWithViews,
availableSystemObjectMetadataItemsForView,
} = getAvailableObjectMetadataForNewSidebarItem({
objectMetadataItems,
activeNonSystemObjectMetadataItems,
objectMetadataIdsWithIndexView,
objectMetadataIdsWithDisplayableViews,
});
const handleBackFromViewPicker = () => {
const selectedObjectMetadataItem = isDefined(
selectedObjectMetadataIdForView,
)
? objectMetadataItems.find(
(item) => item.id === selectedObjectMetadataIdForView,
)
: undefined;
const cameFromSystemObjects = selectedObjectMetadataItem?.isSystem ?? false;
setSelectedObjectMetadataIdForView(null);
setIsInSystemPicker(cameFromSystemObjects);
};
const handleBackFromSystemPicker = () => {
setIsInSystemPicker(false);
};
const handleSelectObject = (objectId: string) => {
setSelectedObjectMetadataIdForView(objectId);
};
const handleSelectObjectFromSystem = (objectId: string) => {
setSelectedObjectMetadataIdForView(objectId);
setIsInSystemPicker(false);
};
if (isDefined(selectedObjectMetadataIdForView) && !isInSystemPicker) {
return (
<CommandMenuNewSidebarItemViewPickerSubView
selectedObjectMetadataIdForView={selectedObjectMetadataIdForView}
onBack={handleBackFromViewPicker}
/>
);
}
if (isInSystemPicker) {
return (
<CommandMenuNewSidebarItemViewSystemSubView
systemObjects={availableSystemObjectMetadataItemsForView}
searchValue={systemObjectSearchInput}
onSearchChange={setSystemObjectSearchInput}
onBack={handleBackFromSystemPicker}
onSelectObject={(item) => handleSelectObjectFromSystem(item.id)}
/>
);
}
return (
<CommandMenuNewSidebarItemViewObjectPickerSubView
objects={objectMetadataItemsWithViews}
searchValue={objectSearchInput}
onSearchChange={setObjectSearchInput}
onBack={onBack}
onOpenSystemPicker={() => setIsInSystemPicker(true)}
onSelectObject={(item) => handleSelectObject(item.id)}
showSystemObjectsOption={
availableSystemObjectMetadataItemsForView.length > 0
}
/>
);
};
@@ -10,7 +10,8 @@ import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
import { CommandMenuSubViewWithSearch } from '@/command-menu/components/CommandMenuSubViewWithSearch';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useFilteredPickerItems } from '@/command-menu/hooks/useFilteredPickerItems';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useAddViewToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft';
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
@@ -33,7 +34,7 @@ export const CommandMenuNewSidebarItemViewPickerSubView = ({
const [searchValue, setSearchValue] = useState('');
const { closeCommandMenu } = useCommandMenu();
const { addViewToDraft } = useAddViewToNavigationMenuDraft();
const { currentDraft } = useNavigationMenuItemEditFolderData();
const { currentDraft } = useDraftNavigationMenuItems();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
@@ -114,7 +115,7 @@ export const CommandMenuNewSidebarItemViewPickerSubView = ({
onClick={() => handleSelectView(view)}
dragIndex={index}
payload={{
type: 'view',
type: NavigationMenuItemType.VIEW,
viewId: view.id,
label: view.name,
}}
@@ -6,6 +6,7 @@ import { useIcons } from 'twenty-ui/display';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { CommandMenuItemWithAddToNavigationDrag } from '@/command-menu/components/CommandMenuItemWithAddToNavigationDrag';
import { IconWithBackground } from '@/navigation-menu-item/components/IconWithBackground';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -55,7 +56,7 @@ export const CommandMenuObjectMenuItem = ({
onClick={handleClick}
dragIndex={dragIndex}
payload={{
type: 'object' as const,
type: NavigationMenuItemType.OBJECT,
objectMetadataId: objectMetadataItem.id,
defaultViewId: defaultViewId ?? '',
label: objectMetadataItem.labelPlural,
@@ -0,0 +1,57 @@
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconFolder } from 'twenty-ui/display';
import { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
export const useAddFolderToNavigationMenu = () => {
const { t } = useLingui();
const { addFolderToDraft } = useAddFolderToNavigationMenuDraft();
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const navigationMenuItemsDraft = useRecoilValue(
navigationMenuItemsDraftState,
);
const setSelectedNavigationMenuItemInEditMode = useSetRecoilState(
selectedNavigationMenuItemInEditModeState,
);
const { openNavigationMenuItemInCommandMenu } =
useOpenNavigationMenuItemInCommandMenu();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
const setAddMenuItemInsertionContext = useSetRecoilState(
addMenuItemInsertionContextState,
);
const currentDraft = isDefined(navigationMenuItemsDraft)
? navigationMenuItemsDraft
: workspaceNavigationMenuItems;
const handleAddFolder = () => {
const targetFolderId = addMenuItemInsertionContext?.targetFolderId ?? null;
const targetIndex = addMenuItemInsertionContext?.targetIndex;
const itemId = addFolderToDraft(
t`New folder`,
currentDraft,
targetFolderId,
targetIndex,
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(itemId);
openNavigationMenuItemInCommandMenu({
pageTitle: t`Edit folder`,
pageIcon: IconFolder,
focusTitleInput: true,
});
};
return { handleAddFolder };
};
@@ -0,0 +1,58 @@
import { useLingui } from '@lingui/react/macro';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconLink } from 'twenty-ui/display';
import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu';
import { addMenuItemInsertionContextState } from '@/navigation-menu-item/states/addMenuItemInsertionContextState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
export const useAddLinkToNavigationMenu = () => {
const { t } = useLingui();
const { addLinkToDraft } = useAddLinkToNavigationMenuDraft();
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const navigationMenuItemsDraft = useRecoilValue(
navigationMenuItemsDraftState,
);
const setSelectedNavigationMenuItemInEditMode = useSetRecoilState(
selectedNavigationMenuItemInEditModeState,
);
const { openNavigationMenuItemInCommandMenu } =
useOpenNavigationMenuItemInCommandMenu();
const addMenuItemInsertionContext = useRecoilValue(
addMenuItemInsertionContextState,
);
const setAddMenuItemInsertionContext = useSetRecoilState(
addMenuItemInsertionContextState,
);
const currentDraft = isDefined(navigationMenuItemsDraft)
? navigationMenuItemsDraft
: workspaceNavigationMenuItems;
const handleAddLink = () => {
const targetFolderId = addMenuItemInsertionContext?.targetFolderId ?? null;
const targetIndex = addMenuItemInsertionContext?.targetIndex;
const itemId = addLinkToDraft(
t`Link label`,
'www.example.com',
currentDraft,
targetFolderId,
targetIndex,
);
setAddMenuItemInsertionContext(null);
setSelectedNavigationMenuItemInEditMode(itemId);
openNavigationMenuItemInCommandMenu({
pageTitle: t`Edit link`,
pageIcon: IconLink,
focusTitleInput: true,
});
};
return { handleAddLink };
};
@@ -2,12 +2,12 @@ import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useNavigationMenuItemEditFolderData } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditFolderData';
import { useNavigationMenuItemEditSubView } from '@/command-menu/pages/navigation-menu-item/hooks/useNavigationMenuItemEditSubView';
import { useSelectedNavigationMenuItemEditData } from '@/command-menu/pages/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditData';
import { useDraftNavigationMenuItemsAllFolders } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItemsAllFolders';
import { useDraftNavigationMenuItemsWorkspaceFolders } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItemsWorkspaceFolders';
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { useNavigationMenuItemMoveRemove } from '@/navigation-menu-item/hooks/useNavigationMenuItemMoveRemove';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
type FolderOption = {
id: string;
@@ -38,42 +38,47 @@ const excludeCurrentFolder = <T extends { id: string }>(
? folders
: folders.filter((folder) => folder.id !== currentFolderId);
export const useFolderPickerSelectionData = () => {
type UseFolderPickerSelectionDataParams = {
onCloseSubView: () => void;
};
export const useFolderPickerSelectionData = ({
onCloseSubView,
}: UseFolderPickerSelectionDataParams) => {
const { closeCommandMenu } = useCommandMenu();
const { clearSubView } = useNavigationMenuItemEditSubView();
const { moveToFolder } = useNavigationMenuItemMoveRemove();
const selectedNavigationMenuItemInEditMode = useRecoilValue(
selectedNavigationMenuItemInEditModeState,
);
const { selectedItem, selectedItemType } =
useSelectedNavigationMenuItemEditData();
const { allFolders, workspaceFolders } =
useNavigationMenuItemEditFolderData();
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const selectedItemType = selectedItem?.itemType ?? null;
const { allFolders } = useDraftNavigationMenuItemsAllFolders();
const { workspaceFolders } = useDraftNavigationMenuItemsWorkspaceFolders();
const selectedFolderId =
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER
selectedItemType === NavigationMenuItemType.FOLDER
? selectedNavigationMenuItemInEditMode
: null;
const currentFolderId =
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER
selectedItemType === NavigationMenuItemType.FOLDER
? (selectedItem?.id ?? null)
: (selectedItem?.folderId ?? null);
const descendantFolderIds =
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER &&
selectedItemType === NavigationMenuItemType.FOLDER &&
isDefined(selectedFolderId)
? getDescendantFolderIds(selectedFolderId, allFolders)
: new Set<string>();
const includeNoFolderOption =
(selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER &&
(selectedItemType === NavigationMenuItemType.FOLDER &&
isDefined(selectedFolderId)) ||
(selectedItemType === NAVIGATION_MENU_ITEM_TYPE.LINK &&
(selectedItemType === NavigationMenuItemType.LINK &&
isDefined(currentFolderId));
const folders =
includeNoFolderOption &&
selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER &&
selectedItemType === NavigationMenuItemType.FOLDER &&
isDefined(selectedFolderId)
? allFolders.filter(
(folder) =>
@@ -89,7 +94,7 @@ export const useFolderPickerSelectionData = () => {
const handleSelectFolder = (folderId: string | null) => {
if (isDefined(selectedNavigationMenuItemInEditMode)) {
moveToFolder(selectedNavigationMenuItemInEditMode, folderId);
clearSubView();
onCloseSubView();
closeCommandMenu();
}
};
@@ -1,37 +0,0 @@
import { useRecoilValue } from 'recoil';
import { useNavigationMenuItemsByFolder } from '@/navigation-menu-item/hooks/useNavigationMenuItemsByFolder';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
export const useNavigationMenuItemEditFolderData = () => {
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const navigationMenuItemsDraft = useRecoilValue(
navigationMenuItemsDraftState,
);
const { workspaceNavigationMenuItemsByFolder } =
useNavigationMenuItemsByFolder();
const currentDraft = navigationMenuItemsDraft ?? workspaceNavigationMenuItems;
const workspaceFolders = workspaceNavigationMenuItemsByFolder.map(
(folder) => ({
id: folder.id,
name: folder.folderName,
}),
);
const allFolders =
currentDraft?.filter(isNavigationMenuItemFolder).map((item) => ({
id: item.id,
name: item.name ?? 'Folder',
folderId: item.folderId ?? undefined,
})) ?? [];
return {
allFolders,
workspaceFolders,
currentDraft,
};
};
@@ -1,16 +0,0 @@
import { useState } from 'react';
export type EditSubView = 'folder-picker' | null;
export const useNavigationMenuItemEditSubView = () => {
const [editSubView, setEditSubView] = useState<EditSubView>(null);
const setFolderPicker = () => setEditSubView('folder-picker');
const clearSubView = () => setEditSubView(null);
return {
editSubView,
setFolderPicker,
clearSubView,
};
};
@@ -1,52 +0,0 @@
import { useRecoilValue } from 'recoil';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { coreViewsState } from '@/views/states/coreViewState';
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
export const useSelectedNavigationMenuItemEditData = () => {
const selectedNavigationMenuItemInEditMode = useRecoilValue(
selectedNavigationMenuItemInEditModeState,
);
const items = useWorkspaceSectionItems();
const coreViews = useRecoilValue(coreViewsState);
const views = coreViews.map(convertCoreViewToView);
const { objectMetadataItems } = useObjectMetadataItems();
const selectedItem = selectedNavigationMenuItemInEditMode
? items.find((item) => item.id === selectedNavigationMenuItemInEditMode)
: undefined;
const selectedItemType = selectedItem?.itemType ?? null;
const selectedItemObjectMetadata = selectedItem
? getObjectMetadataForNavigationMenuItem(
selectedItem,
objectMetadataItems,
views,
)
: null;
const selectedItemLabel = selectedItem
? selectedItemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER
? (selectedItem.name ?? 'Folder')
: selectedItemType === NAVIGATION_MENU_ITEM_TYPE.LINK
? (selectedItem.name ?? 'Link')
: (selectedItemObjectMetadata?.labelPlural ?? '')
: null;
const processedItem =
selectedItem && selectedItem.itemType !== NAVIGATION_MENU_ITEM_TYPE.FOLDER
? selectedItem
: undefined;
return {
selectedItem,
selectedItemType,
selectedItemObjectMetadata,
selectedItemLabel,
processedItem,
};
};
@@ -0,0 +1,55 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
type GetAvailableObjectMetadataForNewSidebarItemProps = {
objectMetadataItems: ObjectMetadataItem[];
activeNonSystemObjectMetadataItems: ObjectMetadataItem[];
objectMetadataIdsWithIndexView: Set<string>;
objectMetadataIdsWithDisplayableViews: Set<string>;
};
type GetAvailableObjectMetadataForNewSidebarItemResult = {
availableObjectMetadataItems: ObjectMetadataItem[];
availableSystemObjectMetadataItems: ObjectMetadataItem[];
objectMetadataItemsWithViews: ObjectMetadataItem[];
availableSystemObjectMetadataItemsForView: ObjectMetadataItem[];
};
export const getAvailableObjectMetadataForNewSidebarItem = ({
objectMetadataItems,
activeNonSystemObjectMetadataItems,
objectMetadataIdsWithIndexView,
objectMetadataIdsWithDisplayableViews,
}: GetAvailableObjectMetadataForNewSidebarItemProps): GetAvailableObjectMetadataForNewSidebarItemResult => {
const availableObjectMetadataItems = [
...activeNonSystemObjectMetadataItems,
].sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const activeSystemObjectMetadataItems = objectMetadataItems
.filter((item) => item.isActive && item.isSystem)
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const availableSystemObjectMetadataItems =
activeSystemObjectMetadataItems.filter((item) =>
objectMetadataIdsWithIndexView.has(item.id),
);
const objectMetadataItemsWithViews = objectMetadataItems
.filter(
(item) =>
item.isActive && objectMetadataIdsWithDisplayableViews.has(item.id),
)
.filter((item) => !item.isSystem)
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
const availableSystemObjectMetadataItemsForView =
activeSystemObjectMetadataItems.filter((item) =>
objectMetadataIdsWithDisplayableViews.has(item.id),
);
return {
availableObjectMetadataItems,
availableSystemObjectMetadataItems,
objectMetadataItemsWithViews,
availableSystemObjectMetadataItemsForView,
};
};
@@ -1,10 +1,27 @@
import { CommandMenuNavigationItemActions } from '@/command-menu/pages/navigation-menu-item/command-menu-navigation-item-actions';
const ORGANIZE_ACTIONS_WITHOUT_MOVE_TO_FOLDER: CommandMenuNavigationItemActions[] =
[
CommandMenuNavigationItemActions.MOVE_UP,
CommandMenuNavigationItemActions.MOVE_DOWN,
CommandMenuNavigationItemActions.ADD_BEFORE,
CommandMenuNavigationItemActions.ADD_AFTER,
CommandMenuNavigationItemActions.REMOVE,
];
const ORGANIZE_ACTIONS_WITH_MOVE_TO_FOLDER: CommandMenuNavigationItemActions[] =
[
CommandMenuNavigationItemActions.MOVE_UP,
CommandMenuNavigationItemActions.MOVE_DOWN,
CommandMenuNavigationItemActions.MOVE_TO_FOLDER,
CommandMenuNavigationItemActions.ADD_BEFORE,
CommandMenuNavigationItemActions.ADD_AFTER,
CommandMenuNavigationItemActions.REMOVE,
];
export const getOrganizeActionsSelectableItemIds = (
includeMoveToFolder: boolean,
) => [
'move-up',
'move-down',
...(includeMoveToFolder ? ['move-to-folder'] : []),
'add-before',
'add-after',
'remove',
];
): CommandMenuNavigationItemActions[] =>
includeMoveToFolder
? ORGANIZE_ACTIONS_WITH_MOVE_TO_FOLDER
: ORGANIZE_ACTIONS_WITHOUT_MOVE_TO_FOLDER;
@@ -3,8 +3,8 @@ import { Droppable } from '@hello-pangea/dnd';
import { useLingui } from '@lingui/react/macro';
import { useContext, useState } from 'react';
import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants';
import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection';
import { NAVIGATION_SECTIONS } from '@/navigation-menu-item/constants/NavigationSections.constants';
import { createPortal } from 'react-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
@@ -20,6 +20,7 @@ import { NavigationMenuItemDroppable } from '@/navigation-menu-item/components/N
import { NavigationMenuItemFolderNavigationDrawerItemDropdown } from '@/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown';
import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon';
import { NAVIGATION_MENU_ITEM_FOLDER_DELETE_MODAL_ID } from '@/navigation-menu-item/constants/NavigationMenuItemFolderDeleteModalId';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
import { useDeleteNavigationMenuItem } from '@/navigation-menu-item/hooks/useDeleteNavigationMenuItem';
import { useDeleteNavigationMenuItemFolder } from '@/navigation-menu-item/hooks/useDeleteNavigationMenuItemFolder';
@@ -103,7 +104,9 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({
if (!isOpen) {
const firstNonLinkItem = folder.navigationMenuItems.find(
(item) => item.itemType !== 'link' && isNonEmptyString(item.link),
(item) =>
item.itemType !== NavigationMenuItemType.LINK &&
isNonEmptyString(item.link),
);
if (isDefined(firstNonLinkItem?.link)) {
navigate(firstNonLinkItem.link);
@@ -221,8 +224,8 @@ export const CurrentWorkspaceMemberNavigationMenuItems = ({
index={0}
sectionId={
isWorkspaceFolder
? NAVIGATION_SECTIONS.WORKSPACE
: NAVIGATION_SECTIONS.FAVORITES
? NavigationSections.WORKSPACE
: NavigationSections.FAVORITES
}
>
<NavigationDrawerItem
@@ -6,10 +6,10 @@ import { IconHeartOff } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { NavigationItemDropTarget } from '@/navigation-menu-item/components/NavigationItemDropTarget';
import { NAVIGATION_SECTIONS } from '@/navigation-menu-item/constants/NavigationSections.constants';
import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants';
import { NavigationMenuItemDroppable } from '@/navigation-menu-item/components/NavigationMenuItemDroppable';
import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon';
import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
import { useDeleteNavigationMenuItem } from '@/navigation-menu-item/hooks/useDeleteNavigationMenuItem';
import { useSortedNavigationMenuItems } from '@/navigation-menu-item/hooks/useSortedNavigationMenuItems';
@@ -41,9 +41,7 @@ export const CurrentWorkspaceMemberOrphanNavigationMenuItems = () => {
return (
<NavigationMenuItemDroppable
droppableId={
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.ORPHAN_NAVIGATION_MENU_ITEMS
}
droppableId={NavigationMenuItemDroppableIds.ORPHAN_NAVIGATION_MENU_ITEMS}
isWorkspaceSection={false}
>
{orphanNavigationMenuItems.length > 0 ? (
@@ -53,7 +51,7 @@ export const CurrentWorkspaceMemberOrphanNavigationMenuItems = () => {
key={navigationMenuItem.id}
folderId={null}
index={index}
sectionId={NAVIGATION_SECTIONS.FAVORITES}
sectionId={NavigationSections.FAVORITES}
>
<DraggableItem
draggableId={navigationMenuItem.id}
@@ -99,7 +97,7 @@ export const CurrentWorkspaceMemberOrphanNavigationMenuItems = () => {
<NavigationItemDropTarget
folderId={null}
index={orphanNavigationMenuItems.length}
sectionId={NAVIGATION_SECTIONS.FAVORITES}
sectionId={NavigationSections.FAVORITES}
/>
</>
) : (
@@ -1,8 +1,8 @@
import styled from '@emotion/styled';
import { type ReactNode, useContext } from 'react';
import { type NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants';
import { NavigationDropTargetContext } from '@/navigation-menu-item/contexts/NavigationDropTargetContext';
import type { NavigationSectionId } from '@/navigation-menu-item/types/NavigationSectionId';
const StyledDropTarget = styled.div<{
$isDragOver: boolean;
@@ -49,12 +49,10 @@ const StyledDropTarget = styled.div<{
`}
`;
export type NavigationItemDropTargetSectionId = NavigationSectionId;
type NavigationItemDropTargetProps = {
folderId: string | null;
index: number;
sectionId: NavigationItemDropTargetSectionId;
sectionId: NavigationSections;
children?: ReactNode;
compact?: boolean;
};
@@ -2,6 +2,7 @@ import { useTheme } from '@emotion/react';
import { Avatar, useIcons } from 'twenty-ui/display';
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
@@ -23,17 +24,19 @@ export const NavigationMenuItemIcon = ({
const placeholderColorSeed = navigationMenuItem.targetRecordId ?? undefined;
const isRecord = navigationMenuItem.itemType === 'record';
const isLink = navigationMenuItem.itemType === 'link';
const isRecord =
navigationMenuItem.itemType === NavigationMenuItemType.RECORD;
const isLink = navigationMenuItem.itemType === NavigationMenuItemType.LINK;
const iconColors = getNavigationMenuItemIconColors(theme);
const isObjectIndexView =
navigationMenuItem.itemType === 'view' &&
navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
navigationMenuItem.viewKey === ViewKey.Index;
const iconBackgroundColor = isRecord
? undefined
: isLink
? iconColors.link
: navigationMenuItem.itemType === 'view' && !isObjectIndexView
: navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
!isObjectIndexView
? iconColors.view
: iconColors.object;
@@ -24,7 +24,7 @@ import {
useWorkspaceSectionItems,
} from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState';
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems';
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState';
@@ -91,7 +91,7 @@ export const WorkspaceNavigationMenuItems = () => {
const { item, objectMetadataItem } = params;
const id = item.id;
setSelectedNavigationMenuItemInEditMode(id);
if (item.itemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER) {
if (item.itemType === NavigationMenuItemType.FOLDER) {
setOpenNavigationMenuItemFolderIds((currentOpenFolders) =>
currentOpenFolders.includes(id)
? currentOpenFolders
@@ -101,7 +101,7 @@ export const WorkspaceNavigationMenuItems = () => {
pageTitle: t`Edit folder`,
pageIcon: IconFolder,
});
} else if (item.itemType === NAVIGATION_MENU_ITEM_TYPE.LINK) {
} else if (item.itemType === NavigationMenuItemType.LINK) {
openNavigationMenuItemInCommandMenu({
pageTitle: t`Edit link`,
pageIcon: IconLink,
@@ -15,7 +15,8 @@ import { useIsMobile } from 'twenty-ui/utilities';
import { NavigationMenuItemDroppable } from '@/navigation-menu-item/components/NavigationMenuItemDroppable';
import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon';
import { WorkspaceNavigationMenuItemFolderDragClone } from '@/navigation-menu-item/components/WorkspaceNavigationMenuItemFolderDragClone';
import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
import { type NavigationMenuItemClickParams } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState';
@@ -113,7 +114,9 @@ export const WorkspaceNavigationMenuItemsFolder = ({
if (!isOpen) {
const firstNonLinkItem = navigationMenuItems.find(
(item) => item.itemType !== 'link' && isNonEmptyString(item.link),
(item) =>
item.itemType !== NavigationMenuItemType.LINK &&
isNonEmptyString(item.link),
);
if (isDefined(firstNonLinkItem?.link)) {
navigate(firstNonLinkItem.link);
@@ -139,7 +142,7 @@ export const WorkspaceNavigationMenuItemsFolder = ({
<StyledFolderContainer $isSelectedInEditMode={isSelectedInEditMode}>
<NavigationDrawerItemsCollapsableContainer isGroup={isGroup}>
<NavigationMenuItemDroppable
droppableId={`${NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_HEADER_PREFIX}${folderId}`}
droppableId={`${NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX}${folderId}`}
isWorkspaceSection={true}
>
<NavigationDrawerItem
@@ -162,7 +165,7 @@ export const WorkspaceNavigationMenuItemsFolder = ({
containAnimation
>
<Droppable
droppableId={`${NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_PREFIX}${folderId}`}
droppableId={`${NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_PREFIX}${folderId}`}
isDropDisabled={folderContentDropDisabled}
ignoreContainerClipping
renderClone={(provided, snapshot, rubric) => (
@@ -190,8 +193,10 @@ export const WorkspaceNavigationMenuItemsFolder = ({
>
{navigationMenuItems.map((navigationMenuItem, index) => {
const objectMetadataItem =
navigationMenuItem.itemType === 'view' ||
navigationMenuItem.itemType === 'record'
navigationMenuItem.itemType ===
NavigationMenuItemType.VIEW ||
navigationMenuItem.itemType ===
NavigationMenuItemType.RECORD
? getObjectMetadataForNavigationMenuItem(
navigationMenuItem,
objectMetadataItems,
@@ -201,7 +206,8 @@ export const WorkspaceNavigationMenuItemsFolder = ({
const handleEditModeClick =
isEditMode &&
isDefined(onNavigationMenuItemClick) &&
(navigationMenuItem.itemType === 'link' ||
(navigationMenuItem.itemType ===
NavigationMenuItemType.LINK ||
isDefined(objectMetadataItem))
? () =>
onNavigationMenuItemClick({
@@ -1,7 +1,6 @@
export const NAVIGATION_MENU_ITEM_DROPPABLE_IDS = {
ORPHAN_NAVIGATION_MENU_ITEMS: 'orphan-navigation-menu-items',
WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS:
'workspace-orphan-navigation-menu-items',
WORKSPACE_FOLDER_PREFIX: 'workspace-folder-',
WORKSPACE_FOLDER_HEADER_PREFIX: 'workspace-folder-header-',
} as const;
export enum NavigationMenuItemDroppableIds {
ORPHAN_NAVIGATION_MENU_ITEMS = 'orphan-navigation-menu-items',
WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS = 'workspace-orphan-navigation-menu-items',
WORKSPACE_FOLDER_PREFIX = 'workspace-folder-',
WORKSPACE_FOLDER_HEADER_PREFIX = 'workspace-folder-header-',
}
@@ -0,0 +1,7 @@
export enum NavigationMenuItemType {
FOLDER = 'folder',
LINK = 'link',
OBJECT = 'object',
RECORD = 'record',
VIEW = 'view',
}
@@ -1,4 +1,4 @@
export const NAVIGATION_SECTIONS = {
WORKSPACE: 'workspace',
FAVORITES: 'favorites',
} as const;
export enum NavigationSections {
WORKSPACE = 'workspace',
FAVORITES = 'favorites',
}
@@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils';
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/add-to-navigation-draft.utils';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition';
export const useAddFolderToNavigationMenuDraft = () => {
const setNavigationMenuItemsDraft = useSetRecoilState(
@@ -4,10 +4,8 @@ import { v4 } from 'uuid';
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import {
computeInsertIndexAndPosition,
normalizeUrl,
} from '@/navigation-menu-item/utils/add-to-navigation-draft.utils';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition';
import { normalizeUrl } from '@/navigation-menu-item/utils/normalizeUrl';
export const useAddLinkToNavigationMenuDraft = () => {
const setNavigationMenuItemsDraft = useSetRecoilState(
@@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils';
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/add-to-navigation-draft.utils';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition';
export const useAddObjectToNavigationMenuDraft = () => {
const setNavigationMenuItemsDraft = useSetRecoilState(
@@ -5,7 +5,7 @@ import type { NavigationMenuItem } from '~/generated-metadata/graphql';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/add-to-navigation-draft.utils';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition';
type SearchRecord = {
recordId: string;
@@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils';
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/add-to-navigation-draft.utils';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition';
export const useAddViewToNavigationMenuDraft = () => {
const setNavigationMenuItemsDraft = useSetRecoilState(
@@ -0,0 +1,15 @@
import { useRecoilValue } from 'recoil';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
export const useDraftNavigationMenuItems = () => {
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
const navigationMenuItemsDraft = useRecoilValue(
navigationMenuItemsDraftState,
);
const currentDraft = navigationMenuItemsDraft ?? workspaceNavigationMenuItems;
return { currentDraft };
};
@@ -0,0 +1,15 @@
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
export const useDraftNavigationMenuItemsAllFolders = () => {
const { currentDraft } = useDraftNavigationMenuItems();
const allFolders =
currentDraft?.filter(isNavigationMenuItemFolder).map((item) => ({
id: item.id,
name: item.name ?? 'Folder',
folderId: item.folderId ?? undefined,
})) ?? [];
return { allFolders };
};
@@ -0,0 +1,15 @@
import { useNavigationMenuItemsByFolder } from '@/navigation-menu-item/hooks/useNavigationMenuItemsByFolder';
export const useDraftNavigationMenuItemsWorkspaceFolders = () => {
const { workspaceNavigationMenuItemsByFolder } =
useNavigationMenuItemsByFolder();
const workspaceFolders = workspaceNavigationMenuItemsByFolder.map(
(folder) => ({
id: folder.id,
name: folder.folderName,
}),
);
return { workspaceFolders };
};
@@ -5,6 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
import { IconFolder, IconLink, useIcons } from 'twenty-ui/display';
import { ADD_TO_NAV_SOURCE_DROPPABLE_ID } from '@/navigation-menu-item/constants/AddToNavSourceDroppableId';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useAddFolderToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddFolderToNavigationMenuDraft';
import { useAddLinkToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddLinkToNavigationMenuDraft';
import { useAddObjectToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddObjectToNavigationMenuDraft';
@@ -77,7 +78,10 @@ export const useHandleAddToNavigationDrop = () => {
);
const index = destination.index;
if (payload.type === 'folder' && folderId !== null) {
if (
payload.type === NavigationMenuItemType.FOLDER &&
folderId !== null
) {
return;
}
@@ -97,7 +101,7 @@ export const useHandleAddToNavigationDrop = () => {
};
switch (payload.type) {
case 'folder': {
case NavigationMenuItemType.FOLDER: {
const newFolderId = addFolderToDraft(
payload.name,
currentDraft,
@@ -111,7 +115,7 @@ export const useHandleAddToNavigationDrop = () => {
});
return;
}
case 'link': {
case NavigationMenuItemType.LINK: {
const newLinkId = addLinkToDraft(
payload.name || t`Link label`,
payload.link,
@@ -126,7 +130,7 @@ export const useHandleAddToNavigationDrop = () => {
});
return;
}
case 'object': {
case NavigationMenuItemType.OBJECT: {
const newItemId = addObjectToDraft(
payload.objectMetadataId,
payload.defaultViewId,
@@ -145,7 +149,7 @@ export const useHandleAddToNavigationDrop = () => {
});
return;
}
case 'view': {
case NavigationMenuItemType.VIEW: {
const newItemId = addViewToDraft(
payload.viewId,
currentDraft,
@@ -160,7 +164,7 @@ export const useHandleAddToNavigationDrop = () => {
});
return;
}
case 'record': {
case NavigationMenuItemType.RECORD: {
const newItemId = addRecordToDraft(
{
recordId: payload.recordId,
@@ -1,7 +1,7 @@
import { type OnDragEndResponder } from '@hello-pangea/dnd';
import { useSetRecoilState } from 'recoil';
import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { isWorkspaceDroppableId } from '@/navigation-menu-item/utils/isWorkspaceDroppableId';
import { useSortedNavigationMenuItems } from '@/navigation-menu-item/hooks/useSortedNavigationMenuItems';
import { useUpdateNavigationMenuItem } from '@/navigation-menu-item/hooks/useUpdateNavigationMenuItem';
@@ -63,12 +63,12 @@ export const useHandleNavigationMenuItemDragAndDrop = () => {
const destinationFolderId = validateAndExtractFolderId({
droppableId: destination.droppableId,
orphanDroppableId:
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.ORPHAN_NAVIGATION_MENU_ITEMS,
NavigationMenuItemDroppableIds.ORPHAN_NAVIGATION_MENU_ITEMS,
});
const sourceFolderId = validateAndExtractFolderId({
droppableId: source.droppableId,
orphanDroppableId:
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.ORPHAN_NAVIGATION_MENU_ITEMS,
NavigationMenuItemDroppableIds.ORPHAN_NAVIGATION_MENU_ITEMS,
});
if (
@@ -2,7 +2,7 @@ import { type OnDragEndResponder } from '@hello-pangea/dnd';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState';
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
import { openNavigationMenuItemFolderIdsState } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsState';
@@ -87,7 +87,7 @@ export const useHandleWorkspaceNavigationMenuItemDragAndDrop = () => {
);
const isDropOnFolderHeader = destination.droppableId.startsWith(
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_HEADER_PREFIX,
NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX,
);
if (isDropOnFolderHeader && isDefined(destinationFolderId)) {
@@ -0,0 +1,17 @@
import { useRecoilValue } from 'recoil';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
export const useSelectedNavigationMenuItemEditItem = () => {
const selectedNavigationMenuItemInEditMode = useRecoilValue(
selectedNavigationMenuItemInEditModeState,
);
const items = useWorkspaceSectionItems();
const selectedItem = selectedNavigationMenuItemInEditMode
? items.find((item) => item.id === selectedNavigationMenuItemInEditMode)
: undefined;
return { selectedItem };
};
@@ -0,0 +1,19 @@
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { useSelectedNavigationMenuItemEditItemObjectMetadata } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItemObjectMetadata';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
export const useSelectedNavigationMenuItemEditItemLabel = () => {
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const { selectedItemObjectMetadata } =
useSelectedNavigationMenuItemEditItemObjectMetadata();
const selectedItemLabel = selectedItem
? selectedItem.itemType === NavigationMenuItemType.FOLDER
? (selectedItem.name ?? 'Folder')
: selectedItem.itemType === NavigationMenuItemType.LINK
? (selectedItem.name ?? 'Link')
: (selectedItemObjectMetadata?.labelPlural ?? '')
: null;
return { selectedItemLabel };
};
@@ -0,0 +1,24 @@
import { useRecoilValue } from 'recoil';
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { coreViewsState } from '@/views/states/coreViewState';
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
export const useSelectedNavigationMenuItemEditItemObjectMetadata = () => {
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
const coreViews = useRecoilValue(coreViewsState);
const views = coreViews.map(convertCoreViewToView);
const { objectMetadataItems } = useObjectMetadataItems();
const selectedItemObjectMetadata = selectedItem
? getObjectMetadataForNavigationMenuItem(
selectedItem,
objectMetadataItems,
views,
)
: null;
return { selectedItemObjectMetadata };
};
@@ -3,7 +3,7 @@ import { type NavigationMenuItem } from '~/generated-metadata/graphql';
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
@@ -18,7 +18,7 @@ import { useSortedNavigationMenuItems } from './useSortedNavigationMenuItems';
export type FlatWorkspaceItem =
| ProcessedNavigationMenuItem
| (NavigationMenuItem & {
itemType: typeof NAVIGATION_MENU_ITEM_TYPE.FOLDER;
itemType: NavigationMenuItemType.FOLDER;
});
export type NavigationMenuItemClickParams = {
@@ -56,13 +56,13 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => {
FlatWorkspaceItem[]
>((acc, item) => {
if (isNavigationMenuItemFolder(item)) {
acc.push({ ...item, itemType: NAVIGATION_MENU_ITEM_TYPE.FOLDER });
acc.push({ ...item, itemType: NavigationMenuItemType.FOLDER });
} else {
const processedItem = processedObjectViewsById.get(item.id);
if (!isDefined(processedItem)) {
return acc;
}
if (processedItem.itemType === NAVIGATION_MENU_ITEM_TYPE.LINK) {
if (processedItem.itemType === NavigationMenuItemType.LINK) {
acc.push(processedItem);
} else {
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
@@ -79,7 +79,7 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => {
}, []);
return flatItems.flatMap((item) =>
item.itemType === NAVIGATION_MENU_ITEM_TYPE.FOLDER
item.itemType === NavigationMenuItemType.FOLDER
? [item, ...(folderChildrenById.get(item.id) ?? [])]
: [item],
);
@@ -1,4 +0,0 @@
import { type NAVIGATION_SECTIONS } from '@/navigation-menu-item/constants/NavigationSections.constants';
export type NavigationSectionId =
(typeof NAVIGATION_SECTIONS)[keyof typeof NAVIGATION_SECTIONS];
@@ -1,10 +0,0 @@
export const NAVIGATION_MENU_ITEM_TYPE = {
FOLDER: 'folder',
LINK: 'link',
OBJECT: 'object',
RECORD: 'record',
VIEW: 'view',
} as const;
export type NavigationMenuItemType =
(typeof NAVIGATION_MENU_ITEM_TYPE)[keyof typeof NAVIGATION_MENU_ITEM_TYPE];
@@ -2,7 +2,7 @@ import { type ViewKey } from '@/views/types/ViewKey';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
import { type NavigationMenuItemDisplayFields } from '@/navigation-menu-item/utils/computeNavigationMenuItemDisplayFields';
import { type NavigationMenuItemType } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { type NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
export type ProcessedNavigationMenuItem = NavigationMenuItem &
NavigationMenuItemDisplayFields & {
@@ -1,17 +1,6 @@
import {
computeInsertIndexAndPosition,
normalizeUrl,
} from '@/navigation-menu-item/utils/add-to-navigation-draft.utils';
import { computeInsertIndexAndPosition } from '@/navigation-menu-item/utils/computeInsertIndexAndPosition';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
describe('normalizeUrl', () => {
it('should leave url unchanged when it has protocol, otherwise prepend https', () => {
expect(normalizeUrl('https://example.com')).toBe('https://example.com');
expect(normalizeUrl('example.com')).toBe('https://example.com');
expect(normalizeUrl(' example.com ')).toBe('https://example.com');
});
});
describe('computeInsertIndexAndPosition', () => {
it('should compute flatIndex and position for insert in folder', () => {
const empty: NavigationMenuItem[] = [];
@@ -1,3 +1,4 @@
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type View } from '@/views/types/View';
@@ -29,7 +30,7 @@ const mockViews: View[] = [
describe('getObjectMetadataForNavigationMenuItem', () => {
it('should return null for link item type', () => {
const result = getObjectMetadataForNavigationMenuItem(
{ itemType: 'link' },
{ itemType: NavigationMenuItemType.LINK },
mockObjectMetadataItems,
mockViews,
);
@@ -38,7 +39,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => {
it('should return object metadata for view item when view and metadata exist', () => {
const result = getObjectMetadataForNavigationMenuItem(
{ itemType: 'view', viewId: 'view-1' },
{ itemType: NavigationMenuItemType.VIEW, viewId: 'view-1' },
mockObjectMetadataItems,
mockViews,
);
@@ -48,7 +49,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => {
it('should return null for view item when view is not found', () => {
const result = getObjectMetadataForNavigationMenuItem(
{ itemType: 'view', viewId: 'non-existent-view' },
{ itemType: NavigationMenuItemType.VIEW, viewId: 'non-existent-view' },
mockObjectMetadataItems,
mockViews,
);
@@ -60,7 +61,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => {
{ id: 'orphan-view', objectMetadataId: 'non-existent-metadata' } as View,
];
const result = getObjectMetadataForNavigationMenuItem(
{ itemType: 'view', viewId: 'orphan-view' },
{ itemType: NavigationMenuItemType.VIEW, viewId: 'orphan-view' },
mockObjectMetadataItems,
viewsWithOrphanView,
);
@@ -70,7 +71,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => {
it('should return object metadata for record item when metadata exists', () => {
const result = getObjectMetadataForNavigationMenuItem(
{
itemType: 'record',
itemType: NavigationMenuItemType.RECORD,
targetObjectMetadataId: 'metadata-2',
},
mockObjectMetadataItems,
@@ -82,7 +83,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => {
it('should return null for record item when targetObjectMetadataId is undefined', () => {
const result = getObjectMetadataForNavigationMenuItem(
{ itemType: 'record' },
{ itemType: NavigationMenuItemType.RECORD },
mockObjectMetadataItems,
mockViews,
);
@@ -92,7 +93,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => {
it('should return null for record item when metadata is not found', () => {
const result = getObjectMetadataForNavigationMenuItem(
{
itemType: 'record',
itemType: NavigationMenuItemType.RECORD,
targetObjectMetadataId: 'non-existent-metadata',
},
mockObjectMetadataItems,
@@ -103,7 +104,7 @@ describe('getObjectMetadataForNavigationMenuItem', () => {
it('should return null for view item when viewId is undefined', () => {
const result = getObjectMetadataForNavigationMenuItem(
{ itemType: 'view' },
{ itemType: NavigationMenuItemType.VIEW },
mockObjectMetadataItems,
mockViews,
);
@@ -1,4 +1,4 @@
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { isLocationMatchingNavigationMenuItem } from '@/navigation-menu-item/utils/isLocationMatchingNavigationMenuItem';
describe('isLocationMatchingNavigationMenuItem', () => {
@@ -8,7 +8,7 @@ describe('isLocationMatchingNavigationMenuItem', () => {
'/app/objects/people',
'/app/objects/people?viewId=123',
{
itemType: NAVIGATION_MENU_ITEM_TYPE.RECORD,
itemType: NavigationMenuItemType.RECORD,
link: '/app/objects/people',
},
),
@@ -18,7 +18,7 @@ describe('isLocationMatchingNavigationMenuItem', () => {
'/app/objects/companies',
'/app/objects/companies?viewId=123',
{
itemType: NAVIGATION_MENU_ITEM_TYPE.VIEW,
itemType: NavigationMenuItemType.VIEW,
link: '/app/objects/companies?viewId=123',
},
),
@@ -31,7 +31,7 @@ describe('isLocationMatchingNavigationMenuItem', () => {
'/app/objects/people',
'/app/objects/people?viewId=123',
{
itemType: NAVIGATION_MENU_ITEM_TYPE.RECORD,
itemType: NavigationMenuItemType.RECORD,
link: '/app/objects/company',
},
),
@@ -41,7 +41,7 @@ describe('isLocationMatchingNavigationMenuItem', () => {
'/app/objects/companies',
'/app/objects/companies?viewId=123',
{
itemType: NAVIGATION_MENU_ITEM_TYPE.VIEW,
itemType: NavigationMenuItemType.VIEW,
link: '/app/objects/companies?viewId=456',
},
),
@@ -0,0 +1,14 @@
import { normalizeUrl } from '@/navigation-menu-item/utils/normalizeUrl';
describe('normalizeUrl', () => {
it('should leave url unchanged when it has protocol, otherwise prepend https', () => {
expect(normalizeUrl('https://example.com')).toBe('https://example.com');
expect(normalizeUrl('example.com')).toBe('https://example.com');
expect(normalizeUrl(' example.com ')).toBe('https://example.com');
});
it('should return empty string for empty or whitespace input', () => {
expect(normalizeUrl('')).toBe('');
expect(normalizeUrl(' ')).toBe('');
});
});
@@ -1,13 +1,6 @@
import { isDefined } from 'twenty-shared/utils';
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
export const normalizeUrl = (url: string) => {
const trimmed = url.trim();
return trimmed.startsWith('http://') || trimmed.startsWith('https://')
? trimmed
: `https://${trimmed}`;
};
export const computeInsertIndexAndPosition = (
currentDraft: NavigationMenuItem[],
targetFolderId: string | null,
@@ -1,4 +1,4 @@
import { NAVIGATION_SECTIONS } from '@/navigation-menu-item/constants/NavigationSections.constants';
import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants';
import { isWorkspaceDroppableId } from '@/navigation-menu-item/utils/isWorkspaceDroppableId';
import { validateAndExtractWorkspaceFolderId } from '@/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId';
import type { DropResult } from '@hello-pangea/dnd';
@@ -11,5 +11,5 @@ export const getDropTargetIdFromDestination = (
}
const folderId = validateAndExtractWorkspaceFolderId(destination.droppableId);
const folderSegment = folderId ?? 'orphan';
return `${NAVIGATION_SECTIONS.WORKSPACE}-${folderSegment}-${destination.index}`;
return `${NavigationSections.WORKSPACE}-${folderSegment}-${destination.index}`;
};
@@ -1,5 +1,6 @@
import type { Theme } from '@emotion/react';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
@@ -9,15 +10,15 @@ export const getIconBackgroundColorForPayload = (
): string | undefined => {
const colors = getNavigationMenuItemIconColors(theme);
switch (payload.type) {
case 'object':
case NavigationMenuItemType.OBJECT:
return colors.object;
case 'view':
case NavigationMenuItemType.VIEW:
return colors.view;
case 'folder':
case NavigationMenuItemType.FOLDER:
return colors.folder;
case 'link':
case NavigationMenuItemType.LINK:
return colors.link;
case 'record':
case NavigationMenuItemType.RECORD:
return undefined;
default:
return undefined;
@@ -1,5 +1,6 @@
import { isDefined } from 'twenty-shared/utils';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type View } from '@/views/types/View';
@@ -14,12 +15,12 @@ export const getObjectMetadataForNavigationMenuItem = (
objectMetadataItems: ObjectMetadataItem[],
views: View[],
): ObjectMetadataItem | null => {
if (navigationMenuItem.itemType === 'link') {
if (navigationMenuItem.itemType === NavigationMenuItemType.LINK) {
return null;
}
if (
navigationMenuItem.itemType === 'view' &&
navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
isDefined(navigationMenuItem.viewId)
) {
const view = views.find((view) => view.id === navigationMenuItem.viewId);
@@ -33,7 +34,7 @@ export const getObjectMetadataForNavigationMenuItem = (
}
if (
navigationMenuItem.itemType === 'record' &&
navigationMenuItem.itemType === NavigationMenuItemType.RECORD &&
isDefined(navigationMenuItem.targetObjectMetadataId)
) {
const objectMetadataItem = objectMetadataItems.find(
@@ -1,4 +1,4 @@
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
export const isLocationMatchingNavigationMenuItem = (
@@ -7,7 +7,7 @@ export const isLocationMatchingNavigationMenuItem = (
navigationMenuItem: Pick<ProcessedNavigationMenuItem, 'itemType' | 'link'>,
) => {
const isViewItem =
navigationMenuItem.itemType === NAVIGATION_MENU_ITEM_TYPE.VIEW;
navigationMenuItem.itemType === NavigationMenuItemType.VIEW;
return isViewItem
? navigationMenuItem.link === currentViewPath
: navigationMenuItem.link === currentPath;
@@ -1,6 +1,6 @@
import { isDefined } from 'twenty-shared/utils';
import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
export const isWorkspaceDroppableId = (
droppableId: string | null | undefined,
@@ -10,12 +10,12 @@ export const isWorkspaceDroppableId = (
}
return (
droppableId ===
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS ||
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS ||
droppableId.startsWith(
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_PREFIX,
NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_PREFIX,
) ||
droppableId.startsWith(
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_HEADER_PREFIX,
NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX,
)
);
};
@@ -0,0 +1,11 @@
export const normalizeUrl = (url: string) => {
const trimmedUrl = url.trim();
if (trimmedUrl === '') {
return trimmedUrl;
}
return trimmedUrl.startsWith('http://') || trimmedUrl.startsWith('https://')
? trimmedUrl
: `https://${trimmedUrl}`;
};
@@ -7,7 +7,7 @@ import { getAppPath, isDefined } from 'twenty-shared/utils';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
import { getObjectMetadataNamePluralFromViewId } from '@/favorites/utils/getObjectMetadataNamePluralFromViewId';
import { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
import {
computeNavigationMenuItemDisplayFields,
@@ -15,8 +15,7 @@ import {
} from './computeNavigationMenuItemDisplayFields';
import { isNavigationMenuItemLink } from './isNavigationMenuItemLink';
export { NAVIGATION_MENU_ITEM_TYPE } from '@/navigation-menu-item/types/navigation-menu-item-type';
export type { NavigationMenuItemType } from '@/navigation-menu-item/types/navigation-menu-item-type';
export { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
export type { ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
export const sortNavigationMenuItems = (
@@ -70,7 +69,7 @@ export const sortNavigationMenuItems = (
...navigationMenuItem,
...displayFields,
viewKey: view.key,
itemType: NAVIGATION_MENU_ITEM_TYPE.VIEW,
itemType: NavigationMenuItemType.VIEW,
};
}
@@ -94,7 +93,7 @@ export const sortNavigationMenuItems = (
return {
...navigationMenuItem,
...displayFields,
itemType: NAVIGATION_MENU_ITEM_TYPE.LINK,
itemType: NavigationMenuItemType.LINK,
};
}
@@ -131,7 +130,7 @@ export const sortNavigationMenuItems = (
...navigationMenuItem,
...displayFields,
link: hasLinkToShowPage ? displayFields.link : '',
itemType: NAVIGATION_MENU_ITEM_TYPE.RECORD,
itemType: NavigationMenuItemType.RECORD,
};
})
.filter(isDefined)
@@ -1,7 +1,8 @@
import { isNonEmptyString } from '@sniptt/guards';
import { CustomError, isDefined } from 'twenty-shared/utils';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
export const matchesWorkspaceFolderId = (
item: NavigationMenuItem,
@@ -15,21 +16,21 @@ export const validateAndExtractWorkspaceFolderId = (
): string | null => {
if (
droppableId ===
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS
) {
return null;
}
if (
droppableId.startsWith(
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_HEADER_PREFIX,
NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX,
)
) {
const folderId = droppableId.replace(
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_HEADER_PREFIX,
NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_HEADER_PREFIX,
'',
);
if (!folderId)
if (!isNonEmptyString(folderId))
throw new CustomError(
`Invalid workspace folder header ID: ${droppableId}`,
'INVALID_WORKSPACE_FOLDER_HEADER_ID',
@@ -39,14 +40,14 @@ export const validateAndExtractWorkspaceFolderId = (
if (
droppableId.startsWith(
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_PREFIX,
NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_PREFIX,
)
) {
const folderId = droppableId.replace(
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_FOLDER_PREFIX,
NavigationMenuItemDroppableIds.WORKSPACE_FOLDER_PREFIX,
'',
);
if (!folderId)
if (!isNonEmptyString(folderId))
throw new CustomError(
`Invalid workspace folder ID: ${droppableId}`,
'INVALID_WORKSPACE_FOLDER_ID',
@@ -1,3 +1,4 @@
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState';
@@ -45,8 +46,9 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
const currentPath = location.pathname;
const currentPathWithSearch = `${location.pathname}${location.search}`;
const isRecord = navigationMenuItem?.itemType === 'record';
const isView = navigationMenuItem?.itemType === 'view';
const isRecord =
navigationMenuItem?.itemType === NavigationMenuItemType.RECORD;
const isView = navigationMenuItem?.itemType === NavigationMenuItemType.VIEW;
const hasCustomLink = isRecord || isView;
const navigationPath = hasCustomLink
@@ -6,12 +6,12 @@ import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconLink, IconPlus } from 'twenty-ui/display';
import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection';
import { NAVIGATION_SECTIONS } from '@/navigation-menu-item/constants/NavigationSections.constants';
import { NavigationItemDropTarget } from '@/navigation-menu-item/components/NavigationItemDropTarget';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection';
import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants';
import { WorkspaceNavigationMenuItemsFolder } from '@/navigation-menu-item/components/WorkspaceNavigationMenuItemsFolder';
import { NAVIGATION_MENU_ITEM_DROPPABLE_IDS } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
import {
type FlatWorkspaceItem,
@@ -85,15 +85,21 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
}, new Map());
const folderCount = flatItems.filter(
(item) => item.itemType === 'folder',
(item) => item.itemType === NavigationMenuItemType.FOLDER,
).length;
const filteredItems = flatItems.filter((item) => {
const type = item.itemType;
if (type === 'folder' || type === 'link') {
if (
type === NavigationMenuItemType.FOLDER ||
type === NavigationMenuItemType.LINK
) {
return true;
}
if (type === 'view' || type === 'record') {
if (
type === NavigationMenuItemType.VIEW ||
type === NavigationMenuItemType.RECORD
) {
const objectMetadataItem = getObjectMetadataForNavigationMenuItem(
item as ProcessedNavigationMenuItem,
objectMetadataItems,
@@ -151,7 +157,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
{isNavigationSectionOpen && (
<Droppable
droppableId={
NAVIGATION_MENU_ITEM_DROPPABLE_IDS.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS
}
isDropDisabled={workspaceDropDisabled}
>
@@ -171,7 +177,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
key={item.id}
folderId={null}
index={index}
sectionId={NAVIGATION_SECTIONS.WORKSPACE}
sectionId={NavigationSections.WORKSPACE}
>
<DraggableItem
draggableId={item.id}
@@ -214,7 +220,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
key={item.id}
folderId={null}
index={index}
sectionId={NAVIGATION_SECTIONS.WORKSPACE}
sectionId={NavigationSections.WORKSPACE}
>
<DraggableItem
draggableId={item.id}
@@ -263,7 +269,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
key={item.id}
folderId={null}
index={index}
sectionId={NAVIGATION_SECTIONS.WORKSPACE}
sectionId={NavigationSections.WORKSPACE}
>
<DraggableItem
draggableId={item.id}
@@ -301,7 +307,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
<NavigationItemDropTarget
folderId={null}
index={filteredItems.length}
sectionId={NAVIGATION_SECTIONS.WORKSPACE}
sectionId={NavigationSections.WORKSPACE}
compact={!!(isEditMode && onAddMenuItem)}
>
{isEditMode && onAddMenuItem && (