Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import { createFetcherSubmitHook } from '~/utils/router';

import type { Route } from './+types/organization.$organizationId.project.$projectId.workspace.delete';

async function deleteWorkspaceFromCloud(workspace: Workspace, project: Project) {
async function deleteCloudSyncWorkspace(workspace: Workspace, project: Project, localOnly: boolean) {
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspace._id);
const isGitSync = !!workspaceMeta.gitRepositoryId;

if (isRemoteProject(project) && !isGitSync) {
try {
const vcs = VCSInstance();
await vcs.switchAndCreateBackendProjectIfNotExist(workspace._id, workspace.name);
await vcs.archiveProject();
// For cloud sync workspaces, delete only local file or also delete remote copy
await (localOnly ? vcs.removeBackendProjectsForRoot(workspace._id) : vcs.archiveProject());
} catch (err) {
return {
error:
Expand All @@ -37,11 +38,11 @@ async function deleteWorkspaceFromLocal(workspace: Workspace) {
await models.workspace.remove(workspace);
}

async function deleteWorkspace(workspace: Workspace | null, project: Project | null) {
async function deleteWorkspace(workspace: Workspace | null, project: Project | null, localOnly: boolean) {
invariant(workspace, 'Workspace not found');
invariant(project, 'Project not found');

const ret = await deleteWorkspaceFromCloud(workspace, project);
const ret = await deleteCloudSyncWorkspace(workspace, project, localOnly);
if (ret?.error) {
return ret;
}
Expand All @@ -65,12 +66,13 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
const formData = await request.formData();

const workspaceId = formData.get('workspaceId');
const localOnly = formData.get('localOnly') === 'true';
invariant(typeof workspaceId === 'string', 'Workspace ID is required');

const workspace = await models.workspace.getById(workspaceId);
invariant(workspace, 'Workspace not found');

const msgObj = await deleteWorkspace(workspace, project);
const msgObj = await deleteWorkspace(workspace, project, localOnly);

if (msgObj?.error) {
return msgObj;
Expand All @@ -90,10 +92,13 @@ export const useWorkspaceDeleteActionFetcher = createFetcherSubmitHook(
organizationId,
projectId,
workspaceId,
// for cloud sync workspaces, delete only local file or also delete remote copy
localOnly = 'true',
}: {
organizationId: string;
projectId: string;
workspaceId: string;
localOnly?: 'true' | 'false';
}) => {
const url = href('/organization/:organizationId/project/:projectId/workspace/delete', {
organizationId,
Expand All @@ -102,6 +107,7 @@ export const useWorkspaceDeleteActionFetcher = createFetcherSubmitHook(

const formData = new FormData();
formData.append('workspaceId', workspaceId);
formData.append('localOnly', localOnly);

return submit(formData, {
action: url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
exportMockServerToFile,
} from 'insomnia/src/ui/components/settings/import-export';
import React, { type FC, Fragment, useCallback, useState } from 'react';
import { Button, Dialog, Heading, Modal, ModalOverlay } from 'react-aria-components';
import { Button, Dialog, Heading, Label, Modal, ModalOverlay, Radio, RadioGroup } from 'react-aria-components';
import { href, useParams } from 'react-router';

import { useWorkspaceDeleteActionFetcher } from '~/routes/organization.$organizationId.project.$projectId.workspace.delete';
Expand Down Expand Up @@ -282,7 +282,36 @@ export const WorkspaceCardDropdown: FC<Props> = props => {
<p>
This will permanently delete the{' '}
{<strong style={{ whiteSpace: 'pre-wrap' }}>{workspace?.name}</strong>}{' '}
{getWorkspaceLabel(workspace).singular} {isRemoteProject(project) ? 'remotely' : ''}.
{getWorkspaceLabel(workspace).singular}
{isRemoteProject(project) && (
<RadioGroup name="localOnly" defaultValue="false" className="mb-2 flex flex-col gap-2">
<Label className="text-sm text-(--hl)">How do you want to delete it?</Label>
<div className="flex gap-2">
<Radio
value="true"
className="flex-1 rounded-sm border border-solid border-(--hl-md) p-4 transition-colors hover:bg-(--hl-xs) focus:bg-(--hl-sm) focus:outline-hidden data-disabled:opacity-25 data-selected:border-(--color-surprise) data-selected:ring-2 data-selected:ring-(--color-surprise)"
>
<div className="flex items-center gap-2">
<Heading className="text-lg font-bold">Remove Local Copy</Heading>
</div>
<p className="pt-2">The project will still exist on the Cloud.</p>
</Radio>
<Radio
value="false"
className="flex-1 rounded-sm border border-solid border-(--hl-md) p-4 transition-colors hover:bg-(--hl-xs) focus:bg-(--hl-sm) focus:outline-hidden data-disabled:opacity-25 data-selected:border-(--color-surprise) data-selected:ring-2 data-selected:ring-(--color-surprise)"
>
<div className="flex items-center gap-2">
<Heading className="text-lg font-bold">
<span>Delete Permanently</span>
</Heading>
</div>
<p className="pt-2">
The project will be deleted everywhere. You cannot undo this action.
</p>
</Radio>
</div>
</RadioGroup>
)}
</p>
{deleteWorkspaceFetcher.data && deleteWorkspaceFetcher.data.error && (
<p className="notice error margin-bottom-sm no-margin-top">{deleteWorkspaceFetcher.data.error}</p>
Expand Down
Loading