mirror of
https://github.com/lingble/twenty.git
synced 2026-03-20 04:04:04 +00:00
Merge remote-tracking branch 'origin/main' into feat/view-groups
This commit is contained in:
@@ -176,7 +176,7 @@ export const CalendarEventRow = ({
|
||||
: participant.displayName
|
||||
}
|
||||
placeholderColorSeed={
|
||||
participant.workspaceMemberId ?? participant.personId
|
||||
participant.workspaceMemberId || participant.personId
|
||||
}
|
||||
type="rounded"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useRef } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRef } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { Avatar, GRAY_SCALE } from 'twenty-ui';
|
||||
|
||||
@@ -155,12 +155,20 @@ export const EmailThreadPreview = ({
|
||||
<Avatar
|
||||
avatarUrl={thread?.firstParticipant?.avatarUrl}
|
||||
placeholder={thread.firstParticipant.displayName}
|
||||
placeholderColorSeed={
|
||||
thread.firstParticipant.workspaceMemberId ||
|
||||
thread.firstParticipant.personId
|
||||
}
|
||||
type="rounded"
|
||||
/>
|
||||
{thread?.lastTwoParticipants?.[0] && (
|
||||
<StyledAvatar
|
||||
avatarUrl={thread.lastTwoParticipants[0].avatarUrl}
|
||||
placeholder={thread.lastTwoParticipants[0].displayName}
|
||||
placeholderColorSeed={
|
||||
thread.lastTwoParticipants[0].workspaceMemberId ||
|
||||
thread.lastTwoParticipants[0].personId
|
||||
}
|
||||
type="rounded"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -13,33 +13,38 @@ import { Button } from '@/ui/input/button/components/Button';
|
||||
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||
import { messageThreadState } from '@/ui/layout/right-drawer/states/messageThreadState';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { IconArrowBackUp } from 'twenty-ui';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: 85%;
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
const StyledButtonContainer = styled.div<{ isMobile: boolean }>`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
bottom: 0;
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
display: flex;
|
||||
height: 110px;
|
||||
left: 0;
|
||||
padding-left: ${({ theme }) => theme.spacing(7)};
|
||||
padding-top: ${({ theme }) => theme.spacing(5)};
|
||||
position: fixed;
|
||||
right: 0;
|
||||
justify-content: flex-end;
|
||||
height: ${({ isMobile }) => (isMobile ? '100px' : '50px')};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
export const RightDrawerEmailThread = () => {
|
||||
const setMessageThread = useSetRecoilState(messageThreadState);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const {
|
||||
thread,
|
||||
messages,
|
||||
@@ -118,47 +123,49 @@ export const RightDrawerEmailThread = () => {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<StyledContainer>
|
||||
{threadLoading ? (
|
||||
<EmailLoader loadingText="Loading thread" />
|
||||
) : (
|
||||
<>
|
||||
<EmailThreadHeader
|
||||
subject={subject}
|
||||
lastMessageSentAt={lastMessage.receivedAt}
|
||||
/>
|
||||
{firstMessages.map((message) => (
|
||||
<EmailThreadMessage
|
||||
key={message.id}
|
||||
participants={message.messageParticipants}
|
||||
body={message.text}
|
||||
sentAt={message.receivedAt}
|
||||
<StyledWrapper>
|
||||
<StyledContainer>
|
||||
{threadLoading ? (
|
||||
<EmailLoader loadingText="Loading thread" />
|
||||
) : (
|
||||
<>
|
||||
<EmailThreadHeader
|
||||
subject={subject}
|
||||
lastMessageSentAt={lastMessage.receivedAt}
|
||||
/>
|
||||
))}
|
||||
<IntermediaryMessages messages={intermediaryMessages} />
|
||||
<EmailThreadMessage
|
||||
key={lastMessage.id}
|
||||
participants={lastMessage.messageParticipants}
|
||||
body={lastMessage.text}
|
||||
sentAt={lastMessage.receivedAt}
|
||||
isExpanded
|
||||
/>
|
||||
<CustomResolverFetchMoreLoader
|
||||
loading={threadLoading}
|
||||
onLastRowVisible={fetchMoreMessages}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{canReply && !messageChannelLoading ? (
|
||||
<StyledButtonContainer>
|
||||
{firstMessages.map((message) => (
|
||||
<EmailThreadMessage
|
||||
key={message.id}
|
||||
participants={message.messageParticipants}
|
||||
body={message.text}
|
||||
sentAt={message.receivedAt}
|
||||
/>
|
||||
))}
|
||||
<IntermediaryMessages messages={intermediaryMessages} />
|
||||
<EmailThreadMessage
|
||||
key={lastMessage.id}
|
||||
participants={lastMessage.messageParticipants}
|
||||
body={lastMessage.text}
|
||||
sentAt={lastMessage.receivedAt}
|
||||
isExpanded
|
||||
/>
|
||||
<CustomResolverFetchMoreLoader
|
||||
loading={threadLoading}
|
||||
onLastRowVisible={fetchMoreMessages}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</StyledContainer>
|
||||
{canReply && !messageChannelLoading && (
|
||||
<StyledButtonContainer isMobile={isMobile}>
|
||||
<Button
|
||||
onClick={handleReplyClick}
|
||||
title="Reply (View in Gmail)"
|
||||
title="Reply"
|
||||
Icon={IconArrowBackUp}
|
||||
disabled={!canReply}
|
||||
></Button>
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
) : null}
|
||||
</StyledContainer>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -51,13 +51,12 @@ export const useActivities = <T extends Task | Note>({
|
||||
),
|
||||
];
|
||||
|
||||
const skipBecauseNoActivityTargetFound = activityIds.length === 0;
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
id:
|
||||
targetableObjects.length > 0
|
||||
? {
|
||||
in: activityIds,
|
||||
}
|
||||
: undefined,
|
||||
id: {
|
||||
in: activityIds,
|
||||
},
|
||||
...activitiesFilters,
|
||||
};
|
||||
|
||||
@@ -69,7 +68,7 @@ export const useActivities = <T extends Task | Note>({
|
||||
|
||||
const { records: activities, loading: loadingActivities } =
|
||||
useFindManyRecords<Task | Note>({
|
||||
skip: skip || loadingActivityTargets,
|
||||
skip: skip || loadingActivityTargets || skipBecauseNoActivityTargetFound,
|
||||
objectNameSingular:
|
||||
FIND_ACTIVITIES_OPERATION_SIGNATURE.objectNameSingular,
|
||||
recordGqlFields: FIND_ACTIVITIES_OPERATION_SIGNATURE.fields,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { MultipleFiltersDropdownButton } from './MultipleFiltersDropdownButton';
|
||||
import { SingleEntityObjectFilterDropdownButton } from './SingleEntityObjectFilterDropdownButton';
|
||||
|
||||
@@ -16,12 +15,9 @@ export const ObjectFilterDropdownButton = ({
|
||||
filterDropdownId,
|
||||
hotkeyScope,
|
||||
}: ObjectFilterDropdownButtonProps) => {
|
||||
const { availableFilterDefinitionsState } = useFilterDropdown({
|
||||
filterDropdownId: filterDropdownId,
|
||||
});
|
||||
|
||||
const availableFilterDefinitions = useRecoilValue(
|
||||
availableFilterDefinitionsState,
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
const hasOnlyOneEntityFilter =
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
|
||||
import { ObjectFilterDropdownFilterSelectMenuItem } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterSelectMenuItem';
|
||||
@@ -12,6 +10,8 @@ import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const StyledInput = styled.input`
|
||||
@@ -43,10 +43,8 @@ export const StyledInput = styled.input`
|
||||
export const ObjectFilterDropdownFilterSelect = () => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const { availableFilterDefinitionsState } = useFilterDropdown();
|
||||
|
||||
const availableFilterDefinitions = useRecoilValue(
|
||||
availableFilterDefinitionsState,
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
);
|
||||
|
||||
const sortedAvailableFilterDefinitions = [...availableFilterDefinitions]
|
||||
|
||||
@@ -7,7 +7,7 @@ import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types
|
||||
import { MultipleRecordSelectDropdown } from '@/object-record/select/components/MultipleRecordSelectDropdown';
|
||||
import { useRecordsForSelect } from '@/object-record/select/hooks/useRecordsForSelect';
|
||||
import { SelectableRecord } from '@/object-record/select/types/SelectableRecord';
|
||||
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
|
||||
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@@ -17,6 +17,7 @@ export const MAX_RECORDS_TO_DISPLAY = 3;
|
||||
type ObjectFilterDropdownRecordSelectProps = {
|
||||
viewComponentId?: string;
|
||||
};
|
||||
|
||||
export const ObjectFilterDropdownRecordSelect = ({
|
||||
viewComponentId,
|
||||
}: ObjectFilterDropdownRecordSelectProps) => {
|
||||
@@ -31,7 +32,9 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
emptyFilterButKeepDefinition,
|
||||
} = useFilterDropdown();
|
||||
|
||||
const { removeCombinedViewFilter } = useCombinedViewFilters(viewComponentId);
|
||||
const { deleteCombinedViewFilter } =
|
||||
useDeleteCombinedViewFilters(viewComponentId);
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts } =
|
||||
useGetCurrentView(viewComponentId);
|
||||
|
||||
@@ -78,7 +81,7 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
|
||||
if (newSelectedRecordIds.length === 0) {
|
||||
emptyFilterButKeepDefinition();
|
||||
removeCombinedViewFilter(fieldId);
|
||||
deleteCombinedViewFilter(fieldId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconChevronDown } from 'twenty-ui';
|
||||
|
||||
@@ -11,8 +11,9 @@ import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/Styl
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
|
||||
|
||||
import { GenericEntityFilterChip } from './GenericEntityFilterChip';
|
||||
import { ObjectFilterDropdownRecordSelect } from './ObjectFilterDropdownRecordSelect';
|
||||
import { ObjectFilterDropdownSearchInput } from './ObjectFilterDropdownSearchInput';
|
||||
@@ -25,14 +26,13 @@ export const SingleEntityObjectFilterDropdownButton = ({
|
||||
hotkeyScope: HotkeyScope;
|
||||
}) => {
|
||||
const {
|
||||
availableFilterDefinitionsState,
|
||||
selectedFilterState,
|
||||
setFilterDefinitionUsedInDropdown,
|
||||
setSelectedOperandInDropdown,
|
||||
} = useFilterDropdown();
|
||||
|
||||
const availableFilterDefinitions = useRecoilValue(
|
||||
availableFilterDefinitionsState,
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
);
|
||||
const selectedFilter = useRecoilValue(selectedFilterState);
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@ import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { TaskGroups } from '@/activities/tasks/components/TaskGroups';
|
||||
import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton';
|
||||
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
|
||||
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { within } from '@storybook/test';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
@@ -17,9 +19,12 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
|
||||
component: MultipleFiltersDropdownButton,
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const { setAvailableFilterDefinitions } = useFilterDropdown({
|
||||
filterDropdownId: 'entity-tasks-filter-scope',
|
||||
});
|
||||
const instanceId = 'entity-tasks-filter-scope';
|
||||
const setAvailableFilterDefinitions = useSetRecoilComponentStateV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
setAvailableFilterDefinitions([
|
||||
{
|
||||
fieldMetadataId: '1',
|
||||
@@ -47,9 +52,11 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
|
||||
},
|
||||
]);
|
||||
return (
|
||||
<ObjectFilterDropdownScope filterScopeId="entity-tasks-filter-scope">
|
||||
<Story />
|
||||
</ObjectFilterDropdownScope>
|
||||
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
|
||||
<ObjectFilterDropdownScope filterScopeId={instanceId}>
|
||||
<Story />
|
||||
</ObjectFilterDropdownScope>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
);
|
||||
},
|
||||
ObjectMetadataItemsDecorator,
|
||||
|
||||
@@ -6,6 +6,8 @@ import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/
|
||||
import { useFilterDropdownStates } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdownStates';
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
|
||||
const filterDropdownId = 'filterDropdownId';
|
||||
@@ -35,11 +37,13 @@ describe('useFilterDropdown', () => {
|
||||
it('should set availableFilterDefinitions', async () => {
|
||||
const { result } = renderHook(() => {
|
||||
useFilterDropdown({ filterDropdownId });
|
||||
const { availableFilterDefinitionsState } =
|
||||
useFilterDropdownStates(filterDropdownId);
|
||||
|
||||
const [availableFilterDefinitions, setAvailableFilterDefinitions] =
|
||||
useRecoilState(availableFilterDefinitionsState);
|
||||
useRecoilComponentStateV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
filterDropdownId,
|
||||
);
|
||||
|
||||
return { availableFilterDefinitions, setAvailableFilterDefinitions };
|
||||
}, renderHookConfig);
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
);
|
||||
|
||||
const {
|
||||
availableFilterDefinitionsState,
|
||||
filterDefinitionUsedInDropdownState,
|
||||
objectFilterDropdownSearchInputState,
|
||||
objectFilterDropdownSelectedRecordIdsState,
|
||||
@@ -73,9 +72,6 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
],
|
||||
);
|
||||
|
||||
const setAvailableFilterDefinitions = useSetRecoilState(
|
||||
availableFilterDefinitionsState,
|
||||
);
|
||||
const setSelectedFilter = useSetRecoilState(selectedFilterState);
|
||||
const setSelectedOperandInDropdown = useSetRecoilState(
|
||||
selectedOperandInDropdownState,
|
||||
@@ -106,7 +102,6 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
resetFilter,
|
||||
setSelectedFilter,
|
||||
setSelectedOperandInDropdown,
|
||||
setAvailableFilterDefinitions,
|
||||
setFilterDefinitionUsedInDropdown,
|
||||
setObjectFilterDropdownSearchInput,
|
||||
// setObjectFilterDropdownSelectedEntityId,
|
||||
@@ -116,7 +111,6 @@ export const useFilterDropdown = (props?: UseFilterDropdownProps) => {
|
||||
setIsObjectFilterDropdownUnfolded,
|
||||
setOnFilterSelect,
|
||||
emptyFilterButKeepDefinition,
|
||||
availableFilterDefinitionsState,
|
||||
filterDefinitionUsedInDropdownState,
|
||||
objectFilterDropdownSearchInputState,
|
||||
// objectFilterDropdownSelectedEntityIdState,
|
||||
|
||||
@@ -8,14 +8,8 @@ import { onFilterSelectComponentState } from '@/object-record/object-filter-drop
|
||||
import { selectedFilterComponentState } from '@/object-record/object-filter-dropdown/states/selectedFilterComponentState';
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
|
||||
export const useFilterDropdownStates = (scopeId: string) => {
|
||||
const availableFilterDefinitionsState = extractComponentState(
|
||||
availableFilterDefinitionsComponentState,
|
||||
scopeId,
|
||||
);
|
||||
|
||||
const filterDefinitionUsedInDropdownState = extractComponentState(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
scopeId,
|
||||
@@ -63,7 +57,6 @@ export const useFilterDropdownStates = (scopeId: string) => {
|
||||
);
|
||||
|
||||
return {
|
||||
availableFilterDefinitionsState,
|
||||
filterDefinitionUsedInDropdownState,
|
||||
objectFilterDropdownSearchInputState,
|
||||
objectFilterDropdownSelectedRecordIdsState,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type ObjectFilterDropdownScopeInternalContextProps = ComponentStateKey;
|
||||
type ObjectFilterDropdownScopeInternalContextProps = RecoilComponentStateKey;
|
||||
|
||||
export const ObjectFilterDropdownScopeInternalContext =
|
||||
createScopeInternalContext<ObjectFilterDropdownScopeInternalContextProps>();
|
||||
|
||||
@@ -6,6 +6,8 @@ import { useSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useS
|
||||
import { useSortDropdownStates } from '@/object-record/object-sort-dropdown/hooks/useSortDropdownStates';
|
||||
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
|
||||
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
|
||||
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<RecoilRoot>{children}</RecoilRoot>
|
||||
@@ -24,11 +26,13 @@ describe('useSortDropdown', () => {
|
||||
it('should set availableSortDefinitions', async () => {
|
||||
const { result } = renderHook(() => {
|
||||
useSortDropdown({ sortDropdownId });
|
||||
const { availableSortDefinitionsState } =
|
||||
useSortDropdownStates(sortDropdownId);
|
||||
|
||||
// TODO: verify this instance id works
|
||||
const [availableSortDefinitions, setAvailableSortDefinitions] =
|
||||
useRecoilState(availableSortDefinitionsState);
|
||||
useRecoilComponentStateV2(
|
||||
availableSortDefinitionsComponentState,
|
||||
sortDropdownId,
|
||||
);
|
||||
|
||||
return {
|
||||
availableSortDefinitions,
|
||||
|
||||
@@ -7,6 +7,8 @@ import selectedSortDirectionState from '@/object-record/object-sort-dropdown/sta
|
||||
import { SortDefinition } from '@/object-record/object-sort-dropdown/types/SortDefinition';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
|
||||
import {
|
||||
OBJECT_SORT_DROPDOWN_ID,
|
||||
VIEW_SORT_DROPDOWN_ID,
|
||||
@@ -41,7 +43,6 @@ export const useObjectSortDropdown = () => {
|
||||
};
|
||||
|
||||
const {
|
||||
availableSortDefinitionsState,
|
||||
onSortSelectState,
|
||||
isSortSelectedState,
|
||||
objectSortDropdownSearchInputState,
|
||||
@@ -52,8 +53,10 @@ export const useObjectSortDropdown = () => {
|
||||
});
|
||||
|
||||
const isSortSelected = useRecoilValue(isSortSelectedState);
|
||||
const availableSortDefinitions = useRecoilValue(
|
||||
availableSortDefinitionsState,
|
||||
|
||||
const availableSortDefinitions = useRecoilComponentValueV2(
|
||||
availableSortDefinitionsComponentState,
|
||||
VIEW_SORT_DROPDOWN_ID,
|
||||
);
|
||||
const onSortSelect = useRecoilValue(onSortSelectState);
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ export const useSortDropdown = (props?: UseSortProps) => {
|
||||
ObjectSortDropdownScopeInternalContext,
|
||||
props?.sortDropdownId,
|
||||
);
|
||||
|
||||
const {
|
||||
availableSortDefinitionsState,
|
||||
isSortSelectedState,
|
||||
onSortSelectState,
|
||||
objectSortDropdownSearchInputState,
|
||||
@@ -35,7 +35,6 @@ export const useSortDropdown = (props?: UseSortProps) => {
|
||||
|
||||
return {
|
||||
scopeId,
|
||||
availableSortDefinitionsState,
|
||||
isSortSelectedState,
|
||||
onSortSelectState,
|
||||
objectSortDropdownSearchInputState,
|
||||
|
||||
@@ -2,14 +2,8 @@ import { isSortSelectedComponentState } from '@/object-record/object-sort-dropdo
|
||||
import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState';
|
||||
import { onSortSelectComponentState } from '@/object-record/object-sort-dropdown/states/onSortSelectScopedState';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import { availableSortDefinitionsComponentState } from '@/views/states/availableSortDefinitionsComponentState';
|
||||
|
||||
export const useSortDropdownStates = (scopeId: string) => {
|
||||
const availableSortDefinitionsState = extractComponentState(
|
||||
availableSortDefinitionsComponentState,
|
||||
scopeId,
|
||||
);
|
||||
|
||||
const isSortSelectedState = extractComponentState(
|
||||
isSortSelectedComponentState,
|
||||
scopeId,
|
||||
@@ -26,7 +20,6 @@ export const useSortDropdownStates = (scopeId: string) => {
|
||||
);
|
||||
|
||||
return {
|
||||
availableSortDefinitionsState,
|
||||
isSortSelectedState,
|
||||
onSortSelectState,
|
||||
objectSortDropdownSearchInputState,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
import { Sort } from '../../types/Sort';
|
||||
|
||||
type ObjectSortDropdownScopeInternalContextProps = ComponentStateKey & {
|
||||
type ObjectSortDropdownScopeInternalContextProps = RecoilComponentStateKey & {
|
||||
onSortSelect?: (sort: Sort) => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinit
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type RecordBoardScopeInternalContextProps = ComponentStateKey & {
|
||||
type RecordBoardScopeInternalContextProps = RecoilComponentStateKey & {
|
||||
onFieldsChange: (fields: FieldDefinition<FieldMetadata>[]) => void;
|
||||
onColumnsChange: (column: RecordGroupDefinition[]) => void;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ export const useInitDraftValueV2 = <FieldValue>() => {
|
||||
const recordFieldInputScopeId = `${getRecordFieldInputId(
|
||||
recordId,
|
||||
fieldDefinition?.metadata?.fieldName,
|
||||
)}-scope`;
|
||||
)}`;
|
||||
|
||||
const getDraftValueSelector = extractComponentSelector<
|
||||
FieldInputDraftValue<FieldValue> | undefined
|
||||
|
||||
@@ -3,8 +3,7 @@ import { FieldDoubleText } from '@/object-record/record-field/types/FieldDoubleT
|
||||
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
|
||||
import { FieldInputOverlay } from '@/ui/field/input/components/FieldInputOverlay';
|
||||
|
||||
import { usePersistField } from '../../../hooks/usePersistField';
|
||||
|
||||
import { isDoubleTextFieldEmpty } from '@/object-record/record-field/meta-types/input/utils/isDoubleTextFieldEmpty';
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
|
||||
const FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS =
|
||||
@@ -28,46 +27,55 @@ export const FullNameFieldInput = ({
|
||||
onTab,
|
||||
onShiftTab,
|
||||
}: FullNameFieldInputProps) => {
|
||||
const { hotkeyScope, draftValue, setDraftValue } = useFullNameField();
|
||||
|
||||
const persistField = usePersistField();
|
||||
const { hotkeyScope, draftValue, setDraftValue, persistFullNameField } =
|
||||
useFullNameField();
|
||||
|
||||
const convertToFullName = (newDoubleText: FieldDoubleText) => {
|
||||
return {
|
||||
firstName: newDoubleText.firstValue,
|
||||
lastName: newDoubleText.secondValue,
|
||||
firstName: newDoubleText.firstValue.trim(),
|
||||
lastName: newDoubleText.secondValue.trim(),
|
||||
};
|
||||
};
|
||||
|
||||
const getRequiredDraftValueFromDoubleText = (
|
||||
newDoubleText: FieldDoubleText,
|
||||
) => {
|
||||
return isDoubleTextFieldEmpty(newDoubleText)
|
||||
? undefined
|
||||
: convertToFullName(newDoubleText);
|
||||
};
|
||||
|
||||
const handleEnter = (newDoubleText: FieldDoubleText) => {
|
||||
onEnter?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onEnter?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleEscape = (newDoubleText: FieldDoubleText) => {
|
||||
onEscape?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onEscape?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleClickOutside = (
|
||||
event: MouseEvent | TouchEvent,
|
||||
newDoubleText: FieldDoubleText,
|
||||
) => {
|
||||
onClickOutside?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onClickOutside?.(() =>
|
||||
persistFullNameField(convertToFullName(newDoubleText)),
|
||||
);
|
||||
};
|
||||
|
||||
const handleTab = (newDoubleText: FieldDoubleText) => {
|
||||
onTab?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onTab?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleShiftTab = (newDoubleText: FieldDoubleText) => {
|
||||
onShiftTab?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
onShiftTab?.(() => persistFullNameField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleChange = (newDoubleText: FieldDoubleText) => {
|
||||
setDraftValue(convertToFullName(newDoubleText));
|
||||
setDraftValue(getRequiredDraftValueFromDoubleText(newDoubleText));
|
||||
};
|
||||
|
||||
const handlePaste = (newDoubleText: FieldDoubleText) => {
|
||||
setDraftValue(convertToFullName(newDoubleText));
|
||||
setDraftValue(getRequiredDraftValueFromDoubleText(newDoubleText));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,7 @@ import { MenuItemMultiSelectTag } from '@/ui/navigation/menu-item/components/Men
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
|
||||
const StyledRelationPickerContainer = styled.div`
|
||||
left: -1px;
|
||||
@@ -109,7 +110,11 @@ export const MultiSelectFieldInput = ({
|
||||
<DropdownMenu data-select-disable>
|
||||
<DropdownMenuSearchInput
|
||||
value={searchFilter}
|
||||
onChange={(event) => setSearchFilter(event.currentTarget.value)}
|
||||
onChange={(event) =>
|
||||
setSearchFilter(
|
||||
turnIntoEmptyStringIfWhitespacesOnly(event.currentTarget.value),
|
||||
)
|
||||
}
|
||||
autoFocus
|
||||
/>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
@@ -60,7 +60,7 @@ export const NumberFieldInput = ({
|
||||
<TextInput
|
||||
placeholder={fieldDefinition.metadata.placeHolder}
|
||||
autoFocus
|
||||
value={draftValue ?? ''}
|
||||
value={draftValue?.toString() ?? ''}
|
||||
onClickOutside={handleClickOutside}
|
||||
onEnter={handleEnter}
|
||||
onEscape={handleEscape}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput';
|
||||
import { usePersistField } from '../../../hooks/usePersistField';
|
||||
import { useTextField } from '../../hooks/useTextField';
|
||||
|
||||
import { turnIntoUndefinedIfWhitespacesOnly } from '~/utils/string/turnIntoUndefinedIfWhitespacesOnly';
|
||||
import { FieldInputEvent } from './DateTimeFieldInput';
|
||||
|
||||
export type TextFieldInputProps = {
|
||||
@@ -25,32 +26,31 @@ export const TextFieldInput = ({
|
||||
useTextField();
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
const handleEnter = (newText: string) => {
|
||||
onEnter?.(() => persistField(newText));
|
||||
onEnter?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleEscape = (newText: string) => {
|
||||
onEscape?.(() => persistField(newText));
|
||||
onEscape?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleClickOutside = (
|
||||
event: MouseEvent | TouchEvent,
|
||||
newText: string,
|
||||
) => {
|
||||
onClickOutside?.(() => persistField(newText));
|
||||
onClickOutside?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleTab = (newText: string) => {
|
||||
onTab?.(() => persistField(newText));
|
||||
onTab?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleShiftTab = (newText: string) => {
|
||||
onShiftTab?.(() => persistField(newText));
|
||||
onShiftTab?.(() => persistField(newText.trim()));
|
||||
};
|
||||
|
||||
const handleChange = (newText: string) => {
|
||||
setDraftValue(newText);
|
||||
setDraftValue(turnIntoUndefinedIfWhitespacesOnly(newText));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { FieldDoubleText } from '@/object-record/record-field/types/FieldDoubleText';
|
||||
|
||||
export const isDoubleTextFieldEmpty = (doubleText: FieldDoubleText) => {
|
||||
const { firstValue, secondValue } = doubleText;
|
||||
|
||||
const totalLength = firstValue.trim().length + secondValue.trim().length;
|
||||
|
||||
return totalLength > 0 ? false : true;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type RecordFieldInputScopeInternalContextProps = ComponentStateKey;
|
||||
type RecordFieldInputScopeInternalContextProps = RecoilComponentStateKey;
|
||||
|
||||
export const RecordFieldInputScopeInternalContext =
|
||||
createScopeInternalContext<RecordFieldInputScopeInternalContextProps>();
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
|
||||
export type FieldTextDraftValue = string;
|
||||
export type FieldNumberDraftValue = string;
|
||||
export type FieldNumberDraftValue = number;
|
||||
export type FieldDateTimeDraftValue = string;
|
||||
export type FieldPhoneDraftValue = string;
|
||||
export type FieldPhonesDraftValue = {
|
||||
|
||||
@@ -24,6 +24,7 @@ import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||
import { ViewBar } from '@/views/components/ViewBar';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
||||
@@ -143,81 +144,91 @@ export const RecordIndexContainer = () => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<InformationBannerWrapper />
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<SpreadsheetImportProvider>
|
||||
<StyledContainerWithPadding>
|
||||
<ViewBar
|
||||
viewBarId={recordIndexId}
|
||||
optionsDropdownButton={
|
||||
<RecordIndexOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
viewType={recordIndexViewType ?? ViewType.Table}
|
||||
/>
|
||||
}
|
||||
onCurrentViewChange={(view) => {
|
||||
if (!view) {
|
||||
return;
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: recordIndexId }}
|
||||
>
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<SpreadsheetImportProvider>
|
||||
<StyledContainerWithPadding>
|
||||
<ViewBar
|
||||
viewBarId={recordIndexId}
|
||||
optionsDropdownButton={
|
||||
<RecordIndexOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
viewType={recordIndexViewType ?? ViewType.Table}
|
||||
/>
|
||||
}
|
||||
onCurrentViewChange={(view) => {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
onViewFieldsChange(view.viewFields);
|
||||
setTableFilters(
|
||||
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
||||
);
|
||||
setRecordIndexFilters(
|
||||
mapViewFiltersToFilters(view.viewFilters, filterDefinitions),
|
||||
);
|
||||
setTableSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexViewType(view.type);
|
||||
setRecordIndexViewKanbanFieldMetadataIdState(
|
||||
view.kanbanFieldMetadataId,
|
||||
);
|
||||
setRecordIndexIsCompactModeActive(view.isCompact);
|
||||
}}
|
||||
/>
|
||||
<RecordIndexViewBarEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
</StyledContainerWithPadding>
|
||||
</SpreadsheetImportProvider>
|
||||
onViewFieldsChange(view.viewFields);
|
||||
setTableFilters(
|
||||
mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
filterDefinitions,
|
||||
),
|
||||
);
|
||||
setRecordIndexFilters(
|
||||
mapViewFiltersToFilters(
|
||||
view.viewFilters,
|
||||
filterDefinitions,
|
||||
),
|
||||
);
|
||||
setTableSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexSorts(
|
||||
mapViewSortsToSorts(view.viewSorts, sortDefinitions),
|
||||
);
|
||||
setRecordIndexViewType(view.type);
|
||||
setRecordIndexViewKanbanFieldMetadataIdState(
|
||||
view.kanbanFieldMetadataId,
|
||||
);
|
||||
setRecordIndexIsCompactModeActive(view.isCompact);
|
||||
}}
|
||||
/>
|
||||
<RecordIndexViewBarEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
</StyledContainerWithPadding>
|
||||
</SpreadsheetImportProvider>
|
||||
|
||||
{recordIndexViewType === ViewType.Table && (
|
||||
<>
|
||||
<RecordIndexTableContainer
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexTableContainerEffect
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{recordIndexViewType === ViewType.Kanban && (
|
||||
<StyledContainerWithPadding fullHeight>
|
||||
<RecordIndexBoardContainer
|
||||
recordBoardId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
<RecordIndexBoardDataLoader
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordBoardId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexBoardDataLoaderEffect
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordBoardId={recordIndexId}
|
||||
/>
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
</RecordFieldValueSelectorContextProvider>
|
||||
{recordIndexViewType === ViewType.Table && (
|
||||
<>
|
||||
<RecordIndexTableContainer
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexTableContainerEffect
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{recordIndexViewType === ViewType.Kanban && (
|
||||
<StyledContainerWithPadding fullHeight>
|
||||
<RecordIndexBoardContainer
|
||||
recordBoardId={recordIndexId}
|
||||
viewBarId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
/>
|
||||
<RecordIndexBoardDataLoader
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordBoardId={recordIndexId}
|
||||
/>
|
||||
<RecordIndexBoardDataLoaderEffect
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordBoardId={recordIndexId}
|
||||
/>
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
</RecordFieldValueSelectorContextProvider>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRecoilState } from 'recoil';
|
||||
|
||||
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useCombinedViewSorts } from '@/views/hooks/useCombinedViewSorts';
|
||||
import { useDeleteCombinedViewSorts } from '@/views/hooks/useDeleteCombinedViewSorts';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
|
||||
export const RecordIndexRemoveSortingModal = ({
|
||||
@@ -21,11 +21,11 @@ export const RecordIndexRemoveSortingModal = ({
|
||||
isRemoveSortingModalOpenState,
|
||||
);
|
||||
|
||||
const { removeCombinedViewSort } = useCombinedViewSorts(recordTableId);
|
||||
const { deleteCombinedViewSort } = useDeleteCombinedViewSorts(recordTableId);
|
||||
|
||||
const handleRemoveClick = () => {
|
||||
fieldMetadataIds.forEach((id) => {
|
||||
removeCombinedViewSort(id);
|
||||
deleteCombinedViewSort(id);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -8,8 +8,9 @@ import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/
|
||||
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
|
||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { useViewStates } from '@/views/hooks/internal/useViewStates';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecordCountInCurrentView } from '@/views/hooks/useSetRecordCountInCurrentView';
|
||||
import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState';
|
||||
|
||||
type RecordIndexTableContainerEffectProps = {
|
||||
objectNameSingular: string;
|
||||
@@ -50,9 +51,10 @@ export const RecordIndexTableContainerEffect = ({
|
||||
const { tableRowIdsState, hasUserSelectedAllRowsState } =
|
||||
useRecordTableStates(recordTableId);
|
||||
|
||||
const { entityCountInCurrentViewState } = useViewStates(recordTableId);
|
||||
const entityCountInCurrentView = useRecoilValue(
|
||||
entityCountInCurrentViewState,
|
||||
// TODO: verify this instance id works
|
||||
const entityCountInCurrentView = useRecoilComponentValueV2(
|
||||
entityCountInCurrentViewComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState);
|
||||
const tableRowIds = useRecoilValue(tableRowIdsState);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { buildShowPageURL } from '@/object-record/record-show/utils/buildShowPageURL';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { currentViewIdComponentState } from '@/views/states/currentViewIdComponentState';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const useHandleIndexIdentifierClick = ({
|
||||
objectMetadataItem,
|
||||
@@ -13,10 +13,9 @@ export const useHandleIndexIdentifierClick = ({
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const currentViewId = useRecoilValue(
|
||||
currentViewIdComponentState({
|
||||
scopeId: recordIndexId,
|
||||
}),
|
||||
const currentViewId = useRecoilComponentValueV2(
|
||||
currentViewIdComponentState,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const handleIndexIdentifierClick = (recordId: string) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldM
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { getOperandsForFilterType } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
|
||||
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
type UseHandleToggleColumnFilterProps = {
|
||||
@@ -26,7 +26,7 @@ export const useHandleToggleColumnFilter = ({
|
||||
const { columnDefinitions } =
|
||||
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||
|
||||
const { upsertCombinedViewFilter } = useCombinedViewFilters(viewBarId);
|
||||
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(viewBarId);
|
||||
const { openDropdown } = useDropdownV2();
|
||||
|
||||
const handleToggleColumnFilter = useCallback(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useCallback } from 'react';
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { Sort } from '@/object-record/object-sort-dropdown/types/Sort';
|
||||
import { useCombinedViewSorts } from '@/views/hooks/useCombinedViewSorts';
|
||||
import { useUpsertCombinedViewSorts } from '@/views/hooks/useUpsertCombinedViewSorts';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
type UseHandleToggleColumnSortProps = {
|
||||
@@ -22,7 +22,7 @@ export const useHandleToggleColumnSort = ({
|
||||
const { columnDefinitions } =
|
||||
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||
|
||||
const { upsertCombinedViewSort } = useCombinedViewSorts(viewBarId);
|
||||
const { upsertCombinedViewSort } = useUpsertCombinedViewSorts(viewBarId);
|
||||
|
||||
const handleToggleColumnSort = useCallback(
|
||||
(fieldMetadataId: string) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
|
||||
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@@ -27,7 +27,7 @@ export const useHandleToggleTrashColumnFilter = ({
|
||||
const { columnDefinitions } =
|
||||
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||
|
||||
const { upsertCombinedViewFilter } = useCombinedViewFilters(viewBarId);
|
||||
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(viewBarId);
|
||||
const { isSoftDeleteActiveState } = useRecordTableStates(viewBarId);
|
||||
|
||||
const handleToggleTrashColumnFilter = useCallback(() => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { percentage, sleep, useTableData } from '../useTableData';
|
||||
|
||||
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
||||
import { recordBoardKanbanFieldMetadataNameComponentState } from '@/object-record/record-board/states/recordBoardKanbanFieldMetadataNameComponentState';
|
||||
import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard';
|
||||
import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { MockedProvider, MockedResponse } from '@apollo/client/testing';
|
||||
@@ -148,15 +148,19 @@ const mocks: MockedResponse[] = [
|
||||
];
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<Router>
|
||||
<RecoilRoot>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<SnackBarManagerScopeInternalContext.Provider
|
||||
value={{
|
||||
scopeId: 'snack-bar-manager',
|
||||
}}
|
||||
>
|
||||
<Router>
|
||||
<RecoilRoot>
|
||||
<MockedProvider addTypename={false} mocks={mocks}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
</SnackBarProviderScope>
|
||||
</RecoilRoot>
|
||||
</Router>
|
||||
</RecoilRoot>
|
||||
</Router>
|
||||
</SnackBarManagerScopeInternalContext.Provider>
|
||||
);
|
||||
|
||||
const graphqlEmptyResponse = [
|
||||
@@ -174,15 +178,19 @@ const graphqlEmptyResponse = [
|
||||
];
|
||||
|
||||
const WrapperWithEmptyResponse = ({ children }: { children: ReactNode }) => (
|
||||
<Router>
|
||||
<RecoilRoot>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<SnackBarManagerScopeInternalContext.Provider
|
||||
value={{
|
||||
scopeId: 'snack-bar-manager',
|
||||
}}
|
||||
>
|
||||
<Router>
|
||||
<RecoilRoot>
|
||||
<MockedProvider addTypename={false} mocks={graphqlEmptyResponse}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
</SnackBarProviderScope>
|
||||
</RecoilRoot>
|
||||
</Router>
|
||||
</RecoilRoot>
|
||||
</Router>
|
||||
</SnackBarManagerScopeInternalContext.Provider>
|
||||
);
|
||||
|
||||
describe('useTableData', () => {
|
||||
@@ -191,13 +199,13 @@ describe('useTableData', () => {
|
||||
describe('data fetching', () => {
|
||||
it('should handle no records', async () => {
|
||||
const callback = jest.fn();
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useTableData({
|
||||
recordIndexId,
|
||||
objectNameSingular,
|
||||
callback,
|
||||
|
||||
delayMs: 0,
|
||||
viewType: ViewType.Kanban,
|
||||
}),
|
||||
@@ -209,7 +217,7 @@ describe('useTableData', () => {
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(callback).toHaveBeenCalledWith([], []);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
@@ -8,8 +8,8 @@ import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoar
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
|
||||
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { useHandleViews } from '@/views/hooks/useHandleViews';
|
||||
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
|
||||
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
|
||||
import { GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { mapBoardFieldDefinitionsToViewFields } from '@/views/utils/mapBoardFieldDefinitionsToViewFields';
|
||||
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
||||
@@ -31,7 +31,7 @@ export const useRecordIndexOptionsForBoard = ({
|
||||
useRecoilState(recordIndexFieldDefinitionsState);
|
||||
|
||||
const { saveViewFields } = useSaveCurrentViewFields(viewBarId);
|
||||
const { updateCurrentView } = useHandleViews(viewBarId);
|
||||
const { updateCurrentView } = useUpdateCurrentView(viewBarId);
|
||||
const { isCompactModeActiveState } = useRecordBoard(recordBoardId);
|
||||
|
||||
const [isCompactModeActive, setIsCompactModeActive] = useRecoilState(
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||
import { ActionBar } from '@/ui/navigation/action-bar/components/ActionBar';
|
||||
import { useViewStates } from '@/views/hooks/internal/useViewStates';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { entityCountInCurrentViewComponentState } from '@/views/states/entityCountInCurrentViewComponentState';
|
||||
|
||||
export const RecordTableActionBar = ({
|
||||
recordTableId,
|
||||
@@ -15,10 +16,12 @@ export const RecordTableActionBar = ({
|
||||
hasUserSelectedAllRowsState,
|
||||
} = useRecordTableStates(recordTableId);
|
||||
|
||||
const { entityCountInCurrentViewState } = useViewStates(recordTableId);
|
||||
const entityCountInCurrentView = useRecoilValue(
|
||||
entityCountInCurrentViewState,
|
||||
// TODO: verify this instance id works
|
||||
const entityCountInCurrentView = useRecoilComponentValueV2(
|
||||
entityCountInCurrentViewComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState);
|
||||
const tableRowIds = useRecoilValue(tableRowIdsState);
|
||||
const selectedRowIds = useRecoilValue(selectedRowIdsSelector());
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/h
|
||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
|
||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
||||
import { useCombinedViewFilters } from '@/views/hooks/useCombinedViewFilters';
|
||||
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
@@ -13,7 +13,8 @@ export const RecordTableEmptyStateSoftDelete = () => {
|
||||
const { objectMetadataItem, objectNameSingular, recordTableId } =
|
||||
useContext(RecordTableContext);
|
||||
|
||||
const { removeCombinedViewFilter } = useCombinedViewFilters(recordTableId);
|
||||
const { deleteCombinedViewFilter } =
|
||||
useDeleteCombinedViewFilters(recordTableId);
|
||||
const { tableFiltersState } = useRecordTableStates(recordTableId);
|
||||
|
||||
const tableFilters = useRecoilValue(tableFiltersState);
|
||||
@@ -24,7 +25,7 @@ export const RecordTableEmptyStateSoftDelete = () => {
|
||||
});
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
removeCombinedViewFilter(
|
||||
deleteCombinedViewFilter(
|
||||
tableFilters.find(
|
||||
(filter) =>
|
||||
filter.definition.label === 'Deleted at' &&
|
||||
|
||||
@@ -48,14 +48,14 @@ describe('useSelectedTableCellEditMode', () => {
|
||||
|
||||
expect(mockCallbackInterface.set).toHaveBeenCalledWith(
|
||||
{
|
||||
key: 'isTableCellInEditModeComponentFamilyState__{"familyKey":{"column":0,"row":0},"scopeId":"yourScopeId-scope"}',
|
||||
key: 'isTableCellInEditModeComponentFamilyState__{"familyKey":{"column":0,"row":0},"scopeId":"yourScopeId"}',
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
expect(mockCallbackInterface.set).toHaveBeenCalledWith(
|
||||
{
|
||||
key: 'isTableCellInEditModeComponentFamilyState__{"familyKey":{"column":5,"row":1},"scopeId":"yourScopeId-scope"}',
|
||||
key: 'isTableCellInEditModeComponentFamilyState__{"familyKey":{"column":5,"row":1},"scopeId":"yourScopeId"}',
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
import { ColumnDefinition } from '../../types/ColumnDefinition';
|
||||
|
||||
// TODO: separate scope contexts from event contexts
|
||||
type RecordTableScopeInternalContextProps = ComponentStateKey & {
|
||||
type RecordTableScopeInternalContextProps = RecoilComponentStateKey & {
|
||||
onColumnsChange: (columns: ColumnDefinition<FieldMetadata>[]) => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
import { createComponentStateV2_alpha } from '@/ui/utilities/state/component-state/utils/createComponentStateV2_alpha';
|
||||
|
||||
export const hasRecordTableFetchedAllRecordsComponentStateV2 =
|
||||
createComponentStateV2<boolean>({
|
||||
createComponentStateV2_alpha<boolean>({
|
||||
key: 'hasRecordTableFetchedAllRecordsComponentStateV2',
|
||||
componentContext: RecordTableScopeInternalContext,
|
||||
defaultValue: false,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
import { createComponentStateV2_alpha } from '@/ui/utilities/state/component-state/utils/createComponentStateV2_alpha';
|
||||
|
||||
export const isRecordTableScrolledLeftComponentState =
|
||||
createComponentStateV2<boolean>({
|
||||
createComponentStateV2_alpha<boolean>({
|
||||
key: 'isRecordTableScrolledLeftComponentState',
|
||||
componentContext: RecordTableScopeInternalContext,
|
||||
defaultValue: true,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
import { createComponentStateV2_alpha } from '@/ui/utilities/state/component-state/utils/createComponentStateV2_alpha';
|
||||
|
||||
export const isRecordTableScrolledTopComponentState =
|
||||
createComponentStateV2<boolean>({
|
||||
createComponentStateV2_alpha<boolean>({
|
||||
key: 'isRecordTableScrolledTopComponentState',
|
||||
componentContext: RecordTableScopeInternalContext,
|
||||
defaultValue: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRef } from 'react';
|
||||
import { Fragment, useRef } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { IconComponent, IconPlus } from 'twenty-ui';
|
||||
@@ -158,16 +158,15 @@ export const SingleEntitySelectMenuItems = ({
|
||||
switch (entity.id) {
|
||||
case 'add-new': {
|
||||
return (
|
||||
<>
|
||||
<Fragment key={entity.id}>
|
||||
{entitiesToSelect.length > 0 && <DropdownMenuSeparator />}
|
||||
<CreateNewButton
|
||||
key={entity.id}
|
||||
onClick={onCreate}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add New"
|
||||
hovered={isSelectedAddNewButton}
|
||||
/>
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
case 'select-none': {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type RelationPickerScopeInternalContextProps = ComponentStateKey;
|
||||
type RelationPickerScopeInternalContextProps = RecoilComponentStateKey;
|
||||
|
||||
export const RelationPickerScopeInternalContext =
|
||||
createScopeInternalContext<RelationPickerScopeInternalContextProps>();
|
||||
|
||||
@@ -44,6 +44,7 @@ export const SettingsAccountsMessageChannelsContainer = () => {
|
||||
in: accounts.map((account) => account.id),
|
||||
},
|
||||
},
|
||||
skip: !accounts.length,
|
||||
});
|
||||
|
||||
const tabs = [
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { useUpdateWorkspaceMutation } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
@@ -40,6 +41,7 @@ export const NameField = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const debouncedUpdate = useCallback(
|
||||
useDebouncedCallback(async (name: string) => {
|
||||
if (isEmpty(name)) return;
|
||||
// update local recoil state when workspace name is updated
|
||||
setCurrentWorkspace((currentValue) => {
|
||||
if (currentValue === null) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { RecordIndexOptionsDropdown } from '@/object-record/record-index/options
|
||||
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
|
||||
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
|
||||
import { ViewBar } from '@/views/components/ViewBar';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@@ -21,28 +22,30 @@ export const SignInBackgroundMockContainer = () => {
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<ViewBar
|
||||
viewBarId={viewBarId}
|
||||
onCurrentViewChange={async () => {}}
|
||||
optionsDropdownButton={
|
||||
<RecordIndexOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
viewType={ViewType.Table}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SignInBackgroundMockContainerEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
recordTableId={recordIndexId}
|
||||
viewId={viewBarId}
|
||||
/>
|
||||
<RecordTableWithWrappers
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={viewBarId}
|
||||
updateRecordMutation={() => {}}
|
||||
/>
|
||||
<ViewComponentInstanceContext.Provider value={{ instanceId: viewBarId }}>
|
||||
<ViewBar
|
||||
viewBarId={viewBarId}
|
||||
onCurrentViewChange={async () => {}}
|
||||
optionsDropdownButton={
|
||||
<RecordIndexOptionsDropdown
|
||||
recordIndexId={recordIndexId}
|
||||
objectNameSingular={objectNameSingular}
|
||||
viewType={ViewType.Table}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SignInBackgroundMockContainerEffect
|
||||
objectNamePlural={objectNamePlural}
|
||||
recordTableId={recordIndexId}
|
||||
viewId={viewBarId}
|
||||
/>
|
||||
<RecordTableWithWrappers
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordTableId={recordIndexId}
|
||||
viewBarId={viewBarId}
|
||||
updateRecordMutation={() => {}}
|
||||
/>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type DialogManagerScopeInternalContextProps = ComponentStateKey;
|
||||
type DialogManagerScopeInternalContextProps = RecoilComponentStateKey;
|
||||
|
||||
export const DialogManagerScopeInternalContext =
|
||||
createScopeInternalContext<DialogManagerScopeInternalContextProps>();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ComponentPropsWithoutRef, ReactNode, useMemo } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { ComponentPropsWithoutRef, ReactNode, useMemo } from 'react';
|
||||
import {
|
||||
IconAlertTriangle,
|
||||
IconInfoCircle,
|
||||
@@ -46,7 +46,6 @@ const StyledContainer = styled.div`
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
height: 61px;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
position: relative;
|
||||
width: 296px;
|
||||
@@ -90,7 +89,6 @@ const StyledDescription = styled.div`
|
||||
padding-left: ${({ theme }) => theme.spacing(6)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 200px;
|
||||
`;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type SnackBarManagerScopeInternalContextProps = ComponentStateKey;
|
||||
type SnackBarManagerScopeInternalContextProps = RecoilComponentStateKey;
|
||||
|
||||
export const SnackBarManagerScopeInternalContext =
|
||||
createScopeInternalContext<SnackBarManagerScopeInternalContextProps>();
|
||||
|
||||
@@ -8,6 +8,7 @@ import { RoundedLink } from '@/ui/navigation/link/components/RoundedLink';
|
||||
|
||||
import { parsePhoneNumber } from 'libphonenumber-js';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
type PhonesDisplayProps = {
|
||||
value?: FieldPhonesValue;
|
||||
@@ -39,7 +40,7 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
|
||||
countryCode: value.primaryPhoneCountryCode,
|
||||
}
|
||||
: null,
|
||||
...(value?.additionalPhones ?? []),
|
||||
...parseAdditionalPhones(value?.additionalPhones),
|
||||
]
|
||||
.filter(isDefined)
|
||||
.map(({ number, countryCode }) => {
|
||||
@@ -85,3 +86,23 @@ export const PhonesDisplay = ({ value, isFocused }: PhonesDisplayProps) => {
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const parseAdditionalPhones = (additionalPhones?: any) => {
|
||||
if (!additionalPhones) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (typeof additionalPhones === 'object') {
|
||||
return additionalPhones;
|
||||
}
|
||||
|
||||
if (typeof additionalPhones === 'string') {
|
||||
try {
|
||||
return JSON.parse(additionalPhones);
|
||||
} catch (error) {
|
||||
logError(`Error parsing additional phones' : ` + error);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { splitFullName } from '~/utils/format/spiltFullName';
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
import { StyledTextInput } from './TextInput';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@@ -167,9 +169,12 @@ export const DoubleTextInput = ({
|
||||
|
||||
const name = event.clipboardData.getData('Text');
|
||||
|
||||
const splittedName = name.split(' ');
|
||||
const splittedName = splitFullName(name);
|
||||
|
||||
onPaste?.({ firstValue: splittedName[0], secondValue: splittedName[1] });
|
||||
onPaste?.({
|
||||
firstValue: splittedName[0],
|
||||
secondValue: splittedName[1],
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickToPreventParentClickEvents = (
|
||||
@@ -189,7 +194,10 @@ export const DoubleTextInput = ({
|
||||
placeholder={firstValuePlaceholder}
|
||||
value={firstInternalValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
handleChange(event.target.value, secondInternalValue);
|
||||
handleChange(
|
||||
turnIntoEmptyStringIfWhitespacesOnly(event.target.value),
|
||||
secondInternalValue,
|
||||
);
|
||||
}}
|
||||
onPaste={(event: ClipboardEvent<HTMLInputElement>) =>
|
||||
handleOnPaste(event)
|
||||
@@ -203,7 +211,10 @@ export const DoubleTextInput = ({
|
||||
placeholder={secondValuePlaceholder}
|
||||
value={secondInternalValue}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
handleChange(firstInternalValue, event.target.value);
|
||||
handleChange(
|
||||
firstInternalValue,
|
||||
turnIntoEmptyStringIfWhitespacesOnly(event.target.value),
|
||||
);
|
||||
}}
|
||||
onClick={handleClickToPreventParentClickEvents}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import styled from '@emotion/styled';
|
||||
import { TEXT_INPUT_STYLE } from 'twenty-ui';
|
||||
|
||||
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
|
||||
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
|
||||
export type TextAreaInputProps = {
|
||||
disabled?: boolean;
|
||||
@@ -67,10 +68,12 @@ export const TextAreaInput = ({
|
||||
copyButton = true,
|
||||
}: TextAreaInputProps) => {
|
||||
const [internalText, setInternalText] = useState(value);
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setInternalText(event.target.value);
|
||||
onChange?.(event.target.value);
|
||||
const targetValue = turnIntoEmptyStringIfWhitespacesOnly(
|
||||
event.target.value,
|
||||
);
|
||||
setInternalText(targetValue);
|
||||
onChange?.(targetValue);
|
||||
};
|
||||
|
||||
const wrapperRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import { TEXT_INPUT_STYLE } from 'twenty-ui';
|
||||
|
||||
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
|
||||
@@ -44,12 +44,11 @@ export const TextInput = ({
|
||||
const copyRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInternalText(event.target.value);
|
||||
onChange?.(event.target.value);
|
||||
setInternalText(event.target.value.trim());
|
||||
onChange?.(event.target.value.trim());
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setInternalText(value);
|
||||
setInternalText(value.trim());
|
||||
}, [value]);
|
||||
|
||||
useRegisterInputEvents({
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { FocusEventHandler } from 'react';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
import { InputHotkeyScope } from '../types/InputHotkeyScope';
|
||||
|
||||
const MAX_ROWS = 5;
|
||||
@@ -75,7 +76,9 @@ export const TextArea = ({
|
||||
maxRows={MAX_ROWS}
|
||||
minRows={computedMinRows}
|
||||
value={value}
|
||||
onChange={(event) => onChange?.(event.target.value)}
|
||||
onChange={(event) =>
|
||||
onChange?.(turnIntoEmptyStringIfWhitespacesOnly(event.target.value))
|
||||
}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from 'react';
|
||||
import { IconComponent, IconEye, IconEyeOff } from 'twenty-ui';
|
||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
|
||||
const StyledContainer = styled.div<
|
||||
Pick<TextInputV2ComponentProps, 'fullWidth'>
|
||||
@@ -180,7 +181,7 @@ const TextInputV2Component = (
|
||||
</StyledLeftIconContainer>
|
||||
)}
|
||||
<StyledInput
|
||||
data-testId={dataTestId}
|
||||
data-testid={dataTestId}
|
||||
autoComplete={autoComplete || 'off'}
|
||||
ref={combinedRef}
|
||||
tabIndex={tabIndex ?? 0}
|
||||
@@ -188,7 +189,9 @@ const TextInputV2Component = (
|
||||
onBlur={onBlur}
|
||||
type={passwordVisible ? 'text' : type}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange?.(event.target.value);
|
||||
onChange?.(
|
||||
turnIntoEmptyStringIfWhitespacesOnly(event.target.value),
|
||||
);
|
||||
}}
|
||||
onKeyDown={onKeyDown}
|
||||
{...{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type DropdownScopeInternalContextProps = ComponentStateKey;
|
||||
type DropdownScopeInternalContextProps = RecoilComponentStateKey;
|
||||
|
||||
export const DropdownScopeInternalContext =
|
||||
createScopeInternalContext<DropdownScopeInternalContextProps>();
|
||||
|
||||
@@ -30,7 +30,6 @@ const StyledTopBarContainer = styled.div<{ width?: number }>`
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: 0;
|
||||
padding-right: ${({ theme }) => theme.spacing(3)};
|
||||
z-index: 20;
|
||||
width: ${({ width }) => width + 'px' || '100%'};
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
|
||||
@@ -32,7 +32,7 @@ const StyledContainer = styled(motion.div)`
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
height: 100%;
|
||||
height: 100dvh;
|
||||
overflow-x: hidden;
|
||||
position: fixed;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type SelectableListScopeInternalContextProps = ComponentStateKey;
|
||||
type SelectableListScopeInternalContextProps = RecoilComponentStateKey;
|
||||
|
||||
export const SelectableListScopeInternalContext =
|
||||
createScopeInternalContext<SelectableListScopeInternalContextProps>();
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListState
|
||||
|
||||
export const useTabList = (tabListId?: string) => {
|
||||
const { activeTabIdState } = useTabListStates({
|
||||
tabListScopeId: `${tabListId}-scope`,
|
||||
tabListScopeId: tabListId,
|
||||
});
|
||||
|
||||
const setActiveTabId = useSetRecoilState(activeTabIdState);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext';
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type TabListScopeInternalContextProps = ComponentStateKey;
|
||||
type TabListScopeInternalContextProps = RecoilComponentStateKey;
|
||||
|
||||
export const TabListScopeInternalContext =
|
||||
createScopeInternalContext<TabListScopeInternalContextProps>();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { RecoilState, useRecoilState } from 'recoil';
|
||||
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
export const useRecoilScopedStateV2 = <StateType>(
|
||||
recoilState: (scopedKey: ComponentStateKey) => RecoilState<StateType>,
|
||||
recoilState: (scopedKey: RecoilComponentStateKey) => RecoilState<StateType>,
|
||||
scopeId: string,
|
||||
) => {
|
||||
return useRecoilState<StateType>(
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Context, createContext } from 'react';
|
||||
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
type ScopeInternalContext<T extends ComponentStateKey> = Context<T | null>;
|
||||
type ScopeInternalContext<T extends RecoilComponentStateKey> =
|
||||
Context<T | null>;
|
||||
|
||||
export const createScopeInternalContext = <T extends ComponentStateKey>(
|
||||
export const createScopeInternalContext = <T extends RecoilComponentStateKey>(
|
||||
initialValue?: T,
|
||||
) => {
|
||||
return createContext<T | null>(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { RecoilValueReadOnly } from 'recoil';
|
||||
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
export type RecoilScopedSelector<StateType> = (
|
||||
scopedKey: ComponentStateKey,
|
||||
scopedKey: RecoilComponentStateKey,
|
||||
) => RecoilValueReadOnly<StateType>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { RecoilState } from 'recoil';
|
||||
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
|
||||
export type RecoilScopedState<StateType> = (
|
||||
scopedKey: ComponentStateKey,
|
||||
scopedKey: RecoilComponentStateKey,
|
||||
) => RecoilState<StateType>;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export const getScopeIdFromComponentId = (componentId: string) =>
|
||||
`${componentId}-scope`;
|
||||
`${componentId}`;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
|
||||
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
export const useAvailableComponentInstanceIdOrThrow = <
|
||||
T extends { instanceId: string },
|
||||
>(
|
||||
Context: ComponentInstanceStateContext<T>,
|
||||
instanceIdFromProps?: string,
|
||||
): string => {
|
||||
const instanceStateContext = useComponentInstanceStateContext(Context);
|
||||
|
||||
const instanceIdFromContext = instanceStateContext?.instanceId;
|
||||
|
||||
if (isNonEmptyString(instanceIdFromProps)) {
|
||||
return instanceIdFromProps;
|
||||
} else if (isNonEmptyString(instanceIdFromContext)) {
|
||||
return instanceIdFromContext;
|
||||
} else {
|
||||
throw new Error(
|
||||
'Instance id is not provided and cannot be found in context.',
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const useComponentInstanceStateContext = <
|
||||
T extends { instanceId: string },
|
||||
>(
|
||||
Context: ComponentInstanceStateContext<T>,
|
||||
) => {
|
||||
const context = useContext(Context);
|
||||
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
|
||||
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
|
||||
|
||||
export const useRecoilCallbackState = <Value>(
|
||||
componentState: RecoilComponentState<Value>,
|
||||
componentId?: string,
|
||||
) => {
|
||||
const componentContext = (window as any).componentContextStateMap?.get(
|
||||
componentState.key,
|
||||
);
|
||||
|
||||
if (!componentContext) {
|
||||
throw new Error(
|
||||
`Component context for key "${componentState.key}" is not defined`,
|
||||
);
|
||||
}
|
||||
|
||||
const internalScopeId = useAvailableScopeIdOrThrow(
|
||||
componentContext,
|
||||
getScopeIdOrUndefinedFromComponentId(componentId),
|
||||
);
|
||||
|
||||
return componentState.atomFamily({
|
||||
scopeId: internalScopeId,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable no-redeclare */
|
||||
/* eslint-disable prefer-arrow/prefer-arrow-functions */
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { ComponentFamilyReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyReadOnlySelectorV2';
|
||||
import { ComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilySelectorV2';
|
||||
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
|
||||
import { ComponentReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentReadOnlySelectorV2';
|
||||
import { ComponentSelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentSelectorV2';
|
||||
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
|
||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||
import { RecoilState, RecoilValueReadOnly, SerializableParam } from 'recoil';
|
||||
|
||||
export function useRecoilComponentCallbackStateV2<ValueType>(
|
||||
componentState: ComponentStateV2<ValueType>,
|
||||
instanceIdFromProps?: string,
|
||||
): RecoilState<ValueType>;
|
||||
export function useRecoilComponentCallbackStateV2<ValueType>(
|
||||
componentSelector: ComponentSelectorV2<ValueType>,
|
||||
instanceIdFromProps?: string,
|
||||
): RecoilState<ValueType>;
|
||||
export function useRecoilComponentCallbackStateV2<ValueType>(
|
||||
componentReadOnlySelector: ComponentReadOnlySelectorV2<ValueType>,
|
||||
instanceIdFromProps?: string,
|
||||
): RecoilValueReadOnly<ValueType>;
|
||||
export function useRecoilComponentCallbackStateV2<
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>(
|
||||
componentFamilyState: ComponentFamilyStateV2<ValueType, FamilyKey>,
|
||||
instanceIdFromProps?: string,
|
||||
): (familyKey: FamilyKey) => RecoilState<ValueType>;
|
||||
export function useRecoilComponentCallbackStateV2<
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>(
|
||||
componentFamilySelector: ComponentFamilySelectorV2<ValueType, FamilyKey>,
|
||||
instanceIdFromProps?: string,
|
||||
): (familyKey: FamilyKey) => RecoilState<ValueType>;
|
||||
export function useRecoilComponentCallbackStateV2<
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>(
|
||||
componentFamilyReadOnlySelector: ComponentFamilyReadOnlySelectorV2<
|
||||
ValueType,
|
||||
FamilyKey
|
||||
>,
|
||||
instanceIdFromProps?: string,
|
||||
): (familyKey: FamilyKey) => RecoilValueReadOnly<ValueType>;
|
||||
export function useRecoilComponentCallbackStateV2<
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>(
|
||||
componentFamilyState: ComponentFamilyStateV2<ValueType, FamilyKey>,
|
||||
instanceIdFromProps?: string,
|
||||
): (familyKey: FamilyKey) => RecoilState<ValueType>;
|
||||
export function useRecoilComponentCallbackStateV2<
|
||||
ComponentState extends
|
||||
| ComponentStateV2<ValueType>
|
||||
| ComponentSelectorV2<ValueType>
|
||||
| ComponentReadOnlySelectorV2<ValueType>
|
||||
| ComponentFamilyStateV2<ValueType, FamilyKey>
|
||||
| ComponentFamilySelectorV2<ValueType, FamilyKey>
|
||||
| ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey>,
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam = never,
|
||||
>(
|
||||
componentState: ComponentState,
|
||||
instanceIdFromProps?: string,
|
||||
):
|
||||
| RecoilState<ValueType>
|
||||
| RecoilValueReadOnly<ValueType>
|
||||
| ((familyKey: FamilyKey) => RecoilState<ValueType>)
|
||||
| ((familyKey: FamilyKey) => RecoilValueReadOnly<ValueType>) {
|
||||
const componentStateKey = componentState.key;
|
||||
|
||||
const componentInstanceContext =
|
||||
globalComponentInstanceContextMap.get(componentStateKey);
|
||||
|
||||
if (!componentInstanceContext) {
|
||||
throw new Error(
|
||||
`Instance context for key "${componentStateKey}" is not defined, check the component state declaration.`,
|
||||
);
|
||||
}
|
||||
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
componentInstanceContext,
|
||||
instanceIdFromProps,
|
||||
);
|
||||
|
||||
switch (componentState.type) {
|
||||
case 'ComponentState': {
|
||||
return componentState.atomFamily({
|
||||
instanceId,
|
||||
});
|
||||
}
|
||||
case 'ComponentSelector': {
|
||||
return componentState.selectorFamily({
|
||||
instanceId,
|
||||
});
|
||||
}
|
||||
case 'ComponentReadOnlySelector': {
|
||||
return componentState.selectorFamily({
|
||||
instanceId,
|
||||
});
|
||||
}
|
||||
case 'ComponentFamilyState': {
|
||||
return (familyKey: FamilyKey) =>
|
||||
componentState.atomFamily({
|
||||
instanceId,
|
||||
familyKey,
|
||||
});
|
||||
}
|
||||
case 'ComponentFamilySelector': {
|
||||
return (familyKey: FamilyKey) =>
|
||||
componentState.selectorFamily({
|
||||
instanceId,
|
||||
familyKey,
|
||||
});
|
||||
}
|
||||
case 'ComponentFamilyReadOnlySelector': {
|
||||
return (familyKey: FamilyKey) =>
|
||||
componentState.selectorFamily({
|
||||
instanceId,
|
||||
familyKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { ComponentFamilyReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyReadOnlySelectorV2';
|
||||
import { ComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilySelectorV2';
|
||||
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
|
||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||
import { SerializableParam, useRecoilValue } from 'recoil';
|
||||
|
||||
export const useRecoilComponentFamilyValueV2 = <
|
||||
StateType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>(
|
||||
componentStateV2:
|
||||
| ComponentFamilyStateV2<StateType, FamilyKey>
|
||||
| ComponentFamilySelectorV2<StateType, FamilyKey>
|
||||
| ComponentFamilyReadOnlySelectorV2<StateType, FamilyKey>,
|
||||
familyKey: FamilyKey,
|
||||
instanceIdFromProps?: string,
|
||||
): StateType => {
|
||||
const instanceContext = globalComponentInstanceContextMap.get(
|
||||
componentStateV2.key,
|
||||
);
|
||||
|
||||
if (!instanceContext) {
|
||||
throw new Error(
|
||||
`Instance context for key "${componentStateV2.key}" is not defined`,
|
||||
);
|
||||
}
|
||||
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
instanceContext,
|
||||
instanceIdFromProps,
|
||||
);
|
||||
|
||||
switch (componentStateV2.type) {
|
||||
case 'ComponentFamilyState': {
|
||||
return useRecoilValue(
|
||||
componentStateV2.atomFamily({ familyKey, instanceId }),
|
||||
);
|
||||
}
|
||||
case 'ComponentFamilySelector': {
|
||||
return useRecoilValue(
|
||||
componentStateV2.selectorFamily({ familyKey, instanceId }),
|
||||
);
|
||||
}
|
||||
case 'ComponentFamilyReadOnlySelector': {
|
||||
return useRecoilValue(
|
||||
componentStateV2.selectorFamily({ familyKey, instanceId }),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
|
||||
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
export const useRecoilComponentState = <StateType>(
|
||||
componentState: RecoilComponentState<StateType>,
|
||||
componentId?: string,
|
||||
) => {
|
||||
const componentContext = (window as any).componentContextStateMap?.get(
|
||||
componentState.key,
|
||||
);
|
||||
|
||||
if (!componentContext) {
|
||||
throw new Error(
|
||||
`Component context for key "${componentState.key}" is not defined`,
|
||||
);
|
||||
}
|
||||
|
||||
const internalComponentId = useAvailableScopeIdOrThrow(
|
||||
componentContext,
|
||||
getScopeIdOrUndefinedFromComponentId(componentId),
|
||||
);
|
||||
|
||||
return useRecoilState(
|
||||
componentState.atomFamily({ scopeId: internalComponentId }),
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
|
||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
export const useRecoilComponentStateV2 = <StateType>(
|
||||
componentState: ComponentStateV2<StateType>,
|
||||
instanceIdFromProps?: string,
|
||||
) => {
|
||||
const componentInstanceContext = globalComponentInstanceContextMap.get(
|
||||
componentState.key,
|
||||
);
|
||||
|
||||
if (!componentInstanceContext) {
|
||||
throw new Error(
|
||||
`Instance context for key "${componentState.key}" is not defined`,
|
||||
);
|
||||
}
|
||||
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
componentInstanceContext,
|
||||
instanceIdFromProps,
|
||||
);
|
||||
|
||||
return useRecoilState(componentState.atomFamily({ instanceId }));
|
||||
};
|
||||
@@ -2,10 +2,10 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
|
||||
import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
|
||||
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
|
||||
|
||||
export const useRecoilComponentValue = <StateType>(
|
||||
componentState: ComponentState<StateType>,
|
||||
componentState: RecoilComponentState<StateType>,
|
||||
componentId?: string,
|
||||
) => {
|
||||
const componentContext = (window as any).componentContextStateMap?.get(
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
|
||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const useRecoilComponentValueV2 = <StateType>(
|
||||
componentStateV2: ComponentStateV2<StateType>,
|
||||
instanceIdFromProps?: string,
|
||||
) => {
|
||||
const instanceContext = globalComponentInstanceContextMap.get(
|
||||
componentStateV2.key,
|
||||
);
|
||||
|
||||
if (!instanceContext) {
|
||||
throw new Error(
|
||||
`Instance context for key "${componentStateV2.key}" is not defined`,
|
||||
);
|
||||
}
|
||||
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
instanceContext,
|
||||
instanceIdFromProps,
|
||||
);
|
||||
|
||||
return useRecoilValue(componentStateV2.atomFamily({ instanceId }));
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useScopeInternalContext';
|
||||
import { ComponentFamilyState } from '@/ui/utilities/state/component-state/types/ComponentFamilyState';
|
||||
import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
|
||||
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
|
||||
|
||||
export const useScopeIdFromStateContext = (
|
||||
componentState: ComponentState<any> | ComponentFamilyState<any, any>,
|
||||
componentState: RecoilComponentState<any> | ComponentFamilyState<any, any>,
|
||||
) => {
|
||||
const componentContext = (window as any).componentContextStateMap?.get(
|
||||
componentState.key,
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
|
||||
// We're disabling rules-of-hooks because we're sure that the call order cannot be modified
|
||||
// because a component state cannot change its type during its lifecycle
|
||||
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { ComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilySelectorV2';
|
||||
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
|
||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||
import { SerializableParam, SetterOrUpdater, useSetRecoilState } from 'recoil';
|
||||
|
||||
export const useSetRecoilComponentFamilyStateV2 = <
|
||||
StateType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>(
|
||||
componentState:
|
||||
| ComponentFamilyStateV2<StateType, FamilyKey>
|
||||
| ComponentFamilySelectorV2<StateType, FamilyKey>,
|
||||
familyKey: FamilyKey,
|
||||
instanceIdFromProps?: string,
|
||||
): SetterOrUpdater<StateType> => {
|
||||
const componentInstanceContext = globalComponentInstanceContextMap.get(
|
||||
componentState.key,
|
||||
);
|
||||
|
||||
if (!componentInstanceContext) {
|
||||
throw new Error(
|
||||
`Instance context for key "${componentState.key}" is not defined`,
|
||||
);
|
||||
}
|
||||
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
componentInstanceContext,
|
||||
instanceIdFromProps,
|
||||
);
|
||||
|
||||
switch (componentState.type) {
|
||||
case 'ComponentFamilyState': {
|
||||
return useSetRecoilState(
|
||||
componentState.atomFamily({ familyKey, instanceId }),
|
||||
);
|
||||
}
|
||||
case 'ComponentFamilySelector': {
|
||||
return useSetRecoilState(
|
||||
componentState.selectorFamily({
|
||||
familyKey,
|
||||
instanceId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2,10 +2,10 @@ import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { getScopeIdOrUndefinedFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdOrUndefinedFromComponentId';
|
||||
import { ComponentState } from '@/ui/utilities/state/component-state/types/ComponentState';
|
||||
import { RecoilComponentState } from '@/ui/utilities/state/component-state/types/RecoilComponentState';
|
||||
|
||||
export const useSetRecoilComponentState = <StateType>(
|
||||
componentState: ComponentState<StateType>,
|
||||
componentState: RecoilComponentState<StateType>,
|
||||
componentId?: string,
|
||||
) => {
|
||||
const componentContext = (window as any).componentContextStateMap?.get(
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { ComponentStateV2 } from '@/ui/utilities/state/component-state/types/ComponentStateV2';
|
||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||
import { SetterOrUpdater, useSetRecoilState } from 'recoil';
|
||||
|
||||
export const useSetRecoilComponentStateV2 = <ValueType>(
|
||||
componentState: ComponentStateV2<ValueType>,
|
||||
instanceIdFromProps?: string,
|
||||
): SetterOrUpdater<ValueType> => {
|
||||
const componentInstanceContext = globalComponentInstanceContextMap.get(
|
||||
componentState.key,
|
||||
);
|
||||
|
||||
if (!componentInstanceContext) {
|
||||
throw new Error(
|
||||
`Instance context for key "${componentState.key}" is not defined`,
|
||||
);
|
||||
}
|
||||
|
||||
const instanceId = useAvailableComponentInstanceIdOrThrow(
|
||||
componentInstanceContext,
|
||||
instanceIdFromProps,
|
||||
);
|
||||
|
||||
return useSetRecoilState(componentState.atomFamily({ instanceId }));
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
|
||||
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
|
||||
import { RecoilValueReadOnly, SerializableParam } from 'recoil';
|
||||
|
||||
export type ComponentFamilyReadOnlySelectorV2<
|
||||
StateType,
|
||||
FamilyKey extends SerializableParam,
|
||||
> = {
|
||||
type: Extract<ComponentStateTypeV2, 'ComponentFamilyReadOnlySelector'>;
|
||||
key: string;
|
||||
selectorFamily: (
|
||||
componentFamilyStateKey: ComponentFamilyStateKeyV2<FamilyKey>,
|
||||
) => RecoilValueReadOnly<StateType>;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
|
||||
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
|
||||
import { RecoilState, SerializableParam } from 'recoil';
|
||||
|
||||
export type ComponentFamilySelectorV2<
|
||||
StateType,
|
||||
FamilyKey extends SerializableParam,
|
||||
> = {
|
||||
type: Extract<ComponentStateTypeV2, 'ComponentFamilySelector'>;
|
||||
key: string;
|
||||
selectorFamily: (
|
||||
componentFamilyStateKey: ComponentFamilyStateKeyV2<FamilyKey>,
|
||||
) => RecoilState<StateType>;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
|
||||
import { SerializableParam } from 'recoil';
|
||||
|
||||
export type ComponentFamilyStateKeyV2<FamilyKey extends SerializableParam> =
|
||||
ComponentStateKeyV2 & {
|
||||
familyKey: FamilyKey;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
|
||||
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
|
||||
import { RecoilState, SerializableParam } from 'recoil';
|
||||
|
||||
export type ComponentFamilyStateV2<
|
||||
StateType,
|
||||
FamilyKey extends SerializableParam,
|
||||
> = {
|
||||
type: Extract<ComponentStateTypeV2, 'ComponentFamilyState'>;
|
||||
key: string;
|
||||
atomFamily: (
|
||||
componentFamilyStateKey: ComponentFamilyStateKeyV2<FamilyKey>,
|
||||
) => RecoilState<StateType>;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
import { Context } from 'react';
|
||||
|
||||
export type ComponentInstanceStateContext<T extends { instanceId: string }> =
|
||||
Context<T | null>;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
|
||||
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
|
||||
import { RecoilValueReadOnly } from 'recoil';
|
||||
|
||||
export type ComponentReadOnlySelectorV2<StateType> = {
|
||||
type: Extract<ComponentStateTypeV2, 'ComponentReadOnlySelector'>;
|
||||
key: string;
|
||||
selectorFamily: (
|
||||
componentStateKey: ComponentStateKeyV2,
|
||||
) => RecoilValueReadOnly<StateType>;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
|
||||
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
|
||||
import { RecoilState } from 'recoil';
|
||||
|
||||
export type ComponentSelectorV2<StateType> = {
|
||||
type: Extract<ComponentStateTypeV2, 'ComponentSelector'>;
|
||||
key: string;
|
||||
selectorFamily: (
|
||||
componentStateKey: ComponentStateKeyV2,
|
||||
) => RecoilState<StateType>;
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
import { RecoilState } from 'recoil';
|
||||
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
|
||||
export type ComponentState<StateType> = {
|
||||
key: string;
|
||||
atomFamily: (componentStateKey: ComponentStateKey) => RecoilState<StateType>;
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
export type ComponentStateKey = {
|
||||
scopeId: string;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export type ComponentStateKeyV2 = {
|
||||
instanceId: string;
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export type ComponentStateTypeV2 =
|
||||
| 'ComponentState'
|
||||
| 'ComponentFamilyState'
|
||||
| 'ComponentSelector'
|
||||
| 'ComponentReadOnlySelector'
|
||||
| 'ComponentFamilySelector'
|
||||
| 'ComponentFamilyReadOnlySelector';
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
|
||||
import { ComponentStateTypeV2 } from '@/ui/utilities/state/component-state/types/ComponentStateTypeV2';
|
||||
import { RecoilState } from 'recoil';
|
||||
|
||||
export type ComponentStateV2<StateType> = {
|
||||
type: Extract<ComponentStateTypeV2, 'ComponentState'>;
|
||||
key: string;
|
||||
atomFamily: (
|
||||
componentStateKey: ComponentStateKeyV2,
|
||||
) => RecoilState<StateType>;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import { RecoilState } from 'recoil';
|
||||
import { RecoilComponentStateKey } from './RecoilComponentStateKey';
|
||||
|
||||
export type RecoilComponentState<StateType> = {
|
||||
key: string;
|
||||
atomFamily: (
|
||||
componentStateKey: RecoilComponentStateKey,
|
||||
) => RecoilState<StateType>;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export type RecoilComponentStateKey = {
|
||||
scopeId: string;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { selectorFamily, SerializableParam } from 'recoil';
|
||||
|
||||
import { ComponentFamilyReadOnlySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyReadOnlySelectorV2';
|
||||
import { ComponentFamilySelectorV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilySelectorV2';
|
||||
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
|
||||
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
|
||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||
import { SelectorGetter } from '@/ui/utilities/state/types/SelectorGetter';
|
||||
import { SelectorSetter } from '@/ui/utilities/state/types/SelectorSetter';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
export const createComponentFamilySelectorV2 = <
|
||||
ValueType,
|
||||
FamilyKey extends SerializableParam,
|
||||
>({
|
||||
key,
|
||||
get,
|
||||
set,
|
||||
componentInstanceContext,
|
||||
}: {
|
||||
key: string;
|
||||
get: SelectorGetter<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>;
|
||||
set?: SelectorSetter<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>;
|
||||
componentInstanceContext: ComponentInstanceStateContext<any> | null;
|
||||
}):
|
||||
| ComponentFamilySelectorV2<ValueType, FamilyKey>
|
||||
| ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey> => {
|
||||
if (isDefined(componentInstanceContext)) {
|
||||
globalComponentInstanceContextMap.set(key, componentInstanceContext);
|
||||
}
|
||||
|
||||
if (isDefined(set)) {
|
||||
return {
|
||||
type: 'ComponentFamilySelector',
|
||||
key,
|
||||
selectorFamily: selectorFamily<
|
||||
ValueType,
|
||||
ComponentFamilyStateKeyV2<FamilyKey>
|
||||
>({
|
||||
key,
|
||||
get,
|
||||
set,
|
||||
}),
|
||||
} satisfies ComponentFamilySelectorV2<ValueType, FamilyKey>;
|
||||
} else {
|
||||
return {
|
||||
type: 'ComponentFamilyReadOnlySelector',
|
||||
key,
|
||||
selectorFamily: selectorFamily<
|
||||
ValueType,
|
||||
ComponentFamilyStateKeyV2<FamilyKey>
|
||||
>({
|
||||
key,
|
||||
get,
|
||||
}),
|
||||
} satisfies ComponentFamilyReadOnlySelectorV2<ValueType, FamilyKey>;
|
||||
}
|
||||
};
|
||||
@@ -1,13 +1,15 @@
|
||||
import { ComponentFamilyStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKeyV2';
|
||||
import { ComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateV2';
|
||||
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
|
||||
import { globalComponentInstanceContextMap } from '@/ui/utilities/state/component-state/utils/globalComponentInstanceContextMap';
|
||||
import { AtomEffect, atomFamily, SerializableParam } from 'recoil';
|
||||
|
||||
import { ScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/types/ScopeInternalContext';
|
||||
import { ComponentFamilyStateKey } from '@/ui/utilities/state/component-state/types/ComponentFamilyStateKey';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
type CreateComponentFamilyStateV2Type<ValueType> = {
|
||||
type CreateComponentFamilyStateArgs<ValueType> = {
|
||||
key: string;
|
||||
defaultValue: ValueType;
|
||||
componentContext: ScopeInternalContext<any> | null;
|
||||
componentInstanceContext: ComponentInstanceStateContext<any> | null;
|
||||
effects?: AtomEffect<ValueType>[];
|
||||
};
|
||||
|
||||
@@ -18,22 +20,22 @@ export const createComponentFamilyStateV2 = <
|
||||
key,
|
||||
effects,
|
||||
defaultValue,
|
||||
componentContext,
|
||||
}: CreateComponentFamilyStateV2Type<ValueType>) => {
|
||||
if (isDefined(componentContext)) {
|
||||
if (!isDefined((window as any).componentContextStateMap)) {
|
||||
(window as any).componentContextStateMap = new Map();
|
||||
}
|
||||
|
||||
(window as any).componentContextStateMap.set(key, componentContext);
|
||||
componentInstanceContext,
|
||||
}: CreateComponentFamilyStateArgs<ValueType>): ComponentFamilyStateV2<
|
||||
ValueType,
|
||||
FamilyKey
|
||||
> => {
|
||||
if (isDefined(componentInstanceContext)) {
|
||||
globalComponentInstanceContextMap.set(key, componentInstanceContext);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'ComponentFamilyState',
|
||||
key,
|
||||
atomFamily: atomFamily<ValueType, ComponentFamilyStateKey<FamilyKey>>({
|
||||
atomFamily: atomFamily<ValueType, ComponentFamilyStateKeyV2<FamilyKey>>({
|
||||
key,
|
||||
default: defaultValue,
|
||||
effects,
|
||||
}),
|
||||
};
|
||||
} satisfies ComponentFamilyStateV2<ValueType, FamilyKey>;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ComponentInstanceStateContext } from '@/ui/utilities/state/component-state/types/ComponentInstanceStateContext';
|
||||
import { ComponentStateKeyV2 } from '@/ui/utilities/state/component-state/types/ComponentStateKeyV2';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const createComponentInstanceContext = <
|
||||
T extends ComponentStateKeyV2 = ComponentStateKeyV2,
|
||||
>(
|
||||
initialValue?: T,
|
||||
) => {
|
||||
return createContext<T | null>(
|
||||
initialValue ?? null,
|
||||
) as ComponentInstanceStateContext<T>;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
import { SelectorGetter } from '@/ui/utilities/state/types/SelectorGetter';
|
||||
|
||||
export const createComponentReadOnlySelector = <ValueType>({
|
||||
@@ -8,9 +8,9 @@ export const createComponentReadOnlySelector = <ValueType>({
|
||||
get,
|
||||
}: {
|
||||
key: string;
|
||||
get: SelectorGetter<ValueType, ComponentStateKey>;
|
||||
get: SelectorGetter<ValueType, RecoilComponentStateKey>;
|
||||
}) => {
|
||||
return selectorFamily<ValueType, ComponentStateKey>({
|
||||
return selectorFamily<ValueType, RecoilComponentStateKey>({
|
||||
key,
|
||||
get,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
|
||||
import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey';
|
||||
import { SelectorGetter } from '@/ui/utilities/state/types/SelectorGetter';
|
||||
import { SelectorSetter } from '@/ui/utilities/state/types/SelectorSetter';
|
||||
|
||||
@@ -10,10 +10,10 @@ export const createComponentSelector = <ValueType>({
|
||||
set,
|
||||
}: {
|
||||
key: string;
|
||||
get: SelectorGetter<ValueType, ComponentStateKey>;
|
||||
set: SelectorSetter<ValueType, ComponentStateKey>;
|
||||
get: SelectorGetter<ValueType, RecoilComponentStateKey>;
|
||||
set: SelectorSetter<ValueType, RecoilComponentStateKey>;
|
||||
}) => {
|
||||
return selectorFamily<ValueType, ComponentStateKey>({
|
||||
return selectorFamily<ValueType, RecoilComponentStateKey>({
|
||||
key,
|
||||
get,
|
||||
set,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user