Skip to content

Commit c2d2ea6

Browse files
authored
add skeleton for listItem sandbox (codesandbox#4004)
* add skeleton for listItem sandbox * fix ts * create sandbox component * add fallback for getPossibleTemplates
1 parent 953dd0e commit c2d2ea6

File tree

14 files changed

+315
-51
lines changed

14 files changed

+315
-51
lines changed

packages/app/src/app/overmind/namespaces/dashboard/actions.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,48 @@ export const makeTemplate: AsyncAction<string[]> = async (
531531
}
532532
};
533533

534+
export const permanentlyDeleteSandboxes: AsyncAction<string[]> = async (
535+
{ effects, state },
536+
ids
537+
) => {
538+
if (!state.dashboard.sandboxes.DELETED) return;
539+
const oldDeleted = state.dashboard.sandboxes.DELETED;
540+
state.dashboard.sandboxes.DELETED = oldDeleted.filter(
541+
sandbox => !ids.includes(sandbox.id)
542+
);
543+
try {
544+
await effects.gql.mutations.permanentlyDeleteSandboxes({ sandboxIds: ids });
545+
} catch (error) {
546+
state.dashboard.sandboxes.DELETED = [...oldDeleted];
547+
effects.notificationToast.error(
548+
'There was a problem deleting your sandbox'
549+
);
550+
}
551+
};
552+
553+
export const recoverSandboxes: AsyncAction<string[]> = async (
554+
{ effects, state },
555+
ids
556+
) => {
557+
if (!state.dashboard.sandboxes.DELETED) return;
558+
const oldDeleted = state.dashboard.sandboxes.DELETED;
559+
state.dashboard.sandboxes.DELETED = oldDeleted.filter(
560+
sandbox => !ids.includes(sandbox.id)
561+
);
562+
try {
563+
await effects.gql.mutations.addSandboxToFolder({
564+
sandboxIds: ids,
565+
collectionPath: '/',
566+
teamId: state.dashboard.activeTeam,
567+
});
568+
} catch (error) {
569+
state.dashboard.sandboxes.DELETED = [...oldDeleted];
570+
effects.notificationToast.error(
571+
'There was a problem recovering your sandbox'
572+
);
573+
}
574+
};
575+
534576
export const downloadSandboxes: AsyncAction<string[]> = async (
535577
{ effects },
536578
ids

packages/app/src/app/overmind/namespaces/dashboard/state.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type State = {
5252
selectedSandboxes: string[];
5353
trashSandboxIds: string[];
5454
isDragging: boolean;
55+
viewMode: 'grid' | 'list';
5556
orderBy: OrderBy;
5657
filters: {
5758
blacklistedTemplates: string[];
@@ -88,6 +89,7 @@ export const state: State = {
8889
ALL: null,
8990
SEARCH: null,
9091
},
92+
viewMode: 'grid',
9193
allCollections: null,
9294
activeTeam: null,
9395
teams: [],

packages/app/src/app/pages/NewDashboard/Components/Header/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ export const Header = ({ templates, path, title }: Props) => (
3131
<Breadcrumbs param={path} />
3232
)}
3333

34-
<Filters possibleTemplates={templates} />
34+
{templates && <Filters possibleTemplates={templates} />}
3535
</Stack>
3636
);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React from 'react';
2+
import { withRouter } from 'react-router-dom';
3+
import { Element } from '@codesandbox/components';
4+
import { useOvermind } from 'app/overmind';
5+
import { SandboxItem } from '../SandboxItem';
6+
import { SandboxCard } from '../SandboxCard';
7+
8+
export const SandboxComponent = props => {
9+
const {
10+
state: { dashboard },
11+
} = useOvermind();
12+
13+
if (dashboard.viewMode === 'list' || props.match.path.includes('deleted')) {
14+
return (
15+
<Element style={{ gridColumnStart: 4 }}>
16+
<SandboxItem {...props} />
17+
</Element>
18+
);
19+
}
20+
return <SandboxCard {...props} />;
21+
};
22+
23+
export const Sandbox = withRouter(SandboxComponent);
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import React from 'react';
2+
import { useOvermind } from 'app/overmind';
3+
import { Menu } from '@codesandbox/components';
4+
import { sandboxUrl } from '@codesandbox/common/lib/utils/url-generator';
5+
import { withRouter } from 'react-router-dom';
6+
7+
export const MenuOptionsComponent = ({
8+
sandbox,
9+
template,
10+
setEdit,
11+
history,
12+
}) => {
13+
const { effects, actions } = useOvermind();
14+
const url = sandboxUrl({
15+
id: sandbox.id,
16+
alias: sandbox.alias,
17+
});
18+
19+
const getFolderUrl = (path, isTemplate) => {
20+
if (isTemplate) return '/new-dashboard/templates';
21+
if (path === '/' || !path) return '/new-dashboard/all/drafts';
22+
23+
return '/new-dashboard/all' + path;
24+
};
25+
26+
if (sandbox.removedAt) {
27+
return (
28+
<Menu>
29+
<Menu.IconButton name="more" size={9} title="Sandbox options" />
30+
<Menu.List>
31+
<Menu.Item
32+
onSelect={() => {
33+
actions.dashboard.recoverSandboxes([sandbox.id]);
34+
}}
35+
>
36+
Recover Sandbox
37+
</Menu.Item>
38+
<Menu.Item
39+
onSelect={() => {
40+
actions.dashboard.permanentlyDeleteSandboxes([sandbox.id]);
41+
}}
42+
>
43+
Delete Permanently
44+
</Menu.Item>
45+
</Menu.List>
46+
</Menu>
47+
);
48+
}
49+
50+
return (
51+
<Menu>
52+
<Menu.IconButton name="more" size={9} title="Sandbox options" />
53+
<Menu.List>
54+
<Menu.Item
55+
onSelect={() => {
56+
history.push(getFolderUrl(sandbox.collection.path, template));
57+
}}
58+
>
59+
Show in Folder
60+
</Menu.Item>
61+
<Menu.Item
62+
onSelect={() => {
63+
window.open(`https://codesandbox.io${url}`);
64+
}}
65+
>
66+
Open sandbox
67+
</Menu.Item>
68+
<Menu.Item
69+
onSelect={() => {
70+
window.open(`https://codesandbox.io${url}`, '_blank');
71+
}}
72+
>
73+
Open sandbox in new tab
74+
</Menu.Item>
75+
<Menu.Item
76+
onSelect={() => {
77+
effects.browser.copyToClipboard(`https://codesandbox.io${url}`);
78+
}}
79+
>
80+
Copy sandbox link
81+
</Menu.Item>
82+
<Menu.Item
83+
onSelect={() => {
84+
actions.editor.forkExternalSandbox({
85+
sandboxId: sandbox.id,
86+
openInNewWindow: true,
87+
});
88+
}}
89+
>
90+
Fork sandbox
91+
</Menu.Item>
92+
<Menu.Item
93+
onSelect={() => {
94+
actions.dashboard.downloadSandboxes([sandbox.id]);
95+
}}
96+
>
97+
Export {template ? 'template' : 'sandbox'}
98+
</Menu.Item>
99+
<Menu.Item onSelect={() => setEdit(true)}>Rename sandbox</Menu.Item>
100+
{template ? (
101+
<Menu.Item
102+
onSelect={() => {
103+
actions.dashboard.unmakeTemplate([sandbox.id]);
104+
}}
105+
>
106+
Convert to Sandbox
107+
</Menu.Item>
108+
) : (
109+
<Menu.Item
110+
onSelect={() => {
111+
actions.dashboard.makeTemplate([sandbox.id]);
112+
}}
113+
>
114+
Make sandbox a template
115+
</Menu.Item>
116+
)}
117+
{template ? (
118+
<Menu.Item onSelect={() => {}}>Delete template</Menu.Item>
119+
) : (
120+
<Menu.Item
121+
onSelect={() => {
122+
actions.dashboard.deleteSandbox([sandbox.id]);
123+
}}
124+
>
125+
Delete sandbox
126+
</Menu.Item>
127+
)}
128+
</Menu.List>
129+
</Menu>
130+
);
131+
};
132+
133+
// @ts-ignore
134+
export const MenuOptions = withRouter(MenuOptionsComponent);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React, { useState } from 'react';
2+
import { Stack, Element, Text, Input } from '@codesandbox/components';
3+
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
4+
import css from '@styled-system/css';
5+
import { useOvermind } from 'app/overmind';
6+
import { MenuOptions } from './Menu';
7+
8+
type Props = {
9+
sandbox: any;
10+
template?: boolean;
11+
style?: any;
12+
};
13+
14+
export const SandboxItem = ({ sandbox, template, ...props }: Props) => {
15+
const sandboxTitle = sandbox.title || sandbox.alias || sandbox.id;
16+
const { actions } = useOvermind();
17+
const [edit, setEdit] = useState(false);
18+
const [newName, setNewName] = useState(sandboxTitle);
19+
20+
const editSandboxTitle = async e => {
21+
e.preventDefault();
22+
await actions.dashboard.renameSandbox({
23+
id: sandbox.id,
24+
title: newName,
25+
oldTitle: sandboxTitle,
26+
});
27+
setEdit(false);
28+
};
29+
30+
return (
31+
<Stack
32+
gap={2}
33+
align="center"
34+
paddingX={2}
35+
justify="space-between"
36+
css={css({
37+
width: '100%',
38+
height: 64,
39+
borderBottom: '1px solid',
40+
borderBottomColor: 'grays.600',
41+
overflow: 'hidden',
42+
})}
43+
{...props}
44+
>
45+
<Stack gap={4} align="center">
46+
<Element
47+
as="div"
48+
css={css({
49+
borderRadius: 'small',
50+
height: 32,
51+
width: 32,
52+
backgroundImage: `url(${sandbox.screenshotUrl})`,
53+
backgroundSize: 'cover',
54+
backgroundPosition: 'center center',
55+
backgroundRepeat: 'no-repeat',
56+
})}
57+
/>
58+
<Element style={{ width: 150 }}>
59+
{edit ? (
60+
<form onSubmit={editSandboxTitle}>
61+
<Input
62+
value={newName}
63+
onChange={e => setNewName(e.target.value)}
64+
/>
65+
</form>
66+
) : (
67+
<Text size={3} weight="medium">
68+
{sandboxTitle}
69+
</Text>
70+
)}
71+
</Element>
72+
</Stack>
73+
{sandbox.removedAt ? (
74+
<Text size={3} variant="muted" block style={{ width: 180 }}>
75+
Deleted {formatDistanceToNow(new Date(sandbox.removedAt))} ago
76+
</Text>
77+
) : (
78+
<Text size={3} variant="muted" block style={{ width: 180 }}>
79+
Updated {formatDistanceToNow(new Date(sandbox.updatedAt))} ago
80+
</Text>
81+
)}
82+
<MenuOptions sandbox={sandbox} template={template} setEdit={setEdit} />
83+
</Stack>
84+
);
85+
};

packages/app/src/app/pages/NewDashboard/Content/routes/All/index.tsx

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import React, { useEffect, useState } from 'react';
2-
import { Element, Column, Grid } from '@codesandbox/components';
2+
import { Element, Grid } from '@codesandbox/components';
33

44
import css from '@styled-system/css';
55
import { withRouter } from 'react-router-dom';
66
import { Header } from 'app/pages/NewDashboard/Components/Header';
77
import { useOvermind } from 'app/overmind';
88
import { Loading } from 'app/pages/NewDashboard/Components/Loading';
99
import { getPossibleTemplates } from '../../utils';
10-
import { SandboxCard } from '../../../Components/SandboxCard';
10+
import { Sandbox } from '../../../Components/Sandbox';
1111
import { FolderCard } from '../../../Components/FolderCard';
1212

1313
export const AllPage = ({ match: { params }, history }) => {
@@ -50,13 +50,9 @@ export const AllPage = ({ match: { params }, history }) => {
5050
collection => collection.level === level && collection.parent === param
5151
);
5252

53-
const possibleTemplates = allCollections
54-
? getPossibleTemplates(allCollections)
55-
: [];
56-
5753
return (
5854
<Element style={{ height: '100%', position: 'relative' }}>
59-
<Header path={param} templates={possibleTemplates} />
55+
<Header path={param} templates={getPossibleTemplates(allCollections)} />
6056
{allCollections ? (
6157
<Grid
6258
rowGap={6}
@@ -67,16 +63,12 @@ export const AllPage = ({ match: { params }, history }) => {
6763
})}
6864
>
6965
{getFoldersByPath.map(folder => (
70-
<Column key={folder.id}>
71-
<FolderCard {...folder} />
72-
</Column>
66+
<FolderCard key={folder.id} {...folder} />
7367
))}
7468
{sandboxes.ALL &&
7569
sandboxes.ALL[cleanParam] &&
7670
sandboxes.ALL[cleanParam].map(sandbox => (
77-
<Column key={sandbox.id}>
78-
<SandboxCard sandbox={sandbox} />
79-
</Column>
71+
<Sandbox key={sandbox.id} sandbox={sandbox} />
8072
))}
8173
</Grid>
8274
) : (

0 commit comments

Comments
 (0)