Skip to content

Commit

Permalink
Allow projects to be toggled local/remote (#6667)
Browse files Browse the repository at this point in the history
* update project settings to handle remote to local and around

* fix styles and remove unused file

* add comments and early returns

* project export rename and name fix

* handle auth errors

* add optional chain

* fix test

---------

Co-authored-by: jackkav <[email protected]>
  • Loading branch information
gatzjames and jackkav authored Oct 10, 2023
1 parent b98d193 commit d006bb0
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ test.describe('Dashboard', async () => {

// Delete project
await page.getByRole('row', { name: 'My Project' }).getByRole('button', { name: 'Project Actions' }).click();
await page.getByRole('menuitemradio', { name: 'Settings' }).click();

await page.getByRole('button', { name: 'Delete' }).click();
await page.getByRole('button', { name: 'Click to confirm' }).click();
await page.getByRole('menuitemradio', { name: 'Delete' }).click();

// After deleting project, return to default Insomnia Dashboard
await expect(page.locator('.app')).toContainText('Personal Workspace');
Expand Down
8 changes: 5 additions & 3 deletions packages/insomnia/src/account/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ export function getCurrentSessionId() {
try {
const { sessionExpiry } = JSON.parse(window.localStorage.getItem(_getSessionKey(sessionId)) || '{}');
if (typeof sessionExpiry !== 'string' || !sessionExpiry) {
console.log('No session expiry found', sessionExpiry);
return '';
}

Expand Down Expand Up @@ -257,13 +256,16 @@ function _getSymmetricKey() {
}

async function _whoami(sessionId: string | null = null): Promise<WhoamiResponse> {
const response = await window.main.insomniaFetch<WhoamiResponse>({
const response = await window.main.insomniaFetch<WhoamiResponse | string>({
method: 'GET',
path: '/auth/whoami',
sessionId: sessionId || getCurrentSessionId(),
});
if (typeof response === 'string') {
throw new Error('Unexpected plaintext response');
throw new Error('Unexpected plaintext response: ' + response);
}
if (response && !response?.encSymmetricKey) {
throw new Error('Unexpected response: ' + JSON.stringify(response));
}
return response;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/insomnia/src/common/export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ const writeExportedFileToFileSystem = (filename: string, jsonData: string, onDon
fs.writeFile(filename, jsonData, {}, onDone);
};

export const exportAllToFile = (activeProjectName: string, workspacesForActiveProject: Workspace[]) => {
export const exportProjectToFile = (activeProjectName: string, workspacesForActiveProject: Workspace[]) => {
if (!workspacesForActiveProject.length) {
showAlert({
title: 'Cannot export',
Expand Down
101 changes: 94 additions & 7 deletions packages/insomnia/src/ui/components/dropdowns/project-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@ import { IconName } from '@fortawesome/fontawesome-svg-core';
import React, { FC, Fragment, useEffect, useState } from 'react';
import {
Button,
Dialog,
Heading,
Input,
Item,
Label,
Menu,
MenuTrigger,
Modal,
ModalOverlay,
Popover,
Radio,
RadioGroup,
TextField,
} from 'react-aria-components';
import { useFetcher } from 'react-router-dom';

Expand All @@ -15,7 +24,6 @@ import {
} from '../../../models/project';
import { Icon } from '../icon';
import { showAlert } from '../modals';
import ProjectSettingsModal from '../modals/project-settings-modal';

interface Props {
project: Project;
Expand All @@ -33,6 +41,7 @@ export const ProjectDropdown: FC<Props> = ({ project, organizationId }) => {
const [isProjectSettingsModalOpen, setIsProjectSettingsModalOpen] =
useState(false);
const deleteProjectFetcher = useFetcher();
const updateProjectFetcher = useFetcher();

const projectActionList: ProjectActionItem[] = [
{
Expand Down Expand Up @@ -98,12 +107,90 @@ export const ProjectDropdown: FC<Props> = ({ project, organizationId }) => {
</Menu>
</Popover>
</MenuTrigger>
{isProjectSettingsModalOpen && (
<ProjectSettingsModal
onHide={() => setIsProjectSettingsModalOpen(false)}
project={project}
/>
)}
<ModalOverlay isOpen={isProjectSettingsModalOpen} onOpenChange={setIsProjectSettingsModalOpen} isDismissable className="w-full h-[--visual-viewport-height] fixed z-10 top-0 left-0 flex items-center justify-center bg-black/30">
<Modal className="max-w-2xl w-full rounded-md border border-solid border-[--hl-sm] p-[--padding-lg] max-h-full bg-[--color-bg] text-[--color-font]">
<Dialog onClose={() => setIsProjectSettingsModalOpen(false)} className="outline-none">
{({ close }) => (
<div className='flex flex-col gap-4'>
<div className='flex gap-2 items-center justify-between'>
<Heading className='text-2xl'>Project Settings</Heading>
<Button
className="flex flex-shrink-0 items-center justify-center aspect-square h-6 aria-pressed:bg-[--hl-sm] rounded-sm text-[--color-font] hover:bg-[--hl-xs] focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-all text-sm"
onPress={close}
>
<Icon icon="x" />
</Button>
</div>
{isDefaultOrganizationProject(project) && <p>
<Icon icon="info-circle" /> This is the default project for your organization. You can not delete it or change its type.
</p>}
<form
className='flex flex-col gap-4'
onSubmit={e => {
updateProjectFetcher.submit(e.currentTarget, {
action: `/organization/${organizationId}/project/${project._id}/update`,
method: 'post',
});

close();
}}
>
<TextField
autoFocus
name="name"
defaultValue={project.name}
className="group relative flex-1 flex flex-col gap-2"
>
<Label className='text-sm text-[--hl]'>
Project name
</Label>
<Input
placeholder="My project"
className="py-1 placeholder:italic w-full pl-2 pr-7 rounded-sm border border-solid border-[--hl-sm] bg-[--color-bg] text-[--color-font] focus:outline-none focus:ring-1 focus:ring-[--hl-md] transition-colors"
/>
</TextField>
<RadioGroup name="type" defaultValue={project.remoteId ? 'remote' : 'local'} className="flex flex-col gap-2">
<Label className="text-sm text-[--hl]">
Project type
</Label>
<div className="flex gap-2">
<Radio
value="remote"
className="data-[selected]:border-[--color-surprise] data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
>
<Icon icon="globe" />
<Heading className="text-lg font-bold">Secure Cloud</Heading>
<p className='pt-2'>
End-to-end encrypted (E2EE) and synced securely to the cloud, ideal for collaboration.
</p>
</Radio>
<Radio
isDisabled={isDefaultOrganizationProject(project)}
value="local"
className="data-[selected]:border-[--color-surprise] data-[disabled]:opacity-25 data-[selected]:ring-2 data-[selected]:ring-[--color-surprise] hover:bg-[--hl-xs] focus:bg-[--hl-sm] border border-solid border-[--hl-md] rounded p-4 focus:outline-none transition-colors"
>
<Icon icon="laptop" />
<Heading className="text-lg font-bold">Local Vault</Heading>
<p className="pt-2">
Stored locally only with no cloud. Ideal when collaboration is not needed.
</p>
</Radio>
</div>
</RadioGroup>
<div className="flex justify-end">
<Button
type="submit"
className="hover:no-underline bg-[#4000BF] hover:bg-opacity-90 border border-solid border-[--hl-md] py-2 px-3 text-[--color-font] transition-colors rounded-sm"
>
Update
</Button>
</div>
</form>
</div>
)}
</Dialog>
</Modal>
</ModalOverlay>
</Fragment>
);
};

This file was deleted.

19 changes: 10 additions & 9 deletions packages/insomnia/src/ui/components/settings/import-export.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRouteLoaderData } from 'react-router-dom';

import { isLoggedIn } from '../../../account/session';
import { getProductName } from '../../../common/constants';
import { exportAllToFile } from '../../../common/export';
import { exportProjectToFile } from '../../../common/export';
import { exportAllData } from '../../../common/export-all-data';
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
import { strings } from '../../../common/strings';
Expand Down Expand Up @@ -48,7 +48,6 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {

const workspaceData = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData | undefined;
const activeWorkspaceName = workspaceData?.activeWorkspace.name;
const projectName = workspaceData?.activeProject.name ?? getProductName();
const { workspaceCount } = useRootLoaderData();
const workspacesFetcher = useFetcher();
useEffect(() => {
Expand All @@ -57,13 +56,15 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
workspacesFetcher.load(`/organization/${organizationId}/project/${projectId}`);
}
}, [organizationId, projectId, workspacesFetcher]);
const projectLoaderData = workspacesFetcher?.data as ProjectLoaderData;
const projectLoaderData = workspacesFetcher?.data as ProjectLoaderData | undefined;
const workspacesForActiveProject = projectLoaderData?.workspaces.map(w => w.workspace) || [];
const projectName = projectLoaderData?.activeProject.name ?? getProductName();

const [isImportModalOpen, setIsImportModalOpen] = useState(false);
const [isExportModalOpen, setIsExportModalOpen] = useState(false);

const handleExportAllToFile = () => {
exportAllToFile(projectName, workspacesForActiveProject);
const handleExportProjectToFile = () => {
exportProjectToFile(projectName, workspacesForActiveProject);
hideSettingsModal();
};

Expand All @@ -75,7 +76,7 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
<Fragment>
<div data-testid="import-export-tab" className='flex flex-col gap-4'>
<div className='rounded-md border border-solid border-[--hl-md] p-4 flex flex-col gap-2'>
<Heading className='text-lg font-bold flex items-center gap-2'><Icon icon="file-export" /> Export:</Heading>
<Heading className='text-lg font-bold flex items-center gap-2'><Icon icon="file-export" /> Export</Heading>
<div className="flex gap-2 flex-wrap">
{workspaceData?.activeWorkspace ?
isScratchpad(workspaceData.activeWorkspace) ?
Expand Down Expand Up @@ -105,15 +106,15 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
<ItemContent
icon="empty"
label={`Export files from the "${projectName}" ${strings.project.singular}`}
onClick={handleExportAllToFile}
onClick={handleExportProjectToFile}
/>
</DropdownItem>
</DropdownSection>
</Dropdown>
) : (
<Button
className="px-4 py-1 font-semibold border border-solid border-[--hl-md] flex items-center justify-center gap-2 aria-pressed:bg-[--hl-sm] rounded-sm text-[--color-font] hover:bg-[--hl-xs] focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-all text-sm"
onPress={handleExportAllToFile}
onPress={handleExportProjectToFile}
>
{`Export files from the "${projectName}" ${strings.project.singular}`}
</Button>
Expand Down Expand Up @@ -171,7 +172,7 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
</div>
</div>
<div className='rounded-md border border-solid border-[--hl-md] p-4 flex flex-col gap-2'>
<Heading className='text-lg font-bold flex items-center gap-2'><Icon icon="file-import" /> Import:</Heading>
<Heading className='text-lg font-bold flex items-center gap-2'><Icon icon="file-import" /> Import</Heading>
<div className="flex gap-2 flex-wrap">
<Button
className="px-4 py-1 font-semibold border border-solid border-[--hl-md] flex items-center justify-center gap-2 aria-pressed:bg-[--hl-sm] rounded-sm text-[--color-font] hover:bg-[--hl-xs] focus:ring-inset ring-1 ring-transparent focus:ring-[--hl-md] transition-all text-sm"
Expand Down
4 changes: 3 additions & 1 deletion packages/insomnia/src/ui/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ html {
input[type="search"]::-webkit-search-cancel-button {
display: none;
}

.new-sidebar {
z-index: 0;
}
.new-sidebar .sidebar {
display: flex;
flex-direction: column;
Expand Down
4 changes: 2 additions & 2 deletions packages/insomnia/src/ui/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,11 +221,11 @@ const router = createMemoryRouter(
).moveProjectAction(...args),
},
{
path: 'rename',
path: 'update',
action: async (...args) =>
(
await import('./routes/actions')
).renameProjectAction(...args),
).updateProjectAction(...args),
},
{
path: 'git',
Expand Down
Loading

0 comments on commit d006bb0

Please sign in to comment.