in webapp/src/share.tsx [332:566]
renderCore() {
const { visible, projectName: newProjectName, loading, recordingState, screenshotUri, thumbnails, recordError, pubId, qrCodeUri, qrCodeExpanded, title, sharingError } = this.state;
const targetTheme = pxt.appTarget.appTheme;
const header = this.props.parent.state.header;
const hideEmbed = !!targetTheme.hideShareEmbed || qrCodeExpanded;
const socialOptions = targetTheme.socialOptions;
const showSocialIcons = !!socialOptions && !pxt.BrowserUtils.isUwpEdge()
&& !qrCodeExpanded;
const ready = !!pubId;
let mode = this.state.mode;
let url = '';
let embed = '';
let shareUrl = pxt.appTarget.appTheme.shareUrl || "https://makecode.com/";
if (!/\/$/.test(shareUrl)) shareUrl += '/';
let rootUrl = pxt.appTarget.appTheme.embedUrl
if (!/\/$/.test(rootUrl)) rootUrl += '/';
const verPrefix = pxt.webConfig.verprefix || '';
if (header) {
if (ready) {
url = `${shareUrl}${pubId}`;
let editUrl = `${rootUrl}${verPrefix}#pub:${pubId}`;
switch (mode) {
case ShareMode.Code:
embed = pxt.docs.codeEmbedUrl(`${rootUrl}${verPrefix}`, pubId);
break;
case ShareMode.Editor:
embed = pxt.docs.embedUrl(`${rootUrl}${verPrefix}`, "pub", pubId);
break;
case ShareMode.Simulator:
let padding = '81.97%';
// TODO: parts aspect ratio
let simulatorRunString = `${verPrefix}---run`;
if (pxt.webConfig.runUrl) {
if (pxt.webConfig.isStatic) {
simulatorRunString = pxt.webConfig.runUrl;
}
else {
// Always use live, not /beta etc.
simulatorRunString = pxt.webConfig.runUrl.replace(pxt.webConfig.relprefix, "/---")
}
}
if (pxt.appTarget.simulator) padding = (100 / pxt.appTarget.simulator.aspectRatio).toPrecision(4) + '%';
const runUrl = rootUrl + simulatorRunString.replace(/^\//, '');
embed = pxt.docs.runUrl(runUrl, padding, pubId);
break;
case ShareMode.Url:
embed = editUrl;
break;
}
}
}
const publish = () => {
pxt.tickEvent("menu.embed.publish", undefined, { interactiveConsent: true });
this.setState({ sharingError: undefined, loading: true });
let p = Promise.resolve();
if (newProjectName && this.props.parent.state.projectName != newProjectName) {
// save project name if we've made a change change
p = this.props.parent.updateHeaderNameAsync(newProjectName);
}
// if screenshots are enabled, always take one
if (targetTheme.simScreenshot && !screenshotUri) {
p = p.then(this.screenshotAsync);
}
p.then(() => this.props.parent.anonymousPublishAsync(this.state.screenshotUri))
.then((id) => {
this.setState({ pubId: id, qrCodeUri: undefined, qrCodeExpanded: false });
if (pxt.appTarget.appTheme.qrCode)
qr.renderAsync(`${shareUrl}${id}`)
.then(qruri => {
if (this.state.pubId == id) // race
this.setState({ qrCodeUri: qruri });
});
this.forceUpdate();
})
.catch((e: Error) => {
pxt.tickEvent("menu.embed.error", { code: (e as any).statusCode })
this.setState({
pubId: undefined,
sharingError: e,
qrCodeUri: undefined,
qrCodeExpanded: false
});
});
this.forceUpdate();
}
const formats = [
{ mode: ShareMode.Code, label: lf("Code") },
{ mode: ShareMode.Editor, label: lf("Editor") },
{ mode: ShareMode.Simulator, label: lf("Simulator") },
];
const action = !ready ? lf("Publish project") : undefined;
const actionLoading = loading && !this.state.sharingError;
let actions: sui.ModalButton[] = [];
if (action) {
actions.push({
label: action,
onclick: publish,
icon: 'share alternate',
loading: actionLoading,
className: 'primary',
disabled: recordingState != ShareRecordingState.None
})
}
const light = !!pxt.options.light;
const disclaimer = lf("You need to publish your project to share it or embed it in other web pages.") + " " +
lf("You acknowledge having consent to publish this project.");
const screenshotDisabled = actionLoading || recordingState != ShareRecordingState.None;
const screenshotText = this.loanedSimulator && targetTheme.simScreenshotKey
? lf("Take Screenshot (shortcut: {0})", targetTheme.simScreenshotKey) : lf("Take Screenshot");
const screenshot = targetTheme.simScreenshot;
const gif = !light && !!targetTheme.simGif;
const isGifRecording = recordingState == ShareRecordingState.GifRecording;
const isGifRendering = recordingState == ShareRecordingState.GifRendering;
const gifIcon = isGifRecording ? "stop" : "circle";
const gifTitle = isGifRecording
? (targetTheme.simGifKey ? lf("Stop recording (shortcut: {0})", targetTheme.simGifKey) : lf("Stop recording"))
: isGifRendering ? lf("Cancel rendering")
: (targetTheme.simGifKey ? lf("Start recording (shortcut: {0})", targetTheme.simGifKey)
: lf("Start recording"));
const gifRecordingClass = isGifRecording ? "glow" : "";
const gifDisabled = actionLoading;
const gifLoading = recordingState == ShareRecordingState.GifLoading
|| isGifRendering;
const screenshotMessage = recordError ? recordError
: isGifRecording ? lf("Recording in progress...")
: isGifRendering ? lf("Rendering gif...")
: undefined;
const screenshotMessageClass = recordError ? "warning" : "";
const tooBigErrorSuggestGitHub = sharingError
&& (sharingError as any).statusCode === 413
&& pxt.appTarget?.cloud?.cloudProviders?.github;
const unknownError = sharingError && !tooBigErrorSuggestGitHub;
const qrCodeFull = !!qrCodeUri && qrCodeExpanded;
const classes = this.props.parent.createModalClasses("sharedialog");
return (
<sui.Modal isOpen={visible} className={classes}
size={thumbnails ? "" : "small"}
onClose={this.hide}
dimmer={true} header={title || lf("Share Project")}
closeIcon={true} buttons={actions}
closeOnDimmerClick
closeOnDocumentClick
closeOnEscape>
<div className={`ui form`}>
{action && !this.loanedSimulator ? <div className="ui field">
<div>
<sui.Input ref="filenameinput" placeholder={lf("Name")} autoFocus={!pxt.BrowserUtils.isMobile()} id={"projectNameInput"}
ariaLabel={lf("Type a name for your project")} autoComplete={false}
value={newProjectName || ''} onChange={this.handleProjectNameChange} />
</div>
</div> : undefined}
{action && this.loanedSimulator ? <div className="ui fields">
<div id="shareLoanedSimulator" className={`simulator ui six wide field landscape only ${gifRecordingClass}`}></div>
<div className="ui ten wide field">
<sui.Input ref="filenameinput" placeholder={lf("Name")} autoFocus={!pxt.BrowserUtils.isMobile()} id={"projectNameInput"}
ariaLabel={lf("Type a name for your project")} autoComplete={false}
value={newProjectName || ''} onChange={this.handleProjectNameChange} />
<label></label>
<div className="ui buttons landscape only">
<sui.Button icon="refresh" title={lf("Restart")} ariaLabel={lf("Restart")} onClick={this.restartSimulator} disabled={screenshotDisabled} />
{screenshot ? <sui.Button icon="camera" title={screenshotText} ariaLabel={screenshotText} onClick={this.handleScreenshotClick} disabled={screenshotDisabled} /> : undefined}
{gif ? <sui.Button icon={gifIcon} title={gifTitle} loading={gifLoading} onClick={this.handleRecordClick} disabled={gifDisabled} /> : undefined}
</div>
{screenshotUri || screenshotMessage ?
<div className={`ui ${screenshotMessageClass} segment landscape only`}>{
(screenshotUri && !screenshotMessage)
? <img className="ui small centered image" src={screenshotUri} alt={lf("Recorded gif")} />
: <p className="no-select">{screenshotMessage}</p>}</div> : undefined}
<p className="ui tiny message info">{disclaimer}</p>
</div>
</div> : undefined}
{action && !this.loanedSimulator ? <p className="ui tiny message info">{disclaimer}</p> : undefined}
{tooBigErrorSuggestGitHub && <p className="ui orange inverted segment">{lf("Oops! Your project is too big. You can create a GitHub repository to share it.")}
<sui.Button className="inverted basic" text={lf("Create")} icon="github" onClick={this.handleCreateGitHubRepository} />
</p>}
{unknownError && <p className="ui red inverted segment">{lf("Oops! There was an error. Please ensure you are connected to the Internet and try again.")}</p>}
{url && ready ? <div>
{!qrCodeFull && <p>{lf("Your project is ready! Use the address below to share your projects.")}</p>}
{!qrCodeFull && <sui.Input id="projectUri" class="mini" readOnly={true} lines={1} value={url} copy={true} autoFocus={!pxt.BrowserUtils.isMobile()} selectOnClick={true} aria-describedby="projectUriLabel" autoComplete={false} />}
{!qrCodeFull && <label htmlFor="projectUri" id="projectUriLabel" className="accessible-hidden">{lf("This is the read-only internet address of your project.")}</label>}
{!!qrCodeUri && <img className={`ui ${qrCodeFull ? "huge" : "small"} image ${qrCodeExpanded ? "centered" : "floated right"} button pixelart`} alt={lf("QR Code of the saved program")}
src={qrCodeUri} onClick={this.handleQrCodeClick} title={lf("Click to expand or collapse.")} tabIndex={0} aria-label={lf("QR Code of the saved program")} onKeyDown={fireClickOnEnter}/>}
{showSocialIcons ? <div className="social-icons">
<SocialButton url={url} ariaLabel="Facebook" type='facebook' heading={lf("Share on Facebook")} />
<SocialButton url={url} ariaLabel="Twitter" type='twitter' heading={lf("Share on Twitter")} />
{socialOptions.discourse ? <SocialButton url={url} icon={"comments"} ariaLabel={lf("Post to Forum")} type='discourse' heading={lf("Share on Forum")} /> : undefined}
</div> : undefined}
</div> : undefined}
{(ready && !hideEmbed) && <div>
<div className="ui divider"></div>
<sui.ExpandableMenu title={lf("Embed")}>
<sui.Menu pointing secondary>
{formats.map(f =>
<EmbedMenuItem key={`tab${f.label}`} onClick={this.setAdvancedMode} currentMode={mode} {...f} />)}
</sui.Menu>
<sui.Field>
<sui.Input id="embedCode" class="mini" readOnly={true} lines={4} value={embed} copy={ready} disabled={!ready} selectOnClick={true} autoComplete={false} />
<label htmlFor="embedCode" id="embedCodeLabel" className="accessible-hidden">{lf("This is the read-only code for the selected tab.")}</label>
</sui.Field>
</sui.ExpandableMenu>
</div>}
</div>
</sui.Modal >
)
}
componentDidUpdate() {
const container = document.getElementById("shareLoanedSimulator");
if (container && this.loanedSimulator && !this.loanedSimulator.parentNode)
container.appendChild(this.loanedSimulator);
}
protected handleKeyDown = (e: KeyboardEvent) => {
const { visible } = this.state;
const targetTheme = pxt.appTarget.appTheme;
const pressed = e.key.toLocaleLowerCase();
// Don't fire events if component is hidden or if they are typing in a name
if (!visible || (document.activeElement && document.activeElement.tagName === "INPUT")) return;
if (targetTheme.simScreenshotKey && pressed === targetTheme.simScreenshotKey.toLocaleLowerCase()) {
this.handleScreenshotClick();
}
else if (targetTheme.simGifKey && pressed === targetTheme.simGifKey.toLocaleLowerCase()) {
this.handleRecordClick();
}
}
}