pxt-core 7.5.2 → 7.5.5

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.
Files changed (67) hide show
  1. package/built/cli.js +61 -46
  2. package/built/pxt.js +256 -91
  3. package/built/pxtblockly.js +652 -657
  4. package/built/pxtblocks.d.ts +1 -0
  5. package/built/pxtblocks.js +98 -42
  6. package/built/pxtcompiler.js +45 -4
  7. package/built/pxtlib.d.ts +7 -2
  8. package/built/pxtlib.js +77 -39
  9. package/built/pxtpy.js +73 -2
  10. package/built/server.js +4 -0
  11. package/built/target.js +1 -1
  12. package/built/web/authcode/css/main.1cf9dc37.css +2 -0
  13. package/built/web/authcode/js/main.03da4c20.js +2 -0
  14. package/built/web/main.js +1 -1
  15. package/built/web/pxtapp.js +1 -1
  16. package/built/web/pxtasseteditor.js +1 -1
  17. package/built/web/pxtblockly.js +2 -2
  18. package/built/web/pxtblocks.js +1 -1
  19. package/built/web/pxtcompiler.js +1 -1
  20. package/built/web/pxtembed.js +2 -2
  21. package/built/web/pxtlib.js +1 -1
  22. package/built/web/pxtpy.js +1 -1
  23. package/built/web/pxtworker.js +1 -1
  24. package/built/web/react-common-authcode.css +6200 -0
  25. package/built/web/react-common-skillmap.css +1 -1
  26. package/built/web/rtlreact-common-skillmap.css +1 -1
  27. package/built/web/rtlsemantic.css +1 -1
  28. package/built/web/semantic.css +1 -1
  29. package/built/web/skillmap/css/{main.e0620cee.chunk.css → main.73b22966.chunk.css} +1 -1
  30. package/built/web/skillmap/js/{2.f7cdfd75.chunk.js → 2.3e47a285.chunk.js} +2 -2
  31. package/built/web/skillmap/js/main.2485091f.chunk.js +1 -0
  32. package/common-docs/faq.md +1 -1
  33. package/common-docs/translate.md +2 -2
  34. package/docfiles/apptracking.html +1 -1
  35. package/docfiles/tracking.html +1 -1
  36. package/localtypings/projectheader.d.ts +6 -0
  37. package/package.json +5 -3
  38. package/pxtarget.json +1 -1
  39. package/react-common/components/controls/Button.tsx +4 -1
  40. package/react-common/components/controls/EditorToggle.tsx +153 -0
  41. package/react-common/components/controls/FocusList.tsx +120 -0
  42. package/react-common/components/controls/Input.tsx +4 -4
  43. package/react-common/components/controls/Link.tsx +36 -0
  44. package/react-common/components/controls/MenuBar.tsx +5 -95
  45. package/react-common/components/controls/MenuDropdown.tsx +4 -1
  46. package/react-common/components/controls/Textarea.tsx +103 -0
  47. package/react-common/components/share/GifInfo.tsx +63 -0
  48. package/react-common/components/share/GifRecorder.tsx +97 -0
  49. package/react-common/components/share/Share.tsx +49 -0
  50. package/react-common/components/share/ShareInfo.tsx +186 -0
  51. package/react-common/components/share/SocialButton.tsx +53 -0
  52. package/react-common/styles/controls/Button.less +4 -0
  53. package/react-common/styles/controls/EditorToggle.less +271 -0
  54. package/react-common/styles/controls/Modal.less +7 -5
  55. package/react-common/styles/controls/Textarea.less +81 -0
  56. package/react-common/styles/react-common-authcode-core.less +10 -0
  57. package/react-common/styles/react-common-authcode.less +12 -0
  58. package/react-common/styles/react-common-variables.less +19 -0
  59. package/react-common/styles/react-common.less +3 -0
  60. package/react-common/styles/share/share.less +116 -0
  61. package/theme/image-editor/imageEditor.less +8 -116
  62. package/webapp/public/authcode.html +1 -0
  63. package/webapp/public/blockly/blockly_compressed.js +554 -615
  64. package/webapp/public/index.html +1 -1
  65. package/webapp/public/run.html +32 -5
  66. package/webapp/public/skillmap.html +2 -2
  67. package/built/web/skillmap/js/main.f6866fc6.chunk.js +0 -1
@@ -0,0 +1,153 @@
1
+ import * as React from "react";
2
+ import { classList, ControlProps } from "../util";
3
+ import { Button } from "./Button";
4
+ import { FocusList } from "./FocusList";
5
+ import { MenuDropdown } from "./MenuDropdown";
6
+
7
+ export interface EditorToggleProps extends ControlProps {
8
+ items: EditorToggleItem[];
9
+ selected: number;
10
+ id: string;
11
+ }
12
+
13
+ export type EditorToggleItem = BasicEditorToggleItem | DropdownEditorToggleItem;
14
+
15
+ export interface BasicEditorToggleItem {
16
+ label: string;
17
+ title: string;
18
+ focusable: boolean;
19
+ icon?: string;
20
+ onClick: () => void;
21
+ }
22
+
23
+ export interface DropdownEditorToggleItem extends BasicEditorToggleItem {
24
+ items: BasicEditorToggleItem[];
25
+ }
26
+
27
+
28
+ export const EditorToggle = (props: EditorToggleProps) => {
29
+ const {
30
+ id,
31
+ className,
32
+ ariaHidden,
33
+ ariaLabel,
34
+ role,
35
+ items,
36
+ selected
37
+ } = props;
38
+
39
+
40
+ const hasDropdown = items.some(item => isDropdownItem(item));
41
+
42
+ const onKeydown = (ev: React.KeyboardEvent) => {
43
+ // TODO
44
+ }
45
+
46
+ return (
47
+ <div className="common-editor-toggle-outer">
48
+ <EditorToggleAccessibleMenu {...props} />
49
+ <div id={id}
50
+ className={classList("common-editor-toggle", hasDropdown && "has-dropdown", className)}
51
+ role={role || "tablist"}
52
+ aria-hidden={true}
53
+ aria-label={ariaLabel}>
54
+ {items.map((item, index) => {
55
+ const isSelected = selected === index;
56
+ return (
57
+ <div key={index}
58
+ className={classList(
59
+ "common-editor-toggle-item",
60
+ isSelected && "selected",
61
+ isDropdownItem(item) && "common-editor-toggle-item-dropdown",
62
+ )} >
63
+ <ToggleButton item={item} isSelected={isSelected} onKeydown={onKeydown} />
64
+ { isDropdownItem(item) &&
65
+ <MenuDropdown
66
+ id="toggle-dropdown"
67
+ className="toggle-dropdown"
68
+ icon="fas fa-chevron-down"
69
+ title={lf("More options")}
70
+ tabIndex={-1}
71
+ ariaHidden={true}
72
+ items={item.items.map(item => ({
73
+ title: item.title,
74
+ label: item.label,
75
+ onClick: item.onClick,
76
+ leftIcon: item.icon
77
+ }))}
78
+ />
79
+ }
80
+ </div>
81
+ );
82
+ })}
83
+
84
+ <div className="common-editor-toggle-handle"
85
+ aria-hidden={true}
86
+ />
87
+ </div>
88
+ </div>
89
+ );
90
+ }
91
+
92
+ interface ToggleButtonProps {
93
+ item: BasicEditorToggleItem;
94
+ isSelected?: boolean;
95
+ onKeydown: (ev: React.KeyboardEvent) => void;
96
+ }
97
+
98
+ const ToggleButton = (props: ToggleButtonProps) => {
99
+ const { item, isSelected, onKeydown } = props;
100
+ const { label, title, onClick, icon, focusable } = item;
101
+
102
+ return <Button
103
+ role={focusable ? "tab" : undefined}
104
+ tabIndex={-1}
105
+ onKeydown={onKeydown}
106
+ label={label}
107
+ title={title}
108
+ onClick={onClick}
109
+ leftIcon={icon}
110
+ ariaHidden={true}
111
+ />
112
+ }
113
+
114
+
115
+ interface ToggleTab extends BasicEditorToggleItem {
116
+ selected?: boolean;
117
+ }
118
+
119
+ const EditorToggleAccessibleMenu = (props: EditorToggleProps) => {
120
+ const { items, id, selected, ariaHidden } = props;
121
+
122
+ const tabs = items.reduce((prev, current, index) => {
123
+ const next: ToggleTab[] = [...prev]
124
+ next.push({...current});
125
+
126
+ // The selected item will always be a top-level option, not in a dropdown
127
+ if (selected === index) next[next.length - 1].selected = true;
128
+
129
+ if (isDropdownItem(current)) {
130
+ next.push(...current.items.filter(i => i.focusable))
131
+ }
132
+ return next;
133
+ }, [] as ToggleTab[]);
134
+
135
+ return <FocusList id={id} role="tablist" className="common-toggle-accessibility" childTabStopId={id + "-selected"}>
136
+ {tabs.map((item, index) =>
137
+ <Button
138
+ key={index}
139
+ className={item.selected ? "selected" : undefined}
140
+ id={item.selected ? id + "-selected" : undefined}
141
+ role="tab"
142
+ title={item.title}
143
+ label={item.label}
144
+ onClick={item.onClick}
145
+ ariaSelected={item.selected}
146
+ ariaHidden={ariaHidden}/>
147
+ )}
148
+ </FocusList>
149
+ }
150
+
151
+ function isDropdownItem(item: EditorToggleItem): item is DropdownEditorToggleItem {
152
+ return !!(item as DropdownEditorToggleItem).items?.length
153
+ }
@@ -0,0 +1,120 @@
1
+ import * as React from "react";
2
+ import { ContainerProps } from "../util";
3
+
4
+ export interface FocusListProps extends ContainerProps {
5
+ role: string;
6
+ childTabStopId?: string;
7
+ }
8
+
9
+ /**
10
+ * A list of focusable items that represents a single tab stop in the tab order. The
11
+ * children of the list can be navigated between using the arrow keys. Any child with
12
+ * a tabindex other than -1 will be included in the list.
13
+ *
14
+ * If childTabStopId is specified, then the tab stop will be placed on the child with
15
+ * the given id instead of the outer div.
16
+ */
17
+ export const FocusList = (props: FocusListProps) => {
18
+ const {
19
+ id,
20
+ className,
21
+ role,
22
+ ariaHidden,
23
+ ariaLabel,
24
+ childTabStopId,
25
+ children,
26
+ } = props;
27
+
28
+ let focusableElements: HTMLElement[];
29
+ let focusList: HTMLDivElement;
30
+
31
+ const handleRef = (ref: HTMLDivElement) => {
32
+ if (!ref || focusList) return;
33
+
34
+ focusList = ref;
35
+
36
+ const focusable = ref.querySelectorAll(`[tabindex]:not([tabindex="-1"]),[data-isfocusable]`);
37
+ focusableElements = [];
38
+
39
+ for (const element of focusable.values()) {
40
+ focusableElements.push(element as HTMLElement);
41
+
42
+ // Remove them from the tab order, menu items are navigable using the arrow keys
43
+ element.setAttribute("tabindex", "-1");
44
+ element.setAttribute("data-isfocusable", "true");
45
+ }
46
+
47
+ if (childTabStopId) {
48
+ const childTabStop = focusList.querySelector("#" + childTabStopId);
49
+
50
+ if (childTabStop) {
51
+ childTabStop.setAttribute("tabindex", "0");
52
+ }
53
+ }
54
+ }
55
+
56
+ const onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
57
+ if (!focusableElements?.length) return;
58
+
59
+ const target = document.activeElement as HTMLElement;
60
+ const index = focusableElements.indexOf(target);
61
+
62
+ if (index === -1 && target !== focusList) return;
63
+
64
+ if (e.key === "Enter" || e.key === " ") {
65
+ e.preventDefault();
66
+ e.stopPropagation();
67
+
68
+ if (target.click) {
69
+ target.click();
70
+ }
71
+ else {
72
+ // SVG Elements
73
+ target.dispatchEvent(new Event("click"));
74
+ }
75
+ }
76
+ else if (e.key === "ArrowRight") {
77
+ if (index === focusableElements.length - 1 || target === focusList) {
78
+ focusableElements[0].focus();
79
+ }
80
+ else {
81
+ focusableElements[index + 1].focus();
82
+ }
83
+ e.preventDefault();
84
+ e.stopPropagation();
85
+ }
86
+ else if (e.key === "ArrowLeft") {
87
+ if (index === 0 || target === focusList) {
88
+ focusableElements[focusableElements.length - 1].focus();
89
+ }
90
+ else {
91
+ focusableElements[Math.max(index - 1, 0)].focus();
92
+ }
93
+ e.preventDefault();
94
+ e.stopPropagation();
95
+ }
96
+ else if (e.key === "Home") {
97
+ focusableElements[0].focus();
98
+ e.preventDefault();
99
+ e.stopPropagation();
100
+ }
101
+ else if (e.key === "End") {
102
+ focusableElements[focusableElements.length - 1].focus();
103
+ e.preventDefault();
104
+ e.stopPropagation();
105
+ }
106
+ }
107
+
108
+ return (
109
+ <div id={id}
110
+ className={className}
111
+ role={role}
112
+ tabIndex={childTabStopId ? undefined : 0}
113
+ onKeyDown={onKeyDown}
114
+ ref={handleRef}
115
+ aria-hidden={ariaHidden}
116
+ aria-label={ariaLabel}>
117
+ {children}
118
+ </div>
119
+ );
120
+ }
@@ -21,7 +21,7 @@ export interface InputProps extends ControlProps {
21
21
  onIconClick?: (value: string) => void;
22
22
  }
23
23
 
24
- export function Input(props: InputProps) {
24
+ export const Input = (props: InputProps) => {
25
25
  const {
26
26
  id,
27
27
  className,
@@ -78,7 +78,7 @@ export function Input(props: InputProps) {
78
78
 
79
79
  return (
80
80
  <div className={classList("common-input-wrapper", disabled && "disabled", className)}>
81
- {label && <label className="common-input-label">
81
+ {label && <label className="common-input-label" htmlFor={id}>
82
82
  {label}
83
83
  </label>}
84
84
  <div className="common-input-group">
@@ -86,8 +86,8 @@ export function Input(props: InputProps) {
86
86
  id={id}
87
87
  className={classList("common-input", icon && "has-icon")}
88
88
  title={title}
89
- role={role || "button"}
90
- tabIndex={disabled ? 0 : -1}
89
+ role={role || "textbox"}
90
+ tabIndex={disabled ? -1 : 0}
91
91
  aria-label={ariaLabel}
92
92
  aria-hidden={ariaHidden}
93
93
  type={type || "text"}
@@ -0,0 +1,36 @@
1
+ import * as React from "react";
2
+ import { classList, ContainerProps } from "../util";
3
+
4
+ export interface LinkProps extends ContainerProps {
5
+ href: string;
6
+ target?: "_self" | "_blank" | "_parent" | "_top";
7
+ }
8
+
9
+ export const Link = (props: LinkProps) => {
10
+ const {
11
+ id,
12
+ className,
13
+ ariaLabel,
14
+ href,
15
+ target,
16
+ children
17
+ } = props;
18
+
19
+ const classes = classList(
20
+ "common-link",
21
+ className
22
+ );
23
+
24
+ return (
25
+ <a
26
+ id={id}
27
+ className={classes}
28
+ aria-label={ariaLabel}
29
+ href={href}
30
+ target={target}
31
+ rel={target === "_blank" ? "noopener noreferrer" : ""}
32
+ >
33
+ {children}
34
+ </a>
35
+ );
36
+ }
@@ -1,101 +1,11 @@
1
1
  import * as React from "react";
2
2
  import { classList, ContainerProps } from "../util";
3
+ import { FocusList } from "./FocusList";
3
4
 
4
5
  export interface MenuBarProps extends ContainerProps {
5
6
  }
6
7
 
7
- export const MenuBar = (props: MenuBarProps) => {
8
- const {
9
- id,
10
- className,
11
- role,
12
- ariaHidden,
13
- ariaLabel,
14
- children
15
- } = props;
16
-
17
- let focusableElements: HTMLElement[];
18
- let menubar: HTMLDivElement;
19
-
20
- const handleRef = (ref: HTMLDivElement) => {
21
- if (!ref || menubar) return;
22
-
23
- menubar = ref;
24
-
25
- const focusable = ref.querySelectorAll(`[tabindex]:not([tabindex="-1"]),[data-isfocusable]`);
26
- focusableElements = [];
27
-
28
- for (const element of focusable.values()) {
29
- focusableElements.push(element as HTMLElement);
30
-
31
- // Remove them from the tab order, menu items are navigable using the arrow keys
32
- element.setAttribute("tabindex", "-1");
33
- element.setAttribute("data-isfocusable", "true");
34
- }
35
- }
36
-
37
- const onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
38
- if (!focusableElements?.length) return;
39
-
40
- const target = document.activeElement as HTMLElement;
41
- const index = focusableElements.indexOf(target);
42
-
43
- if (index === -1 && target !== menubar) return;
44
-
45
- if (e.key === "Enter" || e.key === " ") {
46
- e.preventDefault();
47
- e.stopPropagation();
48
-
49
- if (target.click) {
50
- target.click();
51
- }
52
- else {
53
- // SVG Elements
54
- target.dispatchEvent(new Event("click"));
55
- }
56
- }
57
- else if (e.key === "ArrowRight") {
58
- if (index === focusableElements.length - 1 || target === menubar) {
59
- focusableElements[0].focus();
60
- }
61
- else {
62
- focusableElements[index + 1].focus();
63
- }
64
- e.preventDefault();
65
- e.stopPropagation();
66
- }
67
- else if (e.key === "ArrowLeft") {
68
- if (index === 0 || target === menubar) {
69
- focusableElements[focusableElements.length - 1].focus();
70
- }
71
- else {
72
- focusableElements[Math.max(index - 1, 0)].focus();
73
- }
74
- e.preventDefault();
75
- e.stopPropagation();
76
- }
77
- else if (e.key === "Home") {
78
- focusableElements[0].focus();
79
- e.preventDefault();
80
- e.stopPropagation();
81
- }
82
- else if (e.key === "End") {
83
- focusableElements[focusableElements.length - 1].focus();
84
- e.preventDefault();
85
- e.stopPropagation();
86
- }
87
- }
88
-
89
- return (
90
- <div id={id}
91
- className={classList("common-menubar", className)}
92
- role={role || "menubar"}
93
- tabIndex={0}
94
- onKeyDown={onKeyDown}
95
- ref={handleRef}
96
- aria-hidden={ariaHidden}
97
- aria-label={ariaLabel}>
98
- {children}
99
- </div>
100
- );
101
- }
8
+ export const MenuBar = (props: MenuBarProps) =>
9
+ <FocusList {...props}
10
+ role="menubar"
11
+ className={classList("common-menubar", props.className)} />
@@ -15,6 +15,7 @@ export interface MenuDropdownProps extends ControlProps {
15
15
  label?: string | JSX.Element;
16
16
  title: string;
17
17
  icon?: string;
18
+ tabIndex?: number;
18
19
  }
19
20
 
20
21
  export const MenuDropdown = (props: MenuDropdownProps) => {
@@ -27,7 +28,8 @@ export const MenuDropdown = (props: MenuDropdownProps) => {
27
28
  items,
28
29
  label,
29
30
  title,
30
- icon
31
+ icon,
32
+ tabIndex
31
33
  } = props;
32
34
 
33
35
  const [ expanded, setExpanded ] = React.useState(false);
@@ -66,6 +68,7 @@ export const MenuDropdown = (props: MenuDropdownProps) => {
66
68
  <Button
67
69
  id={id}
68
70
  label={label}
71
+ tabIndex={tabIndex}
69
72
  buttonRef={handleButtonRef}
70
73
  title={title}
71
74
  leftIcon={icon}
@@ -0,0 +1,103 @@
1
+ import * as React from "react";
2
+ import { classList, ControlProps } from "../util";
3
+
4
+ export interface TextareaProps extends ControlProps {
5
+ initialValue?: string;
6
+ label?: string;
7
+ title?: string;
8
+ placeholder?: string;
9
+ autoComplete?: boolean;
10
+ cols?: number;
11
+ rows?: number;
12
+ disabled?: boolean;
13
+ minLength?: number;
14
+ readOnly?: boolean;
15
+ resize?: "both" | "horizontal" | "vertical";
16
+ wrap?: "hard" | "soft" | "off";
17
+
18
+ onChange?: (newValue: string) => void;
19
+ onEnterKey?: (value: string) => void;
20
+ }
21
+
22
+ export const Textarea = (props: TextareaProps) => {
23
+ const {
24
+ id,
25
+ className,
26
+ role,
27
+ ariaHidden,
28
+ ariaLabel,
29
+ initialValue,
30
+ label,
31
+ title,
32
+ placeholder,
33
+ autoComplete,
34
+ cols,
35
+ rows,
36
+ disabled,
37
+ minLength,
38
+ readOnly,
39
+ resize,
40
+ wrap,
41
+ onChange,
42
+ onEnterKey
43
+ } = props;
44
+
45
+ const [value, setValue] = React.useState(initialValue || "");
46
+
47
+ React.useEffect(() => {
48
+ setValue(initialValue)
49
+ }, [initialValue])
50
+
51
+
52
+ const changeHandler = (e: React.ChangeEvent<any>) => {
53
+ const newValue = (e.target as any).value;
54
+ if (!readOnly && (value !== newValue)) {
55
+ setValue(newValue);
56
+ }
57
+ if (onChange) {
58
+ onChange(newValue);
59
+ }
60
+ }
61
+
62
+ const enterKeyHandler = (e: React.KeyboardEvent) => {
63
+ const charCode = (typeof e.which == "number") ? e.which : e.keyCode;
64
+ if (charCode === /*enter*/ 13 || charCode === /*space*/ 32) {
65
+ if (onEnterKey) {
66
+ e.preventDefault();
67
+ onEnterKey(value);
68
+ }
69
+ }
70
+ }
71
+
72
+ return (
73
+ <div className={classList("common-textarea-wrapper", disabled && "disabled", resize && `resize-${resize}`, className)}>
74
+ {label && <label className="common-textarea-label">
75
+ {label}
76
+ </label>}
77
+ <div className="common-textarea-group">
78
+ <textarea
79
+ id={id}
80
+ className={"common-textarea"}
81
+ title={title}
82
+ role={role || "textbox"}
83
+ tabIndex={disabled ? -1 : 0}
84
+ aria-label={ariaLabel}
85
+ aria-hidden={ariaHidden}
86
+ placeholder={placeholder}
87
+ value={value || ''}
88
+ cols={cols}
89
+ rows={rows}
90
+ minLength={minLength}
91
+ wrap={wrap}
92
+ readOnly={!!readOnly}
93
+ onChange={changeHandler}
94
+ onKeyDown={enterKeyHandler}
95
+ autoComplete={autoComplete ? "" : "off"}
96
+ autoCorrect={autoComplete ? "" : "off"}
97
+ autoCapitalize={autoComplete ? "" : "off"}
98
+ spellCheck={autoComplete}
99
+ disabled={disabled} />
100
+ </div>
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,63 @@
1
+ import * as React from "react";
2
+ import { Button } from "../controls/Button";
3
+ import { GifRecorder } from "./GifRecorder";
4
+
5
+ export interface GifInfoProps {
6
+ initialUri?: string;
7
+
8
+ onApply: (uri: string) => void;
9
+ onCancel: () => void;
10
+
11
+ screenshotAsync?: () => Promise<string>;
12
+ gifRecordAsync?: () => Promise<void>;
13
+ gifRenderAsync?: () => Promise<string | void>;
14
+ gifAddFrame?: (dataUri: ImageData, delay?: number) => boolean;
15
+
16
+ registerSimulatorMsgHandler?: (handler: (msg: any) => void) => void;
17
+ unregisterSimulatorMsgHandler?: () => void;
18
+ }
19
+
20
+ export const GifInfo = (props: GifInfoProps) => {
21
+ const { initialUri, onApply, onCancel, screenshotAsync, gifRecordAsync, gifRenderAsync, gifAddFrame,
22
+ registerSimulatorMsgHandler, unregisterSimulatorMsgHandler } = props;
23
+ const [ uri, setUri ] = React.useState(initialUri);
24
+
25
+ const handleApplyClick = (evt?: any) => {
26
+ onApply(uri);
27
+ }
28
+
29
+ const handleScreenshotClick = async () => {
30
+ const screenshotUri = await screenshotAsync();
31
+ setUri(screenshotUri);
32
+ }
33
+
34
+ const handleRecordStopClick = async () => {
35
+ const gifUri = await gifRenderAsync();
36
+ if (gifUri) setUri(gifUri);
37
+ }
38
+
39
+ return <>
40
+ <span className="thumbnail-label">{lf("Current Thumbnail")}</span>
41
+ <div className="thumbnail-image">
42
+ {uri
43
+ ? <img src={uri} />
44
+ : <div className="thumbnail-placeholder" />
45
+ }
46
+ </div>
47
+ <div className="thumbnail-actions">
48
+ <Button className="primary"
49
+ title={lf("Apply")}
50
+ label={lf("Apply")}
51
+ onClick={handleApplyClick} />
52
+ <Button title={lf("Cancel")}
53
+ label={lf("Cancel")}
54
+ onClick={onCancel} />
55
+ </div>
56
+ <GifRecorder onScreenshot={screenshotAsync ? handleScreenshotClick : undefined}
57
+ onRecordStart={gifRecordAsync}
58
+ onRecordStop={handleRecordStopClick}
59
+ onGifFrame={gifAddFrame}
60
+ registerSimulatorMsgHandler={registerSimulatorMsgHandler}
61
+ unregisterSimulatorMsgHandler={unregisterSimulatorMsgHandler} />
62
+ </>
63
+ }