Skip to content

Commit d7aec71

Browse files
authored
Sidebar files (codesandbox#3405)
* add new explorer skeleton * some progress? * create Link variant muted * use inter for redesigned sidebars * Finish dependencies ❇️ * add type to button * add external resources * sort google fonts * add muted stories * copy over directoryentry to new screens * big commits are a bad idea but here we are * clean up structure a tiny bit * error can be multiline * replace folder icon * ugh make ts happy maybe?
1 parent 453d89b commit d7aec71

File tree

31 files changed

+2972
-8
lines changed

31 files changed

+2972
-8
lines changed

packages/app/src/app/pages/Sandbox/Editor/Workspace/index.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ConfigurationFiles } from './items/ConfigurationFiles';
1919
import { ConfigurationFiles as ConfigurationFilesNew } from './screens/ConfigurationFiles';
2020
import { Deployment } from './items/Deployment';
2121
import { FilesItem } from './items/Files';
22+
import { Explorer } from './screens/Explorer';
2223
import { GitHub } from './items/GitHub';
2324
import { Live } from './items/Live';
2425
import { Live as LiveNew } from './screens/Live';
@@ -38,7 +39,7 @@ const WorkspaceWrapper = REDESIGNED_SIDEBAR ? ThemeProvider : React.Fragment;
3839
const workspaceTabs = {
3940
project: REDESIGNED_SIDEBAR ? ProjectInfoNew : ProjectInfo,
4041
'project-summary': NotOwnedSandboxInfo,
41-
files: FilesItem,
42+
files: REDESIGNED_SIDEBAR ? Explorer : FilesItem,
4243
github: GitHub,
4344
deploy: REDESIGNED_SIDEBAR ? DeploymentNew : Deployment,
4445
config: REDESIGNED_SIDEBAR ? ConfigurationFilesNew : ConfigurationFiles,
@@ -76,7 +77,13 @@ export const WorkspaceComponent = ({ theme }) => {
7677
<ItemTitle>{item.name}</ItemTitle>
7778
)}
7879

79-
<div style={{ flex: 1, overflowY: 'auto' }}>
80+
<div
81+
style={{
82+
flex: 1,
83+
overflowY: 'auto',
84+
fontFamily: 'Inter, Roboto, sans-serif',
85+
}}
86+
>
8087
<WorkspaceWrapper theme={theme.vscodeTheme}>
8188
<Component />
8289
</WorkspaceWrapper>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { Stack, Text } from '@codesandbox/components';
3+
4+
const formatSize = (value: number) => {
5+
let unit: string;
6+
let size: number;
7+
if (Math.log10(value) < 3) {
8+
unit = 'B';
9+
size = value;
10+
} else if (Math.log10(value) < 6) {
11+
unit = 'kB';
12+
size = value / 1024;
13+
} else {
14+
unit = 'mB';
15+
size = value / 1024 / 1024;
16+
}
17+
18+
return `${size.toFixed(1)}${unit}`;
19+
};
20+
21+
type Props = {
22+
dependency: string;
23+
version: string;
24+
};
25+
26+
export const BundleSizes = ({ dependency, version = '' }: Props) => {
27+
const [size, setSize] = useState(null);
28+
const [error, setError] = useState(null);
29+
30+
useEffect(() => {
31+
const cleanVersion = version.split('^');
32+
getSizeForPKG(`${dependency}@${cleanVersion[cleanVersion.length - 1]}`);
33+
}, [dependency, version]);
34+
35+
const getSizeForPKG = (pkg: string) => {
36+
fetch(`https://bundlephobia.com/api/size?package=${pkg}`)
37+
.then(rsp => rsp.json())
38+
.then(setSize)
39+
.catch(setError);
40+
};
41+
42+
if (error) {
43+
return (
44+
<Text variant="muted">
45+
There was a problem getting the size for {dependency}
46+
</Text>
47+
);
48+
}
49+
50+
return size ? (
51+
<Stack justify="space-between" css={{ width: '100%' }}>
52+
<Text>
53+
<Text variant="muted">Gzip:</Text> {formatSize(size.gzip)}
54+
</Text>
55+
<Text>
56+
<Text variant="muted">Size:</Text> {formatSize(size.size)}
57+
</Text>
58+
</Stack>
59+
) : null;
60+
};
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import React from 'react';
2+
import CrossIcon from 'react-icons/lib/md/clear';
3+
import RefreshIcon from 'react-icons/lib/md/refresh';
4+
import ArrowDropDown from 'react-icons/lib/md/keyboard-arrow-down';
5+
import ArrowDropUp from 'react-icons/lib/md/keyboard-arrow-up';
6+
import algoliasearch from 'algoliasearch/lite';
7+
import compareVersions from 'compare-versions';
8+
import Tooltip, {
9+
SingletonTooltip,
10+
} from '@codesandbox/common/lib/components/Tooltip';
11+
import { formatVersion } from '@codesandbox/common/lib/utils/ci';
12+
13+
import css from '@styled-system/css';
14+
import {
15+
ListAction,
16+
Stack,
17+
SidebarRow,
18+
Select,
19+
Text,
20+
Link,
21+
Button,
22+
} from '@codesandbox/components';
23+
24+
import { BundleSizes } from './BundleSizes';
25+
26+
interface Props {
27+
dependencies: { [dep: string]: string };
28+
dependency: string;
29+
onRemove: (dep: string) => void;
30+
onRefresh: (dep: string, version?: string) => void;
31+
}
32+
33+
interface State {
34+
hovering: boolean;
35+
version: null | string;
36+
open: boolean;
37+
versions: string[];
38+
}
39+
40+
export class Dependency extends React.PureComponent<Props, State> {
41+
state: State = {
42+
hovering: false,
43+
version: null,
44+
open: false,
45+
versions: [],
46+
};
47+
48+
setVersionsForLatestPkg(pkg: string) {
49+
const that = this;
50+
fetch(`/api/v1/dependencies/${pkg}`)
51+
.then(response => response.json())
52+
.then(data => that.setState({ version: data.data.version }))
53+
.catch(err => {
54+
if (process.env.NODE_ENV === 'development') {
55+
console.error(err); // eslint-disable-line no-console
56+
}
57+
});
58+
}
59+
60+
UNSAFE_componentWillMount() {
61+
const { dependencies, dependency } = this.props;
62+
const client = algoliasearch(
63+
'OFCNCOG2CU',
64+
'00383ecd8441ead30b1b0ff981c426f5'
65+
);
66+
const index = client.initIndex('npm-search');
67+
68+
// @ts-ignore
69+
index.getObject(dependency, ['versions']).then(({ versions: results }) => {
70+
const versions = Object.keys(results).sort((a, b) => {
71+
try {
72+
return compareVersions(b, a);
73+
} catch (e) {
74+
return 0;
75+
}
76+
});
77+
this.setState({
78+
versions,
79+
});
80+
});
81+
82+
try {
83+
const versionRegex = /^\d{1,3}\.\d{1,3}.\d{1,3}$/;
84+
const version = dependencies[dependency];
85+
if (!versionRegex.test(version)) {
86+
this.setVersionsForLatestPkg(`${dependency}@${version}`);
87+
}
88+
} catch (e) {
89+
console.error(e);
90+
}
91+
}
92+
93+
handleRemove = e => {
94+
if (e) {
95+
e.preventDefault();
96+
e.stopPropagation();
97+
}
98+
this.props.onRemove(this.props.dependency);
99+
};
100+
101+
handleRefresh = e => {
102+
if (e) {
103+
e.preventDefault();
104+
e.stopPropagation();
105+
}
106+
this.props.onRefresh(this.props.dependency);
107+
};
108+
109+
onMouseEnter = () => this.setState({ hovering: true });
110+
111+
onMouseLeave = () => this.setState({ hovering: false });
112+
113+
handleOpen = () => this.setState(({ open }) => ({ open: !open }));
114+
115+
render() {
116+
const { dependencies, dependency } = this.props;
117+
118+
if (typeof dependencies[dependency] !== 'string') {
119+
return null;
120+
}
121+
122+
const { hovering, version, open, versions } = this.state;
123+
return (
124+
<>
125+
<ListAction
126+
justify="space-between"
127+
align="center"
128+
onMouseEnter={this.onMouseEnter}
129+
onMouseLeave={this.onMouseLeave}
130+
css={css({
131+
position: 'relative',
132+
'.actions': { backgroundColor: 'sideBar.background' },
133+
':hover .actions': { backgroundColor: 'sideBar.hoverBackground' },
134+
})}
135+
>
136+
<Link
137+
href={`https://www.npmjs.com/package/${dependency}`}
138+
target="_blank"
139+
css={{ position: 'absolute' }}
140+
>
141+
{dependency}
142+
</Link>
143+
144+
{!hovering && (
145+
<Stack
146+
align="center"
147+
justify="flex-end"
148+
css={css({ position: 'absolute', right: 2 })}
149+
>
150+
<Text
151+
variant="muted"
152+
css={{ display: hovering ? 'none' : 'block' }}
153+
>
154+
{formatVersion(dependencies[dependency])}{' '}
155+
{version && <span>({formatVersion(version)})</span>}
156+
</Text>
157+
</Stack>
158+
)}
159+
160+
<Stack
161+
className="actions"
162+
align="center"
163+
justify="flex-end"
164+
css={css({
165+
position: 'absolute',
166+
right: 0,
167+
width: '160px',
168+
})}
169+
>
170+
{hovering && (
171+
<>
172+
<Select
173+
css={{ width: '80px' }}
174+
defaultValue={versions.find(
175+
v => v === dependencies[dependency]
176+
)}
177+
onChange={e => {
178+
this.props.onRefresh(dependency, e.target.value);
179+
this.setState({ hovering: false });
180+
}}
181+
>
182+
{versions.map(a => (
183+
<option key={a}>{a}</option>
184+
))}
185+
</Select>
186+
187+
<SingletonTooltip>
188+
{singleton => (
189+
<>
190+
<Tooltip
191+
content={open ? 'Hide sizes' : 'Show sizes'}
192+
style={{ outline: 'none' }}
193+
singleton={singleton}
194+
>
195+
<Button variant="link" onClick={this.handleOpen}>
196+
{open ? <ArrowDropUp /> : <ArrowDropDown />}
197+
</Button>
198+
</Tooltip>
199+
<Tooltip
200+
content="Update to latest"
201+
style={{ outline: 'none' }}
202+
singleton={singleton}
203+
>
204+
<Button variant="link" onClick={this.handleRefresh}>
205+
<RefreshIcon />
206+
</Button>
207+
</Tooltip>
208+
<Tooltip
209+
content="Remove"
210+
style={{ outline: 'none' }}
211+
singleton={singleton}
212+
>
213+
<Button variant="link" onClick={this.handleRemove}>
214+
<CrossIcon />
215+
</Button>
216+
</Tooltip>
217+
</>
218+
)}
219+
</SingletonTooltip>
220+
</>
221+
)}
222+
</Stack>
223+
</ListAction>
224+
{open ? (
225+
<SidebarRow marginX={2}>
226+
<BundleSizes
227+
dependency={dependency}
228+
version={dependencies[dependency]}
229+
/>
230+
</SidebarRow>
231+
) : null}
232+
</>
233+
);
234+
}
235+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React, { FunctionComponent } from 'react';
2+
import { useOvermind } from 'app/overmind';
3+
4+
import {
5+
Collapsible,
6+
Text,
7+
SidebarRow,
8+
List,
9+
Button,
10+
} from '@codesandbox/components';
11+
import { Dependency } from './Dependency';
12+
13+
export const Dependencies: FunctionComponent = () => {
14+
const {
15+
actions: {
16+
modalOpened,
17+
editor: { addNpmDependency, npmDependencyRemoved },
18+
},
19+
state: {
20+
editor: { parsedConfigurations },
21+
},
22+
} = useOvermind();
23+
24+
if (!parsedConfigurations?.package) {
25+
return (
26+
<SidebarRow marginX={2}>
27+
<Text variant="danger">Unable to find package.json</Text>
28+
</SidebarRow>
29+
);
30+
}
31+
32+
const { error, parsed } = parsedConfigurations.package;
33+
34+
if (error) {
35+
return (
36+
<SidebarRow marginX={2}>
37+
<Text variant="danger">We were not able to parse the package.json</Text>
38+
</SidebarRow>
39+
);
40+
}
41+
42+
const { dependencies = {} } = parsed;
43+
44+
return (
45+
<Collapsible title="Dependencies">
46+
<List marginBottom={2}>
47+
{Object.keys(dependencies)
48+
.sort()
49+
.map(dependency => (
50+
<Dependency
51+
dependencies={dependencies}
52+
dependency={dependency}
53+
key={dependency}
54+
onRefresh={(name, version) => addNpmDependency({ name, version })}
55+
onRemove={name => npmDependencyRemoved({ name })}
56+
/>
57+
))}
58+
</List>
59+
<SidebarRow marginX={2}>
60+
<Button
61+
variant="secondary"
62+
onClick={() => modalOpened({ modal: 'searchDependencies' })}
63+
>
64+
Add dependency
65+
</Button>
66+
</SidebarRow>
67+
</Collapsible>
68+
);
69+
};

0 commit comments

Comments
 (0)