pxt-core 7.5.4 → 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 (52) hide show
  1. package/built/cli.js +4 -41
  2. package/built/pxt.js +62 -80
  3. package/built/pxtblockly.js +15 -10
  4. package/built/pxtblocks.js +15 -10
  5. package/built/pxtcompiler.js +3 -0
  6. package/built/pxtlib.d.ts +5 -2
  7. package/built/pxtlib.js +55 -39
  8. package/built/target.js +1 -1
  9. package/built/web/main.js +1 -1
  10. package/built/web/pxtapp.js +1 -1
  11. package/built/web/pxtasseteditor.js +1 -1
  12. package/built/web/pxtblockly.js +2 -2
  13. package/built/web/pxtblocks.js +1 -1
  14. package/built/web/pxtcompiler.js +1 -1
  15. package/built/web/pxtembed.js +2 -2
  16. package/built/web/pxtlib.js +1 -1
  17. package/built/web/pxtworker.js +1 -1
  18. package/built/web/react-common-authcode.css +196 -4
  19. package/built/web/react-common-skillmap.css +1 -1
  20. package/built/web/rtlreact-common-skillmap.css +1 -1
  21. package/built/web/rtlsemantic.css +1 -1
  22. package/built/web/semantic.css +1 -1
  23. package/built/web/skillmap/css/{main.e0620cee.chunk.css → main.73b22966.chunk.css} +1 -1
  24. package/built/web/skillmap/js/{2.f7cdfd75.chunk.js → 2.3e47a285.chunk.js} +2 -2
  25. package/built/web/skillmap/js/main.2485091f.chunk.js +1 -0
  26. package/docfiles/apptracking.html +1 -1
  27. package/docfiles/tracking.html +1 -1
  28. package/localtypings/projectheader.d.ts +6 -0
  29. package/package.json +1 -1
  30. package/react-common/components/controls/Button.tsx +4 -1
  31. package/react-common/components/controls/EditorToggle.tsx +83 -39
  32. package/react-common/components/controls/FocusList.tsx +120 -0
  33. package/react-common/components/controls/Input.tsx +4 -4
  34. package/react-common/components/controls/Link.tsx +36 -0
  35. package/react-common/components/controls/MenuBar.tsx +5 -95
  36. package/react-common/components/controls/MenuDropdown.tsx +4 -1
  37. package/react-common/components/controls/Textarea.tsx +103 -0
  38. package/react-common/components/share/GifInfo.tsx +63 -0
  39. package/react-common/components/share/GifRecorder.tsx +97 -0
  40. package/react-common/components/share/Share.tsx +49 -0
  41. package/react-common/components/share/ShareInfo.tsx +186 -0
  42. package/react-common/components/share/SocialButton.tsx +53 -0
  43. package/react-common/styles/controls/EditorToggle.less +38 -0
  44. package/react-common/styles/controls/Modal.less +7 -5
  45. package/react-common/styles/controls/Textarea.less +81 -0
  46. package/react-common/styles/react-common-variables.less +12 -0
  47. package/react-common/styles/react-common.less +2 -0
  48. package/react-common/styles/share/share.less +116 -0
  49. package/theme/image-editor/imageEditor.less +1 -0
  50. package/webapp/public/run.html +32 -5
  51. package/webapp/public/skillmap.html +2 -2
  52. package/built/web/skillmap/js/main.f6866fc6.chunk.js +0 -1
@@ -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
+ }
@@ -0,0 +1,97 @@
1
+ import * as React from "react";
2
+ import { Button } from "../controls/Button";
3
+
4
+ type RecorderState = "default" | "recording" | "rendering";
5
+
6
+ export interface GifRecorderProps {
7
+ onScreenshot?: () => void;
8
+ onRecordStart?: () => void;
9
+ onRecordStop?: () => Promise<void>;
10
+ onGifFrame?: (dataUri: ImageData, delay?: number) => boolean;
11
+ registerSimulatorMsgHandler?: (handler: (msg: any) => void) => void;
12
+ unregisterSimulatorMsgHandler?: () => void;
13
+ }
14
+
15
+ export const GifRecorder = (props: GifRecorderProps) => {
16
+ const { onScreenshot, onRecordStart, onRecordStop, onGifFrame,
17
+ registerSimulatorMsgHandler, unregisterSimulatorMsgHandler } = props;
18
+ const [ recorderState, _setRecorderState ] = React.useState<RecorderState>("default");
19
+ const recorderStateRef = React.useRef(recorderState);
20
+ const targetTheme = pxt.appTarget.appTheme;
21
+
22
+ const setRecorderState = (state: RecorderState) => {
23
+ _setRecorderState(state);
24
+ recorderStateRef.current = state;
25
+ }
26
+
27
+ const onGifRecordStop = async () => {
28
+ setRecorderState("rendering");
29
+ await onRecordStop();
30
+ setRecorderState("default");
31
+ }
32
+
33
+ const handleKeyDown = (e: KeyboardEvent) => {
34
+ const pressed = e.key.toLocaleLowerCase();
35
+ if (targetTheme.simScreenshotKey && pressed === targetTheme.simScreenshotKey.toLocaleLowerCase()) {
36
+ onScreenshot();
37
+ } else if (targetTheme.simGifKey && pressed === targetTheme.simGifKey.toLocaleLowerCase()) {
38
+ if (recorderStateRef.current === "recording") {
39
+ onGifRecordStop();
40
+ } else {
41
+ onGifRecordStart();
42
+ }
43
+ }
44
+ }
45
+
46
+ const handleSimulatorMsg = (e: any) => {
47
+ if (e.type === "screenshot") {
48
+ if (recorderStateRef.current === "recording") {
49
+ // Adds frame, returns true if we've exceeded the max frame count
50
+ if (onGifFrame(e.data, e.delay)) {
51
+ onGifRecordStop();
52
+ }
53
+ } else {
54
+ onScreenshot();
55
+ }
56
+ } else if (e.event === "start") {
57
+ onGifRecordStart();
58
+ } else if (e.event === "stop") {
59
+ onGifRecordStop();
60
+ }
61
+ }
62
+
63
+ React.useEffect(() => {
64
+ document.addEventListener("keydown", handleKeyDown);
65
+ if (registerSimulatorMsgHandler) registerSimulatorMsgHandler(handleSimulatorMsg);
66
+
67
+ return () => {
68
+ document.removeEventListener("keydown", handleKeyDown);
69
+ if (unregisterSimulatorMsgHandler) unregisterSimulatorMsgHandler();
70
+ }
71
+ }, []);
72
+
73
+ const onGifRecordStart = () => {
74
+ setRecorderState("recording");
75
+ onRecordStart();
76
+ }
77
+
78
+ const screenshotLabel = lf("Take screenshot ({0})", targetTheme.simScreenshotKey);
79
+ const startRecordingLabel = lf("Record game play ({0})", targetTheme.simGifKey);
80
+ const stopRecordingLabel = lf("Stop recording ({0})", targetTheme.simGifKey) ;
81
+
82
+ return <div className="gif-recorder">
83
+ <div className="gif-recorder-label">{lf("Pick your project thumbnail")}</div>
84
+ <div className="gif-recorder-actions">
85
+ {!!onScreenshot && <Button className="teal inverted"
86
+ title={screenshotLabel}
87
+ label={screenshotLabel}
88
+ leftIcon="fas fa-camera"
89
+ onClick={onScreenshot} />}
90
+ {!!onRecordStart && <Button className="teal inverted"
91
+ title={recorderState === "recording" ? stopRecordingLabel : startRecordingLabel}
92
+ label={recorderState === "recording" ? stopRecordingLabel : startRecordingLabel}
93
+ leftIcon={`fas fa-${recorderState === "recording" ? "square" : "circle"}`}
94
+ onClick={recorderState === "recording" ? onGifRecordStop : onGifRecordStart} />}
95
+ </div>
96
+ </div>
97
+ }
@@ -0,0 +1,49 @@
1
+ /// <reference path="../types.d.ts" />
2
+
3
+ import * as React from "react";
4
+ import { ShareInfo } from "./ShareInfo";
5
+
6
+ export interface ShareData {
7
+ url: string;
8
+ embed: {
9
+ code?: string;
10
+ editor?: string;
11
+ simulator?: string;
12
+ url?: string;
13
+ }
14
+ qr?: string;
15
+ error?: any;
16
+ }
17
+
18
+ export interface ShareProps {
19
+ projectName: string;
20
+ screenshotUri?: string;
21
+
22
+ screenshotAsync: () => Promise<string>;
23
+ gifRecordAsync: () => Promise<void>;
24
+ gifRenderAsync: () => Promise<string | void>;
25
+ gifAddFrame: (dataUri: ImageData, delay?: number) => boolean;
26
+ publishAsync: (name: string, screenshotUri?: string) => Promise<ShareData>;
27
+ registerSimulatorMsgHandler?: (handler: (msg: any) => void) => void;
28
+ unregisterSimulatorMsgHandler?: () => void;
29
+ }
30
+
31
+ export const Share = (props: ShareProps) => {
32
+ const { projectName, screenshotUri, screenshotAsync, gifRecordAsync, gifRenderAsync, gifAddFrame,
33
+ publishAsync, registerSimulatorMsgHandler, unregisterSimulatorMsgHandler } = props;
34
+
35
+ return <div className="project-share">
36
+ {(!!screenshotAsync || !!gifRecordAsync) && <div className="project-share-simulator">
37
+ <div id="shareLoanedSimulator" />
38
+ </div>}
39
+ <ShareInfo projectName={projectName}
40
+ screenshotUri={screenshotUri}
41
+ screenshotAsync={screenshotAsync}
42
+ gifRecordAsync={gifRecordAsync}
43
+ gifRenderAsync={gifRenderAsync}
44
+ gifAddFrame={gifAddFrame}
45
+ publishAsync={publishAsync}
46
+ registerSimulatorMsgHandler={registerSimulatorMsgHandler}
47
+ unregisterSimulatorMsgHandler={unregisterSimulatorMsgHandler} />
48
+ </div>
49
+ }