pxt-core 7.4.11 → 7.4.12
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/cli.js +94 -83
- package/built/pxt.js +95 -84
- package/built/pxtlib.js +1 -1
- 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/pxtembed.js +1 -1
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/react-common-skillmap.css +1 -0
- package/built/web/rtlreact-common-skillmap.css +1 -0
- package/built/web/rtlsemantic.css +1 -1
- package/built/web/semantic.css +1 -1
- package/built/web/skillmap/js/{main.55881627.chunk.js → main.b96caef3.chunk.js} +1 -1
- package/package.json +3 -2
- package/react-common/components/Checkbox.tsx +25 -0
- package/react-common/components/Notification.tsx +82 -0
- package/react-common/components/profile/Badge.tsx +33 -0
- package/react-common/components/profile/BadgeInfo.tsx +74 -0
- package/react-common/components/profile/BadgeList.tsx +67 -0
- package/react-common/components/profile/Profile.tsx +42 -0
- package/react-common/components/profile/UserNotification.tsx +32 -0
- package/react-common/components/profile/UserPane.tsx +64 -0
- package/react-common/components/types.d.ts +29 -0
- package/react-common/components/util.tsx +35 -0
- package/{built/web/react-common.css → react-common/styles/profile/profile.less} +1 -0
- package/react-common/styles/react-common-skillmap-core.less +10 -0
- package/react-common/styles/react-common-skillmap.less +12 -0
- package/react-common/styles/react-common.less +1 -0
- package/react-common/tsconfig.json +36 -0
- package/theme/common-components.less +7 -0
- package/theme/common.less +1 -1
- package/theme/highcontrast.less +4 -0
- package/theme/pxt.less +2 -0
- package/theme/tutorial-sidebar.less +64 -6
- package/webapp/public/skillmap.html +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pxt-core",
|
|
3
|
-
"version": "7.4.
|
|
3
|
+
"version": "7.4.12",
|
|
4
4
|
"description": "Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"TypeScript",
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"built/tests/blocksrunner.js",
|
|
50
50
|
"docfiles",
|
|
51
51
|
"theme",
|
|
52
|
-
"common-docs"
|
|
52
|
+
"common-docs",
|
|
53
|
+
"react-common"
|
|
53
54
|
],
|
|
54
55
|
"main": "built/pxt.js",
|
|
55
56
|
"engines": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
export enum CheckboxStatus {
|
|
4
|
+
Selected,
|
|
5
|
+
Unselected,
|
|
6
|
+
Waiting
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CheckboxProps {
|
|
10
|
+
isChecked: CheckboxStatus;
|
|
11
|
+
onClick: (checked: boolean) => void;
|
|
12
|
+
label: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Checkbox = (props: CheckboxProps) => {
|
|
16
|
+
const { isChecked, label } = props;
|
|
17
|
+
return (
|
|
18
|
+
<div className="checkbox">
|
|
19
|
+
{isChecked == CheckboxStatus.Waiting ? <div className={`ui inline mini loader active`}/> :
|
|
20
|
+
<i className={`icon square outline ${isChecked == CheckboxStatus.Selected ? "check":""}`} onClick={() => props.onClick(!(isChecked == CheckboxStatus.Selected))}
|
|
21
|
+
role="checkbox" aria-checked={isChecked == CheckboxStatus.Selected} aria-labelledby={label}/>}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as ReactDOM from "react-dom";
|
|
3
|
+
|
|
4
|
+
export interface NotificationOptions {
|
|
5
|
+
kind?: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
hc?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface NotificationState {
|
|
11
|
+
notifications?: pxt.Map<NotificationOptions>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface NotificationProps {
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class Notification extends React.Component<NotificationProps, NotificationState> {
|
|
18
|
+
|
|
19
|
+
constructor(props?: NotificationProps) {
|
|
20
|
+
super(props);
|
|
21
|
+
this.state = {
|
|
22
|
+
notifications: {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
push(notification: NotificationOptions) {
|
|
27
|
+
const notifications = this.state.notifications;
|
|
28
|
+
const id = ts.pxtc.Util.guidGen();
|
|
29
|
+
Object.keys(notifications).filter(e => notifications[e].kind == notification.kind)
|
|
30
|
+
.forEach(previousNotification => this.remove(previousNotification));
|
|
31
|
+
notifications[id] = notification;
|
|
32
|
+
const that = this;
|
|
33
|
+
// Show for 3 seconds before removing
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
that.remove(id);
|
|
36
|
+
}, 3000);
|
|
37
|
+
|
|
38
|
+
this.setState({ notifications: notifications });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
remove(id: string) {
|
|
42
|
+
const notifications = this.state.notifications;
|
|
43
|
+
if (notifications[id]) {
|
|
44
|
+
delete notifications[id];
|
|
45
|
+
this.setState({ notifications: notifications });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
render() {
|
|
50
|
+
const { notifications } = this.state;
|
|
51
|
+
|
|
52
|
+
function renderNotification(id: string, notification: NotificationOptions) {
|
|
53
|
+
const { kind, text, hc } = notification;
|
|
54
|
+
let cls = 'ignored info message';
|
|
55
|
+
switch (kind) {
|
|
56
|
+
case 'err': cls = 'red inverted segment'; break;
|
|
57
|
+
case 'warn': cls = 'orange inverted segment'; break;
|
|
58
|
+
case 'info': cls = 'teal inverted segment'; break;
|
|
59
|
+
case 'compile': cls = 'ignored info message'; break;
|
|
60
|
+
}
|
|
61
|
+
return <div key={`${id}`} id={`${kind}msg`} className={`ui ${hc} ${cls}`}>{text}</div>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return <div id="msg" aria-live="polite">
|
|
65
|
+
{Object.keys(notifications).map(k => renderNotification(k, notifications[k]))}
|
|
66
|
+
</div>;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let notificationsInitialized = false;
|
|
71
|
+
let notificationMessages: Notification;
|
|
72
|
+
|
|
73
|
+
export function pushNotificationMessage(options: NotificationOptions): void {
|
|
74
|
+
if (!notificationsInitialized) {
|
|
75
|
+
notificationsInitialized = true;
|
|
76
|
+
const wrapper = document.body.appendChild(document.createElement('div'));
|
|
77
|
+
notificationMessages = ReactDOM.render(React.createElement(Notification, options), wrapper);
|
|
78
|
+
notificationMessages.push(options);
|
|
79
|
+
} else if (notificationMessages) {
|
|
80
|
+
notificationMessages.push(options);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { fireClickOnEnter } from "../util";
|
|
3
|
+
|
|
4
|
+
export interface BadgeProps {
|
|
5
|
+
onClick?: (badge: pxt.auth.Badge) => void;
|
|
6
|
+
badge: pxt.auth.Badge;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
isNew?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Badge = (props: BadgeProps) => {
|
|
12
|
+
const { badge, disabled, isNew, onClick } = props;
|
|
13
|
+
|
|
14
|
+
const onBadgeClick = onClick && (() => {
|
|
15
|
+
onClick(badge);
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const image = (disabled && badge.lockedImage) || badge.image;
|
|
19
|
+
const alt = disabled ? pxt.U.lf("Locked '{0}' badge", badge.title) : badge.title;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className={`profile-badge ${onClick ? "clickable" : ""}`}
|
|
23
|
+
role={onClick ? "button" : undefined}
|
|
24
|
+
tabIndex={onClick ? 0 : undefined}
|
|
25
|
+
title={lf("{0} Badge", badge.title)}
|
|
26
|
+
onClick={onBadgeClick}
|
|
27
|
+
onKeyDown={fireClickOnEnter}>
|
|
28
|
+
{isNew && <div className="profile-badge-notification">{pxt.U.lf("New!")}</div>}
|
|
29
|
+
<img src={image} alt={alt} />
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { jsxLF } from "../util";
|
|
3
|
+
import { Badge } from "./Badge";
|
|
4
|
+
|
|
5
|
+
export interface BadgeInfoProps {
|
|
6
|
+
badge: pxt.auth.Badge;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const BadgeInfo = (props: BadgeInfoProps) => {
|
|
10
|
+
const { badge } = props;
|
|
11
|
+
|
|
12
|
+
const date = new Date(badge.timestamp)
|
|
13
|
+
|
|
14
|
+
return <div className="profile-badge-info">
|
|
15
|
+
<div className="profile-badge-info-image">
|
|
16
|
+
<Badge badge={badge} disabled={!badge.timestamp} />
|
|
17
|
+
</div>
|
|
18
|
+
<div className="profile-badge-info-item">
|
|
19
|
+
<div className="profile-badge-info-header">
|
|
20
|
+
{lf("Awarded For:")}
|
|
21
|
+
</div>
|
|
22
|
+
<div className="profile-badge-info-text">
|
|
23
|
+
{badgeDescription(badge)}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
{ badge.timestamp ?
|
|
27
|
+
<div className="profile-badge-info-item">
|
|
28
|
+
<div className="profile-badge-info-header">
|
|
29
|
+
{lf("Awarded On:")}
|
|
30
|
+
</div>
|
|
31
|
+
<div className="profile-badge-info-text">
|
|
32
|
+
{date.toLocaleDateString(pxt.U.userLanguage())}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
: undefined }
|
|
36
|
+
</div>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
export const badgeDescription = (badge: pxt.auth.Badge) => {
|
|
41
|
+
switch (badge.type) {
|
|
42
|
+
case "skillmap-completion":
|
|
43
|
+
return <span>{jsxLF(
|
|
44
|
+
lf("Completing {0}"),
|
|
45
|
+
<a target="_blank" rel="noopener noreferrer" href={sourceURLToSkillmapURL(badge.sourceURL)}>{pxt.U.rlf(badge.title)}</a>
|
|
46
|
+
)}</span>
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sourceURLToSkillmapURL(sourceURL: string) {
|
|
51
|
+
if (sourceURL.indexOf("/api/md/") !== -1) {
|
|
52
|
+
// docs url: https://www.makecode.com/api/md/arcade/skillmap/forest
|
|
53
|
+
const path = sourceURL.split("/api/md/")[1];
|
|
54
|
+
// remove the target from the url
|
|
55
|
+
const docsPath = path.split("/").slice(1).join("/");
|
|
56
|
+
return pxt.webConfig?.skillmapUrl + "#docs:" + docsPath;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// github url: /user/repo#filename
|
|
60
|
+
const parts = sourceURL.split("#");
|
|
61
|
+
|
|
62
|
+
if (parts.length == 2) {
|
|
63
|
+
return pxt.webConfig.skillmapUrl + "#github:https://github.com/" + parts[0] + "/" + parts[1];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (pxt.BrowserUtils.isLocalHostDev()) {
|
|
68
|
+
// local url: skillmap/forest
|
|
69
|
+
return "http://localhost:3000#local:" + sourceURL
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return sourceURL;
|
|
73
|
+
}
|
|
74
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Badge } from "./Badge";
|
|
3
|
+
|
|
4
|
+
export interface BadgeListProps {
|
|
5
|
+
onBadgeClick: (badge: pxt.auth.Badge) => void;
|
|
6
|
+
availableBadges: pxt.auth.Badge[];
|
|
7
|
+
userState: pxt.auth.UserBadgeState;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const BadgeList = (props: BadgeListProps) => {
|
|
11
|
+
const { onBadgeClick, availableBadges, userState } = props;
|
|
12
|
+
|
|
13
|
+
const badges = availableBadges.slice();
|
|
14
|
+
|
|
15
|
+
let unlocked: pxt.Map<boolean> = {};
|
|
16
|
+
|
|
17
|
+
for (const badge of userState.badges) {
|
|
18
|
+
unlocked[badge.id] = true;
|
|
19
|
+
const existing = badges.findIndex(b => b.id === badge.id);
|
|
20
|
+
if (existing > -1) {
|
|
21
|
+
badges[existing] = {
|
|
22
|
+
...badges[existing],
|
|
23
|
+
timestamp: badges[existing].timestamp || badge.timestamp
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
badges.push(badge);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const bg: JSX.Element[] = []
|
|
31
|
+
for (let i = 0; i < Math.max(badges.length + 10, 20); i++) {
|
|
32
|
+
bg.push(<div key={i} className="placeholder-badge" />)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return <div className="profile-badge-list">
|
|
36
|
+
<div className="profile-badge-header">
|
|
37
|
+
<span className="profile-badge-title">
|
|
38
|
+
{lf("Badges")}
|
|
39
|
+
</span>
|
|
40
|
+
|
|
41
|
+
<span className="profile-badge-subtitle">
|
|
42
|
+
{lf("Click each badge to see details")}
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
<div className="profile-badges-scroller">
|
|
46
|
+
<div className="profile-badges">
|
|
47
|
+
<div className="profile-badges-background-container">
|
|
48
|
+
<div className="profile-badges-background">
|
|
49
|
+
{ bg }
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
{ badges.map(badge =>
|
|
53
|
+
<div className="profile-badge-and-title">
|
|
54
|
+
<Badge key={badge.id}
|
|
55
|
+
onClick={onBadgeClick}
|
|
56
|
+
badge={badge}
|
|
57
|
+
disabled={!unlocked[badge.id]}
|
|
58
|
+
/>
|
|
59
|
+
<div className="profile-badge-name">
|
|
60
|
+
{badge.title}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
) }
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/// <reference path="../types.d.ts" />
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { BadgeList } from "./BadgeList";
|
|
5
|
+
import { UserPane } from "./UserPane";
|
|
6
|
+
import { BadgeInfo } from "./BadgeInfo";
|
|
7
|
+
import { CheckboxStatus } from "../Checkbox";
|
|
8
|
+
|
|
9
|
+
export interface ProfileProps {
|
|
10
|
+
user: pxt.auth.State;
|
|
11
|
+
signOut: () => void;
|
|
12
|
+
deleteProfile: () => void;
|
|
13
|
+
checkedEmail: CheckboxStatus;
|
|
14
|
+
onClickedEmail: (isChecked: boolean) => void;
|
|
15
|
+
notification?: pxt.ProfileNotification;
|
|
16
|
+
showModalAsync(options: DialogOptions): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Profile = (props: ProfileProps) => {
|
|
20
|
+
const { user, signOut, deleteProfile, onClickedEmail, notification, checkedEmail, showModalAsync } = props;
|
|
21
|
+
const userProfile = user?.profile || { idp: {} };
|
|
22
|
+
const userBadges = user?.preferences?.badges || { badges: [] };
|
|
23
|
+
|
|
24
|
+
const onBadgeClick = (badge: pxt.auth.Badge) => {
|
|
25
|
+
showModalAsync({
|
|
26
|
+
header: lf("{0} Badge", badge.title),
|
|
27
|
+
size: "tiny",
|
|
28
|
+
hasCloseIcon: true,
|
|
29
|
+
jsx: <BadgeInfo badge={badge} />
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return <div className="user-profile">
|
|
34
|
+
<UserPane profile={userProfile} onSignOutClick={signOut} onDeleteProfileClick={deleteProfile} notification={notification}
|
|
35
|
+
emailChecked={checkedEmail} onEmailCheckClick={onClickedEmail}/>
|
|
36
|
+
<BadgeList
|
|
37
|
+
availableBadges={pxt.appTarget.defaultBadges || []}
|
|
38
|
+
userState={userBadges}
|
|
39
|
+
onBadgeClick={onBadgeClick}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
export interface UserNotificationProps {
|
|
4
|
+
notification: pxt.ProfileNotification;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const UserNotification = (props: UserNotificationProps) => {
|
|
8
|
+
const { message, icon, actionText, link, xicon, title } = props.notification;
|
|
9
|
+
|
|
10
|
+
const onActionClick = () => {
|
|
11
|
+
window.open(link, "_blank");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="profile-notification">
|
|
17
|
+
<div className="profile-notification-icon">
|
|
18
|
+
<i className={`${xicon ? "xicon" : "ui large circular icon "} ${icon}`} />
|
|
19
|
+
</div>
|
|
20
|
+
<div className="profile-notification-title">
|
|
21
|
+
{title}
|
|
22
|
+
</div>
|
|
23
|
+
<div className="profile-notification-message">
|
|
24
|
+
{message}
|
|
25
|
+
</div>
|
|
26
|
+
<button className="ui icon button profile-notification-button" onClick={onActionClick} role="link" >
|
|
27
|
+
<i className="icon external alternate"></i>
|
|
28
|
+
{actionText}
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { fireClickOnEnter } from "../util";
|
|
3
|
+
import { UserNotification } from "./UserNotification";
|
|
4
|
+
import { Checkbox, CheckboxStatus } from "../Checkbox";
|
|
5
|
+
|
|
6
|
+
export interface UserPaneProps {
|
|
7
|
+
profile: pxt.auth.UserProfile;
|
|
8
|
+
notification?: pxt.ProfileNotification;
|
|
9
|
+
emailChecked: CheckboxStatus;
|
|
10
|
+
|
|
11
|
+
onSignOutClick: () => void;
|
|
12
|
+
onDeleteProfileClick: () => void;
|
|
13
|
+
onEmailCheckClick: (isChecked: boolean) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const UserPane = (props: UserPaneProps) => {
|
|
17
|
+
const { profile, onSignOutClick, onDeleteProfileClick, onEmailCheckClick, notification, emailChecked } = props;
|
|
18
|
+
|
|
19
|
+
const { username, displayName, picture } = profile.idp;
|
|
20
|
+
|
|
21
|
+
const checkboxLabel = "email-optin-label"
|
|
22
|
+
|
|
23
|
+
return <div className="profile-user-pane">
|
|
24
|
+
<div className="profile-portrait">
|
|
25
|
+
{ picture?.dataUrl ?
|
|
26
|
+
<img src={picture?.dataUrl} alt={pxt.U.lf("Profile Picture")} />
|
|
27
|
+
: <div className="profile-initials-portrait">
|
|
28
|
+
{pxt.auth.userInitials(profile)}
|
|
29
|
+
</div>
|
|
30
|
+
}
|
|
31
|
+
</div>
|
|
32
|
+
<div className="profile-user-details">
|
|
33
|
+
<div className="profile-display-name">
|
|
34
|
+
{displayName}
|
|
35
|
+
</div>
|
|
36
|
+
{ username &&
|
|
37
|
+
<div className="profile-username">
|
|
38
|
+
{username}
|
|
39
|
+
</div>
|
|
40
|
+
}
|
|
41
|
+
</div>
|
|
42
|
+
{ notification && <UserNotification notification={notification}/> }
|
|
43
|
+
<div className="profile-spacer"></div>
|
|
44
|
+
<div className="profile-email">
|
|
45
|
+
<Checkbox isChecked={emailChecked} onClick={onEmailCheckClick} label={checkboxLabel}/>
|
|
46
|
+
<div id={checkboxLabel}>
|
|
47
|
+
{lf("I would like to receive the MakeCode newsletter. ")}
|
|
48
|
+
<a href="https://makecode.com/privacy" target="_blank" rel="noopener noreferrer">{lf("View Privacy Statement")}</a>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="profile-actions">
|
|
52
|
+
<a role="button"
|
|
53
|
+
tabIndex={0}
|
|
54
|
+
onKeyPress={fireClickOnEnter}
|
|
55
|
+
onClick={onDeleteProfileClick}>
|
|
56
|
+
{lf("Delete Profile")}
|
|
57
|
+
</a>
|
|
58
|
+
<button onClick={onSignOutClick} className="ui icon button sign-out">
|
|
59
|
+
<i className="icon sign-out"></i>
|
|
60
|
+
{lf("Sign Out")}
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/// <reference path="../../built/pxtlib.d.ts" />
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
interface DialogOptions {
|
|
5
|
+
type?: string;
|
|
6
|
+
hideCancel?: boolean;
|
|
7
|
+
disagreeLbl?: string;
|
|
8
|
+
disagreeClass?: string;
|
|
9
|
+
disagreeIcon?: string;
|
|
10
|
+
logos?: string[];
|
|
11
|
+
className?: string;
|
|
12
|
+
header: string;
|
|
13
|
+
headerIcon?: string;
|
|
14
|
+
body?: string;
|
|
15
|
+
jsx?: JSX.Element;
|
|
16
|
+
jsxd?: () => JSX.Element; // dynamic-er version of jsx
|
|
17
|
+
copyable?: string;
|
|
18
|
+
size?: "" | "small" | "fullscreen" | "large" | "mini" | "tiny"; // defaults to "small"
|
|
19
|
+
onLoaded?: (_: HTMLElement) => void;
|
|
20
|
+
// buttons?: sui.ModalButton[];
|
|
21
|
+
timeout?: number;
|
|
22
|
+
modalContext?: string;
|
|
23
|
+
hasCloseIcon?: boolean;
|
|
24
|
+
helpUrl?: string;
|
|
25
|
+
bigHelpButton?: boolean;
|
|
26
|
+
confirmationText?: string; // Display a text input the user must type to confirm.
|
|
27
|
+
confirmationCheckbox?: string; // Display a checkbox the user must check to confirm.
|
|
28
|
+
confirmationGranted?: boolean;
|
|
29
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
export function jsxLF(loc: string, ...rest: JSX.Element[]) {
|
|
4
|
+
const indices: number[] = [];
|
|
5
|
+
|
|
6
|
+
loc.replace(/\{\d\}/g, match => {
|
|
7
|
+
indices.push(parseInt(match.substr(1, 1)));
|
|
8
|
+
return match;
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const out: JSX.Element[] = [];
|
|
12
|
+
|
|
13
|
+
let parts: string[];
|
|
14
|
+
|
|
15
|
+
let i = 0;
|
|
16
|
+
|
|
17
|
+
for (const index of indices) {
|
|
18
|
+
parts = loc.split(`{${index}}`);
|
|
19
|
+
pxt.U.assert(parts.length === 2);
|
|
20
|
+
out.push(<span key={i++}>{parts[0]}</span>);
|
|
21
|
+
out.push(<span key={i++}>{rest[index]}</span>);
|
|
22
|
+
loc = parts[1]
|
|
23
|
+
}
|
|
24
|
+
out.push(<span key={i++}>{loc}</span>);
|
|
25
|
+
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function fireClickOnEnter(e: React.KeyboardEvent<HTMLElement>) {
|
|
30
|
+
const charCode = (typeof e.which == "number") ? e.which : e.keyCode;
|
|
31
|
+
if (charCode === 13 /* enter */ || charCode === 32 /* space */) {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
e.currentTarget.click();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is the same as react-common-skillmap.less except it doesn't import any
|
|
3
|
+
* variables from the target. This is used for pxt-core's css build
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import variables from pxt-core
|
|
7
|
+
@import "themes/default/globals/site.variables";
|
|
8
|
+
@import "themes/pxt/globals/site.variables";
|
|
9
|
+
|
|
10
|
+
@import "react-common.less";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used for building react-common-skillmap.css
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Import variables from pxt-core
|
|
6
|
+
@import "themes/default/globals/site.variables";
|
|
7
|
+
@import "themes/pxt/globals/site.variables";
|
|
8
|
+
|
|
9
|
+
// Import the variables from the target
|
|
10
|
+
@import "site/globals/site.variables";
|
|
11
|
+
|
|
12
|
+
@import "react-common.less";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "profile/profile.less";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2017",
|
|
4
|
+
"noImplicitAny": true,
|
|
5
|
+
"noImplicitReturns": true,
|
|
6
|
+
"noImplicitThis": true,
|
|
7
|
+
"module": "commonjs",
|
|
8
|
+
"outDir": "../built/react-common",
|
|
9
|
+
"rootDir": ".",
|
|
10
|
+
"newLine": "LF",
|
|
11
|
+
"sourceMap": false,
|
|
12
|
+
"moduleResolution": "node",
|
|
13
|
+
"isolatedModules": false,
|
|
14
|
+
"jsx": "react",
|
|
15
|
+
"experimentalDecorators": true,
|
|
16
|
+
"emitDecoratorMetadata": true,
|
|
17
|
+
"declaration": true,
|
|
18
|
+
"preserveConstEnums": true,
|
|
19
|
+
"lib": [
|
|
20
|
+
"dom",
|
|
21
|
+
"dom.iterable",
|
|
22
|
+
"scripthost",
|
|
23
|
+
"es2017",
|
|
24
|
+
"ES2018.Promise"
|
|
25
|
+
],
|
|
26
|
+
"types": [
|
|
27
|
+
"react",
|
|
28
|
+
"react-dom",
|
|
29
|
+
"resize-observer-browser"
|
|
30
|
+
],
|
|
31
|
+
"incremental": false
|
|
32
|
+
},
|
|
33
|
+
"exclude": [
|
|
34
|
+
"node_modules"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
margin-top: -0.25rem;
|
|
17
17
|
background-color: @red;
|
|
18
18
|
border-radius: 50%;
|
|
19
|
+
animation: notification-glow 5s infinite ease-in-out;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
.tab-icon {
|
|
@@ -31,4 +32,10 @@
|
|
|
31
32
|
.tab-content,
|
|
32
33
|
.tab-content > div {
|
|
33
34
|
height: 100%;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@keyframes notification-glow {
|
|
38
|
+
0%, 50% { box-shadow: 0 0 0.12rem -0.12rem @red; }
|
|
39
|
+
10%, 40% { box-shadow: 0 0 0.12rem 0rem @red; }
|
|
40
|
+
25% { box-shadow: 0 0 0.12rem 0.12rem @white; }
|
|
34
41
|
}
|
package/theme/common.less
CHANGED
package/theme/highcontrast.less
CHANGED