feat: add color property to ObjectMetadata for object icon customization (#18672)
## Summary - Adds a `color` column to `ObjectMetadataEntity` with full GraphQL support so object icon colors are persisted at the metadata level - Adds a `type` column to `NavigationMenuItemEntity` (enum: `OBJECT`, `VIEW`, `FOLDER`, `LINK`, `RECORD`) replacing field-based type inference - Updates frontend to read object colors from `objectMetadata.color` (falling back to standard defaults) in the sidebar nav, record index header, and record show breadcrumb - Simplifies `NavigationMenuItemIcon` color resolution via `getEffectiveNavigationMenuItemColor` util ## Color rules | Item type | Color source | Editable in sidebar? | |-----------|-------------|---------------------| | **Object** | `objectMetadata.color` | Yes — persisted to `objectMetadata.color` on Save | | **Folder** | `navigationMenuItem.color` | Yes | | **Link** | Fixed default (`DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK`) | No | | **View** | `objectMetadata.color` (from the parent object) | No | | **Record** | None | No | - **Object** items represent the whole object (e.g. "Companies") and point to the INDEX view. Changing their color updates `objectMetadata.color` via `useSaveObjectMetadataColorsFromDraft`. - **View** items represent specific non-INDEX views. Their color comes from the parent object's metadata (read-only). - Only **folders** store their color on `navigationMenuItem.color` — enforced by `hasNavigationMenuItemOwnColor` util. - `getEffectiveNavigationMenuItemColor` returns `objectColor` for both OBJECT and VIEW items, folder's own color for folders, and the fixed default for links. ## NavigationMenuItemType enum - Shared enum created in `twenty-shared` with values: `OBJECT`, `VIEW`, `FOLDER`, `LINK`, `RECORD` - Registered as a GraphQL enum on the backend - Replaces string literals across entity, DTOs, input, converters, and frontend hooks - Migration backfills existing rows: INDEX views → `OBJECT`, non-INDEX views → `VIEW`, based on join with the view table ## Design decisions - **OBJECT vs VIEW distinction**: Items pointing to INDEX views are typed as `OBJECT` (represent the whole object, color editable). Items pointing to non-INDEX views are typed as `VIEW` (specific view, color read-only from parent object). - **Dual color storage**: `navigationMenuItem.color` is preserved for folders only. Objects use `objectMetadata.color` as their source of truth. - **Type discriminator**: The `type` column replaces field-based inference (checking `viewId`, `link`, `targetRecordId` presence) with an explicit enum, simplifying `isNavigationMenuItemLink` / `isNavigationMenuItemFolder` to simple `item.type ===` checks. - **No settings page color picker**: Object color editing is done from the sidebar edit panel, not the data model settings page. ## Test plan - [ ] Verify objects display their default standard colors in the sidebar - [ ] Verify object color editing works in the sidebar edit panel (persists to objectMetadata.color) - [ ] Verify folder color editing works in the sidebar edit panel - [ ] Verify views, links, and records do NOT show a color picker in the sidebar edit panel - [ ] Run `npx nx typecheck twenty-front` and `npx nx typecheck twenty-server` - [ ] Verify the database migrations add `color` to `objectMetadata` and `type` to `navigationMenuItem` Made with [Cursor](https://cursor.com)
This commit is contained in:
+8
-10
@@ -1,15 +1,13 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useContext, type ReactNode } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconGripVertical, type IconComponent } from 'twenty-ui/display';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
|
||||
import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER } from '@/navigation-menu-item/constants/NavigationMenuItemDefaultColorFolder';
|
||||
import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK } from '@/navigation-menu-item/constants/NavigationMenuItemDefaultColorLink';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import type { AddToNavigationDragPayload } from '@/navigation-menu-item/types/add-to-navigation-drag-payload';
|
||||
import { getEffectiveNavigationMenuItemColor } from '@/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor';
|
||||
|
||||
const StyledIconSlot = styled.div<{
|
||||
$hasFixedSize: boolean;
|
||||
@@ -83,14 +81,14 @@ export const AddToNavigationDragHandle = ({
|
||||
disableDrag = false,
|
||||
}: AddToNavigationDragHandleProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const effectiveColor =
|
||||
payload.type === 'object' && isNonEmptyString(payload.iconColor)
|
||||
const objectColor =
|
||||
payload.type === NavigationMenuItemType.OBJECT
|
||||
? payload.iconColor
|
||||
: payload.type === 'folder'
|
||||
? DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER
|
||||
: payload.type === 'link'
|
||||
? DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK
|
||||
: undefined;
|
||||
: undefined;
|
||||
const effectiveColor = getEffectiveNavigationMenuItemColor(
|
||||
{ itemType: payload.type as NavigationMenuItemType },
|
||||
objectColor,
|
||||
);
|
||||
const hasBackgroundColor =
|
||||
payload.type !== NavigationMenuItemType.RECORD &&
|
||||
isDefined(effectiveColor) &&
|
||||
|
||||
+5
-3
@@ -8,8 +8,8 @@ import { StyledNavigationMenuItemIconContainer } from '@/navigation-menu-item/co
|
||||
import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/ObjectIconWithViewOverlay';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useObjectNavItemColor } from '@/navigation-menu-item/hooks/useObjectNavItemColor';
|
||||
import { getNavigationMenuItemIconStyleFromColor } from '@/navigation-menu-item/utils/getNavigationMenuItemIconStyleFromColor';
|
||||
import { getEffectiveNavigationMenuItemColor } from '@/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor';
|
||||
import { getNavigationMenuItemIconStyleFromColor } from '@/navigation-menu-item/utils/getNavigationMenuItemIconStyleFromColor';
|
||||
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
|
||||
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
@@ -73,8 +73,10 @@ export const NavigationMenuItemIcon = ({
|
||||
const iconToUse =
|
||||
StandardIcon ??
|
||||
(navigationMenuItem.Icon ? getIcon(navigationMenuItem.Icon) : undefined);
|
||||
const effectiveColor =
|
||||
getEffectiveNavigationMenuItemColor(navigationMenuItem);
|
||||
const effectiveColor = getEffectiveNavigationMenuItemColor(
|
||||
navigationMenuItem,
|
||||
objectNavItemColor,
|
||||
);
|
||||
const useStyledIcon = !isRecord && isNonEmptyString(effectiveColor);
|
||||
const iconStyle = useStyledIcon
|
||||
? getNavigationMenuItemIconStyleFromColor(effectiveColor)
|
||||
|
||||
+1
@@ -39,6 +39,7 @@ export const WorkspaceNavigationMenuItemFolderSubItem = ({
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
|
||||
const views = useAtomStateValue(viewsSelector);
|
||||
const objectMetadataItem =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.OBJECT ||
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW ||
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.RECORD
|
||||
? getObjectMetadataForNavigationMenuItem(
|
||||
|
||||
+6
-3
@@ -28,7 +28,7 @@ import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils
|
||||
import { preloadWorkspaceDndKit } from '@/navigation/preloadWorkspaceDndKit';
|
||||
import { NavigationDrawerSectionForWorkspaceItems } from '@/object-metadata/components/NavigationDrawerSectionForWorkspaceItems';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState';
|
||||
import { navigationMenuItemsSelector } from '@/navigation-menu-item/states/navigationMenuItemsSelector';
|
||||
import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -47,7 +47,9 @@ export const WorkspaceNavigationMenuItems = () => {
|
||||
const { workspaceNavigationMenuItemsSorted } = useSortedNavigationMenuItems();
|
||||
const store = useStore();
|
||||
const enterEditMode = () => {
|
||||
const currentNavigationMenuItems = store.get(navigationMenuItemsState.atom);
|
||||
const currentNavigationMenuItems = store.get(
|
||||
navigationMenuItemsSelector.atom,
|
||||
);
|
||||
const workspaceNavigationMenuItems = filterWorkspaceNavigationMenuItems(
|
||||
currentNavigationMenuItems,
|
||||
);
|
||||
@@ -106,7 +108,8 @@ export const WorkspaceNavigationMenuItems = () => {
|
||||
if (objectMetadataItem) {
|
||||
openNavigationMenuItemInSidePanel({
|
||||
pageTitle:
|
||||
item.itemType === NavigationMenuItemType.VIEW
|
||||
item.itemType === NavigationMenuItemType.VIEW ||
|
||||
item.itemType === NavigationMenuItemType.OBJECT
|
||||
? item.labelIdentifier
|
||||
: objectMetadataItem.labelSingular,
|
||||
pageIcon: getIcon(objectMetadataItem.icon),
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
export enum NavigationMenuItemType {
|
||||
FOLDER = 'folder',
|
||||
LINK = 'link',
|
||||
OBJECT = 'object',
|
||||
RECORD = 'record',
|
||||
VIEW = 'view',
|
||||
FOLDER = 'FOLDER',
|
||||
LINK = 'LINK',
|
||||
OBJECT = 'OBJECT',
|
||||
RECORD = 'RECORD',
|
||||
VIEW = 'VIEW',
|
||||
}
|
||||
|
||||
+1
@@ -3,6 +3,7 @@ import { gql } from '@apollo/client';
|
||||
export const NAVIGATION_MENU_ITEM_FRAGMENT = gql`
|
||||
fragment NavigationMenuItemFields on NavigationMenuItem {
|
||||
id
|
||||
type
|
||||
userWorkspaceId
|
||||
targetRecordId
|
||||
targetObjectMetadataId
|
||||
|
||||
+2
@@ -1,3 +1,4 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
@@ -37,6 +38,7 @@ export const useAddFolderToNavigationMenuDraft = () => {
|
||||
const newItem: NavigationMenuItem = {
|
||||
__typename: 'NavigationMenuItem',
|
||||
id: newItemId,
|
||||
type: NavigationMenuItemType.FOLDER,
|
||||
viewId: undefined,
|
||||
targetObjectMetadataId: undefined,
|
||||
targetRecordId: undefined,
|
||||
|
||||
+2
@@ -1,3 +1,4 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
@@ -40,6 +41,7 @@ export const useAddLinkToNavigationMenuDraft = () => {
|
||||
const newItem: NavigationMenuItem = {
|
||||
__typename: 'NavigationMenuItem',
|
||||
id: newItemId,
|
||||
type: NavigationMenuItemType.LINK,
|
||||
viewId: undefined,
|
||||
targetObjectMetadataId: undefined,
|
||||
targetRecordId: undefined,
|
||||
|
||||
+3
-2
@@ -1,3 +1,4 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
@@ -13,7 +14,6 @@ export const useAddObjectToNavigationMenuDraft = () => {
|
||||
|
||||
const addObjectToDraft = (
|
||||
objectMetadataId: string,
|
||||
defaultViewId: string,
|
||||
currentDraft: NavigationMenuItem[],
|
||||
targetFolderId?: string | null,
|
||||
targetIndex?: number,
|
||||
@@ -38,7 +38,8 @@ export const useAddObjectToNavigationMenuDraft = () => {
|
||||
const newItem: NavigationMenuItem = {
|
||||
__typename: 'NavigationMenuItem',
|
||||
id: newItemId,
|
||||
viewId: defaultViewId,
|
||||
type: NavigationMenuItemType.OBJECT,
|
||||
viewId: undefined,
|
||||
targetObjectMetadataId: objectMetadataId,
|
||||
position,
|
||||
userWorkspaceId: undefined,
|
||||
|
||||
+2
@@ -1,4 +1,5 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
@@ -60,6 +61,7 @@ export const useAddRecordToNavigationMenuDraft = () => {
|
||||
const newItem: NavigationMenuItem = {
|
||||
__typename: 'NavigationMenuItem',
|
||||
id: newItemId,
|
||||
type: NavigationMenuItemType.RECORD,
|
||||
viewId: undefined,
|
||||
targetObjectMetadataId: objectMetadataId,
|
||||
targetRecordId: searchRecord.recordId,
|
||||
|
||||
+2
@@ -1,3 +1,4 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import type { NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
@@ -37,6 +38,7 @@ export const useAddViewToNavigationMenuDraft = () => {
|
||||
const newItem: NavigationMenuItem = {
|
||||
__typename: 'NavigationMenuItem',
|
||||
id: newItemId,
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
viewId,
|
||||
targetObjectMetadataId: undefined,
|
||||
position,
|
||||
|
||||
+3
@@ -1,3 +1,4 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { CreateNavigationMenuItemDocument } from '~/generated-metadata/graphql';
|
||||
@@ -42,6 +43,7 @@ export const useCreateNavigationMenuItem = () => {
|
||||
await createNavigationMenuItemMutation({
|
||||
variables: {
|
||||
input: {
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
viewId: targetRecord.id,
|
||||
userWorkspaceId: currentWorkspaceMemberId,
|
||||
folderId,
|
||||
@@ -75,6 +77,7 @@ export const useCreateNavigationMenuItem = () => {
|
||||
await createNavigationMenuItemMutation({
|
||||
variables: {
|
||||
input: {
|
||||
type: NavigationMenuItemType.RECORD,
|
||||
targetRecordId: targetRecord.id,
|
||||
targetObjectMetadataId: objectMetadataItem.id,
|
||||
userWorkspaceId: currentWorkspaceMemberId,
|
||||
|
||||
+2
@@ -1,3 +1,4 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { CreateNavigationMenuItemDocument } from '~/generated-metadata/graphql';
|
||||
@@ -40,6 +41,7 @@ export const useCreateNavigationMenuItemFolder = () => {
|
||||
await createNavigationMenuItemMutation({
|
||||
variables: {
|
||||
input: {
|
||||
type: NavigationMenuItemType.FOLDER,
|
||||
name,
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
|
||||
-1
@@ -139,7 +139,6 @@ export const useHandleAddToNavigationDrop = () => {
|
||||
);
|
||||
const newItemId = addObjectToDraft(
|
||||
payload.objectMetadataId,
|
||||
payload.defaultViewId,
|
||||
currentDraft,
|
||||
folderId,
|
||||
index,
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
|
||||
import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState';
|
||||
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
|
||||
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems';
|
||||
import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState';
|
||||
import { navigationMenuItemsSelector } from '@/navigation-menu-item/states/navigationMenuItemsSelector';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -17,7 +17,7 @@ type NavigationMenuItemsData = {
|
||||
export const useNavigationMenuItemsData = (): NavigationMenuItemsData => {
|
||||
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
|
||||
const currentWorkspaceMemberId = currentWorkspaceMember?.id;
|
||||
const navigationMenuItems = useAtomStateValue(navigationMenuItemsState);
|
||||
const navigationMenuItems = useAtomStateValue(navigationMenuItemsSelector);
|
||||
const isNavigationMenuInEditMode = useAtomStateValue(
|
||||
isNavigationMenuInEditModeState,
|
||||
);
|
||||
|
||||
+2
-2
@@ -4,14 +4,14 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState';
|
||||
import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils/filterWorkspaceNavigationMenuItems';
|
||||
import { navigationMenuItemsDraftState } from '@/navigation-menu-item/states/navigationMenuItemsDraftState';
|
||||
import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState';
|
||||
import { navigationMenuItemsSelector } from '@/navigation-menu-item/states/navigationMenuItemsSelector';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
export const useNavigationMenuItemsDraftState = () => {
|
||||
const isNavigationMenuInEditMode = useAtomStateValue(
|
||||
isNavigationMenuInEditModeState,
|
||||
);
|
||||
const navigationMenuItems = useAtomStateValue(navigationMenuItemsState);
|
||||
const navigationMenuItems = useAtomStateValue(navigationMenuItemsSelector);
|
||||
const navigationMenuItemsDraft = useAtomStateValue(
|
||||
navigationMenuItemsDraftState,
|
||||
);
|
||||
|
||||
+15
-12
@@ -1,17 +1,20 @@
|
||||
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
|
||||
export const useObjectNavItemColor = (objectNameSingular: string): string => {
|
||||
const items = useWorkspaceSectionItems();
|
||||
const objectNavItem = items.find(
|
||||
(item) =>
|
||||
'viewKey' in item &&
|
||||
item.viewKey === ViewKey.INDEX &&
|
||||
item.objectNameSingular === objectNameSingular,
|
||||
);
|
||||
return (
|
||||
(objectNavItem && 'color' in objectNavItem ? objectNavItem.color : null) ??
|
||||
getStandardObjectIconColor(objectNameSingular)
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === objectNameSingular,
|
||||
);
|
||||
|
||||
const storedColor = objectMetadataItem?.color;
|
||||
const fallbackColor = getStandardObjectIconColor(objectNameSingular);
|
||||
|
||||
if (isNonEmptyString(storedColor)) {
|
||||
return storedColor;
|
||||
}
|
||||
|
||||
return fallbackColor;
|
||||
};
|
||||
|
||||
+46
-3
@@ -1,4 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
@@ -12,7 +13,9 @@ import { filterWorkspaceNavigationMenuItems } from '@/navigation-menu-item/utils
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
import { isNavigationMenuItemLink } from '@/navigation-menu-item/utils/isNavigationMenuItemLink';
|
||||
import { orderFoldersForCreation } from '@/navigation-menu-item/utils/orderFoldersForCreation';
|
||||
import { navigationMenuItemsState } from '@/navigation-menu-item/states/navigationMenuItemsState';
|
||||
import { navigationMenuItemsSelector } from '@/navigation-menu-item/states/navigationMenuItemsSelector';
|
||||
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
export const useSaveNavigationMenuItemsDraft = () => {
|
||||
@@ -24,15 +27,46 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
refetchQueries: ['FindManyNavigationMenuItems'],
|
||||
},
|
||||
);
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const saveDraft = useCallback(async () => {
|
||||
const draft = store.get(navigationMenuItemsDraftState.atom);
|
||||
const currentItems = store.get(navigationMenuItemsState.atom);
|
||||
const currentItems = store.get(navigationMenuItemsSelector.atom);
|
||||
|
||||
if (!draft) return;
|
||||
|
||||
const objectMetadataItems = store.get(objectMetadataItemsState.atom);
|
||||
|
||||
for (const draftItem of draft) {
|
||||
if (draftItem.type !== NavigationMenuItemType.OBJECT) {
|
||||
continue;
|
||||
}
|
||||
if (!isDefined(draftItem.targetObjectMetadataId)) {
|
||||
continue;
|
||||
}
|
||||
if (!isDefined(draftItem.color)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === draftItem.targetObjectMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
continue;
|
||||
}
|
||||
if (objectMetadataItem.color === draftItem.color) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await updateOneObjectMetadataItem({
|
||||
idToUpdate: draftItem.targetObjectMetadataId,
|
||||
updatePayload: { color: draftItem.color },
|
||||
});
|
||||
}
|
||||
|
||||
const workspaceItems = filterWorkspaceNavigationMenuItems(currentItems);
|
||||
const topLevelWorkspace = workspaceItems.filter(
|
||||
(item) => !isDefined(item.folderId),
|
||||
@@ -131,13 +165,17 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
const iconChanged =
|
||||
isNavigationMenuItemFolder(draftItem) &&
|
||||
(original.icon ?? null) !== (draftItem.icon ?? null);
|
||||
const colorChanged =
|
||||
isNavigationMenuItemFolder(draftItem) &&
|
||||
(original.color ?? null) !== (draftItem.color ?? null);
|
||||
|
||||
if (
|
||||
positionChanged ||
|
||||
folderIdChanged ||
|
||||
nameChanged ||
|
||||
linkChanged ||
|
||||
iconChanged
|
||||
iconChanged ||
|
||||
colorChanged
|
||||
) {
|
||||
const updateInput: {
|
||||
id: string;
|
||||
@@ -146,6 +184,7 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
name?: string;
|
||||
link?: string | null;
|
||||
icon?: string | null;
|
||||
color?: string | null;
|
||||
} = { id: draftItem.id };
|
||||
|
||||
if (positionChanged) {
|
||||
@@ -174,6 +213,9 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
if (iconChanged && isNavigationMenuItemFolder(draftItem)) {
|
||||
updateInput.icon = draftItem.icon ?? null;
|
||||
}
|
||||
if (colorChanged) {
|
||||
updateInput.color = draftItem.color ?? null;
|
||||
}
|
||||
|
||||
await updateNavigationMenuItem(updateInput);
|
||||
}
|
||||
@@ -182,6 +224,7 @@ export const useSaveNavigationMenuItemsDraft = () => {
|
||||
updateNavigationMenuItem,
|
||||
deleteNavigationMenuItem,
|
||||
createNavigationMenuItemMutation,
|
||||
updateOneObjectMetadataItem,
|
||||
store,
|
||||
]);
|
||||
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ const getLabelForItemType = (
|
||||
return item.name ?? 'Folder';
|
||||
case NavigationMenuItemType.LINK:
|
||||
return item.name ?? 'Link';
|
||||
case NavigationMenuItemType.OBJECT:
|
||||
case NavigationMenuItemType.VIEW:
|
||||
return item.labelIdentifier ?? objectLabelSingular ?? '';
|
||||
default:
|
||||
|
||||
+4
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
import { isNavigationMenuItemLink } from '@/navigation-menu-item/utils/isNavigationMenuItemLink';
|
||||
import { isNavigationMenuItemObject } from '@/navigation-menu-item/utils/isNavigationMenuItemObject';
|
||||
import { recordIdentifierToObjectRecordIdentifier } from '@/navigation-menu-item/utils/recordIdentifierToObjectRecordIdentifier';
|
||||
import { sortNavigationMenuItems } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
@@ -83,6 +84,9 @@ export const useSortedNavigationMenuItems = () => {
|
||||
if (isNavigationMenuItemLink(item)) {
|
||||
return true;
|
||||
}
|
||||
if (isNavigationMenuItemObject(item)) {
|
||||
return isDefined(item.targetObjectMetadataId);
|
||||
}
|
||||
if (isDefined(item.viewId)) {
|
||||
return views.some((view) => view.id === item.viewId);
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@ import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
export const navigationMenuItemsState = createAtomSelector<
|
||||
export const navigationMenuItemsSelector = createAtomSelector<
|
||||
NavigationMenuItem[]
|
||||
>({
|
||||
key: 'navigationMenuItemsState',
|
||||
key: 'navigationMenuItemsSelector',
|
||||
get: ({ get }) => {
|
||||
const entry = get(metadataStoreState, 'navigationMenuItems');
|
||||
|
||||
+5
-6
@@ -1,19 +1,18 @@
|
||||
export type AddToNavigationDragPayloadObject = {
|
||||
type: 'object';
|
||||
type: 'OBJECT';
|
||||
objectMetadataId: string;
|
||||
defaultViewId: string;
|
||||
label: string;
|
||||
iconColor?: string;
|
||||
};
|
||||
|
||||
export type AddToNavigationDragPayloadView = {
|
||||
type: 'view';
|
||||
type: 'VIEW';
|
||||
viewId: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type AddToNavigationDragPayloadRecord = {
|
||||
type: 'record';
|
||||
type: 'RECORD';
|
||||
recordId: string;
|
||||
objectMetadataId: string;
|
||||
objectNameSingular: string;
|
||||
@@ -22,13 +21,13 @@ export type AddToNavigationDragPayloadRecord = {
|
||||
};
|
||||
|
||||
export type AddToNavigationDragPayloadFolder = {
|
||||
type: 'folder';
|
||||
type: 'FOLDER';
|
||||
folderId: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type AddToNavigationDragPayloadLink = {
|
||||
type: 'link';
|
||||
type: 'LINK';
|
||||
linkId: string;
|
||||
name: string;
|
||||
link: string;
|
||||
|
||||
+11
-30
@@ -1,45 +1,26 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
|
||||
describe('isNavigationMenuItemFolder', () => {
|
||||
it('should return true only when item has name and no link/view/record metadata', () => {
|
||||
it('should return true when type is folder', () => {
|
||||
expect(
|
||||
isNavigationMenuItemFolder({
|
||||
name: 'My Folder',
|
||||
link: null,
|
||||
viewId: null,
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
isNavigationMenuItemFolder({ type: NavigationMenuItemType.FOLDER }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when name is missing or when link/view/record is defined', () => {
|
||||
it('should return false for other types', () => {
|
||||
expect(
|
||||
isNavigationMenuItemFolder({
|
||||
name: undefined,
|
||||
link: null,
|
||||
viewId: null,
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
isNavigationMenuItemFolder({ type: NavigationMenuItemType.LINK }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isNavigationMenuItemFolder({
|
||||
name: 'My Folder',
|
||||
link: 'https://example.com',
|
||||
viewId: null,
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
isNavigationMenuItemFolder({ type: NavigationMenuItemType.VIEW }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isNavigationMenuItemFolder({
|
||||
name: 'My Folder',
|
||||
link: null,
|
||||
viewId: 'view-1',
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
isNavigationMenuItemFolder({ type: NavigationMenuItemType.RECORD }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isNavigationMenuItemFolder({ type: NavigationMenuItemType.OBJECT }),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
+9
-43
@@ -1,60 +1,26 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
|
||||
import { isNavigationMenuItemLink } from '@/navigation-menu-item/utils/isNavigationMenuItemLink';
|
||||
|
||||
describe('isNavigationMenuItemLink', () => {
|
||||
it('should return true only when item has non-empty link and no view/record metadata', () => {
|
||||
it('should return true when type is link', () => {
|
||||
expect(
|
||||
isNavigationMenuItemLink({
|
||||
link: 'https://example.com',
|
||||
viewId: null,
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
isNavigationMenuItemLink({ type: NavigationMenuItemType.LINK }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when link is missing, empty or only whitespace', () => {
|
||||
it('should return false for other types', () => {
|
||||
expect(
|
||||
isNavigationMenuItemLink({
|
||||
link: '',
|
||||
viewId: null,
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
isNavigationMenuItemLink({ type: NavigationMenuItemType.FOLDER }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isNavigationMenuItemLink({
|
||||
link: ' ',
|
||||
viewId: null,
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
isNavigationMenuItemLink({ type: NavigationMenuItemType.VIEW }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isNavigationMenuItemLink({
|
||||
link: undefined,
|
||||
viewId: null,
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when viewId, targetRecordId or targetObjectMetadataId is defined', () => {
|
||||
expect(
|
||||
isNavigationMenuItemLink({
|
||||
link: 'https://example.com',
|
||||
viewId: 'view-1',
|
||||
targetRecordId: null,
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
isNavigationMenuItemLink({ type: NavigationMenuItemType.RECORD }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isNavigationMenuItemLink({
|
||||
link: 'https://example.com',
|
||||
viewId: null,
|
||||
targetRecordId: 'record-1',
|
||||
targetObjectMetadataId: null,
|
||||
}),
|
||||
isNavigationMenuItemLink({ type: NavigationMenuItemType.OBJECT }),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
+4
@@ -1,3 +1,5 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
|
||||
import { sortNavigationMenuItems } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { type ObjectRecordIdentifier } from '@/object-record/types/ObjectRecordIdentifier';
|
||||
@@ -324,6 +326,7 @@ describe('sortNavigationMenuItems', () => {
|
||||
[
|
||||
{
|
||||
id: 'link-1',
|
||||
type: NavigationMenuItemType.LINK,
|
||||
link: 'https://example.com',
|
||||
name: 'My Link',
|
||||
position: 1,
|
||||
@@ -341,6 +344,7 @@ describe('sortNavigationMenuItems', () => {
|
||||
[
|
||||
{
|
||||
id: 'link-2',
|
||||
type: NavigationMenuItemType.LINK,
|
||||
link: 'example.com',
|
||||
position: 2,
|
||||
} as NavigationMenuItem,
|
||||
|
||||
+5
@@ -7,12 +7,14 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/utils/isNavigationMenuItemFolder';
|
||||
import { isNavigationMenuItemLink } from '@/navigation-menu-item/utils/isNavigationMenuItemLink';
|
||||
import { isNavigationMenuItemObject } from '@/navigation-menu-item/utils/isNavigationMenuItemObject';
|
||||
|
||||
export const buildCreateNavigationMenuItemInput = (
|
||||
draftItem: NavigationMenuItem,
|
||||
resolveFolderId: (draftFolderId: string) => string,
|
||||
): CreateNavigationMenuItemInput => {
|
||||
const input: CreateNavigationMenuItemInput = {
|
||||
type: draftItem.type,
|
||||
position: draftItem.position,
|
||||
};
|
||||
|
||||
@@ -28,6 +30,9 @@ export const buildCreateNavigationMenuItemInput = (
|
||||
: linkUrl
|
||||
? `https://${linkUrl}`
|
||||
: undefined;
|
||||
} else if (isNavigationMenuItemObject(draftItem)) {
|
||||
input.targetObjectMetadataId =
|
||||
draftItem.targetObjectMetadataId ?? undefined;
|
||||
} else if (isDefined(draftItem.viewId)) {
|
||||
input.viewId = draftItem.viewId;
|
||||
} else if (isDefined(draftItem.targetRecordId)) {
|
||||
|
||||
+14
-6
@@ -3,16 +3,24 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER } from '@/navigation-menu-item/constants/NavigationMenuItemDefaultColorFolder';
|
||||
import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK } from '@/navigation-menu-item/constants/NavigationMenuItemDefaultColorLink';
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
|
||||
|
||||
export const getEffectiveNavigationMenuItemColor = (
|
||||
navigationMenuItem: ProcessedNavigationMenuItem,
|
||||
navigationMenuItem: {
|
||||
itemType: NavigationMenuItemType;
|
||||
color?: string | null;
|
||||
},
|
||||
objectColor?: string,
|
||||
): string | undefined => {
|
||||
if (isNonEmptyString(navigationMenuItem.color)) {
|
||||
return navigationMenuItem.color;
|
||||
}
|
||||
if (navigationMenuItem.itemType === NavigationMenuItemType.FOLDER) {
|
||||
return DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER;
|
||||
return isNonEmptyString(navigationMenuItem.color)
|
||||
? navigationMenuItem.color
|
||||
: DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER;
|
||||
}
|
||||
if (navigationMenuItem.itemType === NavigationMenuItemType.OBJECT) {
|
||||
return objectColor;
|
||||
}
|
||||
if (navigationMenuItem.itemType === NavigationMenuItemType.VIEW) {
|
||||
return objectColor;
|
||||
}
|
||||
if (navigationMenuItem.itemType === NavigationMenuItemType.LINK) {
|
||||
return DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK;
|
||||
|
||||
+10
@@ -19,6 +19,16 @@ export const getObjectMetadataForNavigationMenuItem = (
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.OBJECT &&
|
||||
isDefined(navigationMenuItem.targetObjectMetadataId)
|
||||
) {
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(meta) => meta.id === navigationMenuItem.targetObjectMetadataId,
|
||||
);
|
||||
return objectMetadataItem ?? null;
|
||||
}
|
||||
|
||||
if (
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW &&
|
||||
isDefined(navigationMenuItem.viewId)
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
|
||||
export const hasNavigationMenuItemOwnColor = (item: { type?: string | null }) =>
|
||||
item.type === NavigationMenuItemType.FOLDER;
|
||||
+4
-3
@@ -6,9 +6,10 @@ export const isLocationMatchingNavigationMenuItem = (
|
||||
currentViewPath: string,
|
||||
navigationMenuItem: Pick<ProcessedNavigationMenuItem, 'itemType' | 'link'>,
|
||||
) => {
|
||||
const isViewItem =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW;
|
||||
return isViewItem
|
||||
const isViewBasedItem =
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.VIEW ||
|
||||
navigationMenuItem.itemType === NavigationMenuItemType.OBJECT;
|
||||
return isViewBasedItem
|
||||
? navigationMenuItem.link === currentViewPath
|
||||
: navigationMenuItem.link === currentPath;
|
||||
};
|
||||
|
||||
+5
-14
@@ -1,15 +1,6 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
export const isNavigationMenuItemFolder = (item: {
|
||||
name?: string | null;
|
||||
link?: string | null;
|
||||
folderId?: string | null;
|
||||
viewId?: string | null;
|
||||
targetRecordId?: string | null;
|
||||
targetObjectMetadataId?: string | null;
|
||||
}) =>
|
||||
isDefined(item.name) &&
|
||||
!isDefined(item.link) &&
|
||||
!isDefined(item.targetRecordId) &&
|
||||
!isDefined(item.targetObjectMetadataId) &&
|
||||
!isDefined(item.viewId);
|
||||
export const isNavigationMenuItemFolder = (
|
||||
item: Pick<NavigationMenuItem, 'type'>,
|
||||
) => item.type === NavigationMenuItemType.FOLDER;
|
||||
|
||||
+5
-12
@@ -1,13 +1,6 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
export const isNavigationMenuItemLink = (item: {
|
||||
link?: string | null;
|
||||
viewId?: string | null;
|
||||
targetRecordId?: string | null;
|
||||
targetObjectMetadataId?: string | null;
|
||||
}) =>
|
||||
isDefined(item.link) &&
|
||||
(item.link ?? '').trim() !== '' &&
|
||||
!isDefined(item.viewId) &&
|
||||
!isDefined(item.targetRecordId) &&
|
||||
!isDefined(item.targetObjectMetadataId);
|
||||
export const isNavigationMenuItemLink = (
|
||||
item: Pick<NavigationMenuItem, 'type'>,
|
||||
) => item.type === NavigationMenuItemType.LINK;
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { NavigationMenuItemType } from 'twenty-shared/types';
|
||||
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
|
||||
|
||||
export const isNavigationMenuItemObject = (
|
||||
item: Pick<NavigationMenuItem, 'type'>,
|
||||
) => item.type === NavigationMenuItemType.OBJECT;
|
||||
+39
@@ -26,6 +26,45 @@ export const sortNavigationMenuItems = (
|
||||
): ProcessedNavigationMenuItem[] => {
|
||||
return navigationMenuItems
|
||||
.map((navigationMenuItem) => {
|
||||
if (
|
||||
navigationMenuItem.type === NavigationMenuItemType.OBJECT &&
|
||||
isDefined(navigationMenuItem.targetObjectMetadataId)
|
||||
) {
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(meta) => meta.id === navigationMenuItem.targetObjectMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const indexView = views.find(
|
||||
(view) =>
|
||||
view.objectMetadataId === objectMetadataItem.id &&
|
||||
view.key === ViewKey.INDEX,
|
||||
);
|
||||
|
||||
const displayFields: NavigationMenuItemDisplayFields = {
|
||||
labelIdentifier: objectMetadataItem.labelPlural,
|
||||
avatarUrl: '',
|
||||
avatarType: 'icon',
|
||||
link: getAppPath(
|
||||
AppPath.RecordIndexPage,
|
||||
{ objectNamePlural: objectMetadataItem.namePlural },
|
||||
indexView ? { viewId: indexView.id } : {},
|
||||
),
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
Icon: objectMetadataItem.icon ?? 'IconBox',
|
||||
};
|
||||
|
||||
return {
|
||||
...navigationMenuItem,
|
||||
...displayFields,
|
||||
viewKey: ViewKey.INDEX,
|
||||
itemType: NavigationMenuItemType.OBJECT,
|
||||
};
|
||||
}
|
||||
|
||||
if (isDefined(navigationMenuItem.viewId)) {
|
||||
const view = views.find(
|
||||
(view) => view.id === navigationMenuItem.viewId,
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ export const FavoritesDragDropProviderContent = ({
|
||||
destination.droppableId,
|
||||
);
|
||||
const isFolderOverFolder =
|
||||
payload?.type === 'folder' && folderId !== null;
|
||||
payload?.type === 'FOLDER' && folderId !== null;
|
||||
setForbiddenDropTargetId(isFolderOverFolder ? dropTargetId : null);
|
||||
} else {
|
||||
setForbiddenDropTargetId(null);
|
||||
|
||||
@@ -229,7 +229,7 @@ export const useWorkspaceDndKit = (): {
|
||||
resolved.destination.droppableId,
|
||||
);
|
||||
const folderDrag =
|
||||
getPayload()?.type === 'folder' && isDefined(folderId);
|
||||
getPayload()?.type === 'FOLDER' && isDefined(folderId);
|
||||
setForbiddenDropTargetId(
|
||||
folderDrag ? resolved.effectiveDropTargetId : null,
|
||||
);
|
||||
|
||||
@@ -9,5 +9,5 @@ export const isFolderDrag = (
|
||||
payload: AddToNavPayload,
|
||||
sourceItem: NavigationMenuItem | undefined,
|
||||
): boolean =>
|
||||
payload?.type === 'folder' ||
|
||||
payload?.type === 'FOLDER' ||
|
||||
(isDefined(sourceItem) && isNavigationMenuItemFolder(sourceItem));
|
||||
|
||||
+5
-8
@@ -2,7 +2,6 @@ import { ObjectIconWithViewOverlay } from '@/navigation-menu-item/components/Obj
|
||||
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
|
||||
import { useObjectNavItemColor } from '@/navigation-menu-item/hooks/useObjectNavItemColor';
|
||||
import { isNavigationMenuInEditModeState } from '@/navigation-menu-item/states/isNavigationMenuInEditModeState';
|
||||
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
|
||||
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
|
||||
import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
@@ -52,7 +51,9 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
|
||||
const isRecord =
|
||||
navigationMenuItem?.itemType === NavigationMenuItemType.RECORD;
|
||||
const isView = navigationMenuItem?.itemType === NavigationMenuItemType.VIEW;
|
||||
const hasCustomLink = isRecord || isView;
|
||||
const isObject =
|
||||
navigationMenuItem?.itemType === NavigationMenuItemType.OBJECT;
|
||||
const hasCustomLink = isRecord || isView || isObject;
|
||||
|
||||
const navigationPath = hasCustomLink
|
||||
? navigationMenuItem!.link
|
||||
@@ -63,7 +64,7 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
|
||||
);
|
||||
|
||||
const isActive = hasCustomLink
|
||||
? (isView ? currentPathWithSearch : currentPath) ===
|
||||
? (isView || isObject ? currentPathWithSearch : currentPath) ===
|
||||
navigationMenuItem!.link
|
||||
: currentPath ===
|
||||
getAppPath(AppPath.RecordIndexPage, {
|
||||
@@ -114,11 +115,7 @@ export const NavigationDrawerItemForObjectMetadataItem = ({
|
||||
)
|
||||
: getIcon(objectMetadataItem.icon);
|
||||
|
||||
const iconThemeColor = !isRecord
|
||||
? isDefined(navigationMenuItem?.color)
|
||||
? navigationMenuItem.color
|
||||
: (getStandardObjectIconColor(objectMetadataItem.nameSingular) ?? 'gray')
|
||||
: undefined;
|
||||
const iconThemeColor = !isRecord ? objectNavItemColor : undefined;
|
||||
|
||||
const secondaryLabel =
|
||||
isRecord || isViewWithCustomName
|
||||
|
||||
+2
-1
@@ -96,6 +96,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
type === NavigationMenuItemType.OBJECT ||
|
||||
type === NavigationMenuItemType.VIEW ||
|
||||
type === NavigationMenuItemType.RECORD
|
||||
) {
|
||||
@@ -123,7 +124,7 @@ export const NavigationDrawerSectionForWorkspaceItems = ({
|
||||
? () => {
|
||||
const type = item.itemType;
|
||||
const objectMetadataItem =
|
||||
type === 'view' || type === 'record'
|
||||
type === 'OBJECT' || type === 'VIEW' || type === 'RECORD'
|
||||
? getObjectMetadataForNavigationMenuItem(
|
||||
item as ProcessedNavigationMenuItem,
|
||||
objectMetadataItems,
|
||||
|
||||
@@ -8,6 +8,7 @@ export const OBJECT_METADATA_FRAGMENT = gql`
|
||||
namePlural
|
||||
labelSingular
|
||||
labelPlural
|
||||
color
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
|
||||
@@ -10,6 +10,7 @@ export const CREATE_ONE_OBJECT_METADATA_ITEM = gql`
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
color
|
||||
isCustom
|
||||
isActive
|
||||
isSearchable
|
||||
@@ -90,6 +91,7 @@ export const UPDATE_ONE_OBJECT_METADATA_ITEM = gql`
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
color
|
||||
isCustom
|
||||
isActive
|
||||
isSearchable
|
||||
@@ -112,6 +114,7 @@ export const DELETE_ONE_OBJECT_METADATA_ITEM = gql`
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
color
|
||||
isCustom
|
||||
isActive
|
||||
isSearchable
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ export const responseData = {
|
||||
labelPlural: 'View Filters',
|
||||
description: '',
|
||||
icon: '',
|
||||
color: null,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSearchable: false,
|
||||
|
||||
+2
@@ -10,6 +10,7 @@ export const query = gql`
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
color
|
||||
isCustom
|
||||
isActive
|
||||
isSearchable
|
||||
@@ -32,6 +33,7 @@ export const responseData = {
|
||||
labelPlural: '',
|
||||
description: '',
|
||||
icon: '',
|
||||
color: null,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSearchable: false,
|
||||
|
||||
+3
-19
@@ -1,11 +1,8 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
|
||||
import { useNavigationMenuItemsData } from '@/navigation-menu-item/hooks/useNavigationMenuItemsData';
|
||||
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useAtomFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilySelectorValue';
|
||||
import { indexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/indexViewIdFromObjectMetadataItemFamilySelector';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
|
||||
@@ -14,11 +11,6 @@ export const RecordIndexPageHeaderIcon = ({
|
||||
}: {
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
}) => {
|
||||
const { workspaceNavigationMenuItems } = useNavigationMenuItemsData();
|
||||
const coreIndexViewId = useAtomFamilySelectorValue(
|
||||
indexViewIdFromObjectMetadataItemFamilySelector,
|
||||
{ objectMetadataItemId: objectMetadataItem?.id ?? '' },
|
||||
);
|
||||
const { getIcon } = useIcons();
|
||||
const ObjectIcon = getIcon(objectMetadataItem?.icon);
|
||||
|
||||
@@ -26,17 +18,9 @@ export const RecordIndexPageHeaderIcon = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const navItem = isDefined(coreIndexViewId)
|
||||
? workspaceNavigationMenuItems.find(
|
||||
(item) => item.viewId === coreIndexViewId,
|
||||
)
|
||||
: undefined;
|
||||
const navigationMenuItemColor = isNonEmptyString(navItem?.color)
|
||||
? navItem.color
|
||||
: undefined;
|
||||
const iconColor =
|
||||
navigationMenuItemColor ??
|
||||
getStandardObjectIconColor(objectMetadataItem?.nameSingular ?? '');
|
||||
const iconColor = isNonEmptyString(objectMetadataItem?.color)
|
||||
? objectMetadataItem.color
|
||||
: getStandardObjectIconColor(objectMetadataItem?.nameSingular ?? '');
|
||||
|
||||
return (
|
||||
<NavigationMenuItemStyleIcon
|
||||
|
||||
+16
-4
@@ -1,3 +1,7 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { NavigationMenuItemStyleIcon } from '@/navigation-menu-item/components/NavigationMenuItemStyleIcon';
|
||||
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
@@ -9,10 +13,9 @@ import { useRecordShowPagePagination } from '@/object-record/record-show/hooks/u
|
||||
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
|
||||
import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledEditableTitleContainer = styled.div`
|
||||
align-items: center;
|
||||
@@ -52,7 +55,6 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
||||
objectLabel: string;
|
||||
labelIdentifierFieldMetadataItem?: FieldMetadataItem;
|
||||
}) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { loading } = useFindOneRecord({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
@@ -82,6 +84,11 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
||||
objectNameSingular,
|
||||
objectRecordId,
|
||||
);
|
||||
|
||||
const iconColor = isNonEmptyString(objectMetadataItem?.color)
|
||||
? objectMetadataItem.color
|
||||
: getStandardObjectIconColor(objectNameSingular);
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
@@ -93,7 +100,12 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
||||
navigateToIndexView();
|
||||
}}
|
||||
>
|
||||
{isDefined(HeaderIcon) && <HeaderIcon size={theme.icon.size.md} />}
|
||||
{isDefined(HeaderIcon) && (
|
||||
<NavigationMenuItemStyleIcon
|
||||
Icon={HeaderIcon}
|
||||
color={iconColor ?? undefined}
|
||||
/>
|
||||
)}
|
||||
{objectLabel}
|
||||
<span>{' / '}</span>
|
||||
</StyledEditableTitlePrefix>
|
||||
|
||||
+4
-4
@@ -7,7 +7,6 @@ import { NavigationMenuItemType } from '@/navigation-menu-item/constants/Navigat
|
||||
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 { ViewKey } from '@/views/types/ViewKey';
|
||||
|
||||
export const SidePanelObjectViewRecordInfo = () => {
|
||||
const { t } = useLingui();
|
||||
@@ -25,19 +24,20 @@ export const SidePanelObjectViewRecordInfo = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isViewOrRecord = [
|
||||
const isObjectViewOrRecord = [
|
||||
NavigationMenuItemType.OBJECT,
|
||||
NavigationMenuItemType.VIEW,
|
||||
NavigationMenuItemType.RECORD,
|
||||
].includes(processedItem.itemType);
|
||||
|
||||
if (!isViewOrRecord) {
|
||||
if (!isObjectViewOrRecord) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const label =
|
||||
processedItem.itemType === NavigationMenuItemType.RECORD
|
||||
? selectedItemObjectMetadata?.labelSingular
|
||||
: processedItem.viewKey === ViewKey.INDEX
|
||||
: processedItem.itemType === NavigationMenuItemType.OBJECT
|
||||
? t`Object`
|
||||
: t`View`;
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ export const SidePanelPageInfo = ({ pageChip }: SidePanelPageInfoProps) => {
|
||||
}
|
||||
|
||||
if (
|
||||
itemType === NavigationMenuItemType.OBJECT ||
|
||||
itemType === NavigationMenuItemType.VIEW ||
|
||||
itemType === NavigationMenuItemType.RECORD
|
||||
) {
|
||||
|
||||
+30
-5
@@ -1,3 +1,8 @@
|
||||
import { useObjectNavItemColor } from '@/navigation-menu-item/hooks/useObjectNavItemColor';
|
||||
import { navigationMenuItemsSelector } from '@/navigation-menu-item/states/navigationMenuItemsSelector';
|
||||
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
|
||||
import { getEffectiveNavigationMenuItemColor } from '@/navigation-menu-item/utils/getEffectiveNavigationMenuItemColor';
|
||||
import { parseThemeColor } from '@/navigation-menu-item/utils/parseThemeColor';
|
||||
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
|
||||
import { SidePanelList } from '@/side-panel/components/SidePanelList';
|
||||
import { SidePanelEditColorOption } from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditColorOption';
|
||||
@@ -6,13 +11,15 @@ import {
|
||||
SidePanelEditOrganizeActions,
|
||||
} from '@/side-panel/pages/navigation-menu-item/components/SidePanelEditOrganizeActions';
|
||||
import { getOrganizeActionsSelectableItemIds } from '@/side-panel/pages/navigation-menu-item/utils/getOrganizeActionsSelectableItemIds';
|
||||
import { useSelectedNavigationMenuItemEditItem } from '@/navigation-menu-item/hooks/useSelectedNavigationMenuItemEditItem';
|
||||
import { parseThemeColor } from '@/navigation-menu-item/utils/parseThemeColor';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type SidePanelEditObjectViewBaseProps = OrganizeActionsProps & {
|
||||
onOpenFolderPicker: () => void;
|
||||
showColorOption?: boolean;
|
||||
selectedItem?: ProcessedNavigationMenuItem | null;
|
||||
};
|
||||
|
||||
export const SidePanelEditObjectViewBase = ({
|
||||
@@ -25,18 +32,36 @@ export const SidePanelEditObjectViewBase = ({
|
||||
onAddBefore,
|
||||
onAddAfter,
|
||||
showColorOption = false,
|
||||
selectedItem,
|
||||
}: SidePanelEditObjectViewBaseProps) => {
|
||||
const { t } = useLingui();
|
||||
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
|
||||
const selectableItemIds = getOrganizeActionsSelectableItemIds(true);
|
||||
const objectColor = useObjectNavItemColor(
|
||||
selectedItem?.objectNameSingular ?? '',
|
||||
);
|
||||
|
||||
const navigationMenuItems = useAtomStateValue(navigationMenuItemsSelector);
|
||||
const persistedNavItem = navigationMenuItems.find(
|
||||
(item) => item.id === selectedItem?.id,
|
||||
);
|
||||
const hasUserChangedColor =
|
||||
isNonEmptyString(selectedItem?.color) &&
|
||||
selectedItem.color !== (persistedNavItem?.color ?? undefined);
|
||||
|
||||
const effectiveColor = isDefined(selectedItem)
|
||||
? getEffectiveNavigationMenuItemColor(selectedItem, objectColor)
|
||||
: undefined;
|
||||
const displayColor = hasUserChangedColor
|
||||
? selectedItem.color
|
||||
: effectiveColor;
|
||||
|
||||
return (
|
||||
<SidePanelList commandGroups={[]} selectableItemIds={selectableItemIds}>
|
||||
{showColorOption && selectedItem && (
|
||||
{showColorOption && isDefined(selectedItem) && (
|
||||
<SidePanelGroup heading={t`Customize`}>
|
||||
<SidePanelEditColorOption
|
||||
navigationMenuItemId={selectedItem.id}
|
||||
color={parseThemeColor(selectedItem.color)}
|
||||
color={parseThemeColor(displayColor)}
|
||||
/>
|
||||
</SidePanelGroup>
|
||||
)}
|
||||
|
||||
+17
-19
@@ -4,9 +4,9 @@ import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/hooks/u
|
||||
import { useOpenAddItemToFolderPage } from '@/navigation-menu-item/hooks/useOpenAddItemToFolderPage';
|
||||
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 { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/types/processed-navigation-menu-item';
|
||||
import { parseThemeColor } from '@/navigation-menu-item/utils/parseThemeColor';
|
||||
import { SidePanelGroup } from '@/side-panel/components/SidePanelGroup';
|
||||
import { SidePanelList } from '@/side-panel/components/SidePanelList';
|
||||
@@ -21,7 +21,6 @@ import { getOrganizeActionsSelectableItemIds } from '@/side-panel/pages/navigati
|
||||
import { SidePanelSubPages } from '@/side-panel/types/SidePanelSubPages';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { ViewKey } from '@/views/types/ViewKey';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -47,8 +46,6 @@ export const SidePanelNavigationMenuItemEditPage = () => {
|
||||
);
|
||||
const { selectedItemLabel } = useSelectedNavigationMenuItemEditItemLabel();
|
||||
const { selectedItem } = useSelectedNavigationMenuItemEditItem();
|
||||
const { selectedItemObjectMetadata } =
|
||||
useSelectedNavigationMenuItemEditItemObjectMetadata();
|
||||
const selectedItemType = selectedItem?.itemType ?? null;
|
||||
|
||||
const { navigateToSidePanelSubPage } = useSidePanelSubPageHistory();
|
||||
@@ -97,8 +94,22 @@ export const SidePanelNavigationMenuItemEditPage = () => {
|
||||
}
|
||||
|
||||
switch (selectedItemType) {
|
||||
case NavigationMenuItemType.VIEW:
|
||||
if (!selectedItemObjectMetadata) return null;
|
||||
case NavigationMenuItemType.OBJECT:
|
||||
return (
|
||||
<SidePanelEditObjectViewBase
|
||||
onOpenFolderPicker={openFolderPicker}
|
||||
canMoveUp={canMoveUp}
|
||||
canMoveDown={canMoveDown}
|
||||
onMoveUp={onMoveUp}
|
||||
onMoveDown={onMoveDown}
|
||||
onRemove={onRemove}
|
||||
onAddBefore={onAddBefore}
|
||||
onAddAfter={onAddAfter}
|
||||
showColorOption={isDefined(selectedItem)}
|
||||
selectedItem={selectedItem as ProcessedNavigationMenuItem | undefined}
|
||||
/>
|
||||
);
|
||||
case NavigationMenuItemType.VIEW:
|
||||
return (
|
||||
<SidePanelEditObjectViewBase
|
||||
onOpenFolderPicker={openFolderPicker}
|
||||
@@ -109,11 +120,6 @@ export const SidePanelNavigationMenuItemEditPage = () => {
|
||||
onRemove={onRemove}
|
||||
onAddBefore={onAddBefore}
|
||||
onAddAfter={onAddAfter}
|
||||
showColorOption={
|
||||
selectedItem &&
|
||||
'viewKey' in selectedItem &&
|
||||
selectedItem.viewKey === ViewKey.INDEX
|
||||
}
|
||||
/>
|
||||
);
|
||||
case NavigationMenuItemType.LINK:
|
||||
@@ -187,14 +193,6 @@ export const SidePanelNavigationMenuItemEditPage = () => {
|
||||
commandGroups={[]}
|
||||
selectableItemIds={getOrganizeActionsSelectableItemIds(true)}
|
||||
>
|
||||
{selectedItem && (
|
||||
<SidePanelGroup heading={t`Customize`}>
|
||||
<SidePanelEditColorOption
|
||||
navigationMenuItemId={selectedItem.id}
|
||||
color={parseThemeColor(selectedItem.color)}
|
||||
/>
|
||||
</SidePanelGroup>
|
||||
)}
|
||||
<SidePanelEditOrganizeActions
|
||||
canMoveUp={canMoveUp}
|
||||
canMoveDown={canMoveDown}
|
||||
|
||||
+1
-5
@@ -56,16 +56,12 @@ export const SidePanelNewSidebarItemObjectSubPage = () => {
|
||||
objectMetadataIdsWithDisplayableViews,
|
||||
});
|
||||
|
||||
const handleSelectObject = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
defaultViewId: string,
|
||||
) => {
|
||||
const handleSelectObject = (objectMetadataItem: ObjectMetadataItem) => {
|
||||
if (objectMetadataIdsInWorkspace.has(objectMetadataItem.id)) {
|
||||
return;
|
||||
}
|
||||
const itemId = addObjectToDraft(
|
||||
objectMetadataItem.id,
|
||||
defaultViewId,
|
||||
currentDraft,
|
||||
addMenuItemInsertionContext?.targetFolderId,
|
||||
addMenuItemInsertionContext?.targetIndex,
|
||||
|
||||
+1
-5
@@ -55,16 +55,12 @@ export const SidePanelNewSidebarItemObjectSystemPickerSubPage = () => {
|
||||
objectMetadataIdsWithDisplayableViews,
|
||||
});
|
||||
|
||||
const handleSelectObject = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
defaultViewId: string,
|
||||
) => {
|
||||
const handleSelectObject = (objectMetadataItem: ObjectMetadataItem) => {
|
||||
if (objectMetadataIdsInWorkspace.has(objectMetadataItem.id)) {
|
||||
return;
|
||||
}
|
||||
const itemId = addObjectToDraft(
|
||||
objectMetadataItem.id,
|
||||
defaultViewId,
|
||||
currentDraft,
|
||||
addMenuItemInsertionContext?.targetFolderId,
|
||||
addMenuItemInsertionContext?.targetIndex,
|
||||
|
||||
+2
-6
@@ -16,10 +16,7 @@ import { indexViewIdFromObjectMetadataItemFamilySelector } from '@/views/states/
|
||||
|
||||
type SidePanelObjectMenuItemProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
onSelect: (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
defaultViewId: string,
|
||||
) => void;
|
||||
onSelect: (objectMetadataItem: ObjectMetadataItem) => void;
|
||||
variant: 'add' | 'edit';
|
||||
dragIndex?: number;
|
||||
disableDrag?: boolean;
|
||||
@@ -52,7 +49,7 @@ export const SidePanelObjectMenuItem = ({
|
||||
if (isDisabled || !defaultViewId) {
|
||||
return;
|
||||
}
|
||||
onSelect(objectMetadataItem, defaultViewId);
|
||||
onSelect(objectMetadataItem);
|
||||
};
|
||||
|
||||
const styledIcon = () => (
|
||||
@@ -72,7 +69,6 @@ export const SidePanelObjectMenuItem = ({
|
||||
payload={{
|
||||
type: NavigationMenuItemType.OBJECT,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
defaultViewId: defaultViewId ?? '',
|
||||
label: objectMetadataItem.labelPlural,
|
||||
iconColor,
|
||||
}}
|
||||
|
||||
+1
-4
@@ -12,10 +12,7 @@ type SidePanelObjectPickerItemProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
isViewItem: boolean;
|
||||
onSelectObjectForViewEdit?: (objectMetadataItem: ObjectMetadataItem) => void;
|
||||
onChangeObject: (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
defaultViewId: string,
|
||||
) => void;
|
||||
onChangeObject: (objectMetadataItem: ObjectMetadataItem) => void;
|
||||
objectMenuItemVariant?: 'add' | 'edit';
|
||||
dragIndex?: number;
|
||||
disableDrag?: boolean;
|
||||
|
||||
+1
-4
@@ -19,10 +19,7 @@ type SidePanelObjectPickerSubViewProps = {
|
||||
onOpenSystemPicker: () => void;
|
||||
isViewItem: boolean;
|
||||
onSelectObjectForViewEdit?: (objectMetadataItem: ObjectMetadataItem) => void;
|
||||
onChangeObject: (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
defaultViewId: string,
|
||||
) => void;
|
||||
onChangeObject: (objectMetadataItem: ObjectMetadataItem) => void;
|
||||
objectMenuItemVariant?: 'add' | 'edit';
|
||||
emptyNoResultsText?: string;
|
||||
disableDrag?: boolean;
|
||||
|
||||
+1
-4
@@ -14,10 +14,7 @@ type SidePanelSystemObjectPickerSubViewProps = {
|
||||
onSearchChange: (value: string) => void;
|
||||
isViewItem: boolean;
|
||||
onSelectObjectForViewEdit?: (objectMetadataItem: ObjectMetadataItem) => void;
|
||||
onChangeObject: (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
defaultViewId: string,
|
||||
) => void;
|
||||
onChangeObject: (objectMetadataItem: ObjectMetadataItem) => void;
|
||||
objectMenuItemVariant?: 'add' | 'edit';
|
||||
emptyNoResultsText?: string;
|
||||
disableDrag?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user