Skip to content

Commit 9bf64fd

Browse files
committed
lessons and improved embed support
1 parent 409e2cf commit 9bf64fd

28 files changed

Lines changed: 3031 additions & 88 deletions
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
@import "../../css/colors.css";
2+
@import "../../css/units.css";
3+
4+
.sprites {
5+
display: flex;
6+
gap: 0.375rem;
7+
flex-shrink: 0;
8+
padding: 0.375rem 0.5rem;
9+
overflow-x: auto;
10+
scrollbar-width: none;
11+
border-bottom: 1px solid $ui-black-transparent;
12+
background-color: $ui-secondary;
13+
}
14+
15+
.sprites::-webkit-scrollbar {
16+
display: none;
17+
}
18+
19+
.chip {
20+
display: inline-flex;
21+
align-items: center;
22+
gap: 0.375rem;
23+
flex-shrink: 0;
24+
height: 1.875rem;
25+
padding: 0 0.625rem 0 0.25rem;
26+
border: 1px solid $ui-black-transparent;
27+
border-radius: 0.5rem;
28+
background-color: $ui-primary;
29+
color: $text-primary;
30+
font: inherit;
31+
font-size: 0.75rem;
32+
font-weight: bold;
33+
cursor: pointer;
34+
}
35+
36+
.chip:hover {
37+
border-color: $looks-secondary;
38+
}
39+
40+
.chip[aria-pressed="true"] {
41+
border-color: $looks-secondary;
42+
background-color: $looks-transparent;
43+
color: $looks-secondary;
44+
}
45+
46+
.thumbnail {
47+
width: 1.375rem;
48+
height: 1.375rem;
49+
object-fit: contain;
50+
}
51+
52+
.name {
53+
max-width: 8rem;
54+
overflow: hidden;
55+
text-overflow: ellipsis;
56+
white-space: nowrap;
57+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import bindAll from 'lodash.bindall';
2+
import PropTypes from 'prop-types';
3+
import React from 'react';
4+
import {connect} from 'react-redux';
5+
import {FormattedMessage} from 'react-intl';
6+
import VM from 'scratch-vm';
7+
8+
import getCostumeUrl from '../../lib/get-costume-url';
9+
10+
import styles from './code-embed-sprites.css';
11+
12+
/**
13+
* @param {object} target A sprite or the stage from the targets state
14+
* @returns {?string} A small picture of its current costume
15+
*/
16+
const thumbnailOf = target => {
17+
const asset = target.costume && target.costume.asset;
18+
return asset ? getCostumeUrl(asset) : null;
19+
};
20+
21+
/**
22+
* The code embed's way to pick whose blocks are shown
23+
*/
24+
class CodeEmbedSprites extends React.Component {
25+
constructor (props) {
26+
super(props);
27+
bindAll(this, ['handleSelect']);
28+
}
29+
handleSelect (event) {
30+
this.props.vm.setEditingTarget(event.currentTarget.dataset.targetId);
31+
}
32+
renderChip (target, label) {
33+
const thumbnail = thumbnailOf(target);
34+
return (
35+
<button
36+
key={target.id}
37+
type="button"
38+
className={styles.chip}
39+
aria-pressed={target.id === this.props.editingTarget}
40+
data-target-id={target.id}
41+
onClick={this.handleSelect}
42+
>
43+
{thumbnail ? (
44+
<img
45+
className={styles.thumbnail}
46+
src={thumbnail}
47+
alt=""
48+
draggable={false}
49+
/>
50+
) : null}
51+
<span className={styles.name}>{label}</span>
52+
</button>
53+
);
54+
}
55+
render () {
56+
const {sprites, stage} = this.props;
57+
const ordered = Object.keys(sprites)
58+
.map(id => sprites[id])
59+
.sort((a, b) => a.order - b.order);
60+
return (
61+
<div className={styles.sprites}>
62+
{ordered.map(sprite => this.renderChip(sprite, sprite.name))}
63+
{stage.id ? this.renderChip(stage, (
64+
<FormattedMessage
65+
defaultMessage="Stage"
66+
description="Label for the stage in the stage selector"
67+
id="gui.stageSelector.stage"
68+
/>
69+
)) : null}
70+
</div>
71+
);
72+
}
73+
}
74+
75+
CodeEmbedSprites.propTypes = {
76+
editingTarget: PropTypes.string,
77+
sprites: PropTypes.objectOf(PropTypes.shape({
78+
id: PropTypes.string,
79+
name: PropTypes.string,
80+
order: PropTypes.number
81+
})).isRequired,
82+
stage: PropTypes.shape({
83+
id: PropTypes.string
84+
}).isRequired,
85+
vm: PropTypes.instanceOf(VM).isRequired
86+
};
87+
88+
const mapStateToProps = state => ({
89+
editingTarget: state.scratchGui.targets.editingTarget,
90+
sprites: state.scratchGui.targets.sprites,
91+
stage: state.scratchGui.targets.stage
92+
});
93+
94+
export default connect(mapStateToProps)(CodeEmbedSprites);
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
import classNames from 'classnames';
4+
import VM from 'scratch-vm';
5+
6+
import Blocks from '../../containers/blocks.jsx';
7+
import CodeEmbedSprites from '../code-embed-sprites/code-embed-sprites.jsx';
8+
import StageWrapper from '../../containers/stage-wrapper.jsx';
9+
import Alerts from '../../containers/alerts.jsx';
10+
import DragLayer from '../../containers/drag-layer.jsx';
11+
import ConnectionModal from '../../containers/connection-modal.jsx';
12+
import Loader from '../loader/loader.jsx';
13+
import Box from '../box/box.jsx';
14+
import {STAGE_DISPLAY_SIZES} from '../../lib/layout-constants';
15+
import {isRendererSupported} from '../../lib/tw-environment-support-prober';
16+
import {Theme} from '../../lib/themes';
17+
18+
import styles from './gui.css';
19+
20+
/**
21+
* The layout an embed uses to show a project's blocks, next to the running project or on
22+
* their own.
23+
* @param {object} props Props
24+
* @returns {React.Element} The layout
25+
*/
26+
const CodeEmbedLayout = props => {
27+
const {
28+
alertsVisible,
29+
basePath,
30+
blocksId,
31+
canSave,
32+
canUseCloud,
33+
connectionModalVisible,
34+
isFullScreen,
35+
isRtl,
36+
loading,
37+
modals,
38+
onOpenCustomExtensionModal,
39+
showStage,
40+
stageSize,
41+
theme,
42+
vm
43+
} = props;
44+
45+
return (
46+
<Box
47+
className={styles.codeEmbedWrapper}
48+
dir={isRtl ? 'rtl' : 'ltr'}
49+
>
50+
{modals}
51+
{loading ? (
52+
<Loader isFullScreen />
53+
) : null}
54+
{alertsVisible ? (
55+
<Alerts className={styles.alertsContainer} />
56+
) : null}
57+
{connectionModalVisible ? (
58+
<ConnectionModal
59+
vm={vm}
60+
/>
61+
) : null}
62+
<Box className={styles.codeEmbedEditor}>
63+
<CodeEmbedSprites vm={vm} />
64+
<Box className={styles.blocksWrapper}>
65+
<Blocks
66+
key={`${blocksId}/${theme.id}`}
67+
canUseCloud={canUseCloud}
68+
grow={1}
69+
isVisible
70+
options={{
71+
media: `${basePath}static/${theme.getBlocksMediaFolder()}/`,
72+
readOnly: true,
73+
scrollbars: true
74+
}}
75+
stageSize={stageSize}
76+
onOpenCustomExtensionModal={onOpenCustomExtensionModal}
77+
theme={theme}
78+
vm={vm}
79+
canSave={canSave}
80+
/>
81+
</Box>
82+
</Box>
83+
<Box
84+
className={classNames(styles.stageAndTargetWrapper, styles.codeEmbedStage, styles[stageSize], {
85+
[styles.codeEmbedStageHidden]: !showStage
86+
})}
87+
>
88+
<StageWrapper
89+
isFullScreen={isFullScreen}
90+
isRendererSupported={isRendererSupported()}
91+
isRtl={isRtl}
92+
stageSize={stageSize}
93+
vm={vm}
94+
/>
95+
</Box>
96+
<DragLayer />
97+
</Box>
98+
);
99+
};
100+
101+
CodeEmbedLayout.propTypes = {
102+
alertsVisible: PropTypes.bool,
103+
basePath: PropTypes.string,
104+
blocksId: PropTypes.string,
105+
canSave: PropTypes.bool,
106+
canUseCloud: PropTypes.bool,
107+
connectionModalVisible: PropTypes.bool,
108+
isFullScreen: PropTypes.bool,
109+
isRtl: PropTypes.bool,
110+
loading: PropTypes.bool,
111+
modals: PropTypes.node,
112+
onOpenCustomExtensionModal: PropTypes.func,
113+
showStage: PropTypes.bool,
114+
stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired,
115+
theme: PropTypes.instanceOf(Theme),
116+
vm: PropTypes.instanceOf(VM).isRequired
117+
};
118+
119+
export default CodeEmbedLayout;

‎src/components/gui/gui.css‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,36 @@ $fade-out-distance: 15px;
334334
/* overridden by JS; this will be used if the browser can't understand that color */
335335
background-color: white;
336336
}
337+
338+
.code-embed-wrapper {
339+
display: flex;
340+
flex-direction: row;
341+
height: 100vh;
342+
width: 100vw;
343+
overflow: hidden;
344+
background-color: $ui-primary;
345+
}
346+
347+
.code-embed-wrapper * {
348+
box-sizing: border-box;
349+
}
350+
351+
.code-embed-editor {
352+
flex-grow: 1;
353+
flex-shrink: 1;
354+
min-width: 0;
355+
position: relative;
356+
display: flex;
357+
flex-direction: column;
358+
}
359+
360+
.code-embed-stage {
361+
flex-shrink: 0;
362+
min-height: 0;
363+
padding-top: $space;
364+
padding-bottom: $space;
365+
}
366+
367+
.code-embed-stage-hidden {
368+
display: none;
369+
}

0 commit comments

Comments
 (0)