Navbar customization improvements (#17863)
- Add folder icon editing support - And other navbar customization fixes/improvements. --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> Co-authored-by: Devessier <baptiste@devessier.fr> Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@@ -1042,6 +1042,7 @@ export type CreateLogicFunctionFromSourceInput = {
|
||||
|
||||
export type CreateNavigationMenuItemInput = {
|
||||
folderId?: InputMaybe<Scalars['UUID']>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
link?: InputMaybe<Scalars['String']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
position?: InputMaybe<Scalars['Int']>;
|
||||
@@ -3229,6 +3230,7 @@ export type NavigationMenuItem = {
|
||||
applicationId?: Maybe<Scalars['UUID']>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
folderId?: Maybe<Scalars['UUID']>;
|
||||
icon?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
link?: Maybe<Scalars['String']>;
|
||||
name?: Maybe<Scalars['String']>;
|
||||
@@ -4548,6 +4550,7 @@ export type UpdateLogicFunctionFromSourceInputUpdates = {
|
||||
|
||||
export type UpdateNavigationMenuItemInput = {
|
||||
folderId?: InputMaybe<Scalars['UUID']>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
link?: InputMaybe<Scalars['String']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
position?: InputMaybe<Scalars['Int']>;
|
||||
@@ -5836,42 +5839,42 @@ export type FindManyMarketplaceAppsQueryVariables = Exact<{ [key: string]: never
|
||||
|
||||
export type FindManyMarketplaceAppsQuery = { __typename?: 'Query', findManyMarketplaceApps: Array<{ __typename?: 'MarketplaceApp', id: string, name: string, description: string, icon: string, version: string, author: string, category: string, logo?: string | null, screenshots: Array<string>, aboutDescription: string, providers: Array<string>, websiteUrl?: string | null, termsUrl?: string | null, objects: Array<{ __typename?: 'MarketplaceAppObject', universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, fields: Array<{ __typename?: 'MarketplaceAppField', universalIdentifier?: string | null, name: string, type: string, label: string, description?: string | null, icon?: string | null }> }>, fields: Array<{ __typename?: 'MarketplaceAppField', name: string, type: string, label: string, description?: string | null, icon?: string | null, objectUniversalIdentifier?: string | null }>, logicFunctions: Array<{ __typename?: 'MarketplaceAppLogicFunction', name: string, description?: string | null, timeoutSeconds?: number | null }>, frontComponents: Array<{ __typename?: 'MarketplaceAppFrontComponent', name: string, description?: string | null }>, defaultRole?: { __typename?: 'MarketplaceAppDefaultRole', id: string, label: string, description?: string | null, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canUpdateAllSettings: boolean, canAccessAllTools: boolean, permissionFlags: Array<string>, objectPermissions: Array<{ __typename?: 'MarketplaceAppRoleObjectPermission', objectUniversalIdentifier: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }>, fieldPermissions: Array<{ __typename?: 'MarketplaceAppRoleFieldPermission', objectUniversalIdentifier: string, fieldUniversalIdentifier: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null }> } | null }> };
|
||||
|
||||
export type NavigationMenuItemFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string };
|
||||
export type NavigationMenuItemFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string };
|
||||
|
||||
export type NavigationMenuItemQueryFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null };
|
||||
export type NavigationMenuItemQueryFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null };
|
||||
|
||||
export type CreateNavigationMenuItemMutationVariables = Exact<{
|
||||
input: CreateNavigationMenuItemInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateNavigationMenuItemMutation = { __typename?: 'Mutation', createNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
|
||||
export type CreateNavigationMenuItemMutation = { __typename?: 'Mutation', createNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type DeleteNavigationMenuItemMutationVariables = Exact<{
|
||||
id: Scalars['UUID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteNavigationMenuItemMutation = { __typename?: 'Mutation', deleteNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
|
||||
export type DeleteNavigationMenuItemMutation = { __typename?: 'Mutation', deleteNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type UpdateNavigationMenuItemMutationVariables = Exact<{
|
||||
input: UpdateOneNavigationMenuItemInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateNavigationMenuItemMutation = { __typename?: 'Mutation', updateNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
|
||||
export type UpdateNavigationMenuItemMutation = { __typename?: 'Mutation', updateNavigationMenuItem: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type FindManyNavigationMenuItemsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type FindManyNavigationMenuItemsQuery = { __typename?: 'Query', navigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> };
|
||||
export type FindManyNavigationMenuItemsQuery = { __typename?: 'Query', navigationMenuItems: Array<{ __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null }> };
|
||||
|
||||
export type FindOneNavigationMenuItemQueryVariables = Exact<{
|
||||
id: Scalars['UUID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type FindOneNavigationMenuItemQuery = { __typename?: 'Query', navigationMenuItem?: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null } | null };
|
||||
export type FindOneNavigationMenuItemQuery = { __typename?: 'Query', navigationMenuItem?: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null } | null };
|
||||
|
||||
export type ObjectMetadataFieldsFragment = { __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> };
|
||||
|
||||
@@ -7361,6 +7364,7 @@ export const NavigationMenuItemFieldsFragmentDoc = gql`
|
||||
folderId
|
||||
name
|
||||
link
|
||||
icon
|
||||
position
|
||||
applicationId
|
||||
createdAt
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import {
|
||||
setSessionId,
|
||||
@@ -17,6 +17,8 @@ import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCapt
|
||||
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
|
||||
import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
@@ -97,6 +99,20 @@ export const PageChangeEffect = () => {
|
||||
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
|
||||
const closeCommandMenuUnlessOnEditPage = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const currentPage = snapshot
|
||||
.getLoadable(commandMenuPageState)
|
||||
.getValue();
|
||||
if (currentPage === CommandMenuPages.NavigationMenuItemEdit) {
|
||||
return;
|
||||
}
|
||||
closeCommandMenu();
|
||||
},
|
||||
[closeCommandMenu],
|
||||
);
|
||||
|
||||
const { resetFocusStackToFocusItem } = useResetFocusStackToFocusItem();
|
||||
|
||||
const { resetFocusStackToRecordIndex } = useResetFocusStackToRecordIndex();
|
||||
@@ -104,8 +120,8 @@ export const PageChangeEffect = () => {
|
||||
const { openNewRecordTitleCell } = useOpenNewRecordTitleCell();
|
||||
|
||||
useEffect(() => {
|
||||
closeCommandMenu();
|
||||
}, [location.pathname, closeCommandMenu]);
|
||||
closeCommandMenuUnlessOnEditPage();
|
||||
}, [location.pathname, closeCommandMenuUnlessOnEditPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!previousLocation || previousLocation !== location.pathname) {
|
||||
|
||||
+37
-15
@@ -1,24 +1,32 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconFolder } from 'twenty-ui/display';
|
||||
import { useIcons } 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 { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useUpdateFolderInDraft } from '@/navigation-menu-item/hooks/useUpdateFolderInDraft';
|
||||
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
|
||||
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { TitleInput } from '@/ui/input/components/TitleInput';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
|
||||
const StyledClickableIconWrapper = styled.div`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const CommandMenuFolderInfo = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useLingui();
|
||||
const { getIcon } = useIcons();
|
||||
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
|
||||
const [shouldFocusTitleInput, setShouldFocusTitleInput] =
|
||||
useRecoilComponentState(
|
||||
@@ -29,7 +37,7 @@ export const CommandMenuFolderInfo = () => {
|
||||
selectedNavigationMenuItemInEditModeStateV2,
|
||||
);
|
||||
const items = useWorkspaceSectionItems();
|
||||
const { updateFolderNameInDraft } = useUpdateFolderNameInDraft();
|
||||
const { updateFolderInDraft } = useUpdateFolderInDraft();
|
||||
|
||||
const defaultLabel = t`New folder`;
|
||||
const placeholder = t`Folder name`;
|
||||
@@ -48,7 +56,7 @@ export const CommandMenuFolderInfo = () => {
|
||||
const itemName = selectedItem.name ?? defaultLabel;
|
||||
|
||||
const handleChange = (text: string) => {
|
||||
updateFolderNameInDraft(itemId, text);
|
||||
updateFolderInDraft(itemId, { name: text });
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
@@ -56,22 +64,36 @@ export const CommandMenuFolderInfo = () => {
|
||||
const finalName = trimmed.length > 0 ? trimmed : defaultLabel;
|
||||
|
||||
if (finalName !== itemName) {
|
||||
updateFolderNameInDraft(itemId, finalName);
|
||||
updateFolderInDraft(itemId, { name: finalName });
|
||||
}
|
||||
};
|
||||
|
||||
const selectedIconKey = selectedItem.icon ?? FOLDER_ICON_DEFAULT;
|
||||
const FolderIconComponent = getIcon(selectedIconKey);
|
||||
|
||||
return (
|
||||
<CommandMenuPageInfoLayout
|
||||
icon={
|
||||
<StyledNavigationMenuItemIconContainer
|
||||
$backgroundColor={getNavigationMenuItemIconColors(theme).folder}
|
||||
>
|
||||
<IconFolder
|
||||
size={theme.spacing(3.5)}
|
||||
color={theme.grayScale.gray1}
|
||||
stroke={theme.icon.stroke.md}
|
||||
/>
|
||||
</StyledNavigationMenuItemIconContainer>
|
||||
<IconPicker
|
||||
dropdownId="command-menu-folder-icon-picker"
|
||||
selectedIconKey={selectedIconKey}
|
||||
onChange={({ iconKey }) =>
|
||||
updateFolderInDraft(itemId, { icon: iconKey })
|
||||
}
|
||||
clickableComponent={
|
||||
<StyledClickableIconWrapper>
|
||||
<StyledNavigationMenuItemIconContainer
|
||||
$backgroundColor={getNavigationMenuItemIconColors(theme).folder}
|
||||
>
|
||||
<FolderIconComponent
|
||||
size={theme.spacing(3.5)}
|
||||
color={theme.grayScale.gray1}
|
||||
stroke={theme.icon.stroke.md}
|
||||
/>
|
||||
</StyledNavigationMenuItemIconContainer>
|
||||
</StyledClickableIconWrapper>
|
||||
}
|
||||
/>
|
||||
}
|
||||
title={
|
||||
<TitleInput
|
||||
|
||||
+10
-10
@@ -3,10 +3,9 @@ import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||
|
||||
import { CommandMenuPageInfoLayout } from '@/command-menu/components/CommandMenuPageInfoLayout';
|
||||
import { NavigationMenuItemIcon } from '@/navigation-menu-item/components/NavigationMenuItemIcon';
|
||||
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 { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
|
||||
export const CommandMenuObjectViewRecordInfo = () => {
|
||||
const { t } = useLingui();
|
||||
@@ -22,19 +21,20 @@ export const CommandMenuObjectViewRecordInfo = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isViewOrRecord =
|
||||
processedItem.itemType === NavigationMenuItemType.VIEW ||
|
||||
processedItem.itemType === NavigationMenuItemType.RECORD;
|
||||
const isViewOrRecord = [
|
||||
NavigationMenuItemType.VIEW,
|
||||
NavigationMenuItemType.RECORD,
|
||||
].includes(processedItem.itemType);
|
||||
|
||||
if (!isViewOrRecord) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const label =
|
||||
processedItem.itemType === NavigationMenuItemType.RECORD
|
||||
const label = isViewOrRecord
|
||||
? processedItem.itemType === NavigationMenuItemType.RECORD
|
||||
? t`record`
|
||||
: processedItem.viewKey === ViewKey.Index
|
||||
? t`object`
|
||||
: t`view`;
|
||||
: t`view`
|
||||
: t`object`;
|
||||
|
||||
return (
|
||||
<CommandMenuPageInfoLayout
|
||||
|
||||
+16
-1
@@ -10,6 +10,7 @@ import { CommandMenuSubViewWithSearch } from '@/command-menu/components/CommandM
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useFilteredPickerItems } from '@/command-menu/hooks/useFilteredPickerItems';
|
||||
import { useDraftNavigationMenuItems } from '@/navigation-menu-item/hooks/useDraftNavigationMenuItems';
|
||||
import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useAddViewToNavigationMenuDraft } from '@/navigation-menu-item/hooks/useAddViewToNavigationMenuDraft';
|
||||
import { useNavigationMenuObjectMetadataFromDraft } from '@/navigation-menu-item/hooks/useNavigationMenuObjectMetadataFromDraft';
|
||||
@@ -110,7 +111,21 @@ export const CommandMenuNewSidebarItemViewPickerSubView = ({
|
||||
onEnter={() => handleSelectView(view)}
|
||||
>
|
||||
<CommandMenuItemWithAddToNavigationDrag
|
||||
icon={getIcon(view.icon)}
|
||||
customIconContent={
|
||||
selectedObjectMetadataItem ? (
|
||||
<ObjectIconWithViewOverlay
|
||||
ObjectIcon={getIcon(
|
||||
selectedObjectMetadataItem.icon,
|
||||
)}
|
||||
ViewIcon={getIcon(view.icon)}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
icon={
|
||||
selectedObjectMetadataItem
|
||||
? undefined
|
||||
: getIcon(view.icon)
|
||||
}
|
||||
label={view.name}
|
||||
id={view.id}
|
||||
onClick={() => handleSelectView(view)}
|
||||
|
||||
+9
-1
@@ -72,14 +72,22 @@ export const AddToNavigationDragHandle = ({
|
||||
const iconSize = theme.icon.size.md;
|
||||
const iconStroke = theme.icon.stroke.sm;
|
||||
|
||||
const showCustomContentWithoutWrapper = isDefined(customIconContent);
|
||||
|
||||
return (
|
||||
<StyledIconSlot $hasFixedSize={payloadHasBackgroundColor}>
|
||||
<StyledIconSlot
|
||||
$hasFixedSize={
|
||||
payloadHasBackgroundColor || showCustomContentWithoutWrapper
|
||||
}
|
||||
>
|
||||
{isHovered ? (
|
||||
<IconGripVertical
|
||||
size={iconSize}
|
||||
stroke={iconStroke}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
) : showCustomContentWithoutWrapper ? (
|
||||
customIconContent
|
||||
) : hasBackgroundColor ? (
|
||||
<StyledNavigationMenuItemIconContainer
|
||||
$backgroundColor={iconBackgroundColor}
|
||||
|
||||
+17
-8
@@ -1,20 +1,22 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { IconCheck, useIcons } from 'twenty-ui/display';
|
||||
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
|
||||
import { useSaveNavigationMenuItemsDraft } from '@/navigation-menu-item/hooks/useSaveNavigationMenuItemsDraft';
|
||||
import { isNavigationMenuInEditModeStateV2 } from '@/navigation-menu-item/states/isNavigationMenuInEditModeStateV2';
|
||||
import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/navigationMenuItemsDraftStateV2';
|
||||
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconCheck, useIcons } from 'twenty-ui/display';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@@ -40,6 +42,7 @@ export const NavigationMenuEditModeBar = () => {
|
||||
const { getIcon } = useIcons();
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const { closeCommandMenu } = useCommandMenu();
|
||||
const commandMenuPage = useRecoilValue(commandMenuPageState);
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const setNavigationMenuItemsDraft = useSetRecoilStateV2(
|
||||
navigationMenuItemsDraftStateV2,
|
||||
@@ -57,6 +60,12 @@ export const NavigationMenuEditModeBar = () => {
|
||||
setNavigationMenuItemsDraft(null);
|
||||
setSelectedNavigationMenuItemInEditMode(null);
|
||||
setIsNavigationMenuInEditMode(false);
|
||||
const isNavItemPageOpen =
|
||||
commandMenuPage === CommandMenuPages.NavigationMenuAddItem ||
|
||||
commandMenuPage === CommandMenuPages.NavigationMenuItemEdit;
|
||||
if (isNavItemPageOpen) {
|
||||
closeCommandMenu();
|
||||
}
|
||||
};
|
||||
|
||||
const isNavigationMenuInEditMode = useRecoilValueV2(
|
||||
|
||||
+52
-15
@@ -1,12 +1,18 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Avatar, useIcons } from 'twenty-ui/display';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/components/NavigationMenuItemIconContainer';
|
||||
import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay';
|
||||
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';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
|
||||
export const NavigationMenuItemIcon = ({
|
||||
navigationMenuItem,
|
||||
@@ -15,30 +21,61 @@ export const NavigationMenuItemIcon = ({
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
|
||||
);
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { Icon: StandardIcon, IconColor } = useGetStandardObjectIcon(
|
||||
navigationMenuItem.objectNameSingular || '',
|
||||
);
|
||||
|
||||
const isRecord =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.RECORD;
|
||||
const isLink = navigationMenuItem.itemType === NavigationMenuItemType.LINK;
|
||||
const isObjectIndexView =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
|
||||
navigationMenuItem.viewKey === ViewKey.Index;
|
||||
const isViewWithOverlay =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
|
||||
!isObjectIndexView;
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === navigationMenuItem.objectNameSingular,
|
||||
);
|
||||
const ObjectIconForView =
|
||||
objectMetadataItem?.icon != null
|
||||
? getIcon(objectMetadataItem.icon)
|
||||
: StandardIcon;
|
||||
|
||||
if (
|
||||
isViewWithOverlay &&
|
||||
isDefined(ObjectIconForView) &&
|
||||
isDefined(navigationMenuItem.Icon)
|
||||
) {
|
||||
const ViewIcon = getIcon(navigationMenuItem.Icon);
|
||||
return (
|
||||
<ObjectIconWithViewOverlay
|
||||
ObjectIcon={ObjectIconForView}
|
||||
ViewIcon={ViewIcon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const IconToUse =
|
||||
StandardIcon ||
|
||||
(navigationMenuItem.Icon ? getIcon(navigationMenuItem.Icon) : undefined);
|
||||
|
||||
const placeholderColorSeed = navigationMenuItem.targetRecordId ?? undefined;
|
||||
|
||||
const isRecord =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.RECORD;
|
||||
const isLink = navigationMenuItem.itemType === NavigationMenuItemType.LINK;
|
||||
const iconColors = getNavigationMenuItemIconColors(theme);
|
||||
const isObjectIndexView =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
|
||||
navigationMenuItem.viewKey === ViewKey.Index;
|
||||
const iconBackgroundColor = isRecord
|
||||
? undefined
|
||||
: isLink
|
||||
? iconColors.link
|
||||
: navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
|
||||
!isObjectIndexView
|
||||
? iconColors.view
|
||||
: iconColors.object;
|
||||
const iconBackgroundColor = isNavigationMenuItemEditingEnabled
|
||||
? isRecord
|
||||
? undefined
|
||||
: isLink
|
||||
? iconColors.link
|
||||
: isViewWithOverlay
|
||||
? iconColors.view
|
||||
: iconColors.object
|
||||
: undefined;
|
||||
|
||||
const iconColorToUse = iconBackgroundColor
|
||||
? theme.grayScale.gray1
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import type { IconComponent } from 'twenty-ui/display';
|
||||
|
||||
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
|
||||
|
||||
const StyledCompositeContainer = styled.div`
|
||||
align-items: center;
|
||||
border-radius: ${({ theme }) => theme.border.radius.xs};
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
height: ${({ theme }) => theme.spacing(4.5)};
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: ${({ theme }) => theme.spacing(4.5)};
|
||||
`;
|
||||
|
||||
const StyledObjectIconWrapper = styled.div<{ $backgroundColor: string }>`
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: ${({ theme }) => theme.border.radius.xs};
|
||||
background-color: ${({ $backgroundColor }) => $backgroundColor};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledViewOverlay = styled.div<{ $backgroundColor: string }>`
|
||||
align-items: center;
|
||||
background-color: ${({ $backgroundColor }) => $backgroundColor};
|
||||
border: 1px solid ${({ theme }) => theme.background.primary};
|
||||
border-radius: ${({ theme }) => theme.border.radius.xs};
|
||||
bottom: -7px;
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.spacing(3.5)};
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: -7px;
|
||||
width: ${({ theme }) => theme.spacing(3.5)};
|
||||
`;
|
||||
|
||||
export type ObjectIconWithViewOverlayProps = {
|
||||
ObjectIcon: IconComponent;
|
||||
ViewIcon: IconComponent;
|
||||
};
|
||||
|
||||
export const ObjectIconWithViewOverlay = ({
|
||||
ObjectIcon,
|
||||
ViewIcon,
|
||||
}: ObjectIconWithViewOverlayProps) => {
|
||||
const theme = useTheme();
|
||||
const iconColors = getNavigationMenuItemIconColors(theme);
|
||||
|
||||
return (
|
||||
<StyledCompositeContainer>
|
||||
<StyledObjectIconWrapper $backgroundColor={iconColors.object}>
|
||||
<ObjectIcon
|
||||
size={theme.spacing(3.5)}
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={theme.grayScale.gray1}
|
||||
/>
|
||||
</StyledObjectIconWrapper>
|
||||
<StyledViewOverlay $backgroundColor={iconColors.view}>
|
||||
<ViewIcon
|
||||
size={theme.spacing(2)}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.grayScale.gray1}
|
||||
/>
|
||||
</StyledViewOverlay>
|
||||
</StyledCompositeContainer>
|
||||
);
|
||||
};
|
||||
+16
-14
@@ -1,39 +1,36 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
IconFolder,
|
||||
IconLink,
|
||||
IconPlus,
|
||||
IconTool,
|
||||
useIcons,
|
||||
} from 'twenty-ui/display';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { IconLink, IconPlus, IconTool, useIcons } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
import { useNavigateCommandMenu } from '@/command-menu/hooks/useNavigateCommandMenu';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useOpenNavigationMenuItemInCommandMenu } from '@/navigation-menu-item/hooks/useOpenNavigationMenuItemInCommandMenu';
|
||||
import {
|
||||
type NavigationMenuItemClickParams,
|
||||
useWorkspaceSectionItems,
|
||||
} from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
|
||||
import { isNavigationMenuInEditModeStateV2 } from '@/navigation-menu-item/states/isNavigationMenuInEditModeStateV2';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/navigationMenuItemsDraftStateV2';
|
||||
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems';
|
||||
import { openNavigationMenuItemFolderIdsStateV2 } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsStateV2';
|
||||
import { selectedNavigationMenuItemInEditModeStateV2 } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeStateV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems';
|
||||
import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader';
|
||||
import { NavigationDrawerSectionForWorkspaceItems } from '@/object-metadata/components/NavigationDrawerSectionForWorkspaceItems';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
||||
import { prefetchNavigationMenuItemsState } from '@/prefetch/states/prefetchNavigationMenuItemsState';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledRightIconsContainer = styled.div`
|
||||
@@ -74,6 +71,7 @@ export const WorkspaceNavigationMenuItems = () => {
|
||||
const setOpenNavigationMenuItemFolderIds = useSetRecoilStateV2(
|
||||
openNavigationMenuItemFolderIdsStateV2,
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
const { navigateCommandMenu } = useNavigateCommandMenu();
|
||||
const { openNavigationMenuItemInCommandMenu } =
|
||||
useOpenNavigationMenuItemInCommandMenu();
|
||||
@@ -101,7 +99,7 @@ export const WorkspaceNavigationMenuItems = () => {
|
||||
);
|
||||
openNavigationMenuItemInCommandMenu({
|
||||
pageTitle: t`Edit folder`,
|
||||
pageIcon: IconFolder,
|
||||
pageIcon: getIcon(item.icon ?? item.Icon ?? FOLDER_ICON_DEFAULT),
|
||||
});
|
||||
} else if (item.itemType === NavigationMenuItemType.LINK) {
|
||||
openNavigationMenuItemInCommandMenu({
|
||||
@@ -113,6 +111,10 @@ export const WorkspaceNavigationMenuItems = () => {
|
||||
pageTitle: objectMetadataItem.labelPlural,
|
||||
pageIcon: getIcon(objectMetadataItem.icon),
|
||||
});
|
||||
|
||||
if (isNonEmptyString(item.link)) {
|
||||
navigate(item.link);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+35
-5
@@ -8,19 +8,19 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconFolder, IconFolderOpen } from 'twenty-ui/display';
|
||||
import { IconChevronDown, IconChevronRight, useIcons } from 'twenty-ui/display';
|
||||
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
|
||||
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 { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault';
|
||||
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 { openNavigationMenuItemFolderIdsStateV2 } from '@/navigation-menu-item/states/openNavigationMenuItemFolderIdsStateV2';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { getNavigationMenuItemIconColors } from '@/navigation-menu-item/utils/getNavigationMenuItemIconColors';
|
||||
import { getNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/utils/getNavigationMenuItemSecondaryLabel';
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
|
||||
@@ -32,11 +32,14 @@ import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/componen
|
||||
import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer';
|
||||
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
|
||||
import { currentNavigationMenuItemFolderIdStateV2 } from '@/ui/navigation/navigation-drawer/states/currentNavigationMenuItemFolderIdStateV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment';
|
||||
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledFolderContainer = styled.div<{ $isSelectedInEditMode: boolean }>`
|
||||
border: ${({ theme, $isSelectedInEditMode }) =>
|
||||
@@ -49,6 +52,9 @@ const StyledFolderContainer = styled.div<{ $isSelectedInEditMode: boolean }>`
|
||||
const StyledFolderDroppableContent = styled.div<{
|
||||
$compact: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.betweenSiblingsGap};
|
||||
padding-bottom: ${({ theme, $compact }) => ($compact ? 0 : theme.spacing(2))};
|
||||
`;
|
||||
|
||||
@@ -61,6 +67,7 @@ const StyledFolderExpandableWrapper = styled.div`
|
||||
type WorkspaceNavigationMenuItemsFolderProps = {
|
||||
folderId: string;
|
||||
folderName: string;
|
||||
folderIconKey?: string | null;
|
||||
navigationMenuItems: ProcessedNavigationMenuItem[];
|
||||
isGroup: boolean;
|
||||
isEditMode?: boolean;
|
||||
@@ -74,6 +81,7 @@ type WorkspaceNavigationMenuItemsFolderProps = {
|
||||
export const WorkspaceNavigationMenuItemsFolder = ({
|
||||
folderId,
|
||||
folderName,
|
||||
folderIconKey,
|
||||
navigationMenuItems,
|
||||
isGroup,
|
||||
isEditMode = false,
|
||||
@@ -84,7 +92,12 @@ export const WorkspaceNavigationMenuItemsFolder = ({
|
||||
isDragging = false,
|
||||
}: WorkspaceNavigationMenuItemsFolderProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
|
||||
);
|
||||
const iconColors = getNavigationMenuItemIconColors(theme);
|
||||
const FolderIcon = getIcon(folderIconKey ?? FOLDER_ICON_DEFAULT);
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const coreViews = useRecoilValue(coreViewsState);
|
||||
const views = coreViews.map(convertCoreViewToView);
|
||||
@@ -149,13 +162,30 @@ export const WorkspaceNavigationMenuItemsFolder = ({
|
||||
>
|
||||
<NavigationDrawerItem
|
||||
label={folderName}
|
||||
Icon={isOpen ? IconFolderOpen : IconFolder}
|
||||
iconBackgroundColor={iconColors.folder}
|
||||
Icon={FolderIcon}
|
||||
iconBackgroundColor={
|
||||
isNavigationMenuItemEditingEnabled ? iconColors.folder : undefined
|
||||
}
|
||||
onClick={handleClick}
|
||||
className="navigation-drawer-item"
|
||||
triggerEvent="CLICK"
|
||||
preventCollapseOnMobile={isMobile}
|
||||
isDragging={isDragging}
|
||||
rightOptions={
|
||||
isOpen ? (
|
||||
<IconChevronDown
|
||||
size={theme.icon.size.sm}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
) : (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.sm}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</NavigationMenuItemDroppable>
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const FOLDER_ICON_DEFAULT = 'IconFolder';
|
||||
+7
@@ -1,10 +1,16 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export type AddToNavigationFallbackDestination = {
|
||||
droppableId: string;
|
||||
index: number;
|
||||
} | null;
|
||||
|
||||
type NavigationDropTargetContextType = {
|
||||
activeDropTargetId: string | null;
|
||||
setActiveDropTargetId: (id: string | null) => void;
|
||||
forbiddenDropTargetId: string | null;
|
||||
setForbiddenDropTargetId: (id: string | null) => void;
|
||||
addToNavigationFallbackDestination: AddToNavigationFallbackDestination;
|
||||
};
|
||||
|
||||
export const NavigationDropTargetContext =
|
||||
@@ -13,4 +19,5 @@ export const NavigationDropTargetContext =
|
||||
setActiveDropTargetId: () => {},
|
||||
forbiddenDropTargetId: null,
|
||||
setForbiddenDropTargetId: () => {},
|
||||
addToNavigationFallbackDestination: null,
|
||||
});
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ export const NAVIGATION_MENU_ITEM_FRAGMENT = gql`
|
||||
folderId
|
||||
name
|
||||
link
|
||||
icon
|
||||
position
|
||||
applicationId
|
||||
createdAt
|
||||
|
||||
+15
-11
@@ -1,7 +1,10 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useCreateNavigationMenuItemMutation } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
type CreateNavigationMenuItemInput,
|
||||
useCreateNavigationMenuItemMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
import { useDeleteNavigationMenuItem } from '@/navigation-menu-item/hooks/useDeleteNavigationMenuItem';
|
||||
import { useUpdateNavigationMenuItem } from '@/navigation-menu-item/hooks/useUpdateNavigationMenuItem';
|
||||
@@ -81,20 +84,13 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
];
|
||||
|
||||
for (const draftItem of idsToCreateIncludingRecreated) {
|
||||
const input: {
|
||||
position: number;
|
||||
folderId?: string | null;
|
||||
name?: string;
|
||||
link?: string;
|
||||
viewId?: string;
|
||||
targetObjectMetadataId?: string;
|
||||
targetRecordId?: string;
|
||||
} = {
|
||||
const input: CreateNavigationMenuItemInput = {
|
||||
position: Math.max(0, Math.round(draftItem.position)),
|
||||
};
|
||||
|
||||
if (isNavigationMenuItemFolder(draftItem)) {
|
||||
input.name = draftItem.name ?? undefined;
|
||||
input.icon = draftItem.icon ?? null;
|
||||
} else if (isNavigationMenuItemLink(draftItem)) {
|
||||
input.name = draftItem.name ?? 'Link';
|
||||
const linkUrl = (draftItem.link ?? '').trim();
|
||||
@@ -138,12 +134,16 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
const linkChanged =
|
||||
isNavigationMenuItemLink(draftItem) &&
|
||||
(original.link ?? null) !== (draftItem.link ?? null);
|
||||
const iconChanged =
|
||||
isNavigationMenuItemFolder(draftItem) &&
|
||||
(original.icon ?? null) !== (draftItem.icon ?? null);
|
||||
|
||||
if (
|
||||
positionChanged ||
|
||||
folderIdChanged ||
|
||||
nameChanged ||
|
||||
linkChanged
|
||||
linkChanged ||
|
||||
iconChanged
|
||||
) {
|
||||
const updateInput: {
|
||||
id: string;
|
||||
@@ -151,6 +151,7 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
folderId?: string | null;
|
||||
name?: string;
|
||||
link?: string | null;
|
||||
icon?: string | null;
|
||||
} = { id: draftItem.id };
|
||||
|
||||
if (positionChanged) {
|
||||
@@ -177,6 +178,9 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
: `https://${linkUrl}`
|
||||
: null;
|
||||
}
|
||||
if (iconChanged && isNavigationMenuItemFolder(draftItem)) {
|
||||
updateInput.icon = draftItem.icon ?? null;
|
||||
}
|
||||
|
||||
await updateNavigationMenuItem(updateInput);
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/navigationMenuItemsDraftStateV2';
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
|
||||
export type UpdateFolderInDraftUpdates = {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
export const useUpdateFolderInDraft = () => {
|
||||
const setNavigationMenuItemsDraft = useSetRecoilStateV2(
|
||||
navigationMenuItemsDraftStateV2,
|
||||
);
|
||||
|
||||
const updateFolderInDraft = (
|
||||
folderId: string,
|
||||
updates: UpdateFolderInDraftUpdates,
|
||||
) => {
|
||||
setNavigationMenuItemsDraft((draft) => {
|
||||
if (!draft) return draft;
|
||||
|
||||
return draft.map((item) => {
|
||||
if (!isNavigationMenuItemFolder(item) || item.id !== folderId) {
|
||||
return item;
|
||||
}
|
||||
return { ...item, ...updates };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return { updateFolderInDraft };
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { navigationMenuItemsDraftStateV2 } from '@/navigation-menu-item/states/navigationMenuItemsDraftStateV2';
|
||||
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
|
||||
|
||||
export type UpdateNavigationMenuItemInDraftUpdates = {
|
||||
color?: string;
|
||||
name?: string;
|
||||
icon?: string;
|
||||
link?: string;
|
||||
};
|
||||
|
||||
export const useUpdateNavigationMenuItemInDraft = () => {
|
||||
const setNavigationMenuItemsDraft = useSetRecoilStateV2(
|
||||
navigationMenuItemsDraftStateV2,
|
||||
);
|
||||
|
||||
const updateNavigationMenuItemInDraft = (
|
||||
navigationMenuItemId: string,
|
||||
updates: UpdateNavigationMenuItemInDraftUpdates,
|
||||
) => {
|
||||
setNavigationMenuItemsDraft((draft) => {
|
||||
if (!draft) return draft;
|
||||
|
||||
return draft.map((item) => {
|
||||
if (item.id !== navigationMenuItemId) return item;
|
||||
return { ...item, ...updates };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return { updateNavigationMenuItemInDraft };
|
||||
};
|
||||
+10
-4
@@ -1,12 +1,13 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
import { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
import { FOLDER_ICON_DEFAULT } from '@/navigation-menu-item/constants/FolderIconDefault';
|
||||
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 { getObjectMetadataForNavigationMenuItem } from '@/navigation-menu-item/utils/getObjectMetadataForNavigationMenuItem';
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -19,6 +20,7 @@ export type FlatWorkspaceItem =
|
||||
| ProcessedNavigationMenuItem
|
||||
| (NavigationMenuItem & {
|
||||
itemType: NavigationMenuItemType.FOLDER;
|
||||
Icon: string;
|
||||
});
|
||||
|
||||
export type NavigationMenuItemClickParams = {
|
||||
@@ -56,7 +58,11 @@ export const useWorkspaceSectionItems = (): FlatWorkspaceItem[] => {
|
||||
FlatWorkspaceItem[]
|
||||
>((acc, item) => {
|
||||
if (isNavigationMenuItemFolder(item)) {
|
||||
acc.push({ ...item, itemType: NavigationMenuItemType.FOLDER });
|
||||
acc.push({
|
||||
...item,
|
||||
itemType: NavigationMenuItemType.FOLDER,
|
||||
Icon: item.icon ?? FOLDER_ICON_DEFAULT,
|
||||
});
|
||||
} else {
|
||||
const processedItem = processedObjectViewsById.get(item.id);
|
||||
if (!isDefined(processedItem)) {
|
||||
|
||||
@@ -12,18 +12,21 @@ import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
import { FavoritesDragContext } from '@/favorites/contexts/FavoritesDragContext';
|
||||
import { useHandleFavoriteDragAndDrop } from '@/favorites/hooks/useHandleFavoriteDragAndDrop';
|
||||
import { ADD_TO_NAV_SOURCE_DROPPABLE_ID } from '@/navigation-menu-item/constants/AddToNavSourceDroppableId';
|
||||
import { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
|
||||
import { NavigationDragSourceContext } from '@/navigation-menu-item/contexts/NavigationDragSourceContext';
|
||||
import { NavigationDropTargetContext } from '@/navigation-menu-item/contexts/NavigationDropTargetContext';
|
||||
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
|
||||
import { useHandleAddToNavigationDrop } from '@/navigation-menu-item/hooks/useHandleAddToNavigationDrop';
|
||||
import { useHandleNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/hooks/useHandleNavigationMenuItemDragAndDrop';
|
||||
import { useHandleWorkspaceNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/hooks/useHandleWorkspaceNavigationMenuItemDragAndDrop';
|
||||
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/useNavigationMenuItemsDraftState';
|
||||
import { addToNavPayloadRegistryStateV2 } from '@/navigation-menu-item/states/addToNavPayloadRegistryStateV2';
|
||||
import { getDropTargetIdFromDestination } from '@/navigation-menu-item/utils/getDropTargetIdFromDestination';
|
||||
import { isWorkspaceDroppableId } from '@/navigation-menu-item/utils/isWorkspaceDroppableId';
|
||||
import { validateAndExtractWorkspaceFolderId } from '@/navigation-menu-item/utils/validateAndExtractWorkspaceFolderId';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type PageDragDropProviderProps = {
|
||||
children: ReactNode;
|
||||
@@ -45,7 +48,12 @@ export const PageDragDropProvider = ({
|
||||
const [forbiddenDropTargetId, setForbiddenDropTargetId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [
|
||||
addToNavigationFallbackDestination,
|
||||
setAddToNavigationFallbackDestination,
|
||||
] = useState<{ droppableId: string; index: number } | null>(null);
|
||||
|
||||
const { workspaceNavigationMenuItems } = useNavigationMenuItemsDraftState();
|
||||
const { handleAddToNavigationDrop } = useHandleAddToNavigationDrop();
|
||||
const { handleNavigationMenuItemDragAndDrop } =
|
||||
useHandleNavigationMenuItemDragAndDrop();
|
||||
@@ -53,9 +61,22 @@ export const PageDragDropProvider = ({
|
||||
useHandleWorkspaceNavigationMenuItemDragAndDrop();
|
||||
const { handleFavoriteDragAndDrop } = useHandleFavoriteDragAndDrop();
|
||||
|
||||
const orphanItemCount = workspaceNavigationMenuItems.filter(
|
||||
(item) => !isDefined(item.folderId),
|
||||
).length;
|
||||
|
||||
const handleDragStart = (dragStart: DragStart) => {
|
||||
setIsDragging(true);
|
||||
setSourceDroppableId(dragStart.source.droppableId);
|
||||
if (dragStart.source.droppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
|
||||
const defaultDestination = {
|
||||
droppableId:
|
||||
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS,
|
||||
index: orphanItemCount,
|
||||
};
|
||||
setAddToNavigationFallbackDestination(defaultDestination);
|
||||
setActiveDropTargetId(getDropTargetIdFromDestination(defaultDestination));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragUpdate = useRecoilCallback(
|
||||
@@ -65,36 +86,53 @@ export const PageDragDropProvider = ({
|
||||
if (source.droppableId !== ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
|
||||
return;
|
||||
}
|
||||
if (!destination || !isWorkspaceDroppableId(destination.droppableId)) {
|
||||
setActiveDropTargetId(null);
|
||||
setForbiddenDropTargetId(null);
|
||||
return;
|
||||
}
|
||||
const dropTargetId = getDropTargetIdFromDestination(destination);
|
||||
setActiveDropTargetId(dropTargetId);
|
||||
if (
|
||||
destination !== null &&
|
||||
isWorkspaceDroppableId(destination.droppableId)
|
||||
) {
|
||||
setAddToNavigationFallbackDestination(destination);
|
||||
const dropTargetId = getDropTargetIdFromDestination(destination);
|
||||
setActiveDropTargetId(dropTargetId);
|
||||
|
||||
const payload =
|
||||
jotaiStore
|
||||
.get(addToNavPayloadRegistryStateV2.atom)
|
||||
.get(update.draggableId) ?? null;
|
||||
const folderId = validateAndExtractWorkspaceFolderId(
|
||||
destination.droppableId,
|
||||
);
|
||||
const isFolderOverFolder =
|
||||
payload?.type === 'folder' && folderId !== null;
|
||||
setForbiddenDropTargetId(isFolderOverFolder ? dropTargetId : null);
|
||||
const payload =
|
||||
jotaiStore
|
||||
.get(addToNavPayloadRegistryStateV2.atom)
|
||||
.get(update.draggableId) ?? null;
|
||||
const folderId = validateAndExtractWorkspaceFolderId(
|
||||
destination.droppableId,
|
||||
);
|
||||
const isFolderOverFolder =
|
||||
payload?.type === 'folder' && folderId !== null;
|
||||
setForbiddenDropTargetId(isFolderOverFolder ? dropTargetId : null);
|
||||
} else {
|
||||
setForbiddenDropTargetId(null);
|
||||
const fallback = addToNavigationFallbackDestination;
|
||||
setActiveDropTargetId(
|
||||
fallback ? getDropTargetIdFromDestination(fallback) : null,
|
||||
);
|
||||
}
|
||||
}) as OnDragUpdateResponder,
|
||||
[],
|
||||
[addToNavigationFallbackDestination],
|
||||
);
|
||||
|
||||
const handleDragEnd = (result: DropResult, provided: ResponderProvided) => {
|
||||
const isAddToNavigationSource =
|
||||
result.source.droppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID;
|
||||
const effectiveResult: DropResult =
|
||||
isAddToNavigationSource &&
|
||||
!result.destination &&
|
||||
addToNavigationFallbackDestination
|
||||
? { ...result, destination: addToNavigationFallbackDestination }
|
||||
: result;
|
||||
|
||||
setIsDragging(false);
|
||||
setSourceDroppableId(null);
|
||||
setActiveDropTargetId(null);
|
||||
setForbiddenDropTargetId(null);
|
||||
setAddToNavigationFallbackDestination(null);
|
||||
|
||||
if (result.source.droppableId === ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
|
||||
handleAddToNavigationDrop(result, provided);
|
||||
if (isAddToNavigationSource) {
|
||||
handleAddToNavigationDrop(effectiveResult, provided);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,6 +160,7 @@ export const PageDragDropProvider = ({
|
||||
setActiveDropTargetId,
|
||||
forbiddenDropTargetId,
|
||||
setForbiddenDropTargetId,
|
||||
addToNavigationFallbackDestination,
|
||||
}}
|
||||
>
|
||||
<DragDropContext
|
||||
|
||||
+17
-6
@@ -1,3 +1,4 @@
|
||||
import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay';
|
||||
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';
|
||||
@@ -12,6 +13,9 @@ import { useRecoilValue } from 'recoil';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { getAppPath, isDefined } from 'twenty-shared/utils';
|
||||
import { Avatar, useIcons } from 'twenty-ui/display';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
|
||||
export type NavigationDrawerItemForObjectMetadataItemProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
@@ -33,6 +37,9 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
|
||||
isDragging = false,
|
||||
}: NavigationDrawerItemForObjectMetadataItemProps) => {
|
||||
const theme = useTheme();
|
||||
const isNavigationMenuItemEditingEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
|
||||
);
|
||||
const iconColors = getNavigationMenuItemIconColors(theme);
|
||||
const lastVisitedViewPerObjectMetadataItem = useRecoilValue(
|
||||
lastVisitedViewPerObjectMetadataItemState,
|
||||
@@ -111,14 +118,18 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
|
||||
/>
|
||||
)
|
||||
: isViewWithCustomName && isDefined(navigationMenuItem?.Icon)
|
||||
? getIcon(navigationMenuItem.Icon)
|
||||
? () => (
|
||||
<ObjectIconWithViewOverlay
|
||||
ObjectIcon={getIcon(objectMetadataItem.icon)}
|
||||
ViewIcon={getIcon(navigationMenuItem!.Icon!)}
|
||||
/>
|
||||
)
|
||||
: getIcon(objectMetadataItem.icon);
|
||||
|
||||
const iconBackgroundColor = isRecord
|
||||
? undefined
|
||||
: isViewWithCustomName
|
||||
? iconColors.view
|
||||
: iconColors.object;
|
||||
const iconBackgroundColor =
|
||||
isNavigationMenuItemEditingEnabled && !isRecord && !isViewWithCustomName
|
||||
? iconColors.object
|
||||
: undefined;
|
||||
|
||||
const secondaryLabel =
|
||||
isRecord || isViewWithCustomName
|
||||
|
||||
+39
-10
@@ -1,4 +1,6 @@
|
||||
import { NavigationDropTargetContext } from '@/navigation-menu-item/contexts/NavigationDropTargetContext';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Droppable } from '@hello-pangea/dnd';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext } from 'react';
|
||||
@@ -7,12 +9,12 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconLink, IconPlus } from 'twenty-ui/display';
|
||||
|
||||
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 { NavigationMenuItemDroppableIds } from '@/navigation-menu-item/constants/NavigationMenuItemDroppableIds';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { NavigationSections } from '@/navigation-menu-item/constants/NavigationSections.constants';
|
||||
import { NavigationMenuItemDragContext } from '@/navigation-menu-item/contexts/NavigationMenuItemDragContext';
|
||||
import { useIsDropDisabledForSection } from '@/navigation-menu-item/hooks/useIsDropDisabledForSection';
|
||||
import {
|
||||
type FlatWorkspaceItem,
|
||||
type NavigationMenuItemClickParams,
|
||||
@@ -25,15 +27,21 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { NavigationDrawerAnimatedCollapseWrapper } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerAnimatedCollapseWrapper';
|
||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
|
||||
import { NavigationDrawerSectionTitle } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitle';
|
||||
import { useNavigationSection } from '@/ui/navigation/navigation-drawer/hooks/useNavigationSection';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { convertCoreViewToView } from '@/views/utils/convertCoreViewToView';
|
||||
|
||||
const StyledWorkspaceDroppableList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.betweenSiblingsGap};
|
||||
`;
|
||||
|
||||
type NavigationDrawerSectionForWorkspaceItemsProps = {
|
||||
sectionTitle: string;
|
||||
items: FlatWorkspaceItem[];
|
||||
@@ -70,8 +78,14 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { isDragging } = useContext(NavigationMenuItemDragContext);
|
||||
const { addToNavigationFallbackDestination } = useContext(
|
||||
NavigationDropTargetContext,
|
||||
);
|
||||
|
||||
const flatItems = items.filter((item) => !isDefined(item.folderId));
|
||||
const isAddToNavigationDropTargetVisible =
|
||||
addToNavigationFallbackDestination?.droppableId ===
|
||||
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS;
|
||||
const folderChildrenById = items.reduce<
|
||||
Map<string, ProcessedNavigationMenuItem[]>
|
||||
>((acc, item) => {
|
||||
@@ -140,7 +154,10 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
};
|
||||
};
|
||||
|
||||
if (flatItems.length === 0) {
|
||||
const isAddMenuItemButtonVisible =
|
||||
isEditMode && isDefined(onAddMenuItem) && !isDragging;
|
||||
|
||||
if (flatItems.length === 0 && !isAddToNavigationDropTargetVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -154,7 +171,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
alwaysShowRightIcon={isEditMode}
|
||||
/>
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
{isNavigationSectionOpen && (
|
||||
{(isNavigationSectionOpen || isAddToNavigationDropTargetVisible) && (
|
||||
<Droppable
|
||||
droppableId={
|
||||
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS
|
||||
@@ -162,7 +179,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
isDropDisabled={workspaceDropDisabled}
|
||||
>
|
||||
{(provided) => (
|
||||
<div
|
||||
<StyledWorkspaceDroppableList
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
@@ -189,6 +206,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
<WorkspaceNavigationMenuItemsFolder
|
||||
folderId={item.id}
|
||||
folderName={item.name ?? 'Folder'}
|
||||
folderIconKey={item.Icon}
|
||||
navigationMenuItems={
|
||||
folderChildrenById.get(item.id) ?? []
|
||||
}
|
||||
@@ -308,9 +326,9 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
folderId={null}
|
||||
index={filteredItems.length}
|
||||
sectionId={NavigationSections.WORKSPACE}
|
||||
compact={!!(isEditMode && onAddMenuItem)}
|
||||
compact={!isAddMenuItemButtonVisible}
|
||||
>
|
||||
{isEditMode && onAddMenuItem && (
|
||||
{isAddMenuItemButtonVisible && (
|
||||
<NavigationDrawerItem
|
||||
Icon={IconPlus}
|
||||
label={t`Add menu item`}
|
||||
@@ -319,8 +337,19 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
/>
|
||||
)}
|
||||
</NavigationItemDropTarget>
|
||||
{addToNavigationFallbackDestination?.droppableId ===
|
||||
NavigationMenuItemDroppableIds.WORKSPACE_ORPHAN_NAVIGATION_MENU_ITEMS &&
|
||||
addToNavigationFallbackDestination.index >
|
||||
filteredItems.length && (
|
||||
<NavigationItemDropTarget
|
||||
folderId={null}
|
||||
index={addToNavigationFallbackDestination.index}
|
||||
sectionId={NavigationSections.WORKSPACE}
|
||||
compact
|
||||
/>
|
||||
)}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
</StyledWorkspaceDroppableList>
|
||||
)}
|
||||
</Droppable>
|
||||
)}
|
||||
|
||||
+25
-3
@@ -1,9 +1,11 @@
|
||||
import { COMMAND_MENU_SEARCH_BAR_HEIGHT_MOBILE } from '@/command-menu/constants/CommandMenuSearchBarHeightMobile';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { isCommandMenuOpenedStateV2 } from '@/command-menu/states/isCommandMenuOpenedStateV2';
|
||||
import { isNavigationMenuInEditModeStateV2 } from '@/navigation-menu-item/states/isNavigationMenuInEditModeStateV2';
|
||||
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
||||
import { PAGE_HEADER_COMMAND_MENU_BUTTON_CLICK_OUTSIDE_ID } from '@/ui/layout/page-header/constants/PageHeaderCommandMenuButtonClickOutsideId';
|
||||
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { t } from '@lingui/core/macro';
|
||||
@@ -12,8 +14,20 @@ import { AppTooltip, TooltipDelay, TooltipPosition } from 'twenty-ui/display';
|
||||
import { AnimatedButton } from 'twenty-ui/input';
|
||||
import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
const StyledButtonWrapper = styled.div`
|
||||
const StyledButtonWrapper = styled.div<{
|
||||
alignWithCommandMenuTopBar: boolean;
|
||||
}>`
|
||||
z-index: ${RootStackingContextZIndices.CommandMenuButton};
|
||||
${({ alignWithCommandMenuTopBar, theme }) =>
|
||||
alignWithCommandMenuTopBar &&
|
||||
css`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: ${COMMAND_MENU_SEARCH_BAR_HEIGHT_MOBILE}px;
|
||||
position: fixed;
|
||||
right: ${theme.spacing(3)};
|
||||
top: 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledTooltipWrapper = styled.div`
|
||||
@@ -109,9 +123,15 @@ const AnimatedIcon = ({
|
||||
export const PageHeaderToggleCommandMenuButton = () => {
|
||||
const { toggleCommandMenu } = useCommandMenu();
|
||||
const isCommandMenuOpened = useRecoilValueV2(isCommandMenuOpenedStateV2);
|
||||
const isNavigationMenuInEditMode = useRecoilValueV2(
|
||||
isNavigationMenuInEditModeStateV2,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const alignWithCommandMenuTopBar =
|
||||
isMobile && isNavigationMenuInEditMode && isCommandMenuOpened;
|
||||
|
||||
const ariaLabel = isCommandMenuOpened
|
||||
? t`Close command menu`
|
||||
: t`Open command menu`;
|
||||
@@ -119,7 +139,9 @@ export const PageHeaderToggleCommandMenuButton = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledButtonWrapper>
|
||||
<StyledButtonWrapper
|
||||
alignWithCommandMenuTopBar={alignWithCommandMenuTopBar}
|
||||
>
|
||||
<div id="toggle-command-menu-button">
|
||||
<AnimatedButton
|
||||
animatedSvg={
|
||||
|
||||
+1
@@ -356,6 +356,7 @@ export const NavigationDrawerItem = ({
|
||||
as={
|
||||
to ? (isExternalLink ? 'a' : Link) : rightOptions ? 'div' : undefined
|
||||
}
|
||||
role={to ? undefined : rightOptions ? 'button' : undefined}
|
||||
to={isExternalLink ? undefined : to}
|
||||
href={isExternalLink ? to : undefined}
|
||||
target={isExternalLink ? '_blank' : undefined}
|
||||
|
||||
+8
-4
@@ -284,8 +284,9 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen
|
||||
folderId: null,
|
||||
folderUniversalIdentifier: null,
|
||||
name: favoriteFolder.name,
|
||||
position: favoriteFolder.position,
|
||||
link: null,
|
||||
icon: null,
|
||||
position: favoriteFolder.position,
|
||||
workspaceId,
|
||||
applicationId: workspaceCustomApplicationId,
|
||||
applicationUniversalIdentifier:
|
||||
@@ -317,8 +318,9 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen
|
||||
folderId: null,
|
||||
folderUniversalIdentifier: null,
|
||||
name: favoriteFolder.name,
|
||||
position: favoriteFolder.position,
|
||||
link: null,
|
||||
icon: null,
|
||||
position: favoriteFolder.position,
|
||||
workspaceId,
|
||||
applicationId: workspaceCustomApplicationId,
|
||||
applicationUniversalIdentifier:
|
||||
@@ -523,8 +525,9 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen
|
||||
folderId,
|
||||
folderUniversalIdentifier: folderId,
|
||||
name: null,
|
||||
position: favorite.position,
|
||||
link: null,
|
||||
icon: null,
|
||||
position: favorite.position,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
@@ -569,8 +572,9 @@ export class MigrateFavoritesToNavigationMenuItemsCommand extends ActiveOrSuspen
|
||||
folderId,
|
||||
folderUniversalIdentifier: folderId,
|
||||
name: null,
|
||||
position: favorite.position,
|
||||
link: null,
|
||||
icon: null,
|
||||
position: favorite.position,
|
||||
workspaceId,
|
||||
applicationId: workspaceCustomApplicationId,
|
||||
applicationUniversalIdentifier:
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddIconToNavigationMenuItem1771247783542
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddIconToNavigationMenuItem1771247783542';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."navigationMenuItem" ADD "icon" text`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."navigationMenuItem" DROP COLUMN "icon"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
@@ -16,6 +16,7 @@ export const fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem =
|
||||
universalIdentifier: navigationMenuItemManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
name: navigationMenuItemManifest.name ?? null,
|
||||
icon: navigationMenuItemManifest.icon ?? null,
|
||||
position: navigationMenuItemManifest.position,
|
||||
viewUniversalIdentifier:
|
||||
navigationMenuItemManifest.viewUniversalIdentifier ?? null,
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should match snapshot 1`] = `
|
||||
{
|
||||
@@ -103,6 +103,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"folderUniversalIdentifier",
|
||||
"name",
|
||||
"link",
|
||||
"icon",
|
||||
],
|
||||
"propertiesToStringify": [],
|
||||
},
|
||||
|
||||
+1
@@ -1019,6 +1019,7 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
},
|
||||
name: { toCompare: true, toStringify: false, universalProperty: undefined },
|
||||
link: { toCompare: true, toStringify: false, universalProperty: undefined },
|
||||
icon: { toCompare: true, toStringify: false, universalProperty: undefined },
|
||||
createdAt: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
|
||||
+1
@@ -5,4 +5,5 @@ export const FLAT_NAVIGATION_MENU_ITEM_EDITABLE_PROPERTIES = [
|
||||
'folderId',
|
||||
'name',
|
||||
'link',
|
||||
'icon',
|
||||
] as const satisfies MetadataEntityPropertyName<'navigationMenuItem'>[];
|
||||
|
||||
+1
@@ -81,6 +81,7 @@ export const fromCreateNavigationMenuItemInputToFlatNavigationMenuItemToCreate =
|
||||
folderUniversalIdentifier,
|
||||
name: createNavigationMenuItemInput.name ?? null,
|
||||
link: createNavigationMenuItemInput.link ?? null,
|
||||
icon: createNavigationMenuItemInput.icon ?? null,
|
||||
position,
|
||||
workspaceId,
|
||||
applicationId: flatApplication.id,
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ export const fromFlatNavigationMenuItemToNavigationMenuItemDto = (
|
||||
folderId: flatNavigationMenuItem.folderId ?? undefined,
|
||||
name: flatNavigationMenuItem.name ?? undefined,
|
||||
link: flatNavigationMenuItem.link ?? undefined,
|
||||
icon: flatNavigationMenuItem.icon ?? undefined,
|
||||
position: flatNavigationMenuItem.position,
|
||||
workspaceId: flatNavigationMenuItem.workspaceId,
|
||||
applicationId: flatNavigationMenuItem.applicationId ?? undefined,
|
||||
|
||||
+1
@@ -82,6 +82,7 @@ export const fromNavigationMenuItemEntityToFlatNavigationMenuItem = ({
|
||||
folderId: navigationMenuItemEntity.folderId,
|
||||
name: navigationMenuItemEntity.name,
|
||||
link: navigationMenuItemEntity.link,
|
||||
icon: navigationMenuItemEntity.icon,
|
||||
position: navigationMenuItemEntity.position,
|
||||
workspaceId: navigationMenuItemEntity.workspaceId,
|
||||
universalIdentifier: navigationMenuItemEntity.universalIdentifier,
|
||||
|
||||
+5
@@ -36,6 +36,11 @@ export class CreateNavigationMenuItemInput {
|
||||
@Field(() => String, { nullable: true })
|
||||
link?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Field(() => String, { nullable: true })
|
||||
icon?: string | null;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
|
||||
+4
@@ -47,6 +47,10 @@ export class NavigationMenuItemDTO {
|
||||
@Field(() => String, { nullable: true })
|
||||
link?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@Field(() => String, { nullable: true })
|
||||
icon?: string | null;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
|
||||
+5
@@ -35,6 +35,11 @@ export class UpdateNavigationMenuItemInput {
|
||||
@IsString()
|
||||
@Field(() => String, { nullable: true })
|
||||
link?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@Field(() => String, { nullable: true })
|
||||
icon?: string | null;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
|
||||
+3
@@ -84,6 +84,9 @@ export class NavigationMenuItemEntity
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
link: string | null;
|
||||
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
icon: string | null;
|
||||
|
||||
@ManyToOne(() => NavigationMenuItemEntity, {
|
||||
onDelete: 'CASCADE',
|
||||
nullable: true,
|
||||
|
||||
+2
-1
@@ -898,8 +898,9 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
folderId: null,
|
||||
folderUniversalIdentifier: null,
|
||||
name: null,
|
||||
position: nextPosition,
|
||||
link: null,
|
||||
icon: null,
|
||||
position: nextPosition,
|
||||
workspaceId,
|
||||
applicationId: workspaceCustomApplicationId,
|
||||
applicationUniversalIdentifier:
|
||||
|
||||
+5
@@ -86,6 +86,11 @@ export const seedFeatureFlags = async ({
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_MARKETPLACE_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
+1
@@ -40,6 +40,7 @@ export const STANDARD_NAVIGATION_MENU_ITEMS = {
|
||||
workflowsFolder: {
|
||||
universalIdentifier: '20202020-b007-4b07-8b07-c0aba11c0007',
|
||||
name: 'Workflows',
|
||||
icon: 'IconSettingsAutomation',
|
||||
position: 6,
|
||||
},
|
||||
workflowsFolderAllWorkflows: {
|
||||
|
||||
+1
@@ -77,6 +77,7 @@ export const buildStandardFlatNavigationMenuItemMaps = ({
|
||||
const workflowsFolder = createStandardNavigationMenuItemFolderFlatMetadata({
|
||||
universalIdentifier: workflowsFolderDefinition.universalIdentifier,
|
||||
name: workflowsFolderDefinition.name,
|
||||
icon: workflowsFolderDefinition.icon,
|
||||
position: workflowsFolderDefinition.position,
|
||||
navigationMenuItemId: workflowsFolderId,
|
||||
workspaceId,
|
||||
|
||||
+2
-1
@@ -64,8 +64,9 @@ export const createStandardNavigationMenuItemFlatMetadata = ({
|
||||
folderId: null,
|
||||
folderUniversalIdentifier: null,
|
||||
name: null,
|
||||
position,
|
||||
link: null,
|
||||
icon: null,
|
||||
position,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
+5
-1
@@ -7,6 +7,7 @@ import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty
|
||||
export const createStandardNavigationMenuItemFolderFlatMetadata = ({
|
||||
universalIdentifier,
|
||||
name,
|
||||
icon,
|
||||
position,
|
||||
navigationMenuItemId,
|
||||
workspaceId,
|
||||
@@ -15,6 +16,7 @@ export const createStandardNavigationMenuItemFolderFlatMetadata = ({
|
||||
}: {
|
||||
universalIdentifier: string;
|
||||
name: string;
|
||||
icon?: string | null;
|
||||
position: number;
|
||||
navigationMenuItemId: string;
|
||||
workspaceId: string;
|
||||
@@ -36,8 +38,9 @@ export const createStandardNavigationMenuItemFolderFlatMetadata = ({
|
||||
folderId: null,
|
||||
folderUniversalIdentifier: null,
|
||||
name,
|
||||
position,
|
||||
link: null,
|
||||
icon: icon ?? null,
|
||||
position,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
@@ -95,6 +98,7 @@ export const createStandardNavigationMenuItemFolderItemFlatMetadata = ({
|
||||
folderUniversalIdentifier,
|
||||
name: null,
|
||||
link: null,
|
||||
icon: null,
|
||||
position,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsT
|
||||
|
||||
export type NavigationMenuItemManifest = SyncableEntityOptions & {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
position: number;
|
||||
viewUniversalIdentifier?: string;
|
||||
link?: string;
|
||||
|
||||
Reference in New Issue
Block a user