Skip to content

Commit d1d845c

Browse files
authored
Stackbit Import Support & Import Modal Redesign (codesandbox#2152)
* Stackbit import * Style tweaks * Update template icons * Gatsby exclusive * More exclusives * Update URL * Fix linting problems * Disable stackbit manually for now
1 parent b4937ea commit d1d845c

File tree

17 files changed

+386
-82
lines changed

17 files changed

+386
-82
lines changed

.eslintrc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
{ "aspects": ["noHref", "invalidHref"] }
5757
],
5858
"jsx-a11y/label-has-for": "off",
59-
"no-bitwise": "off"
59+
"no-bitwise": "off",
60+
"arrow-body-style": ["error", "as-needed"]
6061
},
6162
"overrides": [
6263
{
@@ -88,7 +89,7 @@
8889
],
8990
"no-unused-vars": "off",
9091
"@typescript-eslint/no-unused-vars": [
91-
"warn",
92+
"error",
9293
{ "args": "none", "ignoreRestSiblings": true }
9394
],
9495
"no-useless-constructor": "off",

packages/app/src/app/components/CodeEditor/VSCode/index.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -925,9 +925,7 @@ class MonacoEditor extends React.Component<Props> implements Editor {
925925
),
926926
options: {
927927
inlineClassName: classification.type
928-
? `${classification.kind} ${classification.type}-of-${
929-
classification.parentKind
930-
}`
928+
? `${classification.kind} ${classification.type}-of-${classification.parentKind}`
931929
: classification.kind,
932930
},
933931
}));

packages/app/src/app/components/Preview/DevTools/Tabs/Tab/index.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -172,17 +172,15 @@ const entryTarget = {
172172
const collectTarget = (
173173
connectMonitor: DropTargetConnector,
174174
monitor: DropTargetMonitor
175-
) => {
176-
return {
177-
// Call this function inside render()
178-
// to let React DnD handle the drag events:
179-
connectDropTarget: connectMonitor.dropTarget(),
180-
// You can ask the monitor about the current drag state:
181-
isOver: monitor.isOver({ shallow: true }),
182-
canDrop: monitor.canDrop(),
183-
itemType: monitor.getItemType(),
184-
};
185-
};
175+
) => ({
176+
// Call this function inside render()
177+
// to let React DnD handle the drag events:
178+
connectDropTarget: connectMonitor.dropTarget(),
179+
// You can ask the monitor about the current drag state:
180+
isOver: monitor.isOver({ shallow: true }),
181+
canDrop: monitor.canDrop(),
182+
itemType: monitor.getItemType(),
183+
});
186184

187185
const entrySource = {
188186
canDrag: (props: TabProps) => props.canDrag,

packages/app/src/app/components/Preview/DevTools/Tabs/TabDropZone/index.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,15 @@ const entryTarget = {
8787
const collectTarget = (
8888
connectMonitor: DropTargetConnector,
8989
monitor: DropTargetMonitor
90-
) => {
91-
return {
92-
// Call this function inside render()
93-
// to let React DnD handle the drag events:
94-
connectDropTarget: connectMonitor.dropTarget(),
95-
// You can ask the monitor about the current drag state:
96-
isOver: monitor.isOver({ shallow: true }),
97-
canDrop: monitor.canDrop(),
98-
itemType: monitor.getItemType(),
99-
};
100-
};
90+
) => ({
91+
// Call this function inside render()
92+
// to let React DnD handle the drag events:
93+
connectDropTarget: connectMonitor.dropTarget(),
94+
// You can ask the monitor about the current drag state:
95+
isOver: monitor.isOver({ shallow: true }),
96+
canDrop: monitor.canDrop(),
97+
itemType: monitor.getItemType(),
98+
});
10199

102100
export default DropTarget(PREVIEW_TAB_ID, entryTarget, collectTarget)(
103101
TabDropZone

packages/app/src/app/components/Preview/DevTools/Tabs/index.tsx

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -102,27 +102,25 @@ const DevToolTabs = ({
102102
</Tabs>
103103

104104
<Actions>
105-
{actions.map(({ title, onClick, Icon, disabled }) => {
106-
return (
107-
<Tooltip
105+
{actions.map(({ title, onClick, Icon, disabled }) => (
106+
<Tooltip
107+
style={{
108+
pointerEvents: hidden ? 'none' : 'initial',
109+
}}
110+
content={title}
111+
key={title}
112+
delay={disabled ? [0, 0] : [500, 0]}
113+
>
114+
<Icon
108115
style={{
109-
pointerEvents: hidden ? 'none' : 'initial',
116+
opacity: hidden ? 0 : disabled ? 0.5 : 1,
117+
pointerEvents: disabled ? 'none' : 'initial',
110118
}}
111-
content={title}
119+
onClick={onClick}
112120
key={title}
113-
delay={disabled ? [0, 0] : [500, 0]}
114-
>
115-
<Icon
116-
style={{
117-
opacity: hidden ? 0 : disabled ? 0.5 : 1,
118-
pointerEvents: disabled ? 'none' : 'initial',
119-
}}
120-
onClick={onClick}
121-
key={title}
122-
/>
123-
</Tooltip>
124-
);
125-
})}
121+
/>
122+
</Tooltip>
123+
))}
126124
</Actions>
127125
</Container>
128126
);

packages/app/src/app/pages/Dashboard/Content/CreateNewSandbox/NewSandboxModal/ImportTab.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
import React from 'react';
22
import TerminalIcon from 'react-icons/lib/go/terminal';
33
import UploadIcon from 'react-icons/lib/go/cloud-upload';
4-
import GithubPage from 'app/pages/GitHub/main';
5-
import { ImportChoice, ImportChoices } from './elements';
4+
import { ImportChoice, ImportChoices, ImportWizardContainer } from './elements';
65

7-
export const ImportTab = () => (
6+
import { GitHubImport, StackbitImport } from './Imports';
7+
8+
interface Props {
9+
username: string;
10+
}
11+
12+
const STACKBIT_DISABLED = true;
13+
14+
export const ImportTab = ({ username }: Props) => (
815
<>
9-
<GithubPage />
16+
<ImportWizardContainer>
17+
<GitHubImport />
18+
{username && !STACKBIT_DISABLED && <StackbitImport username={username} />}
19+
</ImportWizardContainer>
1020
<ImportChoices>
1121
<ImportChoice href="/docs/importing#export-with-cli">
1222
<TerminalIcon /> CLI Documentation
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import React, { useState, useCallback } from 'react';
2+
import {
3+
gitHubToSandboxUrl,
4+
protocolAndHost,
5+
gitHubRepoPattern,
6+
} from '@codesandbox/common/lib/utils/url-generator';
7+
import { Button } from '@codesandbox/common/lib/components/Button';
8+
9+
import {
10+
ImportHeader,
11+
ImportDescription,
12+
Section,
13+
GitHubInput,
14+
GitHubLink,
15+
Buttons,
16+
PlaceHolderLink,
17+
DocsLink,
18+
} from './elements';
19+
20+
const getFullGitHubUrl = url =>
21+
`${protocolAndHost()}${gitHubToSandboxUrl(url)}`;
22+
23+
const copyToClipboard = str => {
24+
const el = document.createElement('textarea');
25+
el.value = str;
26+
el.setAttribute('readonly', '');
27+
el.style.position = 'absolute';
28+
el.style.left = '-9999px';
29+
document.body.appendChild(el);
30+
el.select();
31+
document.execCommand('copy');
32+
document.body.removeChild(el);
33+
};
34+
35+
export const GitHubImport = () => {
36+
const [error, setError] = useState(null);
37+
const [transformedUrl, setTransformedUrl] = useState('');
38+
const [url, setUrl] = useState('');
39+
40+
const updateUrl = useCallback(({ target: { value: newUrl } }) => {
41+
if (!newUrl) {
42+
setError(null);
43+
setTransformedUrl('');
44+
setUrl(newUrl);
45+
} else if (!gitHubRepoPattern.test(newUrl)) {
46+
setError('The URL provided is not valid.');
47+
setTransformedUrl('');
48+
setUrl(newUrl);
49+
} else {
50+
setError(null);
51+
setTransformedUrl(getFullGitHubUrl(newUrl.trim()));
52+
setUrl(newUrl);
53+
}
54+
}, []);
55+
56+
return (
57+
<Section style={{ flex: 6 }}>
58+
<ImportHeader>
59+
Import from GitHub{' '}
60+
<DocsLink target="_blank" href="/docs/importing#import-from-github">
61+
docs
62+
</DocsLink>
63+
</ImportHeader>
64+
<ImportDescription>
65+
Enter the URL to your GitHub repository to generate a URL to your
66+
sandbox. The sandbox will stay in sync with your repository.
67+
</ImportDescription>
68+
69+
<ImportDescription>
70+
Tip: you can also link to specific directories, commits and branches
71+
here.
72+
</ImportDescription>
73+
74+
<GitHubInput
75+
placeholder="GitHub Repository URL..."
76+
onChange={updateUrl}
77+
value={url}
78+
/>
79+
80+
{transformedUrl ? (
81+
<GitHubLink
82+
href={transformedUrl}
83+
target="_blank"
84+
rel="noreferrer noopener"
85+
>
86+
{transformedUrl.replace(/^https?:\/\//, '')}
87+
</GitHubLink>
88+
) : (
89+
<PlaceHolderLink error={error}>
90+
{error || 'Enter a URL to see the generated URL'}
91+
</PlaceHolderLink>
92+
)}
93+
94+
<Buttons>
95+
<Button
96+
onClick={() => {
97+
copyToClipboard(transformedUrl);
98+
}}
99+
disabled={!transformedUrl}
100+
secondary
101+
small
102+
>
103+
Copy Link
104+
</Button>
105+
<Button disabled={!transformedUrl} to={gitHubToSandboxUrl(url)} small>
106+
Open Sandbox
107+
</Button>
108+
</Buttons>
109+
</Section>
110+
);
111+
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
import history from 'app/utils/history';
3+
import { Button } from '@codesandbox/common/lib/components/Button';
4+
import { gitHubToSandboxUrl } from '@codesandbox/common/lib/utils/url-generator';
5+
6+
window.addEventListener('message', receiveMessage, false);
7+
8+
function receiveMessage(event) {
9+
if (event.origin === 'https://app.stackbit.com' && event.data) {
10+
const data = JSON.parse(event.data);
11+
12+
if (
13+
data.type === 'project-update' &&
14+
data.project &&
15+
data.project.repository &&
16+
data.project.repository.url
17+
) {
18+
// @ts-ignore
19+
window.stackbitWindow.close();
20+
21+
history.push(gitHubToSandboxUrl(data.project.repository.url));
22+
}
23+
}
24+
}
25+
26+
function openStackbit(username: string) {
27+
// @ts-ignore
28+
window.stackbitWindow = window.open(
29+
`https://app.stackbit.com/create?githubUser=${username}&ref=codesandbox&ssgExclusive=1&ssg=gatsby&cmsExclusive=1&cms=netlifycms,forestry`,
30+
'_blank',
31+
'width=1210,height=800'
32+
);
33+
}
34+
35+
interface Props {
36+
username: string;
37+
style?: React.CSSProperties;
38+
}
39+
40+
export const StackbitButton = ({ username, style }: Props) => (
41+
<Button style={style} small onClick={() => openStackbit(username)}>
42+
Generate Sandbox
43+
</Button>
44+
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import { ImportHeader, ImportDescription, Section } from './elements';
3+
import { StackbitButton } from './StackbitButton';
4+
5+
interface Props {
6+
username: string;
7+
}
8+
9+
export const StackbitImport = ({ username }: Props) => (
10+
<Section style={{ flex: 4 }}>
11+
<ImportHeader>Import from Stackbit</ImportHeader>
12+
<ImportDescription>
13+
Create a project using{' '}
14+
<a href="https://stackbit.com" target="_blank" rel="noreferrer noopener">
15+
Stackbit
16+
</a>
17+
. This generates a project for you that's automatically set up with any
18+
Theme, Site Generator and CMS.
19+
<StackbitButton
20+
style={{ marginTop: '1rem', float: 'right' }}
21+
username={username}
22+
/>
23+
</ImportDescription>
24+
</Section>
25+
);

0 commit comments

Comments
 (0)