Skip to content

Commit 5486751

Browse files
authored
Merge branch 'develop' into centralising-breakpoints
2 parents 3232034 + 795ae09 commit 5486751

File tree

6 files changed

+103
-132
lines changed

6 files changed

+103
-132
lines changed

client/modules/IDE/components/IDEOverlays.jsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ export default function IDEOverlays() {
3434
preferencesIsVisible,
3535
keyboardShortcutVisible,
3636
shareModalVisible,
37-
shareModalProjectId,
38-
shareModalProjectName,
39-
shareModalProjectUsername,
4037
errorType,
4138
previousPath
4239
} = useSelector((state) => state.ide);
@@ -87,11 +84,7 @@ export default function IDEOverlays() {
8784
ariaLabel={t('IDEView.ShareARIA')}
8885
closeOverlay={() => dispatch(closeShareModal())}
8986
>
90-
<ShareModal
91-
projectId={shareModalProjectId}
92-
projectName={shareModalProjectName}
93-
ownerUsername={shareModalProjectUsername}
94-
/>
87+
<ShareModal />
9588
</Overlay>
9689
)}
9790
{keyboardShortcutVisible && (

client/modules/IDE/components/ShareModal.jsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
import PropTypes from 'prop-types';
21
import React from 'react';
32
import { useTranslation } from 'react-i18next';
3+
import { useSelector } from 'react-redux';
44
import CopyableInput from './CopyableInput';
55
// import getConfig from '../../../utils/getConfig';
66

7-
const ShareModal = ({ projectId, ownerUsername, projectName }) => {
7+
const ShareModal = () => {
88
const { t } = useTranslation();
9+
10+
// TODO: store these as nested properties instead of top-level
11+
const projectId = useSelector((state) => state.ide.shareModalProjectId);
12+
const projectName = useSelector((state) => state.ide.shareModalProjectName);
13+
const ownerUsername = useSelector(
14+
(state) => state.ide.shareModalProjectUsername
15+
);
16+
917
const hostname = window.location.origin;
1018
// const previewUrl = getConfig('PREVIEW_URL');
1119
return (
@@ -35,10 +43,4 @@ const ShareModal = ({ projectId, ownerUsername, projectName }) => {
3543
);
3644
};
3745

38-
ShareModal.propTypes = {
39-
projectId: PropTypes.string.isRequired,
40-
ownerUsername: PropTypes.string.isRequired,
41-
projectName: PropTypes.string.isRequired
42-
};
43-
4446
export default ShareModal;

client/modules/Preview/EmbedFrame.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import blobUtil from 'blob-util';
12
import PropTypes from 'prop-types';
23
import React, { useRef, useEffect, useMemo } from 'react';
34
import styled from 'styled-components';
@@ -272,6 +273,7 @@ function getHtmlFile(files) {
272273
function EmbedFrame({ files, isPlaying, basePath, gridOutput, textOutput }) {
273274
const iframe = useRef();
274275
const htmlFile = useMemo(() => getHtmlFile(files), [files]);
276+
const srcRef = useRef();
275277

276278
useEffect(() => {
277279
const unsubscribe = registerFrame(
@@ -296,9 +298,14 @@ function EmbedFrame({ files, isPlaying, basePath, gridOutput, textOutput }) {
296298
content: htmlDoc
297299
};
298300
const htmlUrl = createBlobUrl(generatedHtmlFile);
301+
const toRevoke = srcRef.current;
302+
srcRef.current = htmlUrl;
299303
// BRO FOR SOME REASON YOU HAVE TO DO THIS TO GET IT TO WORK ON SAFARI
300304
setTimeout(() => {
301305
doc.src = htmlUrl;
306+
if (toRevoke) {
307+
blobUtil.revokeObjectURL(toRevoke);
308+
}
302309
}, 0);
303310
} else {
304311
doc.src = '';
Lines changed: 81 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,113 @@
11
import PropTypes from 'prop-types';
2-
import React from 'react';
2+
import React, { useState } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
import { useDispatch, useSelector } from 'react-redux';
35
import Button from '../../../common/Button';
46
import { PlusIcon } from '../../../common/icons';
57
import CopyableInput from '../../IDE/components/CopyableInput';
8+
import { createApiKey, removeApiKey } from '../actions';
69

710
import APIKeyList from './APIKeyList';
811

912
export const APIKeyPropType = PropTypes.shape({
10-
id: PropTypes.objectOf(PropTypes.shape()).isRequired,
11-
token: PropTypes.objectOf(PropTypes.shape()),
13+
id: PropTypes.string.isRequired,
14+
token: PropTypes.string,
1215
label: PropTypes.string.isRequired,
1316
createdAt: PropTypes.string.isRequired,
1417
lastUsedAt: PropTypes.string
1518
});
1619

17-
class APIKeyForm extends React.Component {
18-
constructor(props) {
19-
super(props);
20-
this.state = { keyLabel: '' };
20+
const APIKeyForm = () => {
21+
const { t } = useTranslation();
22+
const apiKeys = useSelector((state) => state.user.apiKeys);
23+
const dispatch = useDispatch();
2124

22-
this.addKey = this.addKey.bind(this);
23-
this.removeKey = this.removeKey.bind(this);
24-
this.renderApiKeys = this.renderApiKeys.bind(this);
25-
}
25+
const [keyLabel, setKeyLabel] = useState('');
2626

27-
addKey(event) {
27+
const addKey = (event) => {
2828
event.preventDefault();
29-
const { keyLabel } = this.state;
29+
dispatch(createApiKey(keyLabel));
30+
setKeyLabel('');
31+
};
3032

31-
this.setState({
32-
keyLabel: ''
33-
});
34-
35-
this.props.createApiKey(keyLabel);
36-
37-
return false;
38-
}
39-
40-
removeKey(key) {
41-
const message = this.props.t('APIKeyForm.ConfirmDelete', {
33+
const removeKey = (key) => {
34+
const message = t('APIKeyForm.ConfirmDelete', {
4235
key_label: key.label
4336
});
4437

4538
if (window.confirm(message)) {
46-
this.props.removeApiKey(key.id);
39+
dispatch(removeApiKey(key.id));
4740
}
48-
}
41+
};
4942

50-
renderApiKeys() {
51-
const hasApiKeys = this.props.apiKeys && this.props.apiKeys.length > 0;
43+
const renderApiKeys = () => {
44+
const hasApiKeys = apiKeys && apiKeys.length > 0;
5245

5346
if (hasApiKeys) {
54-
return (
55-
<APIKeyList apiKeys={this.props.apiKeys} onRemove={this.removeKey} />
56-
);
47+
return <APIKeyList apiKeys={apiKeys} onRemove={removeKey} />;
5748
}
58-
return <p>{this.props.t('APIKeyForm.NoTokens')}</p>;
59-
}
60-
61-
render() {
62-
const keyWithToken = this.props.apiKeys.find((k) => !!k.token);
63-
64-
return (
65-
<div className="api-key-form">
66-
<p className="api-key-form__summary">
67-
{this.props.t('APIKeyForm.Summary')}
68-
</p>
69-
70-
<div className="api-key-form__section">
71-
<h3 className="api-key-form__title">
72-
{this.props.t('APIKeyForm.CreateToken')}
73-
</h3>
74-
<form className="form form--inline" onSubmit={this.addKey}>
75-
<label
76-
htmlFor="keyLabel"
77-
className="form__label form__label--hidden "
78-
>
79-
{this.props.t('APIKeyForm.TokenLabel')}
80-
</label>
81-
<input
82-
className="form__input"
83-
id="keyLabel"
84-
onChange={(event) => {
85-
this.setState({ keyLabel: event.target.value });
86-
}}
87-
placeholder={this.props.t('APIKeyForm.TokenPlaceholder')}
88-
type="text"
89-
value={this.state.keyLabel}
49+
return <p>{t('APIKeyForm.NoTokens')}</p>;
50+
};
51+
52+
const keyWithToken = apiKeys.find((k) => !!k.token);
53+
54+
return (
55+
<div className="api-key-form">
56+
<p className="api-key-form__summary">{t('APIKeyForm.Summary')}</p>
57+
58+
<div className="api-key-form__section">
59+
<h3 className="api-key-form__title">{t('APIKeyForm.CreateToken')}</h3>
60+
<form className="form form--inline" onSubmit={addKey}>
61+
<label
62+
htmlFor="keyLabel"
63+
className="form__label form__label--hidden "
64+
>
65+
{t('APIKeyForm.TokenLabel')}
66+
</label>
67+
<input
68+
className="form__input"
69+
id="keyLabel"
70+
onChange={(event) => {
71+
setKeyLabel(event.target.value);
72+
}}
73+
placeholder={t('APIKeyForm.TokenPlaceholder')}
74+
type="text"
75+
value={keyLabel}
76+
/>
77+
<Button
78+
disabled={keyLabel === ''}
79+
iconBefore={<PlusIcon />}
80+
label="Create new key"
81+
type="submit"
82+
>
83+
{t('APIKeyForm.CreateTokenSubmit')}
84+
</Button>
85+
</form>
86+
87+
{keyWithToken && (
88+
<div className="api-key-form__new-token">
89+
<h4 className="api-key-form__new-token__title">
90+
{t('APIKeyForm.NewTokenTitle')}
91+
</h4>
92+
<p className="api-key-form__new-token__info">
93+
{t('APIKeyForm.NewTokenInfo')}
94+
</p>
95+
<CopyableInput
96+
label={keyWithToken.label}
97+
value={keyWithToken.token}
9098
/>
91-
<Button
92-
disabled={this.state.keyLabel === ''}
93-
iconBefore={<PlusIcon />}
94-
label="Create new key"
95-
type="submit"
96-
>
97-
{this.props.t('APIKeyForm.CreateTokenSubmit')}
98-
</Button>
99-
</form>
100-
101-
{keyWithToken && (
102-
<div className="api-key-form__new-token">
103-
<h4 className="api-key-form__new-token__title">
104-
{this.props.t('APIKeyForm.NewTokenTitle')}
105-
</h4>
106-
<p className="api-key-form__new-token__info">
107-
{this.props.t('APIKeyForm.NewTokenInfo')}
108-
</p>
109-
<CopyableInput
110-
label={keyWithToken.label}
111-
value={keyWithToken.token}
112-
/>
113-
</div>
114-
)}
115-
</div>
116-
117-
<div className="api-key-form__section">
118-
<h3 className="api-key-form__title">
119-
{this.props.t('APIKeyForm.ExistingTokensTitle')}
120-
</h3>
121-
{this.renderApiKeys()}
122-
</div>
99+
</div>
100+
)}
123101
</div>
124-
);
125-
}
126-
}
127102

128-
APIKeyForm.propTypes = {
129-
apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
130-
createApiKey: PropTypes.func.isRequired,
131-
removeApiKey: PropTypes.func.isRequired,
132-
t: PropTypes.func.isRequired
103+
<div className="api-key-form__section">
104+
<h3 className="api-key-form__title">
105+
{t('APIKeyForm.ExistingTokensTitle')}
106+
</h3>
107+
{renderApiKeys()}
108+
</div>
109+
</div>
110+
);
133111
};
134112

135113
export default APIKeyForm;

client/modules/User/pages/AccountView.jsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import React from 'react';
2-
import { useDispatch, useSelector } from 'react-redux';
2+
import { useSelector } from 'react-redux';
33
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
44
import { Helmet } from 'react-helmet';
55
import { useTranslation } from 'react-i18next';
66
import { useHistory, useLocation } from 'react-router-dom';
77
import { parse } from 'query-string';
8-
import { createApiKey, removeApiKey } from '../actions';
98
import AccountForm from '../components/AccountForm';
109
import SocialAuthButton from '../components/SocialAuthButton';
1110
import APIKeyForm from '../components/APIKeyForm';
@@ -51,9 +50,6 @@ function AccountView() {
5150
const showError = !!queryParams.error;
5251
const errorType = queryParams.error;
5352
const accessTokensUIEnabled = window.process.env.UI_ACCESS_TOKEN_ENABLED;
54-
55-
const apiKeys = useSelector((state) => state.user.apiKeys);
56-
const dispatch = useDispatch();
5753
const history = useHistory();
5854

5955
return (
@@ -103,13 +99,7 @@ function AccountView() {
10399
<SocialLoginPanel />
104100
</TabPanel>
105101
<TabPanel>
106-
<APIKeyForm
107-
// TODO: it makes more sense to connect the APIKeyForm component directly -Linda
108-
apiKeys={apiKeys}
109-
createApiKey={() => dispatch(createApiKey)}
110-
removeApiKey={() => dispatch(removeApiKey)}
111-
t={t}
112-
/>
102+
<APIKeyForm />
113103
</TabPanel>
114104
</Tabs>
115105
)}

client/styles/components/_keyboard-shortcuts.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
padding: #{math.div(20, $base-font-size)}rem;
55
margin-right: #{math.div(20, $base-font-size)}rem;
66
padding-bottom: #{math.div(40, $base-font-size)}rem;
7-
width: #{math.div(450, $base-font-size)}rem;
7+
max-width: 100%;
88
overflow-y: auto;
99
}
1010

@@ -26,6 +26,7 @@
2626
text-align: right;
2727
margin-right: #{math.div(10, $base-font-size)}rem;
2828
padding: #{math.div(3, $base-font-size)}rem;
29+
min-inline-size: max-content;
2930
@include themify {
3031
border: 1px solid getThemifyVariable("button-border-color");
3132
border-radius: 3px;

0 commit comments

Comments
 (0)