pxt-core 7.4.13 → 7.4.17
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/built/buildengine.js +1 -1
- package/built/cli.js +29 -6
- package/built/gdb.js +1 -1
- package/built/nodeutil.d.ts +1 -1
- package/built/nodeutil.js +2 -2
- package/built/pxt.js +30 -6
- package/built/pxtblockly.js +5 -2
- package/built/pxtblocks.d.ts +1 -0
- package/built/pxtblocks.js +5 -2
- package/built/pxteditor.d.ts +6 -1
- package/built/pxteditor.js +5 -0
- package/built/pxtlib.js +1 -0
- package/built/target.js +1 -1
- package/built/web/main.js +1 -1
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtasseteditor.js +1 -1
- package/built/web/pxtblockly.js +1 -1
- package/built/web/pxtblocks.js +1 -1
- package/built/web/pxteditor.js +1 -1
- package/built/web/pxtembed.js +2 -2
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/react-common-skillmap.css +13 -1
- package/built/web/rtlreact-common-skillmap.css +13 -1
- package/built/web/rtlsemantic.css +14 -2
- package/built/web/semantic.css +14 -2
- package/built/web/skillmap/css/main.cc257f3c.chunk.css +1 -0
- package/built/web/skillmap/js/2.261d5eab.chunk.js +2 -0
- package/built/web/skillmap/js/main.7c45ef9f.chunk.js +1 -0
- package/package.json +4 -3
- package/react-common/components/controls/Button.tsx +85 -0
- package/react-common/components/controls/Checkbox.tsx +47 -0
- package/react-common/components/controls/FocusTrap.tsx +92 -0
- package/react-common/components/controls/Input.tsx +117 -0
- package/react-common/components/controls/List.tsx +28 -0
- package/react-common/components/controls/MenuBar.tsx +94 -0
- package/react-common/components/controls/MenuDropdown.tsx +108 -0
- package/react-common/components/controls/Modal.tsx +108 -0
- package/react-common/components/profile/Profile.tsx +1 -1
- package/react-common/components/profile/UserPane.tsx +12 -8
- package/react-common/components/util.tsx +35 -0
- package/react-common/styles/controls/Button.less +192 -0
- package/react-common/styles/controls/Checkbox.less +24 -0
- package/react-common/styles/controls/Icon.less +11 -0
- package/react-common/styles/controls/Input.less +113 -0
- package/react-common/styles/controls/List.less +12 -0
- package/react-common/styles/controls/MenuDropdown.less +54 -0
- package/react-common/styles/controls/Modal.less +134 -0
- package/react-common/styles/controls/Spinner.less +24 -0
- package/react-common/styles/profile/profile.less +32 -0
- package/react-common/styles/react-common-skillmap.less +1 -1
- package/react-common/styles/react-common-variables.less +67 -0
- package/react-common/styles/react-common.less +13 -1
- package/theme/asset-editor.less +13 -29
- package/webapp/public/skillmap.html +2 -2
- package/built/web/skillmap/css/main.96b1b3f1.chunk.css +0 -1
- package/built/web/skillmap/js/2.7dd06a3a.chunk.js +0 -2
- package/built/web/skillmap/js/main.b96caef3.chunk.js +0 -1
- package/react-common/components/Checkbox.tsx +0 -25
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { classList, ControlProps } from "../util";
|
|
3
|
+
|
|
4
|
+
import { Button } from "./Button";
|
|
5
|
+
|
|
6
|
+
export interface InputProps extends ControlProps {
|
|
7
|
+
initialValue?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
iconTitle?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
type?: string;
|
|
15
|
+
readOnly?: boolean;
|
|
16
|
+
autoComplete?: boolean;
|
|
17
|
+
selectOnClick?: boolean;
|
|
18
|
+
|
|
19
|
+
onChange?: (newValue: string) => void;
|
|
20
|
+
onEnterKey?: (value: string) => void;
|
|
21
|
+
onIconClick?: (value: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Input(props: InputProps) {
|
|
25
|
+
const {
|
|
26
|
+
id,
|
|
27
|
+
className,
|
|
28
|
+
role,
|
|
29
|
+
ariaHidden,
|
|
30
|
+
ariaLabel,
|
|
31
|
+
initialValue,
|
|
32
|
+
label,
|
|
33
|
+
title,
|
|
34
|
+
placeholder,
|
|
35
|
+
icon,
|
|
36
|
+
iconTitle,
|
|
37
|
+
disabled,
|
|
38
|
+
type,
|
|
39
|
+
readOnly,
|
|
40
|
+
autoComplete,
|
|
41
|
+
selectOnClick,
|
|
42
|
+
onChange,
|
|
43
|
+
onEnterKey,
|
|
44
|
+
onIconClick
|
|
45
|
+
} = props;
|
|
46
|
+
|
|
47
|
+
const [value, setValue] = React.useState(initialValue || "");
|
|
48
|
+
|
|
49
|
+
const clickHandler = (evt: React.MouseEvent<any>) => {
|
|
50
|
+
if (selectOnClick) {
|
|
51
|
+
(evt.target as any).select()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const changeHandler = (e: React.ChangeEvent<any>) => {
|
|
56
|
+
const newValue = (e.target as any).value;
|
|
57
|
+
if (!readOnly && (value !== newValue)) {
|
|
58
|
+
setValue(newValue);
|
|
59
|
+
}
|
|
60
|
+
if (onChange) {
|
|
61
|
+
onChange(newValue);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const enterKeyHandler = (e: React.KeyboardEvent) => {
|
|
66
|
+
const charCode = (typeof e.which == "number") ? e.which : e.keyCode;
|
|
67
|
+
if (charCode === /*enter*/13 || charCode === /*space*/32) {
|
|
68
|
+
if (onEnterKey) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
onEnterKey(value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const iconClickHandler = () => {
|
|
76
|
+
if (onIconClick) onIconClick(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className={classList("common-input-wrapper", disabled && "disabled", className)}>
|
|
81
|
+
{label && <label className="common-input-label">
|
|
82
|
+
{label}
|
|
83
|
+
</label>}
|
|
84
|
+
<div className="common-input-group">
|
|
85
|
+
<input
|
|
86
|
+
id={id}
|
|
87
|
+
className={classList("common-input", icon && "has-icon")}
|
|
88
|
+
title={title}
|
|
89
|
+
role={role || "button"}
|
|
90
|
+
tabIndex={disabled ? 0 : -1}
|
|
91
|
+
aria-label={ariaLabel}
|
|
92
|
+
aria-hidden={ariaHidden}
|
|
93
|
+
type={type || "text"}
|
|
94
|
+
placeholder={placeholder}
|
|
95
|
+
value={value || ''}
|
|
96
|
+
readOnly={!!readOnly}
|
|
97
|
+
onClick={clickHandler}
|
|
98
|
+
onChange={changeHandler}
|
|
99
|
+
onKeyDown={enterKeyHandler}
|
|
100
|
+
autoComplete={autoComplete ? "" : "off"}
|
|
101
|
+
autoCorrect={autoComplete ? "" : "off"}
|
|
102
|
+
autoCapitalize={autoComplete ? "" : "off"}
|
|
103
|
+
spellCheck={autoComplete}
|
|
104
|
+
disabled={disabled} />
|
|
105
|
+
{icon && (onIconClick
|
|
106
|
+
? <Button
|
|
107
|
+
leftIcon={icon}
|
|
108
|
+
title={iconTitle}
|
|
109
|
+
disabled={disabled}
|
|
110
|
+
onClick={iconClickHandler} />
|
|
111
|
+
: <i
|
|
112
|
+
className={icon}
|
|
113
|
+
aria-hidden={true} />) }
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { classList, ContainerProps } from "../util";
|
|
3
|
+
|
|
4
|
+
export interface ListProps extends ContainerProps {
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const List = (props: ListProps) => {
|
|
8
|
+
const {
|
|
9
|
+
id,
|
|
10
|
+
className,
|
|
11
|
+
ariaHidden,
|
|
12
|
+
ariaLabel,
|
|
13
|
+
role
|
|
14
|
+
} = props;
|
|
15
|
+
|
|
16
|
+
return <div
|
|
17
|
+
id={id}
|
|
18
|
+
aria-hidden={ariaHidden}
|
|
19
|
+
aria-label={ariaLabel}
|
|
20
|
+
role={role}
|
|
21
|
+
className={classList("common-list", className)}>
|
|
22
|
+
{React.Children.map(props.children, (child, index) =>
|
|
23
|
+
<div key={index} className="common-list-item">
|
|
24
|
+
{child}
|
|
25
|
+
</div>
|
|
26
|
+
)}
|
|
27
|
+
</div>
|
|
28
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { classList, ContainerProps } from "../util";
|
|
3
|
+
|
|
4
|
+
export interface MenuBarProps extends ContainerProps {
|
|
5
|
+
}
|
|
6
|
+
|
|
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
|
+
target.click();
|
|
49
|
+
}
|
|
50
|
+
else if (e.key === "ArrowRight") {
|
|
51
|
+
if (index === focusableElements.length - 1 || target === menubar) {
|
|
52
|
+
focusableElements[0].focus();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
focusableElements[index + 1].focus();
|
|
56
|
+
}
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
e.stopPropagation();
|
|
59
|
+
}
|
|
60
|
+
else if (e.key === "ArrowLeft") {
|
|
61
|
+
if (index === 0 || target === menubar) {
|
|
62
|
+
focusableElements[focusableElements.length - 1].focus();
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
focusableElements[Math.max(index - 1, 0)].focus();
|
|
66
|
+
}
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
e.stopPropagation();
|
|
69
|
+
}
|
|
70
|
+
else if (e.key === "Home") {
|
|
71
|
+
focusableElements[0].focus();
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
}
|
|
75
|
+
else if (e.key === "End") {
|
|
76
|
+
focusableElements[focusableElements.length - 1].focus();
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
e.stopPropagation();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div id={id}
|
|
84
|
+
className={classList("common-menubar", className)}
|
|
85
|
+
role={role || "menubar"}
|
|
86
|
+
tabIndex={0}
|
|
87
|
+
onKeyDown={onKeyDown}
|
|
88
|
+
ref={handleRef}
|
|
89
|
+
aria-hidden={ariaHidden}
|
|
90
|
+
aria-label={ariaLabel}>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { classList, ControlProps } from "../util";
|
|
3
|
+
import { Button, ButtonProps } from "./Button";
|
|
4
|
+
import { FocusTrap } from "./FocusTrap";
|
|
5
|
+
|
|
6
|
+
export interface MenuItem extends ButtonProps {
|
|
7
|
+
role?: "menuitem" | undefined;
|
|
8
|
+
ariaPosInSet?: undefined;
|
|
9
|
+
ariaSetSize?: undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface MenuDropdownProps extends ControlProps {
|
|
13
|
+
id: string;
|
|
14
|
+
items: MenuItem[];
|
|
15
|
+
label?: string | JSX.Element;
|
|
16
|
+
title: string;
|
|
17
|
+
icon?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const MenuDropdown = (props: MenuDropdownProps) => {
|
|
21
|
+
const {
|
|
22
|
+
id,
|
|
23
|
+
className,
|
|
24
|
+
ariaHidden,
|
|
25
|
+
ariaLabel,
|
|
26
|
+
role,
|
|
27
|
+
items,
|
|
28
|
+
label,
|
|
29
|
+
title,
|
|
30
|
+
icon
|
|
31
|
+
} = props;
|
|
32
|
+
|
|
33
|
+
const [ expanded, setExpanded ] = React.useState(false);
|
|
34
|
+
|
|
35
|
+
let container: HTMLDivElement;
|
|
36
|
+
let expandButton: HTMLButtonElement;
|
|
37
|
+
|
|
38
|
+
const handleContainerRef = (ref: HTMLDivElement) => {
|
|
39
|
+
if (!ref) return;
|
|
40
|
+
container = ref;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleButtonRef = (ref: HTMLButtonElement) => {
|
|
44
|
+
if (!ref) return;
|
|
45
|
+
expandButton = ref;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const onMenuButtonClick = () => {
|
|
49
|
+
setExpanded(!expanded);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const onSubpaneEscape = () => {
|
|
53
|
+
setExpanded(false);
|
|
54
|
+
if (expandButton) expandButton.focus();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const onBlur = (e: React.FocusEvent) => {
|
|
58
|
+
if (!container) return;
|
|
59
|
+
if (expanded && !container.contains(e.relatedTarget as HTMLElement)) setExpanded(false);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const classes = classList("common-menu-dropdown", className);
|
|
63
|
+
const menuId = id + "-menu";
|
|
64
|
+
|
|
65
|
+
return <div className={classes} ref={handleContainerRef} onBlur={onBlur}>
|
|
66
|
+
<Button
|
|
67
|
+
id={id}
|
|
68
|
+
label={label}
|
|
69
|
+
buttonRef={handleButtonRef}
|
|
70
|
+
title={title}
|
|
71
|
+
leftIcon={icon}
|
|
72
|
+
role={role || "menuitem"}
|
|
73
|
+
className={classList("menu-button", expanded && "expanded")}
|
|
74
|
+
onClick={onMenuButtonClick}
|
|
75
|
+
ariaHasPopup="true"
|
|
76
|
+
ariaExpanded={expanded}
|
|
77
|
+
ariaControls={expanded ? menuId : undefined}
|
|
78
|
+
ariaLabel={ariaLabel}
|
|
79
|
+
ariaHidden={ariaHidden}
|
|
80
|
+
/>
|
|
81
|
+
{expanded &&
|
|
82
|
+
<div role="menu"
|
|
83
|
+
className="common-menu-dropdown-pane"
|
|
84
|
+
tabIndex={0}
|
|
85
|
+
id={menuId}
|
|
86
|
+
aria-labelledby={id}>
|
|
87
|
+
<FocusTrap arrowKeyNavigation={true} onEscape={onSubpaneEscape}>
|
|
88
|
+
<ul role="presentation">
|
|
89
|
+
{ items.map((item, index) =>
|
|
90
|
+
<li key={index} role="presentation">
|
|
91
|
+
<Button
|
|
92
|
+
{...item}
|
|
93
|
+
className={classList("common-menu-dropdown-item", item.className)}
|
|
94
|
+
onClick={() => {
|
|
95
|
+
setExpanded(false);
|
|
96
|
+
item.onClick();
|
|
97
|
+
}}
|
|
98
|
+
role="menuitem"
|
|
99
|
+
ariaPosInSet={index + 1}
|
|
100
|
+
ariaSetSize={items.length}/>
|
|
101
|
+
</li>
|
|
102
|
+
)}
|
|
103
|
+
</ul>
|
|
104
|
+
</FocusTrap>
|
|
105
|
+
</div>
|
|
106
|
+
}
|
|
107
|
+
</div>
|
|
108
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React = require("react");
|
|
2
|
+
import { classList, ContainerProps } from "../util";
|
|
3
|
+
import { Button } from "./Button";
|
|
4
|
+
import { FocusTrap } from "./FocusTrap";
|
|
5
|
+
|
|
6
|
+
export interface ModalAction {
|
|
7
|
+
label: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
icon?: string;
|
|
11
|
+
xicon?: boolean;
|
|
12
|
+
onClick: () => void;
|
|
13
|
+
url?: string;
|
|
14
|
+
|
|
15
|
+
// TODO: It would be nice to make fullscreen modals their own thing and deprecate this prop. right
|
|
16
|
+
// now it's required to render the back arrow
|
|
17
|
+
fullscreen?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ModalProps extends ContainerProps {
|
|
21
|
+
title: string;
|
|
22
|
+
ariaDescribedBy?: string;
|
|
23
|
+
actions?: ModalAction[];
|
|
24
|
+
onClose?: () => void;
|
|
25
|
+
fullscreen?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const Modal = (props: ModalProps) => {
|
|
29
|
+
const {
|
|
30
|
+
children,
|
|
31
|
+
id,
|
|
32
|
+
className,
|
|
33
|
+
ariaLabel,
|
|
34
|
+
ariaHidden,
|
|
35
|
+
ariaDescribedBy,
|
|
36
|
+
role,
|
|
37
|
+
title,
|
|
38
|
+
actions,
|
|
39
|
+
onClose,
|
|
40
|
+
fullscreen
|
|
41
|
+
} = props;
|
|
42
|
+
|
|
43
|
+
const closeClickHandler = (e?: React.MouseEvent<HTMLButtonElement>) => {
|
|
44
|
+
if (onClose) onClose();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const classes = classList(
|
|
48
|
+
"common-modal-container",
|
|
49
|
+
fullscreen && "fullscreen",
|
|
50
|
+
className
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return <FocusTrap className={classes} onEscape={closeClickHandler}>
|
|
54
|
+
<div id={id}
|
|
55
|
+
className="common-modal"
|
|
56
|
+
role={role || "dialog"}
|
|
57
|
+
aria-hidden={ariaHidden}
|
|
58
|
+
aria-label={ariaLabel}
|
|
59
|
+
aria-describedby={ariaDescribedBy}
|
|
60
|
+
aria-labelledby="modal-title">
|
|
61
|
+
<div className="common-modal-header">
|
|
62
|
+
{fullscreen &&
|
|
63
|
+
<div className="common-modal-back">
|
|
64
|
+
<Button
|
|
65
|
+
className="menu-button"
|
|
66
|
+
onClick={closeClickHandler}
|
|
67
|
+
title={lf("Go Back")}
|
|
68
|
+
label={lf("Go Back")}
|
|
69
|
+
leftIcon="fas fa-arrow-left"
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
}
|
|
73
|
+
<div id="modal-title" className="common-modal-title">
|
|
74
|
+
{title}
|
|
75
|
+
</div>
|
|
76
|
+
{!fullscreen &&
|
|
77
|
+
<div className="common-modal-close">
|
|
78
|
+
<Button
|
|
79
|
+
className="menu-button"
|
|
80
|
+
onClick={closeClickHandler}
|
|
81
|
+
title={lf("Close")}
|
|
82
|
+
rightIcon="fas fa-times-circle"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
}
|
|
86
|
+
</div>
|
|
87
|
+
<div className="common-modal-body">
|
|
88
|
+
{children}
|
|
89
|
+
</div>
|
|
90
|
+
{actions?.length &&
|
|
91
|
+
<div className="common-modal-footer">
|
|
92
|
+
{ actions.map((action, index) =>
|
|
93
|
+
<Button
|
|
94
|
+
key={index}
|
|
95
|
+
className="primary inverted"
|
|
96
|
+
disabled={action.disabled}
|
|
97
|
+
onClick={action.onClick}
|
|
98
|
+
href={action.url}
|
|
99
|
+
label={action.label}
|
|
100
|
+
title={action.label}
|
|
101
|
+
rightIcon={(action.xicon ? "xicon " : "") + action.icon}
|
|
102
|
+
/>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
}
|
|
106
|
+
</div>
|
|
107
|
+
</FocusTrap>
|
|
108
|
+
}
|
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
import { BadgeList } from "./BadgeList";
|
|
5
5
|
import { UserPane } from "./UserPane";
|
|
6
6
|
import { BadgeInfo } from "./BadgeInfo";
|
|
7
|
-
import { CheckboxStatus } from "../
|
|
7
|
+
import { CheckboxStatus } from "../util";
|
|
8
8
|
|
|
9
9
|
export interface ProfileProps {
|
|
10
10
|
user: pxt.auth.State;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { fireClickOnEnter } from "../util";
|
|
2
|
+
import { fireClickOnEnter, CheckboxStatus } from "../util";
|
|
3
3
|
import { UserNotification } from "./UserNotification";
|
|
4
|
-
import { Checkbox
|
|
4
|
+
import { Checkbox } from "../controls/Checkbox";
|
|
5
5
|
|
|
6
6
|
export interface UserPaneProps {
|
|
7
7
|
profile: pxt.auth.UserProfile;
|
|
@@ -18,7 +18,11 @@ export const UserPane = (props: UserPaneProps) => {
|
|
|
18
18
|
|
|
19
19
|
const { username, displayName, picture } = profile.idp;
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const emailLabel = <>
|
|
22
|
+
{emailChecked === CheckboxStatus.Waiting ? <div className="common-spinner" /> : undefined}
|
|
23
|
+
{lf("I would like to receive the MakeCode newsletter. ")}
|
|
24
|
+
<a href="https://makecode.com/privacy" target="_blank" rel="noopener noreferrer">{lf("View Privacy Statement")}</a>
|
|
25
|
+
</>
|
|
22
26
|
|
|
23
27
|
return <div className="profile-user-pane">
|
|
24
28
|
<div className="profile-portrait">
|
|
@@ -42,11 +46,11 @@ export const UserPane = (props: UserPaneProps) => {
|
|
|
42
46
|
{ notification && <UserNotification notification={notification}/> }
|
|
43
47
|
<div className="profile-spacer"></div>
|
|
44
48
|
<div className="profile-email">
|
|
45
|
-
<Checkbox
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
<Checkbox id="profile-email-checkbox"
|
|
50
|
+
className={emailChecked === CheckboxStatus.Waiting ? "loading" : ""}
|
|
51
|
+
isChecked={emailChecked === CheckboxStatus.Selected}
|
|
52
|
+
onChange={onEmailCheckClick}
|
|
53
|
+
label={emailLabel}/>
|
|
50
54
|
</div>
|
|
51
55
|
<div className="profile-actions">
|
|
52
56
|
<a role="button"
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
|
+
export interface ControlProps {
|
|
4
|
+
id?: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
ariaLabel?: string;
|
|
7
|
+
ariaHidden?: boolean;
|
|
8
|
+
role?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ContainerProps extends React.PropsWithChildren<ControlProps> {
|
|
12
|
+
}
|
|
13
|
+
|
|
3
14
|
export function jsxLF(loc: string, ...rest: JSX.Element[]) {
|
|
4
15
|
const indices: number[] = [];
|
|
5
16
|
|
|
@@ -32,4 +43,28 @@ export function fireClickOnEnter(e: React.KeyboardEvent<HTMLElement>) {
|
|
|
32
43
|
e.preventDefault();
|
|
33
44
|
e.currentTarget.click();
|
|
34
45
|
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function classList(...classes: string[]) {
|
|
49
|
+
return classes
|
|
50
|
+
.filter(c => typeof c === "string")
|
|
51
|
+
.reduce((prev, c) => prev.concat(c.split(" ")), [] as string[])
|
|
52
|
+
.map(c => c.trim())
|
|
53
|
+
.filter(c => !!c)
|
|
54
|
+
.join(" ");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function nodeListToArray<U extends Node>(list: NodeListOf<U>): U[] {
|
|
58
|
+
const out: U[] = [];
|
|
59
|
+
|
|
60
|
+
for (const node of list) {
|
|
61
|
+
out.push(node);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export enum CheckboxStatus {
|
|
67
|
+
Selected,
|
|
68
|
+
Unselected,
|
|
69
|
+
Waiting
|
|
35
70
|
}
|