pxt-core 8.2.9 → 8.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pxt-core",
3
- "version": "8.2.9",
3
+ "version": "8.2.10",
4
4
  "description": "Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors",
5
5
  "keywords": [
6
6
  "TypeScript",
@@ -1,6 +1,7 @@
1
1
  /// <reference path="../types.d.ts" />
2
2
 
3
3
  import * as React from "react";
4
+ import { SimRecorder } from "./ThumbnailRecorder";
4
5
  import { ShareInfo } from "./ShareInfo";
5
6
 
6
7
  export interface ShareData {
@@ -18,34 +19,20 @@ export interface ShareData {
18
19
  export interface ShareProps {
19
20
  projectName: string;
20
21
  screenshotUri?: string;
21
- showShareDropdown?: boolean;
22
+ isLoggedIn?: boolean;
22
23
 
23
- screenshotAsync: () => Promise<string>;
24
- gifRecordAsync: () => Promise<void>;
25
- gifRenderAsync: () => Promise<string | void>;
26
- gifAddFrame: (dataUri: ImageData, delay?: number) => boolean;
24
+ simRecorder: SimRecorder;
27
25
  publishAsync: (name: string, screenshotUri?: string, forceAnonymous?: boolean) => Promise<ShareData>;
28
- registerSimulatorMsgHandler?: (handler: (msg: any) => void) => void;
29
- unregisterSimulatorMsgHandler?: () => void;
30
26
  }
31
27
 
32
28
  export const Share = (props: ShareProps) => {
33
- const { projectName, screenshotUri, showShareDropdown, screenshotAsync, gifRecordAsync, gifRenderAsync,
34
- gifAddFrame, publishAsync, registerSimulatorMsgHandler, unregisterSimulatorMsgHandler } = props;
29
+ const { projectName, screenshotUri, isLoggedIn, simRecorder, publishAsync} = props;
35
30
 
36
- return <div className="project-share">
37
- {(!!screenshotAsync || !!gifRecordAsync) && <div className="project-share-simulator">
38
- <div id="shareLoanedSimulator" />
39
- </div>}
31
+ return <div className="project-share">
40
32
  <ShareInfo projectName={projectName}
41
- showShareDropdown={showShareDropdown}
33
+ isLoggedIn={isLoggedIn}
42
34
  screenshotUri={screenshotUri}
43
- screenshotAsync={screenshotAsync}
44
- gifRecordAsync={gifRecordAsync}
45
- gifRenderAsync={gifRenderAsync}
46
- gifAddFrame={gifAddFrame}
47
- publishAsync={publishAsync}
48
- registerSimulatorMsgHandler={registerSimulatorMsgHandler}
49
- unregisterSimulatorMsgHandler={unregisterSimulatorMsgHandler} />
35
+ simRecorder={simRecorder}
36
+ publishAsync={publishAsync} />
50
37
  </div>
51
38
  }
@@ -2,32 +2,26 @@ import * as React from "react";
2
2
  import { Button } from "../controls/Button";
3
3
  import { EditorToggle } from "../controls/EditorToggle";
4
4
  import { Input } from "../controls/Input";
5
- import { MenuDropdown } from "../controls/MenuDropdown";
6
5
  import { Textarea } from "../controls/Textarea";
7
6
  import { Modal } from "../controls/Modal";
8
7
 
9
8
  import { ShareData } from "./Share";
10
- import { GifInfo } from "./GifInfo";
9
+ import { ThumbnailRecorder } from "./ThumbnailRecorder";
11
10
  import { SocialButton } from "./SocialButton";
11
+ import { Checkbox } from "../controls/Checkbox";
12
+ import { SimRecorder } from "./ThumbnailRecorder";
12
13
 
13
14
  export interface ShareInfoProps {
14
15
  projectName: string;
15
16
  description?: string;
16
17
  screenshotUri?: string;
17
- showShareDropdown?: boolean;
18
-
19
- screenshotAsync?: () => Promise<string>;
20
- gifRecordAsync?: () => Promise<void>;
21
- gifRenderAsync?: () => Promise<string | void>;
22
- gifAddFrame?: (dataUri: ImageData, delay?: number) => boolean;
18
+ isLoggedIn?: boolean;
19
+ simRecorder: SimRecorder;
23
20
  publishAsync: (name: string, screenshotUri?: string, forceAnonymous?: boolean) => Promise<ShareData>;
24
- registerSimulatorMsgHandler?: (handler: (msg: any) => void) => void;
25
- unregisterSimulatorMsgHandler?: () => void;
26
21
  }
27
22
 
28
23
  export const ShareInfo = (props: ShareInfoProps) => {
29
- const { projectName, description, screenshotUri, showShareDropdown, screenshotAsync, gifRecordAsync,
30
- gifRenderAsync, gifAddFrame, publishAsync, registerSimulatorMsgHandler, unregisterSimulatorMsgHandler } = props;
24
+ const { projectName, description, screenshotUri, isLoggedIn, simRecorder, publishAsync } = props;
31
25
  const [ name, setName ] = React.useState(projectName);
32
26
  const [ thumbnailUri, setThumbnailUri ] = React.useState(screenshotUri);
33
27
  const [ shareState, setShareState ] = React.useState<"share" | "gifrecord" | "publish" | "publishing">("share");
@@ -35,8 +29,9 @@ export const ShareInfo = (props: ShareInfoProps) => {
35
29
  const [ embedState, setEmbedState ] = React.useState<"none" | "code" | "editor" | "simulator">("none");
36
30
  const [ showQRCode, setShowQRCode ] = React.useState(false);
37
31
  const [ copySuccessful, setCopySuccessful ] = React.useState(false);
32
+ const [ isAnonymous, setIsAnonymous ] = React.useState(!isLoggedIn);
38
33
 
39
- const showSimulator = !!screenshotAsync || !!gifRecordAsync;
34
+ const showSimulator = !!simRecorder;
40
35
  const showDescription = shareState !== "publish";
41
36
  let qrCodeButtonRef: HTMLButtonElement;
42
37
  let inputRef: HTMLInputElement;
@@ -54,9 +49,9 @@ export const ShareInfo = (props: ShareInfoProps) => {
54
49
  exitGifRecord();
55
50
  }
56
51
 
57
- const handlePublishClick = async (forceAnonymous?: boolean) => {
52
+ const handlePublishClick = async () => {
58
53
  setShareState("publishing");
59
- let publishedShareData = await publishAsync(name, thumbnailUri, forceAnonymous);
54
+ let publishedShareData = await publishAsync(name, thumbnailUri, isAnonymous);
60
55
  setShareData(publishedShareData);
61
56
  if (!publishedShareData?.error) setShareState("publish");
62
57
  else setShareState("share")
@@ -132,12 +127,6 @@ export const ShareInfo = (props: ShareInfoProps) => {
132
127
  onClick: () => setEmbedState("simulator")
133
128
  }];
134
129
 
135
- const dropdownOptions = [{
136
- title: lf("Create snapshot"),
137
- label: lf("Create snapshot"),
138
- onClick: () => handlePublishClick(true)
139
- }];
140
-
141
130
  const handleQRCodeButtonRef = (ref: HTMLButtonElement) => {
142
131
  if (ref) qrCodeButtonRef = ref;
143
132
  }
@@ -153,124 +142,139 @@ export const ShareInfo = (props: ShareInfoProps) => {
153
142
 
154
143
  const prePublish = shareState === "share" || shareState === "publishing";
155
144
 
145
+ const inputTitle = showSimulator && prePublish ? lf("Project Title") : lf("Project Link")
146
+
156
147
  return <>
157
148
  <div className="project-share-info">
158
- {(prePublish|| shareState === "publish") && <>
159
- {showSimulator && <div className="project-share-title">
160
- <h2>{lf("About your project")}</h2>
161
- {showShareDropdown && prePublish && <MenuDropdown id="project-share-dropdown"
162
- icon="fas fa-ellipsis-h"
163
- title={lf("More share options")}
164
- items={dropdownOptions}
165
- />}
166
- </div>}
167
- {showDescription && <>
168
- <Input label={lf("Project Name")}
169
- initialValue={name}
170
- placeholder={lf("Name your project")}
171
- onChange={setName} />
172
- <Textarea label={lf("Description")}
173
- initialValue={description}
174
- placeholder={lf("Tell others about your project")}
175
- rows={5} />
176
- </>
177
- }
178
- {prePublish && <>
179
- {showSimulator && <div className="project-share-thumbnail">
180
- {thumbnailUri
181
- ? <img src={thumbnailUri} />
182
- : <div className="project-thumbnail-placeholder" />
183
- }
184
- <Button title={lf("Update project thumbnail")}
149
+ {showSimulator && shareState !== "gifrecord" &&
150
+ <div className="project-share-thumbnail">
151
+ {thumbnailUri
152
+ ? <img src={thumbnailUri} />
153
+ : <div className="project-thumbnail-placeholder" />
154
+ }
155
+ {shareState !== "publish" &&
156
+ <Button
157
+ className="link-button"
158
+ title={lf("Update project thumbnail")}
185
159
  label={lf("Update project thumbnail")}
186
160
  onClick={() => setShareState("gifrecord")} />
187
- </div>}
188
- <div>{lf("You need to publish your project to share it or embed it in other web pages. You acknowledge having consent to publish this project.")}</div>
189
- {shareData?.error && <div className="project-share-error">
190
- {(shareData.error.statusCode === 413
191
- && pxt.appTarget?.cloud?.cloudProviders?.github)
192
- ? lf("Oops! Your project is too big. You can create a GitHub repository to share it.")
193
- : lf("Oops! There was an error. Please ensure you are connected to the Internet and try again.")}
194
- </div>}
195
- {shareState === "share" ?
196
- <Button className="primary share-publish-button"
197
- title={lf("Publish to share")}
198
- label={lf("Publish to share")}
199
- onClick={handlePublishClick} /> :
200
- <Button className="primary share-publish-button"
201
- title={lf("Publishing...")}
202
- label={ <div className="common-spinner" />}
203
- onClick={() => {}} />
204
161
  }
205
- </>}
206
-
207
- {shareState === "publish" &&
208
- <div className="project-share-data">
209
- <div className="project-share-text">
210
- {lf("Your project is ready! Use the address below to share your projects.")}
211
- </div>
212
- <div className="common-input-attached-button">
213
- <Input
214
- handleInputRef={handleInputRef}
215
- initialValue={shareData.url}
216
- readOnly={true}
217
- onChange={setName} />
218
- <Button className={copySuccessful ? "green" : "primary"}
219
- title={lf("Copy link")}
220
- label={copySuccessful ? lf("Copied!") : lf("Copy link")}
221
- leftIcon="fas fa-link"
222
- onClick={handleCopyClick}
223
- onBlur={handleCopyBlur} />
224
- </div>
225
- <div className="project-share-actions">
226
- <Button className="circle-button gray embed mobile-portrait-hidden"
227
- title={lf("Show embed code")}
228
- leftIcon="fas fa-code"
229
- onClick={handleEmbedClick} />
230
- <SocialButton className="circle-button facebook"
231
- url={shareData?.url}
232
- type='facebook'
233
- heading={lf("Share on Facebook")} />
234
- <SocialButton className="circle-button twitter"
235
- url={shareData?.url}
236
- type='twitter'
237
- heading={lf("Share on Twitter")} />
238
- {navigator.share && <Button className="circle-button device-share"
239
- title={lf("Show device share options")}
240
- ariaLabel={lf("Show device share options")}
241
- leftIcon={"icon share"}
242
- onClick={handleDeviceShareClick}
162
+ </div>
163
+ }
164
+ <div className="project-share-content">
165
+ {(prePublish || shareState === "publish") && <>
166
+ <div className="project-share-title">
167
+ <h2>{inputTitle}</h2>
168
+ </div>
169
+ {showDescription && <>
170
+ <Input
171
+ className="name-input"
172
+ initialValue={name}
173
+ placeholder={lf("Name your project")}
174
+ onChange={setName} />
175
+ {isLoggedIn && <Checkbox
176
+ id="persistent-share-checkbox"
177
+ label={lf("Allow people to see future changes to my project")}
178
+ isChecked={!isAnonymous}
179
+ onChange={val => setIsAnonymous(!val)}
243
180
  />}
244
- <Button
245
- className="menu-button project-qrcode"
246
- buttonRef={handleQRCodeButtonRef}
247
- title={lf("Show QR Code")}
248
- label={<img className="qrcode-image" src={shareData?.qr} />}
249
- onClick={handleQRCodeClick}
250
- />
181
+ </>
182
+ }
183
+ {prePublish && <>
184
+ {shareData?.error && <div className="project-share-error">
185
+ {(shareData.error.statusCode === 413
186
+ && pxt.appTarget?.cloud?.cloudProviders?.github)
187
+ ? lf("Oops! Your project is too big. You can create a GitHub repository to share it.")
188
+ : lf("Oops! There was an error. Please ensure you are connected to the Internet and try again.")}
189
+ </div>}
190
+ <div>
191
+ {shareState === "share" &&
192
+ <Button className="primary share-publish-button"
193
+ title={lf("Continue")}
194
+ label={lf("Continue")}
195
+ onClick={handlePublishClick} />
196
+ }
197
+ { shareState === "publishing" &&
198
+ <Button className="primary share-publish-button"
199
+ title={lf("Publishing...")}
200
+ label={ <div className="common-spinner" />}
201
+ onClick={() => {}} />
202
+ }
251
203
  </div>
252
- </div>
253
- }
254
- {embedState !== "none" && <div className="project-embed">
255
- <EditorToggle id="project-embed-toggle"
256
- className="slim tablet-compact"
257
- items={embedOptions}
258
- selected={embedOptions.findIndex(i => i.name === embedState)} />
259
- <Textarea readOnly={true}
260
- rows={5}
261
- initialValue={shareData?.embed[embedState]} />
262
- </div>}
263
- </>}
264
- {shareState === "gifrecord" && <GifInfo
265
- initialUri={thumbnailUri}
266
- onApply={applyGifChange}
267
- onCancel={exitGifRecord}
268
- screenshotAsync={screenshotAsync}
269
- gifRecordAsync={gifRecordAsync}
270
- gifRenderAsync={gifRenderAsync}
271
- gifAddFrame={gifAddFrame}
272
- registerSimulatorMsgHandler={registerSimulatorMsgHandler}
273
- unregisterSimulatorMsgHandler={unregisterSimulatorMsgHandler} />}
204
+ </>}
205
+
206
+ {shareState === "publish" &&
207
+ <div className="project-share-data">
208
+ <div className="common-input-attached-button">
209
+ <Input
210
+ handleInputRef={handleInputRef}
211
+ initialValue={shareData.url}
212
+ readOnly={true}
213
+ onChange={setName} />
214
+ <Button className={copySuccessful ? "green" : "primary"}
215
+ title={lf("Copy link")}
216
+ label={copySuccessful ? lf("Copied!") : lf("Copy")}
217
+ leftIcon="fas fa-link"
218
+ onClick={handleCopyClick}
219
+ onBlur={handleCopyBlur} />
220
+ </div>
221
+ <div className="project-share-actions">
222
+ <Button className="square-button gray embed mobile-portrait-hidden"
223
+ title={lf("Show embed code")}
224
+ leftIcon="fas fa-code"
225
+ onClick={handleEmbedClick} />
226
+ <SocialButton className="square-button facebook"
227
+ url={shareData?.url}
228
+ type='facebook'
229
+ heading={lf("Share on Facebook")} />
230
+ <SocialButton className="square-button twitter"
231
+ url={shareData?.url}
232
+ type='twitter'
233
+ heading={lf("Share on Twitter")} />
234
+ <SocialButton className="square-button google-classroom"
235
+ url={shareData?.url}
236
+ type='google-classroom'
237
+ heading={lf("Share on Google Classroom")} />
238
+ <SocialButton className="square-button microsoft-teams"
239
+ url={shareData?.url}
240
+ type='microsoft-teams'
241
+ heading={lf("Share on Microsoft Teams")} />
242
+ <SocialButton className="square-button whatsapp"
243
+ url={shareData?.url}
244
+ type='whatsapp'
245
+ heading={lf("Share on WhatsApp")} />
246
+ {navigator.share && <Button className="square-button device-share"
247
+ title={lf("Show device share options")}
248
+ ariaLabel={lf("Show device share options")}
249
+ leftIcon={"icon share"}
250
+ onClick={handleDeviceShareClick}
251
+ />}
252
+ <Button
253
+ className="menu-button project-qrcode"
254
+ buttonRef={handleQRCodeButtonRef}
255
+ title={lf("Show QR Code")}
256
+ label={<img className="qrcode-image" src={shareData?.qr} />}
257
+ onClick={handleQRCodeClick}
258
+ />
259
+ </div>
260
+ </div>
261
+ }
262
+ {embedState !== "none" && <div className="project-embed">
263
+ <EditorToggle id="project-embed-toggle"
264
+ className="slim tablet-compact"
265
+ items={embedOptions}
266
+ selected={embedOptions.findIndex(i => i.name === embedState)} />
267
+ <Textarea readOnly={true}
268
+ rows={5}
269
+ initialValue={shareData?.embed[embedState]} />
270
+ </div>}
271
+ </>}
272
+ {shareState === "gifrecord" && <ThumbnailRecorder
273
+ initialUri={thumbnailUri}
274
+ onApply={applyGifChange}
275
+ onCancel={exitGifRecord}
276
+ simRecorder={simRecorder}/>}
277
+ </div>
274
278
 
275
279
  {showQRCode &&
276
280
  <Modal title={lf("QR Code")} onClose={handleQRCodeModalClose}>
@@ -1,16 +1,19 @@
1
1
  import * as React from "react";
2
2
  import { Button } from "../controls/Button";
3
+ import { classList } from "../util";
3
4
 
4
5
  interface SocialButtonProps {
5
6
  className?: string;
6
7
  url?: string;
7
- type?: "facebook" | "twitter" | "discourse";
8
+ type?: "facebook" | "twitter" | "discourse" | "google-classroom" | "microsoft-teams" | "whatsapp";
8
9
  heading?: string;
9
10
  }
10
11
 
11
12
  export const SocialButton = (props: SocialButtonProps) => {
12
13
  const { className, url, type, heading } = props;
13
14
 
15
+ const classes = classList(className, "social-button", "type")
16
+
14
17
  const handleClick = () => {
15
18
  const socialOptions = pxt.appTarget.appTheme.socialOptions;
16
19
  let socialUrl = '';
@@ -41,13 +44,40 @@ export const SocialButton = (props: SocialButtonProps) => {
41
44
  socialUrl += `&category=${encodeURIComponent(socialOptions.discourseCategory)}`;
42
45
  break;
43
46
  }
47
+ case "google-classroom":
48
+ socialUrl = `https://classroom.google.com/share?url=${encodeURIComponent(url)}`;
49
+ break;
50
+ case "microsoft-teams":
51
+ socialUrl = `https://teams.microsoft.com/share?href=${encodeURIComponent(url)}`;
52
+ break;
53
+ case "whatsapp":
54
+ socialUrl = `https://api.whatsapp.com/send?text=${encodeURIComponent(url)}`;
55
+ break;
44
56
  }
45
57
  pxt.BrowserUtils.popupWindow(socialUrl, heading, 600, 600);
46
58
  }
47
59
 
48
- return <Button className={className}
49
- ariaLabel={type}
50
- title={heading}
51
- leftIcon={`icon ${type}`}
52
- onClick={handleClick} />
60
+ switch (type) {
61
+ // Icon buttons
62
+ case "facebook":
63
+ case "twitter":
64
+ case "discourse":
65
+ case "whatsapp":
66
+ return <Button className={classes}
67
+ ariaLabel={type}
68
+ title={heading}
69
+ leftIcon={`icon ${type}`}
70
+ onClick={handleClick} />
71
+
72
+ // Image buttons
73
+ case "google-classroom":
74
+ case "microsoft-teams":
75
+ return <Button className={classes}
76
+ ariaLabel={type}
77
+ title={heading}
78
+ label={<img src={`/static/logo/social-buttons/${type}.png`} alt={heading || pxt.U.rlf(type)} />}
79
+ onClick={handleClick} />
80
+ }
81
+
82
+
53
83
  }
@@ -0,0 +1,137 @@
1
+ import * as React from "react";
2
+ import { Button } from "../controls/Button";
3
+
4
+ export interface ThumbnailRecorderProps {
5
+ initialUri?: string;
6
+
7
+ onApply: (uri: string) => void;
8
+ onCancel: () => void;
9
+
10
+ simRecorder: SimRecorder;
11
+ }
12
+
13
+ export interface SimRecorderProps {
14
+ onSimRecorderInit: (ref: SimRecorderRef) => void;
15
+ }
16
+ export type SimRecorder = (props: SimRecorderProps) => JSX.Element
17
+ export type SimRecorderState = "default" | "recording" | "rendering"
18
+ export interface SimRecorderRef {
19
+ state: SimRecorderState;
20
+ startRecordingAsync: () => Promise<void>;
21
+ stopRecordingAsync: () => Promise<string>;
22
+ screenshotAsync: () => Promise<string>;
23
+ addStateChangeListener: (handler: (newState: SimRecorderState) => void) => void;
24
+ addThumbnailListener: (handler: (uri: string, type: "gif" | "png") => void) => void;
25
+ addErrorListener: (handler: (message: string) => void) => void;
26
+ removeStateChangeListener: (handler: (newState: SimRecorderState) => void) => void;
27
+ removeThumbnailListener: (handler: (uri: string, type: "gif" | "png") => void) => void;
28
+ removeErrorListener: (handler: (message: string) => void) => void;
29
+ }
30
+
31
+
32
+ export const ThumbnailRecorder = (props: ThumbnailRecorderProps) => {
33
+ const { initialUri, onApply, onCancel, simRecorder } = props;
34
+ const [ uri, setUri ] = React.useState(initialUri);
35
+ const [ error, setError] = React.useState<string>(undefined)
36
+ const [ recorderRef, setRecorderRef ] = React.useState<SimRecorderRef>(undefined);
37
+ const [ recorderState, setRecorderState ] = React.useState<SimRecorderState>("default");
38
+
39
+ React.useEffect(() => {
40
+ if (!recorderRef) return undefined;
41
+ recorderRef.addStateChangeListener(setRecorderState);
42
+ recorderRef.addThumbnailListener(setUri);
43
+ recorderRef.addErrorListener(setError);
44
+
45
+ return () => {
46
+ recorderRef.removeStateChangeListener(setRecorderState);
47
+ recorderRef.removeThumbnailListener(setUri);
48
+ recorderRef.removeErrorListener(setError);
49
+ }
50
+ }, [recorderRef])
51
+
52
+ const handleApplyClick = (evt?: any) => {
53
+ onApply(uri);
54
+ }
55
+
56
+ const handleScreenshotClick = async () => {
57
+ setError(undefined);
58
+ if (recorderRef) recorderRef.screenshotAsync();
59
+ }
60
+
61
+ const handleRecordClick = async () => {
62
+ setError(undefined);
63
+ if (!recorderRef) return;
64
+
65
+ if (recorderRef.state === "recording") {
66
+ recorderRef.stopRecordingAsync();
67
+ }
68
+ else if (recorderRef.state === "default") {
69
+ recorderRef.startRecordingAsync();
70
+ }
71
+ }
72
+
73
+ const targetTheme = pxt.appTarget.appTheme;
74
+
75
+ const handleSimRecorderRef = (ref: SimRecorderRef) => {
76
+ setRecorderRef(ref);
77
+ }
78
+
79
+ const screenshotLabel = lf("Take screenshot ({0})", targetTheme.simScreenshotKey);
80
+ const startRecordingLabel = lf("Record gameplay ({0})", targetTheme.simGifKey);
81
+ const stopRecordingLabel = lf("Stop recording ({0})", targetTheme.simGifKey) ;
82
+
83
+ return <>
84
+ <div className="gif-recorder-content">
85
+ <div className="gif-recorder-sim-embed">
86
+ {React.createElement(simRecorder, { onSimRecorderInit: handleSimRecorderRef })}
87
+ </div>
88
+ <div className="thumbnail-controls">
89
+ <div className="thumbnail-preview">
90
+ <div>
91
+ <span className="thumbnail-label">{lf("Current Thumbnail")}</span>
92
+ <div className="thumbnail-image">
93
+ {initialUri
94
+ ? <img src={initialUri} />
95
+ : <div className="thumbnail-placeholder" />
96
+ }
97
+ </div>
98
+ </div>
99
+ <div>
100
+ <span className="thumbnail-label">{lf("New Thumbnail")}</span>
101
+ <div className="thumbnail-image">
102
+ {uri
103
+ ? <img src={uri} />
104
+ : <div className="thumbnail-placeholder" />
105
+ }
106
+ </div>
107
+ </div>
108
+ </div>
109
+ <div className="thumbnail-actions">
110
+ <Button className="primary"
111
+ title={lf("Apply")}
112
+ label={lf("Apply")}
113
+ onClick={handleApplyClick} />
114
+ <Button title={lf("Cancel")}
115
+ label={lf("Cancel")}
116
+ onClick={onCancel} />
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <div className="gif-recorder">
122
+ <div className="gif-recorder-label">{lf("Pick your project thumbnail")}</div>
123
+ <div className="gif-recorder-actions">
124
+ {<Button className="teal inverted"
125
+ title={screenshotLabel}
126
+ label={screenshotLabel}
127
+ leftIcon="fas fa-camera"
128
+ onClick={handleScreenshotClick} />}
129
+ {<Button className="teal inverted"
130
+ title={recorderState === "recording" ? stopRecordingLabel : startRecordingLabel}
131
+ label={recorderState === "recording" ? stopRecordingLabel : startRecordingLabel}
132
+ leftIcon={`fas fa-${recorderState === "recording" ? "square" : "circle"}`}
133
+ onClick={handleRecordClick} />}
134
+ </div>
135
+ </div>
136
+ </>
137
+ }
@@ -230,21 +230,30 @@
230
230
  }
231
231
 
232
232
  /****************************************************
233
- * Circle Buttons *
233
+ * Square Buttons *
234
234
  ****************************************************/
235
235
 
236
- .common-button.circle-button {
236
+ .common-button.square-button {
237
237
  width: 3rem;
238
238
  height: 3rem;
239
239
  overflow: hidden;
240
240
  padding: 0;
241
- border-radius: 2rem;
242
241
 
243
242
  i, i.fas, i.far {
244
243
  margin: 0;
245
244
  }
246
245
  }
247
246
 
247
+
248
+ /****************************************************
249
+ * Circle Buttons *
250
+ ****************************************************/
251
+
252
+ .common-button.circle-button {
253
+ &:extend(.common-button.square-button);
254
+ border-radius: 2rem;
255
+ }
256
+
248
257
  .common-button.circle-button:focus::after {
249
258
  border-radius: 2rem;
250
259
  }
@@ -253,6 +262,14 @@
253
262
  * Social Buttons *
254
263
  ****************************************************/
255
264
 
265
+ .common-button.social-button {
266
+ font-size: 1.5rem;
267
+
268
+ img {
269
+ max-width: 100%;
270
+ }
271
+ }
272
+
256
273
  .common-button.facebook {
257
274
  background: #4267B2;
258
275
  color: @buttonTextColorDarkBackground;
@@ -273,6 +290,16 @@
273
290
  color: @buttonTextColorDarkBackground;
274
291
  }
275
292
 
293
+ .common-button.whatsapp {
294
+ background: #59CE72;
295
+ color: @buttonTextColorDarkBackground;
296
+ }
297
+
298
+ .common-button.microsoft-teams {
299
+ background: #35258F;
300
+ color: @buttonTextColorDarkBackground;
301
+ }
302
+
276
303
  /****************************************************
277
304
  * High Contrast *
278
305
  ****************************************************/