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 {
|
||||
AppTooltip,
|
||||
IconCopy,
|
||||
IconLayout,
|
||||
IconLayoutList,
|
||||
IconList,
|
||||
IconTag,
|
||||
IconTrash,
|
||||
MenuItem,
|
||||
useIcons,
|
||||
} 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 { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
|
||||
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 { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
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 { 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 { useTheme } from '@emotion/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
@ -66,6 +74,21 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
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 (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={CurrentViewIcon ?? IconList}>
|
||||
@ -122,6 +145,39 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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 { ViewDeleteOnePreQueryHook } from './pre-hooks.ts/view-delete-one.pre-query.hook';
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [ViewService],
|
||||
providers: [ViewService, ViewDeleteOnePreQueryHook],
|
||||
exports: [ViewService],
|
||||
})
|
||||
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