Remove overlay-scroll-bar (#11258)
## What - Deprecate overlayscrollbars as we decided to follow the native behavior - rework on performances (avoid calling recoil states too much at field level which is quite expensive) - Also implements: https://github.com/twentyhq/core-team-issues/issues/569 --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -138,8 +138,6 @@
|
|||||||
"next-mdx-remote": "^4.4.1",
|
"next-mdx-remote": "^4.4.1",
|
||||||
"nodemailer": "^6.9.8",
|
"nodemailer": "^6.9.8",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
"overlayscrollbars": "^2.6.1",
|
|
||||||
"overlayscrollbars-react": "^0.5.4",
|
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigation } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
|
||||||
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated We should now use useScrollToPosition instead
|
|
||||||
* Note that `location.key` is used in the cache key, not `location.pathname`,
|
|
||||||
* so the same path navigated to at different points in the history stack will
|
|
||||||
* not share the same scroll position.
|
|
||||||
*/
|
|
||||||
export const useScrollRestoration = (viewportHeight?: number) => {
|
|
||||||
const { state } = useNavigation();
|
|
||||||
|
|
||||||
const [scrollTop, setScrollTop] = useRecoilComponentStateV2(
|
|
||||||
scrollWrapperScrollTopComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const overlayScrollbars = useRecoilComponentValueV2(
|
|
||||||
scrollWrapperInstanceComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const scrollWrapper = overlayScrollbars?.elements().viewport;
|
|
||||||
const skip = isDefined(viewportHeight) && scrollTop > viewportHeight;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (state === 'loading') {
|
|
||||||
setScrollTop(scrollWrapper?.scrollTop ?? 0);
|
|
||||||
} else if (state === 'idle' && isDefined(scrollWrapper) && !skip) {
|
|
||||||
scrollWrapper.scrollTo({ top: scrollTop });
|
|
||||||
}
|
|
||||||
}, [state, scrollWrapper, skip, scrollTop, setScrollTop]);
|
|
||||||
};
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
|
||||||
import { useRecoilCallback } from 'recoil';
|
|
||||||
|
|
||||||
export const useScrollToPosition = () => {
|
|
||||||
const scrollWrapperInstanceState = useRecoilComponentCallbackStateV2(
|
|
||||||
scrollWrapperInstanceComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const scrollToPosition = useRecoilCallback(
|
|
||||||
({ snapshot }) =>
|
|
||||||
(scrollPositionInPx: number) => {
|
|
||||||
const overlayScrollbars = snapshot
|
|
||||||
.getLoadable(scrollWrapperInstanceState)
|
|
||||||
.getValue();
|
|
||||||
|
|
||||||
const scrollWrapper = overlayScrollbars?.elements().viewport;
|
|
||||||
|
|
||||||
scrollWrapper?.scrollTo({ top: scrollPositionInPx });
|
|
||||||
},
|
|
||||||
[scrollWrapperInstanceState],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { scrollToPosition };
|
|
||||||
};
|
|
||||||
@ -17,10 +17,6 @@ form {
|
|||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
|
||||||
overscroll-behavior: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge */
|
/* https://stackoverflow.com/questions/44543157/how-to-hide-the-google-invisible-recaptcha-badge */
|
||||||
.grecaptcha-badge {
|
.grecaptcha-badge {
|
||||||
visibility: hidden !important;
|
visibility: hidden !important;
|
||||||
|
|||||||
@ -107,6 +107,7 @@ export const CalendarEventDetails = ({
|
|||||||
}),
|
}),
|
||||||
useUpdateRecord: () => [() => undefined, { loading: false }],
|
useUpdateRecord: () => [() => undefined, { loading: false }],
|
||||||
maxWidth: 300,
|
maxWidth: 300,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordFieldComponentInstanceContext.Provider
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
|
|||||||
@ -8,17 +8,14 @@ import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttac
|
|||||||
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||||
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
|
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
|
||||||
import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope';
|
import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope';
|
||||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
|
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
|
||||||
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
@ -26,6 +23,7 @@ import { ActivityRichTextEditorChangeOnActivityIdEffect } from '@/activities/com
|
|||||||
import { Note } from '@/activities/types/Note';
|
import { Note } from '@/activities/types/Note';
|
||||||
import { Task } from '@/activities/types/Task';
|
import { Task } from '@/activities/types/Task';
|
||||||
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
import { CommandMenuHotkeyScope } from '@/command-menu/types/CommandMenuHotkeyScope';
|
||||||
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
|
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
|
||||||
import { PartialBlock } from '@blocknote/core';
|
import { PartialBlock } from '@blocknote/core';
|
||||||
import '@blocknote/core/fonts/inter.css';
|
import '@blocknote/core/fonts/inter.css';
|
||||||
@ -56,17 +54,11 @@ export const ActivityRichTextEditor = ({
|
|||||||
objectNameSingular: activityObjectNameSingular,
|
objectNameSingular: activityObjectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const contextStoreCurrentViewType = useRecoilComponentValueV2(
|
const isRecordReadOnly = useIsRecordReadOnly({ recordId: activityId });
|
||||||
contextStoreCurrentViewTypeComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
|
||||||
|
|
||||||
const isReadOnly = isFieldValueReadOnly({
|
const isReadOnly = isFieldValueReadOnly({
|
||||||
objectNameSingular: activityObjectNameSingular,
|
objectNameSingular: activityObjectNameSingular,
|
||||||
hasObjectReadOnlyPermission,
|
isRecordReadOnly,
|
||||||
contextStoreCurrentViewType,
|
|
||||||
isRecordDeleted: activityInStore?.deletedAt !== null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -196,10 +196,7 @@ export const AttachmentList = ({
|
|||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
</StyledModalHeader>
|
</StyledModalHeader>
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="modalContent"
|
|
||||||
componentInstanceId={`preview-modal-${previewedAttachment.id}`}
|
componentInstanceId={`preview-modal-${previewedAttachment.id}`}
|
||||||
scrollbarVariant="no-padding"
|
|
||||||
heightMode="fit-content"
|
|
||||||
>
|
>
|
||||||
<StyledModalContent>
|
<StyledModalContent>
|
||||||
<Suspense
|
<Suspense
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { ActivityTargetWithTargetRecord } from '@/activities/types/ActivityTargetObject';
|
|
||||||
import { Note } from '@/activities/types/Note';
|
import { Note } from '@/activities/types/Note';
|
||||||
import { NoteTarget } from '@/activities/types/NoteTarget';
|
import { NoteTarget } from '@/activities/types/NoteTarget';
|
||||||
import { Task } from '@/activities/types/Task';
|
import { Task } from '@/activities/types/Task';
|
||||||
import { TaskTarget } from '@/activities/types/TaskTarget';
|
import { TaskTarget } from '@/activities/types/TaskTarget';
|
||||||
|
import { getActivityTargetObjectRecords } from '@/activities/utils/getActivityTargetObjectRecords';
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
export const useActivityTargetObjectRecords = (
|
export const useActivityTargetObjectRecords = (
|
||||||
@ -25,49 +23,11 @@ export const useActivityTargetObjectRecords = (
|
|||||||
return { activityTargetObjectRecords: [] };
|
return { activityTargetObjectRecords: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const targets = activityTargets
|
const activityTargetObjectRecords = getActivityTargetObjectRecords({
|
||||||
? activityTargets
|
activityRecord: activity as Note | Task,
|
||||||
: activity && 'noteTargets' in activity && activity.noteTargets
|
objectMetadataItems,
|
||||||
? activity.noteTargets
|
activityTargets,
|
||||||
: activity && 'taskTargets' in activity && activity.taskTargets
|
});
|
||||||
? activity.taskTargets
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const activityTargetObjectRecords = targets
|
|
||||||
.map<ActivityTargetWithTargetRecord | undefined>((activityTarget) => {
|
|
||||||
if (!isDefined(activityTarget)) {
|
|
||||||
throw new Error(`Cannot find activity target`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const correspondingObjectMetadataItem = objectMetadataItems.find(
|
|
||||||
(objectMetadataItem) =>
|
|
||||||
isDefined(activityTarget[objectMetadataItem.nameSingular]) &&
|
|
||||||
![CoreObjectNameSingular.Note, CoreObjectNameSingular.Task].includes(
|
|
||||||
objectMetadataItem.nameSingular as CoreObjectNameSingular,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!correspondingObjectMetadataItem) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetObjectRecord = activityTarget[
|
|
||||||
correspondingObjectMetadataItem.nameSingular
|
|
||||||
] as ObjectRecord | undefined;
|
|
||||||
|
|
||||||
if (!isDefined(targetObjectRecord)) {
|
|
||||||
throw new Error(
|
|
||||||
`Cannot find target object record of type ${correspondingObjectMetadataItem.nameSingular}, make sure the request for activities eagerly loads for the target objects on activity target relation.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
activityTarget,
|
|
||||||
targetObject: targetObjectRecord,
|
|
||||||
targetObjectMetadataItem: correspondingObjectMetadataItem,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(isDefined);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activityTargetObjectRecords,
|
activityTargetObjectRecords,
|
||||||
|
|||||||
@ -2,13 +2,12 @@ import { useContext } from 'react';
|
|||||||
|
|
||||||
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
|
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
|
||||||
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
|
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
|
||||||
import { useOpenActivityTargetInlineCellEditMode } from '@/activities/inline-cell/hooks/useOpenActivityTargetInlineCellEditMode';
|
import { useOpenActivityTargetCellEditMode } from '@/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode';
|
||||||
import { useUpdateActivityTargetFromInlineCell } from '@/activities/inline-cell/hooks/useUpdateActivityTargetFromInlineCell';
|
import { useUpdateActivityTargetFromCell } from '@/activities/inline-cell/hooks/useUpdateActivityTargetFromCell';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { FieldContextProvider } from '@/object-record/record-field/components/FieldContextProvider';
|
import { FieldContextProvider } from '@/object-record/record-field/components/FieldContextProvider';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
|
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
|
||||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { RecordInlineCellContainer } from '@/object-record/record-inline-cell/components/RecordInlineCellContainer';
|
import { RecordInlineCellContainer } from '@/object-record/record-inline-cell/components/RecordInlineCellContainer';
|
||||||
import { RecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
import { RecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
||||||
@ -39,15 +38,12 @@ export const ActivityTargetsInlineCell = ({
|
|||||||
|
|
||||||
const { closeInlineCell } = useInlineCell(componentInstanceId);
|
const { closeInlineCell } = useInlineCell(componentInstanceId);
|
||||||
|
|
||||||
const { fieldDefinition } = useContext(FieldContext);
|
const { fieldDefinition, isReadOnly } = useContext(FieldContext);
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
const { openActivityTargetCellEditMode } =
|
||||||
|
useOpenActivityTargetCellEditMode();
|
||||||
|
|
||||||
const { openActivityTargetInlineCellEditMode } =
|
const { updateActivityTargetFromCell } = useUpdateActivityTargetFromCell({
|
||||||
useOpenActivityTargetInlineCellEditMode();
|
|
||||||
|
|
||||||
const { updateActivityTargetFromInlineCell } =
|
|
||||||
useUpdateActivityTargetFromInlineCell({
|
|
||||||
activityObjectNameSingular,
|
activityObjectNameSingular,
|
||||||
activityId: activityRecordId,
|
activityId: activityRecordId,
|
||||||
});
|
});
|
||||||
@ -73,7 +69,7 @@ export const ActivityTargetsInlineCell = ({
|
|||||||
MultipleRecordPickerHotkeyScope.MultipleRecordPicker,
|
MultipleRecordPickerHotkeyScope.MultipleRecordPicker,
|
||||||
IconLabel: showLabel ? IconArrowUpRight : undefined,
|
IconLabel: showLabel ? IconArrowUpRight : undefined,
|
||||||
showLabel: showLabel,
|
showLabel: showLabel,
|
||||||
readonly: isFieldReadOnly,
|
readonly: isReadOnly,
|
||||||
labelWidth: fieldDefinition?.labelWidth,
|
labelWidth: fieldDefinition?.labelWidth,
|
||||||
editModeContent: (
|
editModeContent: (
|
||||||
<MultipleRecordPicker
|
<MultipleRecordPicker
|
||||||
@ -82,7 +78,7 @@ export const ActivityTargetsInlineCell = ({
|
|||||||
closeInlineCell();
|
closeInlineCell();
|
||||||
}}
|
}}
|
||||||
onChange={(morphItem) => {
|
onChange={(morphItem) => {
|
||||||
updateActivityTargetFromInlineCell({
|
updateActivityTargetFromCell({
|
||||||
recordPickerInstanceId: componentInstanceId,
|
recordPickerInstanceId: componentInstanceId,
|
||||||
morphItem,
|
morphItem,
|
||||||
activityTargetWithTargetRecords:
|
activityTargetWithTargetRecords:
|
||||||
@ -102,7 +98,7 @@ export const ActivityTargetsInlineCell = ({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
onOpenEditMode: () => {
|
onOpenEditMode: () => {
|
||||||
openActivityTargetInlineCellEditMode({
|
openActivityTargetCellEditMode({
|
||||||
recordPickerInstanceId: componentInstanceId,
|
recordPickerInstanceId: componentInstanceId,
|
||||||
activityTargetObjectRecords,
|
activityTargetObjectRecords,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,21 +7,22 @@ import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/
|
|||||||
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
|
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
type OpenActivityTargetInlineCellEditModeProps = {
|
type OpenActivityTargetCellEditModeProps = {
|
||||||
recordPickerInstanceId: string;
|
recordPickerInstanceId: string;
|
||||||
activityTargetObjectRecords: ActivityTargetWithTargetRecord[];
|
activityTargetObjectRecords: ActivityTargetWithTargetRecord[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useOpenActivityTargetInlineCellEditMode = () => {
|
// TODO: deprecate this once we are supporting one to many through relations
|
||||||
|
export const useOpenActivityTargetCellEditMode = () => {
|
||||||
const { performSearch: multipleRecordPickerPerformSearch } =
|
const { performSearch: multipleRecordPickerPerformSearch } =
|
||||||
useMultipleRecordPickerPerformSearch();
|
useMultipleRecordPickerPerformSearch();
|
||||||
|
|
||||||
const openActivityTargetInlineCellEditMode = useRecoilCallback(
|
const openActivityTargetCellEditMode = useRecoilCallback(
|
||||||
({ set, snapshot }) =>
|
({ set, snapshot }) =>
|
||||||
({
|
({
|
||||||
recordPickerInstanceId,
|
recordPickerInstanceId,
|
||||||
activityTargetObjectRecords,
|
activityTargetObjectRecords,
|
||||||
}: OpenActivityTargetInlineCellEditModeProps) => {
|
}: OpenActivityTargetCellEditModeProps) => {
|
||||||
const objectMetadataItems = snapshot
|
const objectMetadataItems = snapshot
|
||||||
.getLoadable(objectMetadataItemsState)
|
.getLoadable(objectMetadataItemsState)
|
||||||
.getValue()
|
.getValue()
|
||||||
@ -82,5 +83,5 @@ export const useOpenActivityTargetInlineCellEditMode = () => {
|
|||||||
[multipleRecordPickerPerformSearch],
|
[multipleRecordPickerPerformSearch],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { openActivityTargetInlineCellEditMode };
|
return { openActivityTargetCellEditMode };
|
||||||
};
|
};
|
||||||
@ -11,16 +11,17 @@ import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/typ
|
|||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { isNull } from '@sniptt/guards';
|
import { isNull } from '@sniptt/guards';
|
||||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||||
import { v4 } from 'uuid';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
type UpdateActivityTargetFromInlineCellProps = {
|
type UpdateActivityTargetFromCellProps = {
|
||||||
recordPickerInstanceId: string;
|
recordPickerInstanceId: string;
|
||||||
morphItem: RecordPickerPickableMorphItem;
|
morphItem: RecordPickerPickableMorphItem;
|
||||||
activityTargetWithTargetRecords: ActivityTargetWithTargetRecord[];
|
activityTargetWithTargetRecords: ActivityTargetWithTargetRecord[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdateActivityTargetFromInlineCell = ({
|
// TODO: deprecate this hook once we implement one-to-many relation through
|
||||||
|
export const useUpdateActivityTargetFromCell = ({
|
||||||
activityObjectNameSingular,
|
activityObjectNameSingular,
|
||||||
activityId,
|
activityId,
|
||||||
}: {
|
}: {
|
||||||
@ -29,27 +30,37 @@ export const useUpdateActivityTargetFromInlineCell = ({
|
|||||||
| CoreObjectNameSingular.Task;
|
| CoreObjectNameSingular.Task;
|
||||||
activityId: string;
|
activityId: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const joinObjectNameSingular = getJoinObjectNameSingular(
|
||||||
|
activityObjectNameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
const { createOneRecord: createOneActivityTarget } = useCreateOneRecord<
|
const { createOneRecord: createOneActivityTarget } = useCreateOneRecord<
|
||||||
NoteTarget | TaskTarget
|
NoteTarget | TaskTarget
|
||||||
>({
|
>({
|
||||||
objectNameSingular: getJoinObjectNameSingular(activityObjectNameSingular),
|
objectNameSingular:
|
||||||
|
joinObjectNameSingular === ''
|
||||||
|
? activityObjectNameSingular
|
||||||
|
: joinObjectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { deleteOneRecord: deleteOneActivityTarget } = useDeleteOneRecord({
|
const { deleteOneRecord: deleteOneActivityTarget } = useDeleteOneRecord({
|
||||||
objectNameSingular: getJoinObjectNameSingular(activityObjectNameSingular),
|
objectNameSingular:
|
||||||
|
joinObjectNameSingular === ''
|
||||||
|
? activityObjectNameSingular
|
||||||
|
: joinObjectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
const setActivityFromStore = useSetRecoilState(
|
const setActivityFromStore = useSetRecoilState(
|
||||||
recordStoreFamilyState(activityId),
|
recordStoreFamilyState(activityId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateActivityTargetFromInlineCell = useRecoilCallback(
|
const updateActivityTargetFromCell = useRecoilCallback(
|
||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
async ({
|
async ({
|
||||||
morphItem,
|
morphItem,
|
||||||
activityTargetWithTargetRecords,
|
activityTargetWithTargetRecords,
|
||||||
recordPickerInstanceId,
|
recordPickerInstanceId,
|
||||||
}: UpdateActivityTargetFromInlineCellProps) => {
|
}: UpdateActivityTargetFromCellProps) => {
|
||||||
const targetObjectName =
|
const targetObjectName =
|
||||||
activityObjectNameSingular === CoreObjectNameSingular.Task
|
activityObjectNameSingular === CoreObjectNameSingular.Task
|
||||||
? 'task'
|
? 'task'
|
||||||
@ -179,5 +190,5 @@ export const useUpdateActivityTargetFromInlineCell = ({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { updateActivityTargetFromInlineCell };
|
return { updateActivityTargetFromCell };
|
||||||
};
|
};
|
||||||
@ -45,7 +45,6 @@ export const EventList = ({ events, targetableObject }: EventListProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="eventList"
|
|
||||||
componentInstanceId={`scroll-wrapper-event-list-${targetableObject.id}`}
|
componentInstanceId={`scroll-wrapper-event-list-${targetableObject.id}`}
|
||||||
>
|
>
|
||||||
<StyledTimelineContainer>
|
<StyledTimelineContainer>
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export const EventFieldDiffValue = ({
|
|||||||
defaultValue: fieldMetadataItem.defaultValue,
|
defaultValue: fieldMetadataItem.defaultValue,
|
||||||
},
|
},
|
||||||
hotkeyScope: 'field-event-diff',
|
hotkeyScope: 'field-event-diff',
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FieldDisplay />
|
<FieldDisplay />
|
||||||
|
|||||||
@ -0,0 +1,75 @@
|
|||||||
|
import { ActivityTargetWithTargetRecord } from '@/activities/types/ActivityTargetObject';
|
||||||
|
import { Note } from '@/activities/types/Note';
|
||||||
|
import { NoteTarget } from '@/activities/types/NoteTarget';
|
||||||
|
import { Task } from '@/activities/types/Task';
|
||||||
|
import { TaskTarget } from '@/activities/types/TaskTarget';
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
type GetActivityTargetObjectRecordsProps = {
|
||||||
|
activityRecord: Note | Task;
|
||||||
|
objectMetadataItems: ObjectMetadataItem[];
|
||||||
|
activityTargets?: NoteTarget[] | TaskTarget[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActivityTargetObjectRecords = ({
|
||||||
|
activityRecord,
|
||||||
|
objectMetadataItems,
|
||||||
|
activityTargets,
|
||||||
|
}: GetActivityTargetObjectRecordsProps) => {
|
||||||
|
if (!isDefined(activityRecord) && !isDefined(activityTargets)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const targets = activityTargets
|
||||||
|
? activityTargets
|
||||||
|
: activityRecord &&
|
||||||
|
'noteTargets' in activityRecord &&
|
||||||
|
activityRecord.noteTargets
|
||||||
|
? activityRecord.noteTargets
|
||||||
|
: activityRecord &&
|
||||||
|
'taskTargets' in activityRecord &&
|
||||||
|
activityRecord.taskTargets
|
||||||
|
? activityRecord.taskTargets
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const activityTargetObjectRecords = targets
|
||||||
|
.map<ActivityTargetWithTargetRecord | undefined>((activityTarget) => {
|
||||||
|
if (!isDefined(activityTarget)) {
|
||||||
|
throw new Error(`Cannot find activity target`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const correspondingObjectMetadataItem = objectMetadataItems.find(
|
||||||
|
(objectMetadataItem) =>
|
||||||
|
isDefined(activityTarget[objectMetadataItem.nameSingular]) &&
|
||||||
|
![CoreObjectNameSingular.Note, CoreObjectNameSingular.Task].includes(
|
||||||
|
objectMetadataItem.nameSingular as CoreObjectNameSingular,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!correspondingObjectMetadataItem) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetObjectRecord = activityTarget[
|
||||||
|
correspondingObjectMetadataItem.nameSingular
|
||||||
|
] as ObjectRecord | undefined;
|
||||||
|
|
||||||
|
if (!isDefined(targetObjectRecord)) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot find target object record of type ${correspondingObjectMetadataItem.nameSingular}, make sure the request for activities eagerly loads for the target objects on activity target relation.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
activityTarget,
|
||||||
|
targetObject: targetObjectRecord,
|
||||||
|
targetObjectMetadataItem: correspondingObjectMetadataItem,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(isDefined);
|
||||||
|
|
||||||
|
return activityTargetObjectRecords;
|
||||||
|
};
|
||||||
@ -12,10 +12,7 @@ type AuthModalProps = { children: React.ReactNode };
|
|||||||
|
|
||||||
export const AuthModal = ({ children }: AuthModalProps) => (
|
export const AuthModal = ({ children }: AuthModalProps) => (
|
||||||
<Modal padding={'none'} modalVariant="primary">
|
<Modal padding={'none'} modalVariant="primary">
|
||||||
<ScrollWrapper
|
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
|
||||||
contextProviderName="modalContent"
|
|
||||||
componentInstanceId="scroll-wrapper-modal-content"
|
|
||||||
>
|
|
||||||
<StyledContent>{children}</StyledContent>
|
<StyledContent>{children}</StyledContent>
|
||||||
</ScrollWrapper>
|
</ScrollWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@ -79,10 +79,7 @@ export const CommandMenuList = ({
|
|||||||
<CommandMenuDefaultSelectionEffect
|
<CommandMenuDefaultSelectionEffect
|
||||||
selectableItemIds={selectableItemIds}
|
selectableItemIds={selectableItemIds}
|
||||||
/>
|
/>
|
||||||
<ScrollWrapper
|
<ScrollWrapper componentInstanceId={`scroll-wrapper-command-menu`}>
|
||||||
contextProviderName="commandMenu"
|
|
||||||
componentInstanceId={`scroll-wrapper-command-menu`}
|
|
||||||
>
|
|
||||||
<StyledInnerList>
|
<StyledInnerList>
|
||||||
<SelectableList
|
<SelectableList
|
||||||
selectableListId="command-menu-list"
|
selectableListId="command-menu-list"
|
||||||
|
|||||||
@ -24,6 +24,7 @@ const StyledMainSection = styled(NavigationDrawerSection)`
|
|||||||
`;
|
`;
|
||||||
const StyledInnerContainer = styled.div`
|
const StyledInnerContainer = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const MainNavigationDrawerItems = () => {
|
export const MainNavigationDrawerItems = () => {
|
||||||
@ -66,10 +67,8 @@ export const MainNavigationDrawerItems = () => {
|
|||||||
</StyledMainSection>
|
</StyledMainSection>
|
||||||
)}
|
)}
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="navigationDrawer"
|
|
||||||
componentInstanceId={`scroll-wrapper-navigation-drawer`}
|
componentInstanceId={`scroll-wrapper-navigation-drawer`}
|
||||||
defaultEnableXScroll={false}
|
defaultEnableXScroll={false}
|
||||||
scrollbarVariant="no-padding"
|
|
||||||
>
|
>
|
||||||
<StyledInnerContainer>
|
<StyledInnerContainer>
|
||||||
<NavigationDrawerOpenedSection />
|
<NavigationDrawerOpenedSection />
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export const useAttachRelatedRecordFromRecord = ({
|
|||||||
getRelatedRecordFromCache<ObjectRecord>(relatedRecordId);
|
getRelatedRecordFromCache<ObjectRecord>(relatedRecordId);
|
||||||
|
|
||||||
if (!cachedRelatedRecord) {
|
if (!cachedRelatedRecord) {
|
||||||
throw new Error('could not find cached related record');
|
throw new Error('Could not find cached related record');
|
||||||
}
|
}
|
||||||
|
|
||||||
const previousRecordId = cachedRelatedRecord?.[`${fieldOnRelatedObject}Id`];
|
const previousRecordId = cachedRelatedRecord?.[`${fieldOnRelatedObject}Id`];
|
||||||
|
|||||||
@ -35,7 +35,6 @@ import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-
|
|||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
import { useScrollRestoration } from '~/hooks/useScrollRestoration';
|
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -61,11 +60,6 @@ const StyledBoardContentContainer = styled.div`
|
|||||||
height: calc(100% - 48px);
|
height: calc(100% - 48px);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const RecordBoardScrollRestoreEffect = () => {
|
|
||||||
useScrollRestoration();
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RecordBoard = () => {
|
export const RecordBoard = () => {
|
||||||
const { updateOneRecord, selectFieldMetadataItem, recordBoardId } =
|
const { updateOneRecord, selectFieldMetadataItem, recordBoardId } =
|
||||||
useContext(RecordBoardContext);
|
useContext(RecordBoardContext);
|
||||||
@ -239,7 +233,6 @@ export const RecordBoard = () => {
|
|||||||
value={{ instanceId: recordBoardId }}
|
value={{ instanceId: recordBoardId }}
|
||||||
>
|
>
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="recordBoard"
|
|
||||||
componentInstanceId={`scroll-wrapper-record-board-${recordBoardId}`}
|
componentInstanceId={`scroll-wrapper-record-board-${recordBoardId}`}
|
||||||
>
|
>
|
||||||
<RecordBoardStickyHeaderEffect />
|
<RecordBoardStickyHeaderEffect />
|
||||||
@ -258,7 +251,6 @@ export const RecordBoard = () => {
|
|||||||
</StyledColumnContainer>
|
</StyledColumnContainer>
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
<RecordBoardScrollRestoreEffect />
|
|
||||||
<DragSelect
|
<DragSelect
|
||||||
dragSelectable={boardRef}
|
dragSelectable={boardRef}
|
||||||
onDragSelectionEnd={handleDragSelectionEnd}
|
onDragSelectionEnd={handleDragSelectionEnd}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { RecordValueSetterEffect } from '@/object-record/record-store/components
|
|||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||||
@ -144,10 +144,10 @@ export const RecordBoardCard = () => {
|
|||||||
}
|
}
|
||||||
}, 800);
|
}, 800);
|
||||||
|
|
||||||
const scrollWrapperRef = useContext(RecordBoardScrollWrapperContext);
|
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||||
|
|
||||||
const { ref: cardRef } = useInView({
|
const { ref: cardRef } = useInView({
|
||||||
root: scrollWrapperRef?.ref.current,
|
root: scrollWrapperHTMLElement,
|
||||||
rootMargin: '1000px',
|
rootMargin: '1000px',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||||
|
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||||
@ -21,7 +22,7 @@ export const RecordBoardCardBody = ({
|
|||||||
}: {
|
}: {
|
||||||
fieldDefinitions: RecordBoardFieldDefinition<FieldMetadata>[];
|
fieldDefinitions: RecordBoardFieldDefinition<FieldMetadata>[];
|
||||||
}) => {
|
}) => {
|
||||||
const { recordId } = useContext(RecordBoardCardContext);
|
const { recordId, isRecordReadOnly } = useContext(RecordBoardCardContext);
|
||||||
|
|
||||||
const { updateOneRecord } = useContext(RecordBoardContext);
|
const { updateOneRecord } = useContext(RecordBoardContext);
|
||||||
|
|
||||||
@ -45,6 +46,13 @@ export const RecordBoardCardBody = ({
|
|||||||
recordId,
|
recordId,
|
||||||
maxWidth: 156,
|
maxWidth: 156,
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: isFieldValueReadOnly({
|
||||||
|
objectNameSingular:
|
||||||
|
fieldDefinition.metadata.objectMetadataNameSingular,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
fieldType: fieldDefinition.type,
|
||||||
|
isRecordReadOnly,
|
||||||
|
}),
|
||||||
fieldDefinition: {
|
fieldDefinition: {
|
||||||
disableTooltip: false,
|
disableTooltip: false,
|
||||||
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
fieldMetadataId: fieldDefinition.fieldMetadataId,
|
||||||
@ -60,6 +68,7 @@ export const RecordBoardCardBody = ({
|
|||||||
},
|
},
|
||||||
useUpdateRecord: useUpdateOneRecordHook,
|
useUpdateRecord: useUpdateOneRecordHook,
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
|
isDisplayModeFixHeight: true,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordFieldComponentInstanceContext.Provider
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { Draggable } from '@hello-pangea/dnd';
|
import { Draggable } from '@hello-pangea/dnd';
|
||||||
|
|
||||||
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
|
||||||
|
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
||||||
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
|
|
||||||
export const RecordBoardCardDraggableContainer = ({
|
export const RecordBoardCardDraggableContainer = ({
|
||||||
recordId,
|
recordId,
|
||||||
@ -9,7 +11,12 @@ export const RecordBoardCardDraggableContainer = ({
|
|||||||
recordId: string;
|
recordId: string;
|
||||||
index: number;
|
index: number;
|
||||||
}) => {
|
}) => {
|
||||||
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
|
recordId,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<RecordBoardCardContext.Provider value={{ recordId, isRecordReadOnly }}>
|
||||||
<Draggable key={recordId} draggableId={recordId} index={index}>
|
<Draggable key={recordId} draggableId={recordId} index={index}>
|
||||||
{(draggableProvided) => (
|
{(draggableProvided) => (
|
||||||
<div
|
<div
|
||||||
@ -26,5 +33,6 @@ export const RecordBoardCardDraggableContainer = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
|
</RecordBoardCardContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { createContext } from 'react';
|
|||||||
|
|
||||||
type RecordBoardCardContextProps = {
|
type RecordBoardCardContextProps = {
|
||||||
recordId: string;
|
recordId: string;
|
||||||
|
isRecordReadOnly: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordBoardCardContext =
|
export const RecordBoardCardContext =
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { RecordBoardCardDraggableContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer';
|
import { RecordBoardCardDraggableContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer';
|
||||||
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext';
|
|
||||||
|
|
||||||
type RecordBoardColumnCardsMemoProps = {
|
type RecordBoardColumnCardsMemoProps = {
|
||||||
recordIds: string[];
|
recordIds: string[];
|
||||||
@ -10,9 +9,11 @@ type RecordBoardColumnCardsMemoProps = {
|
|||||||
export const RecordBoardColumnCardsMemo = React.memo(
|
export const RecordBoardColumnCardsMemo = React.memo(
|
||||||
({ recordIds }: RecordBoardColumnCardsMemoProps) => {
|
({ recordIds }: RecordBoardColumnCardsMemoProps) => {
|
||||||
return recordIds.map((recordId, index) => (
|
return recordIds.map((recordId, index) => (
|
||||||
<RecordBoardCardContext.Provider value={{ recordId }} key={recordId}>
|
<RecordBoardCardDraggableContainer
|
||||||
<RecordBoardCardDraggableContainer recordId={recordId} index={index} />
|
key={recordId}
|
||||||
</RecordBoardCardContext.Provider>
|
recordId={recordId}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,9 +11,9 @@ import { RecordGroupDefinitionType } from '@/object-record/record-group/types/Re
|
|||||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
import { Tag } from 'twenty-ui/components';
|
||||||
import { IconDotsVertical, IconPlus } from 'twenty-ui/display';
|
import { IconDotsVertical, IconPlus } from 'twenty-ui/display';
|
||||||
import { LightIconButton } from 'twenty-ui/input';
|
import { LightIconButton } from 'twenty-ui/input';
|
||||||
import { Tag } from 'twenty-ui/components';
|
|
||||||
|
|
||||||
const StyledHeader = styled.div`
|
const StyledHeader = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -78,6 +78,7 @@ export const FieldContextProvider = ({
|
|||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
clearable,
|
clearable,
|
||||||
overridenIsFieldEmpty,
|
overridenIsFieldEmpty,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export type GenericFieldContextType = {
|
|||||||
overridenIsFieldEmpty?: boolean;
|
overridenIsFieldEmpty?: boolean;
|
||||||
displayedMaxRows?: number;
|
displayedMaxRows?: number;
|
||||||
isDisplayModeFixHeight?: boolean;
|
isDisplayModeFixHeight?: boolean;
|
||||||
|
isReadOnly: boolean;
|
||||||
onOpenEditMode?: () => void;
|
onOpenEditMode?: () => void;
|
||||||
onCloseEditMode?: () => void;
|
onCloseEditMode?: () => void;
|
||||||
isLabelHidden?: boolean;
|
isLabelHidden?: boolean;
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const getWrapper =
|
|||||||
recordId,
|
recordId,
|
||||||
hotkeyScope: 'hotkeyScope',
|
hotkeyScope: 'hotkeyScope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecoilRoot>{children}</RecoilRoot>
|
<RecoilRoot>{children}</RecoilRoot>
|
||||||
|
|||||||
@ -16,6 +16,7 @@ const Wrapper = ({ children }: { children: ReactNode }) => (
|
|||||||
recordId,
|
recordId,
|
||||||
hotkeyScope: 'hotkeyScope',
|
hotkeyScope: 'hotkeyScope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecoilRoot>{children}</RecoilRoot>
|
<RecoilRoot>{children}</RecoilRoot>
|
||||||
|
|||||||
@ -22,6 +22,7 @@ const getWrapper =
|
|||||||
recordId,
|
recordId,
|
||||||
hotkeyScope: 'hotkeyScope',
|
hotkeyScope: 'hotkeyScope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecoilRoot>{children}</RecoilRoot>
|
<RecoilRoot>{children}</RecoilRoot>
|
||||||
|
|||||||
@ -1,79 +1,28 @@
|
|||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { ReactNode } from 'react';
|
|
||||||
import { RecoilRoot } from 'recoil';
|
|
||||||
|
|
||||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
import { phonesFieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions';
|
||||||
import {
|
|
||||||
actorFieldDefinition,
|
|
||||||
phonesFieldDefinition,
|
|
||||||
} from '@/object-record/record-field/__mocks__/fieldDefinitions';
|
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
|
||||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
|
||||||
|
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
|
||||||
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
|
|
||||||
import { JestRecordStoreSetter } from '~/testing/jest/JestRecordStoreSetter';
|
|
||||||
|
|
||||||
import { useIsFieldValueReadOnly } from '../useIsFieldValueReadOnly';
|
import { useIsFieldValueReadOnly } from '../useIsFieldValueReadOnly';
|
||||||
|
|
||||||
const recordId = 'recordId';
|
|
||||||
const mockInstanceId = 'mock-instance-id';
|
|
||||||
|
|
||||||
const getWrapper =
|
|
||||||
(fieldDefinition: FieldDefinition<FieldMetadata>, isRecordDeleted: boolean) =>
|
|
||||||
({ children }: { children: ReactNode }) => {
|
|
||||||
return (
|
|
||||||
<RecoilRoot>
|
|
||||||
<ContextStoreComponentInstanceContext.Provider
|
|
||||||
value={{ instanceId: mockInstanceId }}
|
|
||||||
>
|
|
||||||
<JestObjectMetadataItemSetter>
|
|
||||||
<JestRecordStoreSetter
|
|
||||||
records={[
|
|
||||||
{
|
|
||||||
id: recordId,
|
|
||||||
deletedAt: isRecordDeleted ? new Date().toISOString() : null,
|
|
||||||
__typename: 'standardObject',
|
|
||||||
} as ObjectRecord,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<FieldContext.Provider
|
|
||||||
value={{
|
|
||||||
fieldDefinition,
|
|
||||||
recordId,
|
|
||||||
hotkeyScope: 'hotkeyScope',
|
|
||||||
isLabelIdentifier: false,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</FieldContext.Provider>
|
|
||||||
</JestRecordStoreSetter>
|
|
||||||
</JestObjectMetadataItemSetter>
|
|
||||||
</ContextStoreComponentInstanceContext.Provider>
|
|
||||||
</RecoilRoot>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('useIsFieldValueReadOnly', () => {
|
describe('useIsFieldValueReadOnly', () => {
|
||||||
it('should take fieldDefinition into account', () => {
|
it('should return true if the field is read only', () => {
|
||||||
const { result } = renderHook(() => useIsFieldValueReadOnly(), {
|
const { result } = renderHook(() =>
|
||||||
wrapper: getWrapper(phonesFieldDefinition, false),
|
useIsFieldValueReadOnly({
|
||||||
});
|
fieldDefinition: phonesFieldDefinition,
|
||||||
|
isRecordReadOnly: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
expect(result.current).toBe(false);
|
expect(result.current).toBe(false);
|
||||||
|
|
||||||
const { result: result2 } = renderHook(() => useIsFieldValueReadOnly(), {
|
|
||||||
wrapper: getWrapper(actorFieldDefinition, false),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result2.current).toBe(true);
|
it('should return true if the record is read only', () => {
|
||||||
});
|
const { result } = renderHook(() =>
|
||||||
|
useIsFieldValueReadOnly({
|
||||||
it('should take isRecordDeleted into account', () => {
|
fieldDefinition: phonesFieldDefinition,
|
||||||
const { result } = renderHook(() => useIsFieldValueReadOnly(), {
|
isRecordReadOnly: true,
|
||||||
wrapper: getWrapper(phonesFieldDefinition, true),
|
}),
|
||||||
});
|
);
|
||||||
|
|
||||||
expect(result.current).toBe(true);
|
expect(result.current).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -105,6 +105,7 @@ const getWrapper =
|
|||||||
hotkeyScope: 'hotkeyScope',
|
hotkeyScope: 'hotkeyScope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
useUpdateRecord: useUpdateOneRecordMutation,
|
useUpdateRecord: useUpdateOneRecordMutation,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -342,6 +342,7 @@ const Wrapper = ({ children }: { children: ReactNode }) => {
|
|||||||
hotkeyScope: 'hotkeyScope',
|
hotkeyScope: 'hotkeyScope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
useUpdateRecord: useUpdateOneRecordMutation,
|
useUpdateRecord: useUpdateOneRecordMutation,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -1,45 +1,22 @@
|
|||||||
import { useContext } from 'react';
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
|
||||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
|
||||||
import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly';
|
import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
export const useIsFieldValueReadOnly = () => {
|
type UseIsFieldValueReadOnlyParams = {
|
||||||
const { fieldDefinition, recordId } = useContext(FieldContext);
|
isRecordReadOnly: boolean;
|
||||||
|
fieldDefinition: FieldDefinition<FieldMetadata>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useIsFieldValueReadOnly = ({
|
||||||
|
fieldDefinition,
|
||||||
|
isRecordReadOnly,
|
||||||
|
}: UseIsFieldValueReadOnlyParams) => {
|
||||||
const { metadata, type } = fieldDefinition;
|
const { metadata, type } = fieldDefinition;
|
||||||
|
|
||||||
const recordDeletedAt = useRecoilValue<ObjectRecord | null>(
|
|
||||||
recordStoreFamilySelector({
|
|
||||||
recordId,
|
|
||||||
fieldName: 'deletedAt',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const contextStoreCurrentViewType = useRecoilComponentValueV2(
|
|
||||||
contextStoreCurrentViewTypeComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { objectMetadataItem } = useObjectMetadataItem({
|
|
||||||
objectNameSingular: metadata.objectMetadataNameSingular ?? '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
|
||||||
|
|
||||||
return isFieldValueReadOnly({
|
return isFieldValueReadOnly({
|
||||||
objectNameSingular: metadata.objectMetadataNameSingular,
|
objectNameSingular: metadata.objectMetadataNameSingular,
|
||||||
fieldName: metadata.fieldName,
|
fieldName: metadata.fieldName,
|
||||||
fieldType: type,
|
fieldType: type,
|
||||||
isObjectRemote: objectMetadataItem.isRemote,
|
isRecordReadOnly,
|
||||||
isRecordDeleted: isDefined(recordDeletedAt),
|
|
||||||
hasObjectReadOnlyPermission,
|
|
||||||
contextStoreCurrentViewType,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
type UseIsRecordReadOnlyParams = {
|
||||||
|
recordId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useIsRecordReadOnly = ({
|
||||||
|
recordId,
|
||||||
|
}: UseIsRecordReadOnlyParams) => {
|
||||||
|
const recordDeletedAt = useRecoilValue<ObjectRecord | null>(
|
||||||
|
recordStoreFamilySelector({
|
||||||
|
recordId,
|
||||||
|
fieldName: 'deletedAt',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||||
|
|
||||||
|
return hasObjectReadOnlyPermission || isDefined(recordDeletedAt);
|
||||||
|
};
|
||||||
@ -1,9 +1,23 @@
|
|||||||
|
import { useOpenActivityTargetCellEditMode } from '@/activities/inline-cell/hooks/useOpenActivityTargetCellEditMode';
|
||||||
|
import { Note } from '@/activities/types/Note';
|
||||||
|
import { NoteTarget } from '@/activities/types/NoteTarget';
|
||||||
|
import { Task } from '@/activities/types/Task';
|
||||||
|
import { TaskTarget } from '@/activities/types/TaskTarget';
|
||||||
|
import { getActivityTargetObjectRecords } from '@/activities/utils/getActivityTargetObjectRecords';
|
||||||
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
import { useOpenRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput';
|
import { useOpenRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationFromManyFieldInput';
|
||||||
import { useOpenRelationToOneFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationToOneFieldInput';
|
import { useOpenRelationToOneFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useOpenRelationToOneFieldInput';
|
||||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import {
|
||||||
|
FieldMetadata,
|
||||||
|
FieldRelationFromManyValue,
|
||||||
|
FieldRelationValue,
|
||||||
|
} from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
|
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/types/guards/isFieldRelationFromManyObjects';
|
||||||
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
|
import { isFieldRelationToOneObject } from '@/object-record/record-field/types/guards/isFieldRelationToOneObject';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
export const useOpenFieldInputEditMode = () => {
|
export const useOpenFieldInputEditMode = () => {
|
||||||
@ -11,7 +25,12 @@ export const useOpenFieldInputEditMode = () => {
|
|||||||
const { openRelationFromManyFieldInput } =
|
const { openRelationFromManyFieldInput } =
|
||||||
useOpenRelationFromManyFieldInput();
|
useOpenRelationFromManyFieldInput();
|
||||||
|
|
||||||
const openFieldInput = ({
|
const { openActivityTargetCellEditMode } =
|
||||||
|
useOpenActivityTargetCellEditMode();
|
||||||
|
|
||||||
|
const openFieldInput = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
({
|
||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
recordId,
|
recordId,
|
||||||
}: {
|
}: {
|
||||||
@ -25,9 +44,47 @@ export const useOpenFieldInputEditMode = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isFieldRelationFromManyObjects(fieldDefinition) &&
|
||||||
|
['taskTarget', 'noteTarget'].includes(
|
||||||
|
fieldDefinition.metadata.relationObjectMetadataNameSingular,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const fieldValue = snapshot
|
||||||
|
.getLoadable<FieldRelationValue<FieldRelationFromManyValue>>(
|
||||||
|
recordStoreFamilySelector({
|
||||||
|
recordId,
|
||||||
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const activity = snapshot
|
||||||
|
.getLoadable(recordStoreFamilyState(recordId))
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const objectMetadataItems = snapshot
|
||||||
|
.getLoadable(objectMetadataItemsState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const activityTargetObjectRecords = getActivityTargetObjectRecords({
|
||||||
|
activityRecord: activity as Task | Note,
|
||||||
|
objectMetadataItems,
|
||||||
|
activityTargets: fieldValue as NoteTarget[] | TaskTarget[],
|
||||||
|
});
|
||||||
|
|
||||||
|
openActivityTargetCellEditMode({
|
||||||
|
recordPickerInstanceId: `relation-from-many-field-input-${recordId}`,
|
||||||
|
activityTargetObjectRecords,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isFieldRelationFromManyObjects(fieldDefinition)) {
|
if (isFieldRelationFromManyObjects(fieldDefinition)) {
|
||||||
if (
|
if (
|
||||||
isDefined(fieldDefinition.metadata.relationObjectMetadataNameSingular)
|
isDefined(
|
||||||
|
fieldDefinition.metadata.relationObjectMetadataNameSingular,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
openRelationFromManyFieldInput({
|
openRelationFromManyFieldInput({
|
||||||
fieldName: fieldDefinition.metadata.fieldName,
|
fieldName: fieldDefinition.metadata.fieldName,
|
||||||
@ -37,7 +94,13 @@ export const useOpenFieldInputEditMode = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[
|
||||||
|
openActivityTargetCellEditMode,
|
||||||
|
openRelationFromManyFieldInput,
|
||||||
|
openRelationToOneFieldInput,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
openFieldInput: openFieldInput,
|
openFieldInput: openFieldInput,
|
||||||
|
|||||||
@ -70,6 +70,7 @@ const meta: Meta = {
|
|||||||
...relationFromManyFieldDisplayMock.fieldDefinition,
|
...relationFromManyFieldDisplayMock.fieldDefinition,
|
||||||
} as unknown as FieldDefinition<FieldMetadata>,
|
} as unknown as FieldDefinition<FieldMetadata>,
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RelationFieldValueSetterEffect />
|
<RelationFieldValueSetterEffect />
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
|
||||||
|
import { useUpdateActivityTargetFromCell } from '@/activities/inline-cell/hooks/useUpdateActivityTargetFromCell';
|
||||||
|
import { NoteTarget } from '@/activities/types/NoteTarget';
|
||||||
|
import { TaskTarget } from '@/activities/types/TaskTarget';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useRelationField } from '@/object-record/record-field/meta-types/hooks/useRelationField';
|
||||||
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||||
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
|
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
|
||||||
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
|
import { recordFieldInputLayoutDirectionComponentState } from '@/object-record/record-field/states/recordFieldInputLayoutDirectionComponentState';
|
||||||
@ -22,11 +28,34 @@ export const RelationFromManyFieldInput = ({
|
|||||||
const recordPickerInstanceId = `relation-from-many-field-input-${recordId}`;
|
const recordPickerInstanceId = `relation-from-many-field-input-${recordId}`;
|
||||||
|
|
||||||
const { updateRelation } = useUpdateRelationFromManyFieldInput();
|
const { updateRelation } = useUpdateRelationFromManyFieldInput();
|
||||||
|
const fieldName = fieldDefinition.metadata.fieldName;
|
||||||
|
const objectMetadataNameSingular =
|
||||||
|
fieldDefinition.metadata.objectMetadataNameSingular;
|
||||||
|
|
||||||
|
const { updateActivityTargetFromCell } = useUpdateActivityTargetFromCell({
|
||||||
|
activityObjectNameSingular: objectMetadataNameSingular as
|
||||||
|
| CoreObjectNameSingular.Note
|
||||||
|
| CoreObjectNameSingular.Task,
|
||||||
|
activityId: recordId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { fieldValue } = useRelationField();
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
onSubmit?.(() => {});
|
onSubmit?.(() => {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isRelationFromActivityTargets =
|
||||||
|
(fieldName === 'noteTargets' &&
|
||||||
|
objectMetadataNameSingular === CoreObjectNameSingular.Note) ||
|
||||||
|
(fieldName === 'taskTargets' &&
|
||||||
|
objectMetadataNameSingular === CoreObjectNameSingular.Task);
|
||||||
|
|
||||||
|
const { activityTargetObjectRecords } = useActivityTargetObjectRecords(
|
||||||
|
recordId,
|
||||||
|
fieldValue as NoteTarget[] | TaskTarget[],
|
||||||
|
);
|
||||||
|
|
||||||
const relationFieldDefinition =
|
const relationFieldDefinition =
|
||||||
fieldDefinition as FieldDefinition<FieldRelationMetadata>;
|
fieldDefinition as FieldDefinition<FieldRelationMetadata>;
|
||||||
|
|
||||||
@ -57,8 +86,22 @@ export const RelationFromManyFieldInput = ({
|
|||||||
<MultipleRecordPicker
|
<MultipleRecordPicker
|
||||||
componentInstanceId={recordPickerInstanceId}
|
componentInstanceId={recordPickerInstanceId}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onChange={updateRelation}
|
onChange={(morphItem) => {
|
||||||
onCreate={createNewRecordAndOpenRightDrawer}
|
if (isRelationFromActivityTargets) {
|
||||||
|
updateActivityTargetFromCell({
|
||||||
|
morphItem,
|
||||||
|
activityTargetWithTargetRecords: activityTargetObjectRecords,
|
||||||
|
recordPickerInstanceId,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
updateRelation(morphItem);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCreate={
|
||||||
|
!isRelationFromActivityTargets
|
||||||
|
? createNewRecordAndOpenRightDrawer
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
onClickOutside={handleSubmit}
|
onClickOutside={handleSubmit}
|
||||||
layoutDirection={
|
layoutDirection={
|
||||||
layoutDirection === 'downward'
|
layoutDirection === 'downward'
|
||||||
|
|||||||
@ -75,6 +75,7 @@ const AddressInputWithContext = ({
|
|||||||
recordId: recordId ?? '123',
|
recordId: recordId ?? '123',
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AddressValueSetterEffect value={value} />
|
<AddressValueSetterEffect value={value} />
|
||||||
|
|||||||
@ -66,6 +66,7 @@ const BooleanFieldInputWithContext = ({
|
|||||||
recordId: recordId ?? '123',
|
recordId: recordId ?? '123',
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BooleanFieldValueSetterEffect
|
<BooleanFieldValueSetterEffect
|
||||||
|
|||||||
@ -93,6 +93,7 @@ const DateFieldInputWithContext = ({
|
|||||||
recordId: '123',
|
recordId: '123',
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StorybookFieldInputDropdownFocusIdSetterEffect />
|
<StorybookFieldInputDropdownFocusIdSetterEffect />
|
||||||
|
|||||||
@ -70,6 +70,7 @@ const NumberFieldInputWithContext = ({
|
|||||||
recordId: '123',
|
recordId: '123',
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StorybookFieldInputDropdownFocusIdSetterEffect />
|
<StorybookFieldInputDropdownFocusIdSetterEffect />
|
||||||
|
|||||||
@ -68,6 +68,7 @@ const RatingFieldInputWithContext = ({
|
|||||||
recordId: recordId ?? '123',
|
recordId: recordId ?? '123',
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RatingFieldValueSetterEffect value={value} />
|
<RatingFieldValueSetterEffect value={value} />
|
||||||
|
|||||||
@ -95,6 +95,7 @@ const RelationManyFieldInputWithContext = () => {
|
|||||||
recordId: 'recordId',
|
recordId: 'recordId',
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RelationWorkspaceSetterEffect />
|
<RelationWorkspaceSetterEffect />
|
||||||
|
|||||||
@ -88,6 +88,7 @@ const RelationToOneFieldInputWithContext = ({
|
|||||||
recordId: recordId,
|
recordId: recordId,
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordFieldComponentInstanceContext.Provider
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
|
|||||||
@ -63,6 +63,7 @@ const TextFieldInputWithContext = ({
|
|||||||
},
|
},
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StorybookFieldInputDropdownFocusIdSetterEffect />
|
<StorybookFieldInputDropdownFocusIdSetterEffect />
|
||||||
|
|||||||
@ -1,142 +1,90 @@
|
|||||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
|
|
||||||
describe('isFieldValueReadOnly', () => {
|
describe('isFieldValueReadOnly', () => {
|
||||||
it('should return true if fieldName is noteTargets or taskTargets', () => {
|
it('should return true if record is read only', () => {
|
||||||
const result = isFieldValueReadOnly({
|
const result = isFieldValueReadOnly({
|
||||||
fieldName: 'noteTargets',
|
isRecordReadOnly: true,
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
|
||||||
expect(result).toBe(true);
|
|
||||||
|
|
||||||
const result2 = isFieldValueReadOnly({
|
|
||||||
fieldName: 'taskTargets',
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result2).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if fieldName is noteTargets or taskTargets but is not in table or kanban view', () => {
|
|
||||||
const result = isFieldValueReadOnly({
|
|
||||||
fieldName: 'noteTargets',
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.ShowPage,
|
|
||||||
});
|
|
||||||
expect(result).toBe(false);
|
|
||||||
|
|
||||||
const result2 = isFieldValueReadOnly({
|
|
||||||
fieldName: 'taskTargets',
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.ShowPage,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result2).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false if fieldName is not noteTargets or taskTargets', () => {
|
|
||||||
const result = isFieldValueReadOnly({
|
|
||||||
fieldName: 'test',
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if isObjectRemote is true', () => {
|
|
||||||
const result = isFieldValueReadOnly({
|
|
||||||
isObjectRemote: true,
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if isObjectRemote is false', () => {
|
it('should return true if object is a workflow sub object', () => {
|
||||||
const result = isFieldValueReadOnly({
|
const result = isFieldValueReadOnly({
|
||||||
isObjectRemote: false,
|
objectNameSingular: 'workflowRun',
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if isRecordDeleted is true', () => {
|
|
||||||
const result = isFieldValueReadOnly({
|
|
||||||
isRecordDeleted: true,
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if isRecordDeleted is false', () => {
|
it('should return true if object is a calendar event', () => {
|
||||||
const result = isFieldValueReadOnly({
|
const result = isFieldValueReadOnly({
|
||||||
isRecordDeleted: false,
|
objectNameSingular: CoreObjectNameSingular.CalendarEvent,
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if objectNameSingular is Workflow and fieldName is not name', () => {
|
|
||||||
const result = isFieldValueReadOnly({
|
|
||||||
objectNameSingular: 'workflow',
|
|
||||||
fieldName: 'test',
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if objectNameSingular is Workflow and fieldName is name', () => {
|
it('should return true if object is a workflow and field is not name', () => {
|
||||||
const result = isFieldValueReadOnly({
|
const result = isFieldValueReadOnly({
|
||||||
objectNameSingular: 'Workflow',
|
objectNameSingular: CoreObjectNameSingular.Workflow,
|
||||||
|
fieldName: 'description',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false if object is a workflow and field is name', () => {
|
||||||
|
const result = isFieldValueReadOnly({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.Workflow,
|
||||||
fieldName: 'name',
|
fieldName: 'name',
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if isWorkflowSubObjectMetadata is true', () => {
|
describe('when checking field types', () => {
|
||||||
const result = isFieldValueReadOnly({
|
it('should return true if fieldType is RICH_TEXT', () => {
|
||||||
objectNameSingular: 'workflowVersion',
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if fieldType is FieldMetadataType.ACTOR', () => {
|
|
||||||
const result = isFieldValueReadOnly({
|
|
||||||
fieldType: FieldMetadataType.ACTOR,
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true if fieldType is FieldMetadataType.RICH_TEXT', () => {
|
|
||||||
const result = isFieldValueReadOnly({
|
const result = isFieldValueReadOnly({
|
||||||
fieldType: FieldMetadataType.RICH_TEXT,
|
fieldType: FieldMetadataType.RICH_TEXT,
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if fieldType is not FieldMetadataType.ACTOR or FieldMetadataType.RICH_TEXT', () => {
|
it('should return true if fieldType is RICH_TEXT_V2', () => {
|
||||||
|
const result = isFieldValueReadOnly({
|
||||||
|
fieldType: FieldMetadataType.RICH_TEXT_V2,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true if fieldType is ACTOR', () => {
|
||||||
|
const result = isFieldValueReadOnly({
|
||||||
|
fieldType: FieldMetadataType.ACTOR,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for other field types', () => {
|
||||||
const result = isFieldValueReadOnly({
|
const result = isFieldValueReadOnly({
|
||||||
fieldType: FieldMetadataType.TEXT,
|
fieldType: FieldMetadataType.TEXT,
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return false if none of the conditions are met', () => {
|
it('should return false for standard editable fields', () => {
|
||||||
const result = isFieldValueReadOnly({
|
const result = isFieldValueReadOnly({
|
||||||
contextStoreCurrentViewType: ContextStoreViewType.Table,
|
objectNameSingular: 'company',
|
||||||
|
fieldName: 'name',
|
||||||
|
fieldType: FieldMetadataType.TEXT,
|
||||||
|
isRecordReadOnly: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
|
|||||||
@ -1,51 +1,25 @@
|
|||||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
|
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
|
||||||
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
import { isFieldActor } from '@/object-record/record-field/types/guards/isFieldActor';
|
||||||
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
import { isFieldRichText } from '@/object-record/record-field/types/guards/isFieldRichText';
|
||||||
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
import { isFieldRichTextV2 } from '@/object-record/record-field/types/guards/isFieldRichTextV2';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
type isFieldValueReadOnlyParams = {
|
type isFieldValueReadOnlyParams = {
|
||||||
objectNameSingular?: string;
|
objectNameSingular?: string;
|
||||||
fieldName?: string;
|
fieldName?: string;
|
||||||
fieldType?: FieldMetadataType;
|
fieldType?: FieldMetadataType;
|
||||||
isObjectRemote?: boolean;
|
isRecordReadOnly?: boolean;
|
||||||
isRecordDeleted?: boolean;
|
|
||||||
hasObjectReadOnlyPermission?: boolean;
|
|
||||||
contextStoreCurrentViewType: ContextStoreViewType | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isFieldValueReadOnly = ({
|
export const isFieldValueReadOnly = ({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
fieldName,
|
fieldName,
|
||||||
fieldType,
|
fieldType,
|
||||||
isObjectRemote = false,
|
isRecordReadOnly = false,
|
||||||
isRecordDeleted = false,
|
|
||||||
hasObjectReadOnlyPermission = false,
|
|
||||||
contextStoreCurrentViewType,
|
|
||||||
}: isFieldValueReadOnlyParams) => {
|
}: isFieldValueReadOnlyParams) => {
|
||||||
const isTableViewOrKanbanView =
|
if (isRecordReadOnly) {
|
||||||
contextStoreCurrentViewType === ContextStoreViewType.Table ||
|
|
||||||
contextStoreCurrentViewType === ContextStoreViewType.Kanban;
|
|
||||||
|
|
||||||
const isTargetField =
|
|
||||||
fieldName === 'noteTargets' || fieldName === 'taskTargets';
|
|
||||||
|
|
||||||
if (isTableViewOrKanbanView && isTargetField) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isObjectRemote) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRecordDeleted) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasObjectReadOnlyPermission) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +38,20 @@ export const isFieldValueReadOnly = ({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
objectNameSingular !== CoreObjectNameSingular.Note &&
|
||||||
|
fieldName === 'noteTargets'
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
objectNameSingular !== CoreObjectNameSingular.Task &&
|
||||||
|
fieldName === 'taskTargets'
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isDefined(fieldType) &&
|
isDefined(fieldType) &&
|
||||||
(isFieldActor({ type: fieldType }) ||
|
(isFieldActor({ type: fieldType }) ||
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import {
|
|||||||
} from '@/object-record/record-field/types/FieldInputEvent';
|
} from '@/object-record/record-field/types/FieldInputEvent';
|
||||||
|
|
||||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
|
||||||
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
|
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
|
||||||
|
|
||||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
@ -46,14 +45,13 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
|||||||
isDisplayModeFixHeight,
|
isDisplayModeFixHeight,
|
||||||
onOpenEditMode,
|
onOpenEditMode,
|
||||||
onCloseEditMode,
|
onCloseEditMode,
|
||||||
|
isReadOnly,
|
||||||
} = useContext(FieldContext);
|
} = useContext(FieldContext);
|
||||||
|
|
||||||
const buttonIcon = useGetButtonIcon();
|
const buttonIcon = useGetButtonIcon();
|
||||||
|
|
||||||
const isFieldInputOnly = useIsFieldInputOnly();
|
const isFieldInputOnly = useIsFieldInputOnly();
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
|
||||||
|
|
||||||
const { closeInlineCell } = useInlineCell();
|
const { closeInlineCell } = useInlineCell();
|
||||||
|
|
||||||
const handleEnter: FieldInputEvent = (persistField) => {
|
const handleEnter: FieldInputEvent = (persistField) => {
|
||||||
@ -134,7 +132,7 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const RecordInlineCellContextValue: RecordInlineCellContextProps = {
|
const RecordInlineCellContextValue: RecordInlineCellContextProps = {
|
||||||
readonly: isFieldReadOnly,
|
readonly: isReadOnly,
|
||||||
buttonIcon: buttonIcon,
|
buttonIcon: buttonIcon,
|
||||||
IconLabel: fieldDefinition.iconName
|
IconLabel: fieldDefinition.iconName
|
||||||
? getIcon(fieldDefinition.iconName)
|
? getIcon(fieldDefinition.iconName)
|
||||||
@ -152,7 +150,7 @@ export const RecordInlineCell = ({ loading }: RecordInlineCellProps) => {
|
|||||||
onTab={handleTab}
|
onTab={handleTab}
|
||||||
onShiftTab={handleShiftTab}
|
onShiftTab={handleShiftTab}
|
||||||
onClickOutside={handleClickOutside}
|
onClickOutside={handleClickOutside}
|
||||||
isReadOnly={isFieldReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
displayModeContent: <FieldDisplay />,
|
displayModeContent: <FieldDisplay />,
|
||||||
|
|||||||
@ -9,13 +9,13 @@ import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInput
|
|||||||
|
|
||||||
import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata';
|
import { assertFieldMetadata } from '@/object-record/record-field/types/guards/assertFieldMetadata';
|
||||||
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
import { isFieldText } from '@/object-record/record-field/types/guards/isFieldText';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|
||||||
import { useRecordInlineCellContext } from './RecordInlineCellContext';
|
|
||||||
import {
|
import {
|
||||||
AppTooltip,
|
AppTooltip,
|
||||||
OverflowingTextWithTooltip,
|
OverflowingTextWithTooltip,
|
||||||
TooltipDelay,
|
TooltipDelay,
|
||||||
} from 'twenty-ui/display';
|
} from 'twenty-ui/display';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
import { useRecordInlineCellContext } from './RecordInlineCellContext';
|
||||||
|
|
||||||
const StyledIconContainer = styled.div`
|
const StyledIconContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -38,7 +38,6 @@ const StyledLabelAndIconContainer = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
height: 18px;
|
height: 18px;
|
||||||
padding-top: 3px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledValueContainer = styled.div`
|
const StyledValueContainer = styled.div`
|
||||||
|
|||||||
@ -63,13 +63,8 @@ export const RecordInlineCellDisplayMode = ({
|
|||||||
|
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const {
|
const { editModeContentOnly, showLabel, label, buttonIcon } =
|
||||||
editModeContentOnly,
|
useRecordInlineCellContext();
|
||||||
|
|
||||||
showLabel,
|
|
||||||
label,
|
|
||||||
buttonIcon,
|
|
||||||
} = useRecordInlineCellContext();
|
|
||||||
|
|
||||||
const isDisplayModeContentEmpty = useIsFieldEmpty();
|
const isDisplayModeContentEmpty = useIsFieldEmpty();
|
||||||
const showEditButton =
|
const showEditButton =
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
|
|||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||||
@ -84,6 +85,10 @@ export const FieldsCard = ({
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
|
recordId: objectRecordId,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PropertyBox>
|
<PropertyBox>
|
||||||
@ -109,6 +114,7 @@ export const FieldsCard = ({
|
|||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
isDisplayModeFixHeight: true,
|
isDisplayModeFixHeight: true,
|
||||||
|
isReadOnly: isRecordReadOnly,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ActivityTargetsInlineCell
|
<ActivityTargetsInlineCell
|
||||||
@ -146,6 +152,7 @@ export const FieldsCard = ({
|
|||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
isDisplayModeFixHeight: true,
|
isDisplayModeFixHeight: true,
|
||||||
|
isReadOnly: isRecordReadOnly,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordFieldComponentInstanceContext.Provider
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
@ -182,6 +189,7 @@ export const FieldsCard = ({
|
|||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
isDisplayModeFixHeight: true,
|
isDisplayModeFixHeight: true,
|
||||||
|
isReadOnly: isRecordReadOnly,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordDetailRelationSection
|
<RecordDetailRelationSection
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
||||||
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
|
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
|
||||||
@ -55,6 +56,10 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
|||||||
objectRecordId,
|
objectRecordId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
|
recordId: objectRecordId,
|
||||||
|
});
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -87,6 +92,7 @@ export const ObjectRecordShowPageBreadcrumb = ({
|
|||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
isCentered: false,
|
isCentered: false,
|
||||||
isDisplayModeFixHeight: true,
|
isDisplayModeFixHeight: true,
|
||||||
|
isReadOnly: isRecordReadOnly,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordTitleCell sizeVariant="xs" />
|
<RecordTitleCell sizeVariant="xs" />
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
||||||
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
||||||
@ -10,8 +11,8 @@ import { RecordTitleCell } from '@/object-record/record-title-cell/components/Re
|
|||||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { FieldMetadataType } from '~/generated/graphql';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
|
|
||||||
type SummaryCardProps = {
|
type SummaryCardProps = {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
@ -54,6 +55,10 @@ export const SummaryCard = ({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
|
recordId: objectRecordId,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShowPageSummaryCard
|
<ShowPageSummaryCard
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
@ -88,6 +93,7 @@ export const SummaryCard = ({
|
|||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
isCentered: !isMobile,
|
isCentered: !isMobile,
|
||||||
isDisplayModeFixHeight: true,
|
isDisplayModeFixHeight: true,
|
||||||
|
isReadOnly: isRecordReadOnly,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordTitleCell sizeVariant="md" />
|
<RecordTitleCell sizeVariant="md" />
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
RecordUpdateHookParams,
|
RecordUpdateHookParams,
|
||||||
} from '@/object-record/record-field/contexts/FieldContext';
|
} from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||||
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
@ -209,7 +210,14 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
[isExpanded],
|
[isExpanded],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isReadOnly = useIsFieldValueReadOnly();
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
|
recordId: relationRecord.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||||
|
fieldDefinition,
|
||||||
|
isRecordReadOnly,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -226,7 +234,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
accent="tertiary"
|
accent="tertiary"
|
||||||
/>
|
/>
|
||||||
</StyledClickableZone>
|
</StyledClickableZone>
|
||||||
{!isReadOnly && (
|
{!isFieldReadOnly && (
|
||||||
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
<DropdownScope dropdownScopeId={dropdownScopeId}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={dropdownScopeId}
|
dropdownId={dropdownScopeId}
|
||||||
@ -279,6 +287,7 @@ export const RecordDetailRelationRecordsListItem = ({
|
|||||||
}),
|
}),
|
||||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordFieldComponentInstanceContext.Provider
|
<RecordFieldComponentInstanceContext.Provider
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||||
|
import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly';
|
||||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||||
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
import { useAddNewRecordAndOpenRightDrawer } from '@/object-record/record-field/meta-types/input/hooks/useAddNewRecordAndOpenRightDrawer';
|
||||||
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
|
import { useUpdateRelationFromManyFieldInput } from '@/object-record/record-field/meta-types/input/hooks/useUpdateRelationFromManyFieldInput';
|
||||||
@ -190,7 +191,14 @@ export const RecordDetailRelationSection = ({
|
|||||||
recordId,
|
recordId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isReadOnly = useIsFieldValueReadOnly();
|
const isRecordReadOnly = useIsRecordReadOnly({
|
||||||
|
recordId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||||
|
fieldDefinition,
|
||||||
|
isRecordReadOnly,
|
||||||
|
});
|
||||||
|
|
||||||
if (loading) return null;
|
if (loading) return null;
|
||||||
|
|
||||||
@ -250,7 +258,7 @@ export const RecordDetailRelationSection = ({
|
|||||||
hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile}
|
hideRightAdornmentOnMouseLeave={!isDropdownOpen && !isMobile}
|
||||||
areRecordsAvailable={relationRecords.length > 0}
|
areRecordsAvailable={relationRecords.length > 0}
|
||||||
rightAdornment={
|
rightAdornment={
|
||||||
!isReadOnly && (
|
!isFieldReadOnly && (
|
||||||
<DropdownScope dropdownScopeId={dropdownId}>
|
<DropdownScope dropdownScopeId={dropdownId}>
|
||||||
<StyledAddDropdown
|
<StyledAddDropdown
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
|
|||||||
@ -46,6 +46,7 @@ const meta: Meta<typeof RecordDetailRelationSection> = {
|
|||||||
objectMetadataItem: mockedCompanyObjectMetadataItem,
|
objectMetadataItem: mockedCompanyObjectMetadataItem,
|
||||||
}),
|
}),
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Story />
|
<Story />
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { scrollWrapperScrollBottomComponentState } from '@/ui/utilities/scroll/states/scrollWrappeScrollBottomComponentState';
|
import { scrollWrapperScrollBottomComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollBottomComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordTableStickyBottomEffect = () => {
|
export const RecordTableStickyBottomEffect = () => {
|
||||||
|
|||||||
@ -102,7 +102,6 @@ export const RecordTableWithWrappers = ({
|
|||||||
>
|
>
|
||||||
<EntityDeleteContext.Provider value={deleteOneRecord}>
|
<EntityDeleteContext.Provider value={deleteOneRecord}>
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="recordTableWithWrappers"
|
|
||||||
componentInstanceId={`record-table-scroll-${recordTableId}`}
|
componentInstanceId={`record-table-scroll-${recordTableId}`}
|
||||||
>
|
>
|
||||||
<RecordUpdateContext.Provider value={updateRecordMutation}>
|
<RecordUpdateContext.Provider value={updateRecordMutation}>
|
||||||
|
|||||||
@ -137,6 +137,7 @@ const meta: Meta = {
|
|||||||
...mockPerformance.fieldDefinition,
|
...mockPerformance.fieldDefinition,
|
||||||
},
|
},
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RelationFieldValueSetterEffect />
|
<RelationFieldValueSetterEffect />
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export type RecordTableRowContextValue = {
|
|||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
inView: boolean;
|
inView: boolean;
|
||||||
isPendingRow?: boolean;
|
isPendingRow?: boolean;
|
||||||
|
isReadOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const [RecordTableRowContextProvider, useRecordTableRowContextOrThrow] =
|
export const [RecordTableRowContextProvider, useRecordTableRowContextOrThrow] =
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useContext } from 'react';
|
|
||||||
import { useInView } from 'react-intersection-observer';
|
import { useInView } from 'react-intersection-observer';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
import { isRecordIndexLoadMoreLockedComponentState } from '@/object-record/record-index/states/isRecordIndexLoadMoreLockedComponentState';
|
import { isRecordIndexLoadMoreLockedComponentState } from '@/object-record/record-index/states/isRecordIndexLoadMoreLockedComponentState';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record/record-table/states/hasRecordTableFetchedAllRecordsComponentStateV2';
|
import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record/record-table/states/hasRecordTableFetchedAllRecordsComponentStateV2';
|
||||||
import { RecordTableWithWrappersScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { GRAY_SCALE } from 'twenty-ui/theme';
|
import { GRAY_SCALE } from 'twenty-ui/theme';
|
||||||
|
|
||||||
@ -38,9 +37,7 @@ export const RecordTableBodyFetchMoreLoader = () => {
|
|||||||
[setRecordTableLastRowVisible, isRecordTableLoadMoreLocked],
|
[setRecordTableLastRowVisible, isRecordTableLoadMoreLocked],
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollWrapperRef = useContext(
|
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||||
RecordTableWithWrappersScrollWrapperContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasRecordTableFetchedAllRecordsComponents = useRecoilComponentValueV2(
|
const hasRecordTableFetchedAllRecordsComponents = useRecoilComponentValueV2(
|
||||||
hasRecordTableFetchedAllRecordsComponentStateV2,
|
hasRecordTableFetchedAllRecordsComponentStateV2,
|
||||||
@ -53,9 +50,7 @@ export const RecordTableBodyFetchMoreLoader = () => {
|
|||||||
onChange: onLastRowVisible,
|
onChange: onLastRowVisible,
|
||||||
delay: 1000,
|
delay: 1000,
|
||||||
rootMargin: '1000px',
|
rootMargin: '1000px',
|
||||||
root: scrollWrapperRef?.ref.current?.querySelector(
|
root: scrollWrapperHTMLElement,
|
||||||
'[data-overlayscrollbars-viewport]',
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!showLoadingMoreRow) {
|
if (!showLoadingMoreRow) {
|
||||||
|
|||||||
@ -11,11 +11,11 @@ import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record
|
|||||||
import { tableEncounteredUnrecoverableErrorComponentState } from '@/object-record/record-table/states/tableEncounteredUnrecoverableErrorComponentState';
|
import { tableEncounteredUnrecoverableErrorComponentState } from '@/object-record/record-table/states/tableEncounteredUnrecoverableErrorComponentState';
|
||||||
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
|
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
|
||||||
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
||||||
|
import { useScrollToPosition } from '@/ui/utilities/scroll/hooks/useScrollToPosition';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { isNonEmptyString, isNull } from '@sniptt/guards';
|
import { isNonEmptyString, isNull } from '@sniptt/guards';
|
||||||
import { useScrollToPosition } from '~/hooks/useScrollToPosition';
|
|
||||||
|
|
||||||
export const RecordTableNoRecordGroupBodyEffect = () => {
|
export const RecordTableNoRecordGroupBodyEffect = () => {
|
||||||
const { objectNameSingular } = useRecordTableContextOrThrow();
|
const { objectNameSingular } = useRecordTableContextOrThrow();
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/
|
|||||||
import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState';
|
import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState';
|
||||||
import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight';
|
import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { useScrollToPosition } from '@/ui/utilities/scroll/hooks/useScrollToPosition';
|
||||||
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
|
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
|
||||||
import { isNonEmptyString, isNull } from '@sniptt/guards';
|
import { isNonEmptyString, isNull } from '@sniptt/guards';
|
||||||
import { useScrollToPosition } from '~/hooks/useScrollToPosition';
|
|
||||||
|
|
||||||
export const RecordTableRecordGroupBodyEffect = () => {
|
export const RecordTableRecordGroupBodyEffect = () => {
|
||||||
const { objectNameSingular } = useRecordTableContextOrThrow();
|
const { objectNameSingular } = useRecordTableContextOrThrow();
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { ReactNode, useContext } from 'react';
|
|||||||
|
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
|
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
|
||||||
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
|
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
|
||||||
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
@ -52,11 +51,11 @@ export const RecordTableCellBaseContainer = ({
|
|||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isReadOnly } = useContext(FieldContext);
|
||||||
const { setIsFocused } = useFieldFocus();
|
const { setIsFocused } = useFieldFocus();
|
||||||
const { openTableCell } = useOpenRecordTableCellFromCell();
|
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||||
const { theme } = useContext(ThemeContext);
|
const { theme } = useContext(ThemeContext);
|
||||||
|
|
||||||
const isReadOnly = useIsFieldValueReadOnly();
|
|
||||||
const { hasSoftFocus, cellPosition } = useContext(RecordTableCellContext);
|
const { hasSoftFocus, cellPosition } = useContext(RecordTableCellContext);
|
||||||
|
|
||||||
const { onMoveSoftFocusToCurrentCell, onCellMouseEnter } =
|
const { onMoveSoftFocusToCurrentCell, onCellMouseEnter } =
|
||||||
@ -98,7 +97,7 @@ export const RecordTableCellBaseContainer = ({
|
|||||||
fontColorExtraLight={theme.font.color.extraLight}
|
fontColorExtraLight={theme.font.color.extraLight}
|
||||||
fontColorMedium={theme.border.color.medium}
|
fontColorMedium={theme.border.color.medium}
|
||||||
hasSoftFocus={hasSoftFocus}
|
hasSoftFocus={hasSoftFocus}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly ?? false}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledBaseContainer>
|
</StyledBaseContainer>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ReactNode, useContext } from 'react';
|
|
||||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
@ -8,16 +8,17 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
|
|||||||
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
|
import { MultipleRecordPickerHotkeyScope } from '@/object-record/record-picker/multiple-record-picker/types/MultipleRecordPickerHotkeyScope';
|
||||||
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
import { SingleRecordPickerHotkeyScope } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerHotkeyScope';
|
||||||
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
|
||||||
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope';
|
import { SelectFieldHotkeyScope } from '@/object-record/select/types/SelectFieldHotkeyScope';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { ReactNode, useContext } from 'react';
|
||||||
|
import { useIsMobile } from 'twenty-ui/utilities';
|
||||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||||
import { isRecordTableScrolledLeftComponentState } from '../../states/isRecordTableScrolledLeftComponentState';
|
import { isRecordTableScrolledLeftComponentState } from '../../states/isRecordTableScrolledLeftComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
|
||||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
|
||||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
|
||||||
import { useIsMobile } from 'twenty-ui/utilities';
|
|
||||||
|
|
||||||
export const RecordTableCellFieldContext = ({
|
export const RecordTableCellFieldContext = ({
|
||||||
children,
|
children,
|
||||||
@ -27,7 +28,8 @@ export const RecordTableCellFieldContext = ({
|
|||||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||||
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
|
||||||
const { columnDefinition } = useContext(RecordTableCellContext);
|
const { columnDefinition } = useContext(RecordTableCellContext);
|
||||||
const { recordId } = useRecordTableRowContextOrThrow();
|
const { recordId, isReadOnly: isTableRowReadOnly } =
|
||||||
|
useRecordTableRowContextOrThrow();
|
||||||
const updateRecord = useContext(RecordUpdateContext);
|
const updateRecord = useContext(RecordUpdateContext);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
@ -70,6 +72,11 @@ export const RecordTableCellFieldContext = ({
|
|||||||
|
|
||||||
const customHotkeyScope = computedHotkeyScope(columnDefinition);
|
const customHotkeyScope = computedHotkeyScope(columnDefinition);
|
||||||
|
|
||||||
|
const isFieldReadOnly = useIsFieldValueReadOnly({
|
||||||
|
fieldDefinition: columnDefinition,
|
||||||
|
isRecordReadOnly: isTableRowReadOnly ?? false,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -87,6 +94,7 @@ export const RecordTableCellFieldContext = ({
|
|||||||
}),
|
}),
|
||||||
displayedMaxRows: 1,
|
displayedMaxRows: 1,
|
||||||
isLabelHidden,
|
isLabelHidden,
|
||||||
|
isReadOnly: isFieldReadOnly,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { ReactNode, useContext } from 'react';
|
|
||||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/states/contexts/RecordFieldComponentInstanceContext';
|
||||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
|
import { RecordTableCellFieldContext } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContext';
|
||||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||||
|
import { ReactNode, useContext } from 'react';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
import { RecordTableCellFieldContext } from './RecordTableCellFieldContext';
|
|
||||||
|
|
||||||
export const RecordTableCellFieldContextWrapper = ({
|
export const RecordTableCellFieldContextWrapper = ({
|
||||||
children,
|
children,
|
||||||
@ -12,6 +12,7 @@ export const RecordTableCellFieldContextWrapper = ({
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const { columnDefinition } = useContext(RecordTableCellContext);
|
const { columnDefinition } = useContext(RecordTableCellContext);
|
||||||
|
|
||||||
const { recordId } = useRecordTableRowContextOrThrow();
|
const { recordId } = useRecordTableRowContextOrThrow();
|
||||||
|
|
||||||
if (isUndefinedOrNull(columnDefinition)) {
|
if (isUndefinedOrNull(columnDefinition)) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import {
|
import {
|
||||||
FieldInputClickOutsideEvent,
|
FieldInputClickOutsideEvent,
|
||||||
FieldInputEvent,
|
FieldInputEvent,
|
||||||
@ -7,12 +7,13 @@ import {
|
|||||||
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
|
||||||
|
import { useContext } from 'react';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
export const RecordTableCellFieldInput = () => {
|
export const RecordTableCellFieldInput = () => {
|
||||||
const { onMoveFocus, onCloseTableCell } = useRecordTableBodyContextOrThrow();
|
const { onMoveFocus, onCloseTableCell } = useRecordTableBodyContextOrThrow();
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
const { isReadOnly } = useContext(FieldContext);
|
||||||
|
|
||||||
const handleEnter: FieldInputEvent = (persistField) => {
|
const handleEnter: FieldInputEvent = (persistField) => {
|
||||||
persistField();
|
persistField();
|
||||||
@ -77,7 +78,7 @@ export const RecordTableCellFieldInput = () => {
|
|||||||
onShiftTab={handleShiftTab}
|
onShiftTab={handleShiftTab}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onTab={handleTab}
|
onTab={handleTab}
|
||||||
isReadOnly={isFieldReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,12 +10,11 @@ import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/rec
|
|||||||
import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState';
|
import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState';
|
||||||
|
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
|
||||||
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { IconArrowUpRight } from 'twenty-ui/display';
|
import { IconArrowUpRight } from 'twenty-ui/display';
|
||||||
import { useIsMobile } from 'twenty-ui/utilities';
|
import { useIsMobile } from 'twenty-ui/utilities';
|
||||||
|
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
|
||||||
|
|
||||||
type RecordTableCellSoftFocusModeProps = {
|
type RecordTableCellSoftFocusModeProps = {
|
||||||
editModeContent: ReactElement;
|
editModeContent: ReactElement;
|
||||||
@ -27,12 +26,10 @@ export const RecordTableCellSoftFocusMode = ({
|
|||||||
nonEditModeContent,
|
nonEditModeContent,
|
||||||
}: RecordTableCellSoftFocusModeProps) => {
|
}: RecordTableCellSoftFocusModeProps) => {
|
||||||
const { columnIndex, columnDefinition } = useContext(RecordTableCellContext);
|
const { columnIndex, columnDefinition } = useContext(RecordTableCellContext);
|
||||||
const { recordId } = useContext(FieldContext);
|
const { recordId, isReadOnly } = useContext(FieldContext);
|
||||||
|
|
||||||
const { onActionMenuDropdownOpened } = useRecordTableBodyContextOrThrow();
|
const { onActionMenuDropdownOpened } = useRecordTableBodyContextOrThrow();
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
|
||||||
|
|
||||||
const { openTableCell } = useOpenRecordTableCellFromCell();
|
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||||
|
|
||||||
const editModeContentOnly = useIsFieldInputOnly();
|
const editModeContentOnly = useIsFieldInputOnly();
|
||||||
@ -52,7 +49,7 @@ export const RecordTableCellSoftFocusMode = ({
|
|||||||
}, [isSoftFocusUsingMouse]);
|
}, [isSoftFocusUsingMouse]);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (!isFieldInputOnly && !isFieldReadOnly) {
|
if (!isFieldInputOnly && !isReadOnly) {
|
||||||
openTableCell();
|
openTableCell();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -85,13 +82,13 @@ export const RecordTableCellSoftFocusMode = ({
|
|||||||
const showButton =
|
const showButton =
|
||||||
isDefined(buttonIcon) &&
|
isDefined(buttonIcon) &&
|
||||||
!editModeContentOnly &&
|
!editModeContentOnly &&
|
||||||
!isFieldReadOnly &&
|
!isReadOnly &&
|
||||||
!(isMobile && isFirstColumn);
|
!(isMobile && isFirstColumn);
|
||||||
|
|
||||||
const dontShowContent = isEmpty && isFieldReadOnly;
|
const dontShowContent = isEmpty && isReadOnly;
|
||||||
|
|
||||||
const showPlaceholder =
|
const showPlaceholder =
|
||||||
!editModeContentOnly && !isFieldReadOnly && isFirstColumn && isEmpty;
|
!editModeContentOnly && !isReadOnly && isFirstColumn && isEmpty;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useContext, useEffect, useRef } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
@ -11,14 +11,12 @@ import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/
|
|||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
||||||
|
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
|
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
|
||||||
|
|
||||||
export const RecordTableCellSoftFocusModeHotkeysSetterEffect = () => {
|
export const RecordTableCellSoftFocusModeHotkeysSetterEffect = () => {
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
|
||||||
|
|
||||||
const { openTableCell } = useOpenRecordTableCellFromCell();
|
const { openTableCell } = useOpenRecordTableCellFromCell();
|
||||||
|
const { isReadOnly } = useContext(FieldContext);
|
||||||
|
|
||||||
const isFieldInputOnly = useIsFieldInputOnly();
|
const isFieldInputOnly = useIsFieldInputOnly();
|
||||||
|
|
||||||
@ -50,7 +48,7 @@ export const RecordTableCellSoftFocusModeHotkeysSetterEffect = () => {
|
|||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
Key.Enter,
|
Key.Enter,
|
||||||
() => {
|
() => {
|
||||||
if (isFieldReadOnly) {
|
if (isReadOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +65,7 @@ export const RecordTableCellSoftFocusModeHotkeysSetterEffect = () => {
|
|||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
'*',
|
'*',
|
||||||
(keyboardEvent) => {
|
(keyboardEvent) => {
|
||||||
if (isFieldReadOnly) {
|
if (isReadOnly) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -54,6 +54,7 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|||||||
recordId: 'recordId',
|
recordId: 'recordId',
|
||||||
hotkeyScope: TableHotkeyScope.Table,
|
hotkeyScope: TableHotkeyScope.Table,
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
||||||
|
|||||||
@ -54,6 +54,7 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|||||||
recordId: 'recordId',
|
recordId: 'recordId',
|
||||||
hotkeyScope: TableHotkeyScope.Table,
|
hotkeyScope: TableHotkeyScope.Table,
|
||||||
isLabelIdentifier: false,
|
isLabelIdentifier: false,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
|
|
||||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
|
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
|
||||||
@ -29,7 +28,7 @@ export type OpenTableCellArgs = {
|
|||||||
|
|
||||||
export const useOpenRecordTableCellFromCell = () => {
|
export const useOpenRecordTableCellFromCell = () => {
|
||||||
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
|
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
|
||||||
const { recordId, fieldDefinition } = useContext(FieldContext);
|
const { recordId, fieldDefinition, isReadOnly } = useContext(FieldContext);
|
||||||
|
|
||||||
const { pathToShowPage, objectNameSingular } =
|
const { pathToShowPage, objectNameSingular } =
|
||||||
useRecordTableRowContextOrThrow();
|
useRecordTableRowContextOrThrow();
|
||||||
@ -38,8 +37,6 @@ export const useOpenRecordTableCellFromCell = () => {
|
|||||||
|
|
||||||
const cellPosition = useCurrentTableCellPosition();
|
const cellPosition = useCurrentTableCellPosition();
|
||||||
|
|
||||||
const isFieldReadOnly = useIsFieldValueReadOnly();
|
|
||||||
|
|
||||||
const openTableCell = (
|
const openTableCell = (
|
||||||
initialValue?: string,
|
initialValue?: string,
|
||||||
isActionButtonClick = false,
|
isActionButtonClick = false,
|
||||||
@ -50,7 +47,7 @@ export const useOpenRecordTableCellFromCell = () => {
|
|||||||
customCellHotkeyScope,
|
customCellHotkeyScope,
|
||||||
recordId,
|
recordId,
|
||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
isReadOnly: isFieldReadOnly,
|
isReadOnly,
|
||||||
pathToShowPage,
|
pathToShowPage,
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
initialValue,
|
initialValue,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { RecordTableAggregateFooterCell } from '@/object-record/record-table/rec
|
|||||||
import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
|
import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
|
||||||
import { FIRST_TH_WIDTH } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
import { FIRST_TH_WIDTH } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
||||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||||
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { isUndefined } from '@sniptt/guards';
|
import { isUndefined } from '@sniptt/guards';
|
||||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||||
@ -86,14 +86,11 @@ export const RecordTableAggregateFooter = ({
|
|||||||
visibleTableColumnsComponentSelector,
|
visibleTableColumnsComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
const overlayScrollbarsInstance = useRecoilComponentValueV2(
|
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||||
scrollWrapperInstanceComponentState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasHorizontalOverflow = overlayScrollbarsInstance
|
const hasHorizontalOverflow =
|
||||||
? overlayScrollbarsInstance.elements().scrollOffsetElement.scrollWidth >
|
(scrollWrapperHTMLElement?.scrollWidth ?? 0) >
|
||||||
overlayScrollbarsInstance.elements().scrollOffsetElement.clientWidth
|
(scrollWrapperHTMLElement?.clientWidth ?? 0);
|
||||||
: false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTableRow
|
<StyledTableRow
|
||||||
|
|||||||
@ -1,16 +1,13 @@
|
|||||||
import { Theme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/constants/HiddenTableColumnDropdownId';
|
import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/constants/HiddenTableColumnDropdownId';
|
||||||
import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent';
|
import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { useScrollWrapperScopedRef } from '@/ui/utilities/scroll/hooks/useScrollWrapperScopedRef';
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
import { IconPlus } from 'twenty-ui/display';
|
import { IconPlus } from 'twenty-ui/display';
|
||||||
import { ThemeContext } from 'twenty-ui/theme';
|
|
||||||
|
|
||||||
const StyledPlusIconHeaderCell = styled.th<{
|
const StyledPlusIconHeaderCell = styled.th<{
|
||||||
theme: Theme;
|
|
||||||
isTableWiderThanScreen: boolean;
|
isTableWiderThanScreen: boolean;
|
||||||
}>`
|
}>`
|
||||||
${({ theme }) => {
|
${({ theme }) => {
|
||||||
@ -54,20 +51,17 @@ const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
|
|||||||
'hidden-table-columns-dropdown-hotkey-scope-id';
|
'hidden-table-columns-dropdown-hotkey-scope-id';
|
||||||
|
|
||||||
export const RecordTableHeaderLastColumn = () => {
|
export const RecordTableHeaderLastColumn = () => {
|
||||||
const { theme } = useContext(ThemeContext);
|
const theme = useTheme();
|
||||||
|
|
||||||
const scrollWrapper = useScrollWrapperScopedRef('recordTableWithWrappers');
|
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||||
|
|
||||||
const isTableWiderThanScreen =
|
const isTableWiderThanScreen =
|
||||||
(scrollWrapper.ref.current?.clientWidth ?? 0) <
|
(scrollWrapperHTMLElement?.clientWidth ?? 0) <
|
||||||
(scrollWrapper.ref.current?.scrollWidth ?? 0);
|
(scrollWrapperHTMLElement?.scrollWidth ?? 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledPlusIconHeaderCell
|
<StyledPlusIconHeaderCell isTableWiderThanScreen={isTableWiderThanScreen}>
|
||||||
theme={theme}
|
|
||||||
isTableWiderThanScreen={isTableWiderThanScreen}
|
|
||||||
>
|
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={HIDDEN_TABLE_COLUMN_DROPDOWN_ID}
|
dropdownId={HIDDEN_TABLE_COLUMN_DROPDOWN_ID}
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ReactNode, useContext, useEffect } from 'react';
|
import { ReactNode, useEffect } from 'react';
|
||||||
|
|
||||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
@ -6,7 +6,7 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
|
|||||||
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
|
import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
|
||||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||||
import { RecordTableWithWrappersScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
import { useInView } from 'react-intersection-observer';
|
import { useInView } from 'react-intersection-observer';
|
||||||
|
|
||||||
@ -34,14 +34,10 @@ export const RecordTableRowWrapper = ({
|
|||||||
|
|
||||||
const { onIndexRecordsLoaded } = useRecordIndexContextOrThrow();
|
const { onIndexRecordsLoaded } = useRecordIndexContextOrThrow();
|
||||||
|
|
||||||
const scrollWrapperRef = useContext(
|
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||||
RecordTableWithWrappersScrollWrapperContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { ref: elementRef, inView } = useInView({
|
const { ref: elementRef, inView } = useInView({
|
||||||
root: scrollWrapperRef.ref.current?.querySelector(
|
root: scrollWrapperHTMLElement,
|
||||||
'[data-overlayscrollbars-viewport]',
|
|
||||||
),
|
|
||||||
rootMargin: '1000px',
|
rootMargin: '1000px',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -41,10 +41,7 @@ export const SettingsNavigationDrawerItems = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="navigationDrawer"
|
|
||||||
componentInstanceId={`scroll-wrapper-settings-navigation-drawer`}
|
componentInstanceId={`scroll-wrapper-settings-navigation-drawer`}
|
||||||
scrollbarVariant="no-padding"
|
|
||||||
heightMode="fit-content"
|
|
||||||
defaultEnableXScroll={false}
|
defaultEnableXScroll={false}
|
||||||
>
|
>
|
||||||
<StyledInnerContainer>
|
<StyledInnerContainer>
|
||||||
|
|||||||
@ -30,11 +30,7 @@ export const SettingsPageContainer = ({
|
|||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) => (
|
}) => (
|
||||||
<ScrollWrapper
|
<ScrollWrapper componentInstanceId={'scroll-wrapper-settings-page-container'}>
|
||||||
contextProviderName="settingsPageContainer"
|
|
||||||
heightMode="full"
|
|
||||||
componentInstanceId={'scroll-wrapper-settings-page-container'}
|
|
||||||
>
|
|
||||||
<StyledSettingsPageContainer>{children}</StyledSettingsPageContainer>
|
<StyledSettingsPageContainer>{children}</StyledSettingsPageContainer>
|
||||||
</ScrollWrapper>
|
</ScrollWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -146,6 +146,7 @@ export const SettingsDataModelFieldPreview = ({
|
|||||||
defaultValue: fieldMetadataItem.defaultValue,
|
defaultValue: fieldMetadataItem.defaultValue,
|
||||||
},
|
},
|
||||||
hotkeyScope: 'field-preview',
|
hotkeyScope: 'field-preview',
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fieldMetadataItem.type === FieldMetadataType.BOOLEAN ? (
|
{fieldMetadataItem.type === FieldMetadataType.BOOLEAN ? (
|
||||||
|
|||||||
@ -4,11 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { Heading } from '@/spreadsheet-import/components/Heading';
|
import { Heading } from '@/spreadsheet-import/components/Heading';
|
||||||
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
|
import { StepNavigationButton } from '@/spreadsheet-import/components/StepNavigationButton';
|
||||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||||
import {
|
import { ImportedRow, ImportedStructuredRow } from '@/spreadsheet-import/types';
|
||||||
ImportedRow,
|
|
||||||
ImportedStructuredRow,
|
|
||||||
SpreadsheetImportField,
|
|
||||||
} from '@/spreadsheet-import/types';
|
|
||||||
import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields';
|
import { findUnmatchedRequiredFields } from '@/spreadsheet-import/utils/findUnmatchedRequiredFields';
|
||||||
import { getMatchedColumns } from '@/spreadsheet-import/utils/getMatchedColumns';
|
import { getMatchedColumns } from '@/spreadsheet-import/utils/getMatchedColumns';
|
||||||
import { normalizeTableData } from '@/spreadsheet-import/utils/normalizeTableData';
|
import { normalizeTableData } from '@/spreadsheet-import/utils/normalizeTableData';
|
||||||
@ -21,13 +17,14 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
|||||||
|
|
||||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||||
|
|
||||||
import { initialComputedColumnsSelector } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/states/initialComputedColumnsState';
|
|
||||||
import { UnmatchColumn } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn';
|
import { UnmatchColumn } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/UnmatchColumn';
|
||||||
|
import { initialComputedColumnsSelector } from '@/spreadsheet-import/steps/components/MatchColumnsStep/components/states/initialComputedColumnsState';
|
||||||
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
|
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
|
||||||
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
|
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
|
||||||
import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn';
|
import { SpreadsheetColumn } from '@/spreadsheet-import/types/SpreadsheetColumn';
|
||||||
import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns';
|
|
||||||
import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType';
|
import { SpreadsheetColumnType } from '@/spreadsheet-import/types/SpreadsheetColumnType';
|
||||||
|
import { SpreadsheetColumns } from '@/spreadsheet-import/types/SpreadsheetColumns';
|
||||||
|
import { SpreadsheetImportField } from '@/spreadsheet-import/types/SpreadsheetImportField';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
@ -273,11 +270,7 @@ export const MatchColumnsStep = <T extends string>({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrollWrapper
|
<ScrollWrapper componentInstanceId="scroll-wrapper-modal-content">
|
||||||
contextProviderName="modalContent"
|
|
||||||
componentInstanceId="scroll-wrapper-modal-content"
|
|
||||||
heightMode="full"
|
|
||||||
>
|
|
||||||
<StyledContent>
|
<StyledContent>
|
||||||
<Heading
|
<Heading
|
||||||
title={t`Match Columns`}
|
title={t`Match Columns`}
|
||||||
|
|||||||
@ -55,7 +55,6 @@ export const DropdownMenuItemsContainer = ({
|
|||||||
>
|
>
|
||||||
{hasMaxHeight ? (
|
{hasMaxHeight ? (
|
||||||
<StyledScrollWrapper
|
<StyledScrollWrapper
|
||||||
contextProviderName="dropdownMenuItemsContainer"
|
|
||||||
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
|
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
|
||||||
>
|
>
|
||||||
<StyledDropdownMenuItemsInternalContainer>
|
<StyledDropdownMenuItemsInternalContainer>
|
||||||
@ -69,10 +68,7 @@ export const DropdownMenuItemsContainer = ({
|
|||||||
)}
|
)}
|
||||||
</StyledDropdownMenuItemsExternalContainer>
|
</StyledDropdownMenuItemsExternalContainer>
|
||||||
) : (
|
) : (
|
||||||
<ScrollWrapper
|
<ScrollWrapper componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}>
|
||||||
contextProviderName="dropdownMenuItemsContainer"
|
|
||||||
componentInstanceId={`scroll-wrapper-dropdown-menu-${id}`}
|
|
||||||
>
|
|
||||||
<StyledDropdownMenuItemsExternalContainer
|
<StyledDropdownMenuItemsExternalContainer
|
||||||
hasMaxHeight={hasMaxHeight}
|
hasMaxHeight={hasMaxHeight}
|
||||||
className={className}
|
className={className}
|
||||||
|
|||||||
@ -30,17 +30,7 @@ const StyledLayout = styled.div`
|
|||||||
scrollbar-width: 4px;
|
scrollbar-width: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
*::-webkit-scrollbar {
|
|
||||||
height: 4px;
|
|
||||||
width: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
*::-webkit-scrollbar-corner {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
*::-webkit-scrollbar-thumb {
|
*::-webkit-scrollbar-thumb {
|
||||||
background-color: transparent;
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -32,7 +32,6 @@ export const ShowPageContainer = ({ children }: ShowPageContainerProps) => {
|
|||||||
return isMobile ? (
|
return isMobile ? (
|
||||||
<StyledOuterContainer>
|
<StyledOuterContainer>
|
||||||
<StyledScrollWrapper
|
<StyledScrollWrapper
|
||||||
contextProviderName="showPageContainer"
|
|
||||||
componentInstanceId={'scroll-wrapper-show-page-container'}
|
componentInstanceId={'scroll-wrapper-show-page-container'}
|
||||||
>
|
>
|
||||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
<StyledInnerContainer>{children}</StyledInnerContainer>
|
||||||
|
|||||||
@ -70,7 +70,6 @@ export const ShowPageActivityContainer = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="showPageActivityContainer"
|
|
||||||
componentInstanceId={`scroll-wrapper-tab-list-${targetableObject.id}`}
|
componentInstanceId={`scroll-wrapper-tab-list-${targetableObject.id}`}
|
||||||
>
|
>
|
||||||
<StyledShowPageActivityContainer>
|
<StyledShowPageActivityContainer>
|
||||||
|
|||||||
@ -47,7 +47,6 @@ export const ShowPageLeftContainer = ({
|
|||||||
</StyledInnerContainer>
|
</StyledInnerContainer>
|
||||||
) : (
|
) : (
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
contextProviderName="showPageLeftContainer"
|
|
||||||
componentInstanceId={`scroll-wrapper-show-page-left-container`}
|
componentInstanceId={`scroll-wrapper-show-page-left-container`}
|
||||||
>
|
>
|
||||||
<StyledIntermediateContainer>
|
<StyledIntermediateContainer>
|
||||||
|
|||||||
@ -79,7 +79,6 @@ export const TabList = ({
|
|||||||
/>
|
/>
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
defaultEnableYScroll={false}
|
defaultEnableYScroll={false}
|
||||||
contextProviderName="tabList"
|
|
||||||
componentInstanceId={`scroll-wrapper-tab-list-${componentInstanceId}`}
|
componentInstanceId={`scroll-wrapper-tab-list-${componentInstanceId}`}
|
||||||
>
|
>
|
||||||
<StyledContainer className={className}>
|
<StyledContainer className={className}>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { createState } from 'twenty-ui/utilities';
|
||||||
import { INITIAL_HOTKEYS_SCOPE } from '../../constants/InitialHotkeysScope';
|
import { INITIAL_HOTKEYS_SCOPE } from '../../constants/InitialHotkeysScope';
|
||||||
import { HotkeyScope } from '../../types/HotkeyScope';
|
import { HotkeyScope } from '../../types/HotkeyScope';
|
||||||
import { createState } from 'twenty-ui/utilities';
|
|
||||||
|
|
||||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||||
key: 'currentHotkeyScopeState',
|
key: 'currentHotkeyScopeState',
|
||||||
|
|||||||
@ -1,102 +1,44 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
|
||||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
|
||||||
import { useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ContextProviderName,
|
|
||||||
getContextByProviderName,
|
|
||||||
} from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
|
||||||
|
|
||||||
|
import { ScrollWrapperInitEffect } from '@/ui/utilities/scroll/components/internal/ScrollWrapperInitEffect';
|
||||||
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||||
import { scrollWrapperScrollBottomComponentState } from '@/ui/utilities/scroll/states/scrollWrappeScrollBottomComponentState';
|
import { scrollWrapperScrollBottomComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollBottomComponentState';
|
||||||
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
|
||||||
import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
|
import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
|
||||||
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { css } from '@emotion/react';
|
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
|
||||||
|
|
||||||
type HeightMode = 'full' | 'fit-content';
|
const StyledScrollWrapper = styled.div`
|
||||||
|
&.scroll-wrapper-x-enabled {
|
||||||
const StyledScrollWrapper = styled.div<{
|
overflow-x: scroll;
|
||||||
heightMode: HeightMode;
|
}
|
||||||
scrollbarVariant: 'with-padding' | 'no-padding';
|
&.scroll-wrapper-y-enabled {
|
||||||
}>`
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
display: flex;
|
display: flex;
|
||||||
height: ${({ heightMode }) => {
|
|
||||||
switch (heightMode) {
|
|
||||||
case 'full':
|
|
||||||
return '100%';
|
|
||||||
case 'fit-content':
|
|
||||||
return 'fit-content';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
.os-scrollbar-handle {
|
|
||||||
background-color: ${({ theme }) => theme.border.color.strong};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep horizontal scrollbar always visible
|
|
||||||
.os-scrollbar-horizontal {
|
|
||||||
&.os-scrollbar-auto-hide {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
.os-scrollbar-track {
|
|
||||||
visibility: visible !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.os-scrollbar {
|
|
||||||
transition:
|
|
||||||
opacity 300ms,
|
|
||||||
visibility 300ms,
|
|
||||||
top 300ms,
|
|
||||||
right 300ms,
|
|
||||||
bottom 300ms,
|
|
||||||
left 300ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
${({ scrollbarVariant }) =>
|
|
||||||
scrollbarVariant === 'no-padding' &&
|
|
||||||
css`
|
|
||||||
.os-scrollbar {
|
|
||||||
--os-size: 6px;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledInnerContainer = styled.div`
|
const StyledInnerContainer = styled.div`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export type ScrollWrapperProps = {
|
export type ScrollWrapperProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
heightMode?: HeightMode;
|
|
||||||
defaultEnableXScroll?: boolean;
|
defaultEnableXScroll?: boolean;
|
||||||
defaultEnableYScroll?: boolean;
|
defaultEnableYScroll?: boolean;
|
||||||
contextProviderName: ContextProviderName;
|
|
||||||
componentInstanceId: string;
|
componentInstanceId: string;
|
||||||
scrollbarVariant?: 'with-padding' | 'no-padding';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ScrollWrapper = ({
|
export const ScrollWrapper = ({
|
||||||
componentInstanceId,
|
componentInstanceId,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
heightMode = 'full',
|
|
||||||
defaultEnableXScroll = true,
|
defaultEnableXScroll = true,
|
||||||
defaultEnableYScroll = true,
|
defaultEnableYScroll = true,
|
||||||
contextProviderName,
|
|
||||||
scrollbarVariant = 'with-padding',
|
|
||||||
}: ScrollWrapperProps) => {
|
}: ScrollWrapperProps) => {
|
||||||
const scrollableRef = useRef<HTMLDivElement>(null);
|
|
||||||
const Context = getContextByProviderName(contextProviderName);
|
|
||||||
|
|
||||||
const setScrollTop = useSetRecoilComponentStateV2(
|
const setScrollTop = useSetRecoilComponentStateV2(
|
||||||
scrollWrapperScrollTopComponentState,
|
scrollWrapperScrollTopComponentState,
|
||||||
componentInstanceId,
|
componentInstanceId,
|
||||||
@ -112,8 +54,8 @@ export const ScrollWrapper = ({
|
|||||||
componentInstanceId,
|
componentInstanceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleScroll = (overlayScroll: OverlayScrollbars) => {
|
const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
|
||||||
const target = overlayScroll.elements().scrollOffsetElement;
|
const target = event.currentTarget;
|
||||||
setScrollTop(target.scrollTop);
|
setScrollTop(target.scrollTop);
|
||||||
setScrollLeft(target.scrollLeft);
|
setScrollLeft(target.scrollLeft);
|
||||||
setScrollBottom(
|
setScrollBottom(
|
||||||
@ -121,103 +63,21 @@ export const ScrollWrapper = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setOverlayScrollbars = useSetRecoilComponentStateV2(
|
|
||||||
scrollWrapperInstanceComponentState,
|
|
||||||
componentInstanceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [initialize, instance] = useOverlayScrollbars({
|
|
||||||
options: {
|
|
||||||
scrollbars: {
|
|
||||||
autoHide: 'scroll',
|
|
||||||
autoHideDelay: 500,
|
|
||||||
},
|
|
||||||
overflow: {
|
|
||||||
x: defaultEnableXScroll ? undefined : 'hidden',
|
|
||||||
y: defaultEnableYScroll ? undefined : 'hidden',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
updated: (osInstance) => {
|
|
||||||
const {
|
|
||||||
scrollOffsetElement: target,
|
|
||||||
scrollbarVertical,
|
|
||||||
scrollbarHorizontal,
|
|
||||||
} = osInstance.elements();
|
|
||||||
|
|
||||||
if (scrollbarVertical !== null) {
|
|
||||||
scrollbarVertical.track.dataset.selectDisable = 'true';
|
|
||||||
}
|
|
||||||
if (scrollbarHorizontal !== null) {
|
|
||||||
scrollbarHorizontal.track.dataset.selectDisable = 'true';
|
|
||||||
}
|
|
||||||
setScrollBottom(
|
|
||||||
target.scrollHeight - target.clientHeight - target.scrollTop,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
scroll: (osInstance) => {
|
|
||||||
const { scrollOffsetElement: target, scrollbarVertical } =
|
|
||||||
osInstance.elements();
|
|
||||||
// Hide vertical scrollbar by default
|
|
||||||
if (scrollbarVertical !== null) {
|
|
||||||
scrollbarVertical.track.style.visibility = 'hidden';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show vertical scrollbar based on scroll direction
|
|
||||||
const isVerticalScroll =
|
|
||||||
target.scrollTop !== Number(target.dataset.lastScrollTop || '0');
|
|
||||||
|
|
||||||
if (
|
|
||||||
isVerticalScroll === true &&
|
|
||||||
scrollbarVertical !== null &&
|
|
||||||
target.scrollHeight > target.clientHeight
|
|
||||||
) {
|
|
||||||
scrollbarVertical.track.style.visibility = 'visible';
|
|
||||||
}
|
|
||||||
// Update vertical scroll positions
|
|
||||||
target.dataset.lastScrollTop = target.scrollTop.toString();
|
|
||||||
|
|
||||||
handleScroll(osInstance);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const currentRef = scrollableRef.current;
|
|
||||||
if (currentRef !== null) {
|
|
||||||
initialize(currentRef);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
// Reset vertical scroll component-specific Recoil state
|
|
||||||
setScrollTop(0);
|
|
||||||
setOverlayScrollbars(null);
|
|
||||||
instance()?.destroy();
|
|
||||||
};
|
|
||||||
}, [initialize, instance, setScrollTop, setOverlayScrollbars]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setOverlayScrollbars(instance());
|
|
||||||
}, [instance, setOverlayScrollbars]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollWrapperComponentInstanceContext.Provider
|
<ScrollWrapperComponentInstanceContext.Provider
|
||||||
value={{ instanceId: componentInstanceId }}
|
value={{ instanceId: componentInstanceId }}
|
||||||
>
|
>
|
||||||
<Context.Provider
|
<ScrollWrapperInitEffect
|
||||||
value={{
|
defaultEnableXScroll={defaultEnableXScroll}
|
||||||
ref: scrollableRef,
|
defaultEnableYScroll={defaultEnableYScroll}
|
||||||
id: contextProviderName,
|
/>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledScrollWrapper
|
<StyledScrollWrapper
|
||||||
ref={scrollableRef}
|
id={`scroll-wrapper-${componentInstanceId}`}
|
||||||
className={className}
|
className={className}
|
||||||
heightMode={heightMode}
|
onScroll={handleScroll}
|
||||||
scrollbarVariant={scrollbarVariant}
|
|
||||||
>
|
>
|
||||||
<StyledInnerContainer>{children}</StyledInnerContainer>
|
<StyledInnerContainer>{children}</StyledInnerContainer>
|
||||||
</StyledScrollWrapper>
|
</StyledScrollWrapper>
|
||||||
</Context.Provider>
|
|
||||||
</ScrollWrapperComponentInstanceContext.Provider>
|
</ScrollWrapperComponentInstanceContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
export type ScrollWrapperInitEffectProps = {
|
||||||
|
defaultEnableXScroll?: boolean;
|
||||||
|
defaultEnableYScroll?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ScrollWrapperInitEffect = ({
|
||||||
|
defaultEnableXScroll = true,
|
||||||
|
defaultEnableYScroll = true,
|
||||||
|
}: ScrollWrapperInitEffectProps) => {
|
||||||
|
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
||||||
|
useToggleScrollWrapper();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
toggleScrollXWrapper(defaultEnableXScroll);
|
||||||
|
toggleScrollYWrapper(defaultEnableYScroll);
|
||||||
|
}, [
|
||||||
|
defaultEnableXScroll,
|
||||||
|
defaultEnableYScroll,
|
||||||
|
toggleScrollXWrapper,
|
||||||
|
toggleScrollYWrapper,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -1,98 +0,0 @@
|
|||||||
import { createContext, RefObject } from 'react';
|
|
||||||
|
|
||||||
type ScrollWrapperContextValue = {
|
|
||||||
ref: RefObject<HTMLDivElement>;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ContextProviderName =
|
|
||||||
| 'eventList'
|
|
||||||
| 'commandMenu'
|
|
||||||
| 'recordBoard'
|
|
||||||
| 'recordTableWithWrappers'
|
|
||||||
| 'settingsPageContainer'
|
|
||||||
| 'dropdownMenuItemsContainer'
|
|
||||||
| 'showPageContainer'
|
|
||||||
| 'showPageLeftContainer'
|
|
||||||
| 'tabList'
|
|
||||||
| 'releases'
|
|
||||||
| 'test'
|
|
||||||
| 'showPageActivityContainer'
|
|
||||||
| 'navigationDrawer'
|
|
||||||
| 'aggregateFooterCell'
|
|
||||||
| 'modalContent';
|
|
||||||
|
|
||||||
const createScrollWrapperContext = (id: string) =>
|
|
||||||
createContext<ScrollWrapperContextValue>({
|
|
||||||
ref: { current: null },
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const EventListScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('eventList');
|
|
||||||
export const CommandMenuScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('commandMenu');
|
|
||||||
export const RecordBoardScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('recordBoard');
|
|
||||||
export const RecordTableWithWrappersScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('recordTableWithWrappers');
|
|
||||||
export const SettingsPageContainerScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('settingsPageContainer');
|
|
||||||
export const DropdownMenuItemsContainerScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('dropdownMenuItemsContainer');
|
|
||||||
export const ShowPageContainerScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('showPageContainer');
|
|
||||||
export const ShowPageLeftContainerScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('showPageLeftContainer');
|
|
||||||
export const TabListScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('tabList');
|
|
||||||
export const ReleasesScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('releases');
|
|
||||||
export const ShowPageActivityContainerScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('showPageActivityContainer');
|
|
||||||
export const NavigationDrawerScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('navigationDrawer');
|
|
||||||
export const TestScrollWrapperContext = createScrollWrapperContext('test');
|
|
||||||
export const AggregateFooterCellScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('aggregateFooterCell');
|
|
||||||
export const ModalContentScrollWrapperContext =
|
|
||||||
createScrollWrapperContext('modalContent');
|
|
||||||
|
|
||||||
export const getContextByProviderName = (
|
|
||||||
contextProviderName: ContextProviderName,
|
|
||||||
) => {
|
|
||||||
switch (contextProviderName) {
|
|
||||||
case 'eventList':
|
|
||||||
return EventListScrollWrapperContext;
|
|
||||||
case 'commandMenu':
|
|
||||||
return CommandMenuScrollWrapperContext;
|
|
||||||
case 'recordBoard':
|
|
||||||
return RecordBoardScrollWrapperContext;
|
|
||||||
case 'recordTableWithWrappers':
|
|
||||||
return RecordTableWithWrappersScrollWrapperContext;
|
|
||||||
case 'settingsPageContainer':
|
|
||||||
return SettingsPageContainerScrollWrapperContext;
|
|
||||||
case 'dropdownMenuItemsContainer':
|
|
||||||
return DropdownMenuItemsContainerScrollWrapperContext;
|
|
||||||
case 'showPageContainer':
|
|
||||||
return ShowPageContainerScrollWrapperContext;
|
|
||||||
case 'showPageLeftContainer':
|
|
||||||
return ShowPageLeftContainerScrollWrapperContext;
|
|
||||||
case 'tabList':
|
|
||||||
return TabListScrollWrapperContext;
|
|
||||||
case 'releases':
|
|
||||||
return ReleasesScrollWrapperContext;
|
|
||||||
case 'test':
|
|
||||||
return TestScrollWrapperContext;
|
|
||||||
case 'showPageActivityContainer':
|
|
||||||
return ShowPageActivityContainerScrollWrapperContext;
|
|
||||||
case 'navigationDrawer':
|
|
||||||
return NavigationDrawerScrollWrapperContext;
|
|
||||||
case 'aggregateFooterCell':
|
|
||||||
return AggregateFooterCellScrollWrapperContext;
|
|
||||||
case 'modalContent':
|
|
||||||
return ModalContentScrollWrapperContext;
|
|
||||||
default:
|
|
||||||
throw new Error('Context Provider not available');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
import { renderHook } from '@testing-library/react';
|
|
||||||
|
|
||||||
import { useScrollWrapperScopedRef } from '@/ui/utilities/scroll/hooks/useScrollWrapperScopedRef';
|
|
||||||
|
|
||||||
jest.mock('react', () => {
|
|
||||||
const originalModule = jest.requireActual('react');
|
|
||||||
return {
|
|
||||||
...originalModule,
|
|
||||||
useContext: () => ({ current: {} }),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('useScrollWrapperScopedRef', () => {
|
|
||||||
it('should return the scrollWrapperRef if available', () => {
|
|
||||||
const { result } = renderHook(() => useScrollWrapperScopedRef('test'));
|
|
||||||
|
|
||||||
expect(result.current).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
|
|
||||||
|
export const useScrollToPosition = () => {
|
||||||
|
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||||
|
|
||||||
|
const scrollToPosition = (scrollPositionInPx: number) => {
|
||||||
|
scrollWrapperHTMLElement?.scrollTo({ top: scrollPositionInPx });
|
||||||
|
};
|
||||||
|
|
||||||
|
return { scrollToPosition };
|
||||||
|
};
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||||
|
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
|
|
||||||
|
export const useScrollWrapperElement = (targetComponentInstanceId?: string) => {
|
||||||
|
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||||
|
ScrollWrapperComponentInstanceContext,
|
||||||
|
targetComponentInstanceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const scrollWrapperHTMLElement = document.getElementById(
|
||||||
|
`scroll-wrapper-${instanceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
scrollWrapperHTMLElement,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ContextProviderName,
|
|
||||||
getContextByProviderName,
|
|
||||||
} from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
|
||||||
|
|
||||||
export const useScrollWrapperScopedRef = (
|
|
||||||
contextProviderName: ContextProviderName,
|
|
||||||
) => {
|
|
||||||
const Context = getContextByProviderName(contextProviderName);
|
|
||||||
const scrollWrapperRef = useContext(Context);
|
|
||||||
|
|
||||||
if (isUndefinedOrNull(scrollWrapperRef))
|
|
||||||
throw new Error(
|
|
||||||
`Using a scroll ref without a ScrollWrapper : verify that you are using a ScrollWrapper if you intended to do so.`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return scrollWrapperRef;
|
|
||||||
};
|
|
||||||
@ -1,34 +1,38 @@
|
|||||||
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
|
|
||||||
export const useToggleScrollWrapper = () => {
|
export const useToggleScrollWrapper = (targetComponentInstanceId?: string) => {
|
||||||
const instanceOverlay = useRecoilComponentValueV2(
|
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||||
scrollWrapperInstanceComponentState,
|
ScrollWrapperComponentInstanceContext,
|
||||||
|
targetComponentInstanceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleScrollXWrapper = (isEnabled: boolean) => {
|
const toggleScrollXWrapper = (isEnabled: boolean) => {
|
||||||
if (!instanceOverlay) {
|
if (isEnabled) {
|
||||||
return;
|
document
|
||||||
|
.getElementById(`scroll-wrapper-${instanceId}`)
|
||||||
|
?.classList.add('scroll-wrapper-x-enabled');
|
||||||
|
} else {
|
||||||
|
document
|
||||||
|
.getElementById(`scroll-wrapper-${instanceId}`)
|
||||||
|
?.classList.remove('scroll-wrapper-x-enabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceOverlay.options({
|
|
||||||
overflow: {
|
|
||||||
x: isEnabled ? 'scroll' : 'hidden',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleScrollYWrapper = (isEnabled: boolean) => {
|
const toggleScrollYWrapper = (isEnabled: boolean) => {
|
||||||
if (!instanceOverlay) {
|
if (isEnabled) {
|
||||||
return;
|
document
|
||||||
|
.getElementById(`scroll-wrapper-${instanceId}`)
|
||||||
|
?.classList.add('scroll-wrapper-y-enabled');
|
||||||
|
} else {
|
||||||
|
document
|
||||||
|
.getElementById(`scroll-wrapper-${instanceId}`)
|
||||||
|
?.classList.remove('scroll-wrapper-y-enabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceOverlay.options({
|
|
||||||
overflow: {
|
|
||||||
y: isEnabled ? 'scroll' : 'hidden',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return { toggleScrollXWrapper, toggleScrollYWrapper };
|
return {
|
||||||
|
toggleScrollXWrapper,
|
||||||
|
toggleScrollYWrapper,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
|
||||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
|
||||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
|
||||||
|
|
||||||
export const scrollWrapperInstanceComponentState =
|
|
||||||
createComponentStateV2<OverlayScrollbars | null>({
|
|
||||||
key: 'scrollWrapperInstanceComponentState',
|
|
||||||
defaultValue: null,
|
|
||||||
componentInstanceContext: ScrollWrapperComponentInstanceContext,
|
|
||||||
});
|
|
||||||
@ -118,10 +118,7 @@ export const Releases = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
<ScrollWrapper
|
<ScrollWrapper componentInstanceId="scroll-wrapper-releases">
|
||||||
contextProviderName="releases"
|
|
||||||
componentInstanceId="scroll-wrapper-releases"
|
|
||||||
>
|
|
||||||
<StyledReleaseContainer>
|
<StyledReleaseContainer>
|
||||||
{releases.map((release) => (
|
{releases.map((release) => (
|
||||||
<React.Fragment key={release.slug}>
|
<React.Fragment key={release.slug}>
|
||||||
|
|||||||
@ -12,11 +12,11 @@ import {
|
|||||||
} from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
} from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
|
||||||
import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people';
|
import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people';
|
||||||
import { mockedTasks } from '~/testing/mock-data/tasks';
|
import { mockedTasks } from '~/testing/mock-data/tasks';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
const RecordMockSetterEffect = ({
|
const RecordMockSetterEffect = ({
|
||||||
companies,
|
companies,
|
||||||
@ -141,6 +141,7 @@ export const getFieldDecorator =
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
}),
|
}),
|
||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordMockSetterEffect
|
<RecordMockSetterEffect
|
||||||
|
|||||||
@ -56,6 +56,7 @@ export const useMockFieldContext = ({
|
|||||||
hotkeyScope:
|
hotkeyScope:
|
||||||
customHotkeyScope ?? InlineCellHotkeyScope.InlineCell,
|
customHotkeyScope ?? InlineCellHotkeyScope.InlineCell,
|
||||||
clearable,
|
clearable,
|
||||||
|
isReadOnly: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user