Delete view & copy link (#10760)
adding the delete view and copy link additional menu option Fixes https://github.com/twentyhq/core-team-issues/issues/480 Fixes https://github.com/twentyhq/core-team-issues/issues/481
This commit is contained in:
@ -1,10 +1,12 @@
|
|||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
import {
|
import {
|
||||||
AppTooltip,
|
AppTooltip,
|
||||||
|
IconCopy,
|
||||||
IconLayout,
|
IconLayout,
|
||||||
IconLayoutList,
|
IconLayoutList,
|
||||||
IconList,
|
IconList,
|
||||||
IconTag,
|
IconTag,
|
||||||
|
IconTrash,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
useIcons,
|
useIcons,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
@ -13,14 +15,20 @@ import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdow
|
|||||||
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
import { useOptionsDropdown } from '@/object-record/object-options-dropdown/hooks/useOptionsDropdown';
|
||||||
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||||
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
|
||||||
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||||
import { ViewType } from '@/views/types/ViewType';
|
import { ViewType } from '@/views/types/ViewType';
|
||||||
|
import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDeleteViewFromCurrentState';
|
||||||
|
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
@ -66,6 +74,21 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
|||||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { deleteViewFromCurrentState } = useDeleteViewFromCurrentState();
|
||||||
|
const setViewPickerReferenceViewId = useSetRecoilComponentStateV2(
|
||||||
|
viewPickerReferenceViewIdComponentState,
|
||||||
|
);
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (!currentView?.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setViewPickerReferenceViewId(currentView?.id);
|
||||||
|
deleteViewFromCurrentState();
|
||||||
|
closeDropdown();
|
||||||
|
};
|
||||||
|
const theme = useTheme();
|
||||||
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuHeader StartIcon={CurrentViewIcon ?? IconList}>
|
<DropdownMenuHeader StartIcon={CurrentViewIcon ?? IconList}>
|
||||||
@ -122,6 +145,39 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
|||||||
width="100%"
|
width="100%"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
navigator.clipboard.writeText(currentUrl);
|
||||||
|
enqueueSnackBar('Link copied to clipboard', {
|
||||||
|
variant: SnackBarVariant.Success,
|
||||||
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
LeftIcon={IconCopy}
|
||||||
|
text={t`Copy link to view`}
|
||||||
|
/>
|
||||||
|
<div id="delete-view-menu-item">
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => handleDelete()}
|
||||||
|
LeftIcon={IconTrash}
|
||||||
|
text={t`Delete view`}
|
||||||
|
disabled={currentView?.key === 'INDEX'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{currentView?.key === 'INDEX' && (
|
||||||
|
<AppTooltip
|
||||||
|
// eslint-disable-next-line
|
||||||
|
anchorSelect={`#delete-view-menu-item`}
|
||||||
|
content={t`Not available on Default View`}
|
||||||
|
noArrow
|
||||||
|
place="bottom"
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { DeleteManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import {
|
||||||
|
ViewException,
|
||||||
|
ViewExceptionCode,
|
||||||
|
ViewExceptionMessage,
|
||||||
|
} from 'src/modules/view/views.exception';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`view.deleteMany`)
|
||||||
|
export class ViewDeleteManyPreQueryHook implements WorkspaceQueryHookInstance {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
_authContext: AuthContext,
|
||||||
|
_objectName: string,
|
||||||
|
_payload: DeleteManyResolverArgs,
|
||||||
|
): Promise<DeleteManyResolverArgs> {
|
||||||
|
throw new ViewException(
|
||||||
|
ViewExceptionMessage.METHOD_NOT_IMPLEMENTED,
|
||||||
|
ViewExceptionCode.METHOD_NOT_IMPLEMENTED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import {
|
||||||
|
ViewException,
|
||||||
|
ViewExceptionCode,
|
||||||
|
ViewExceptionMessage,
|
||||||
|
} from 'src/modules/view/views.exception';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`view.deleteOne`)
|
||||||
|
export class ViewDeleteOnePreQueryHook implements WorkspaceQueryHookInstance {
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
authContext: AuthContext,
|
||||||
|
_objectName: string,
|
||||||
|
payload: DeleteOneResolverArgs,
|
||||||
|
): Promise<DeleteOneResolverArgs> {
|
||||||
|
const targettedViewId = payload.id;
|
||||||
|
|
||||||
|
const viewRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
authContext.workspace.id,
|
||||||
|
'view',
|
||||||
|
);
|
||||||
|
|
||||||
|
const view = await viewRepository.findOne({
|
||||||
|
where: { id: targettedViewId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!view) {
|
||||||
|
throw new ViewException(
|
||||||
|
ViewExceptionMessage.VIEW_NOT_FOUND,
|
||||||
|
ViewExceptionCode.VIEW_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (view.key === 'INDEX') {
|
||||||
|
throw new ViewException(
|
||||||
|
ViewExceptionMessage.CANNOT_DELETE_INDEX_VIEW,
|
||||||
|
ViewExceptionCode.CANNOT_DELETE_INDEX_VIEW,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { ViewService } from 'src/modules/view/services/view.service';
|
import { ViewService } from 'src/modules/view/services/view.service';
|
||||||
|
|
||||||
|
import { ViewDeleteOnePreQueryHook } from './pre-hooks.ts/view-delete-one.pre-query.hook';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [],
|
||||||
providers: [ViewService],
|
providers: [ViewService, ViewDeleteOnePreQueryHook],
|
||||||
exports: [ViewService],
|
exports: [ViewService],
|
||||||
})
|
})
|
||||||
export class ViewModule {}
|
export class ViewModule {}
|
||||||
|
|||||||
19
packages/twenty-server/src/modules/view/views.exception.ts
Normal file
19
packages/twenty-server/src/modules/view/views.exception.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
|
||||||
|
export class ViewException extends CustomException {
|
||||||
|
constructor(message: string, code: ViewExceptionCode) {
|
||||||
|
super(message, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ViewExceptionCode {
|
||||||
|
VIEW_NOT_FOUND = 'VIEW_NOT_FOUND',
|
||||||
|
CANNOT_DELETE_INDEX_VIEW = 'CANNOT_DELETE_INDEX_VIEW',
|
||||||
|
METHOD_NOT_IMPLEMENTED = 'METHOD_NOT_IMPLEMENTED',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ViewExceptionMessage {
|
||||||
|
VIEW_NOT_FOUND = 'View not found',
|
||||||
|
CANNOT_DELETE_INDEX_VIEW = 'Cannot delete index view',
|
||||||
|
METHOD_NOT_IMPLEMENTED = 'Method not implemented',
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user