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:
+11
-34
@@ -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`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+13
-5
@@ -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 />;
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -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',
|
||||
}
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+25
-12
@@ -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
-4
@@ -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
|
||||
|
||||
+81
-86
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+11
-10
@@ -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',
|
||||
|
||||
+106
@@ -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"
|
||||
/>
|
||||
);
|
||||
};
|
||||
+6
-236
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+4
-3
@@ -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,
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+111
@@ -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
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+4
-3
@@ -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,
|
||||
}}
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+57
@@ -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 };
|
||||
};
|
||||
+58
@@ -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 };
|
||||
};
|
||||
+22
-17
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
-37
@@ -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,
|
||||
};
|
||||
};
|
||||
-16
@@ -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,
|
||||
};
|
||||
};
|
||||
-52
@@ -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,
|
||||
};
|
||||
};
|
||||
+55
@@ -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,
|
||||
};
|
||||
};
|
||||
+25
-8
@@ -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;
|
||||
|
||||
+7
-4
@@ -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
|
||||
|
||||
+5
-7
@@ -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}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
||||
+2
-4
@@ -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;
|
||||
};
|
||||
|
||||
+7
-4
@@ -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;
|
||||
|
||||
|
||||
+3
-3
@@ -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,
|
||||
|
||||
+13
-7
@@ -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({
|
||||
|
||||
+6
-7
@@ -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-',
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export enum NavigationMenuItemType {
|
||||
FOLDER = 'folder',
|
||||
LINK = 'link',
|
||||
OBJECT = 'object',
|
||||
RECORD = 'record',
|
||||
VIEW = 'view',
|
||||
}
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
export const NAVIGATION_SECTIONS = {
|
||||
WORKSPACE: 'workspace',
|
||||
FAVORITES: 'favorites',
|
||||
} as const;
|
||||
export enum NavigationSections {
|
||||
WORKSPACE = 'workspace',
|
||||
FAVORITES = 'favorites',
|
||||
}
|
||||
|
||||
+1
-1
@@ -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(
|
||||
|
||||
+2
-4
@@ -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(
|
||||
|
||||
+1
-1
@@ -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(
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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(
|
||||
|
||||
+15
@@ -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 };
|
||||
};
|
||||
+15
@@ -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 };
|
||||
};
|
||||
+15
@@ -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 };
|
||||
};
|
||||
+10
-6
@@ -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,
|
||||
|
||||
+3
-3
@@ -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
-2
@@ -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)) {
|
||||
|
||||
+17
@@ -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 };
|
||||
};
|
||||
+19
@@ -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 };
|
||||
};
|
||||
+24
@@ -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 };
|
||||
};
|
||||
+5
-5
@@ -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];
|
||||
-10
@@ -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];
|
||||
+1
-1
@@ -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
-12
@@ -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[] = [];
|
||||
+9
-8
@@ -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,
|
||||
);
|
||||
|
||||
+5
-5
@@ -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',
|
||||
},
|
||||
),
|
||||
|
||||
+14
@@ -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('');
|
||||
});
|
||||
});
|
||||
-7
@@ -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,
|
||||
+2
-2
@@ -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}`;
|
||||
};
|
||||
|
||||
+6
-5
@@ -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;
|
||||
|
||||
+4
-3
@@ -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(
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+4
-4
@@ -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}`;
|
||||
};
|
||||
+5
-6
@@ -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)
|
||||
|
||||
+9
-8
@@ -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',
|
||||
|
||||
+4
-2
@@ -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
|
||||
|
||||
+18
-12
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user