pxt-core 7.5.38 → 7.5.39

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": "7.5.38",
3
+ "version": "7.5.39",
4
4
  "description": "Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors",
5
5
  "keywords": [
6
6
  "TypeScript",
@@ -0,0 +1,48 @@
1
+ import * as React from "react";
2
+ import { classList, ContainerProps, fireClickOnEnter } from "../util";
3
+
4
+ export interface CardProps extends ContainerProps {
5
+ onClick?: () => void;
6
+ tabIndex?: number;
7
+ ariaLabelledBy?: string;
8
+ label?: string;
9
+ labelClass?: string;
10
+ }
11
+
12
+ export const Card = (props: CardProps) => {
13
+ const {
14
+ id,
15
+ className,
16
+ role,
17
+ children,
18
+ ariaDescribedBy,
19
+ ariaLabelledBy,
20
+ ariaHidden,
21
+ ariaLabel,
22
+ onClick,
23
+ label,
24
+ labelClass,
25
+ tabIndex
26
+ } = props;
27
+
28
+ return <div
29
+ id={id}
30
+ className={classList("common-card", className)}
31
+ role={role || (onClick ? "button" : undefined)}
32
+ aria-describedby={ariaDescribedBy}
33
+ aria-labelledby={ariaLabelledBy}
34
+ aria-hidden={ariaHidden}
35
+ aria-label={ariaLabel}
36
+ onClick={onClick}
37
+ tabIndex={tabIndex}
38
+ onKeyDown={fireClickOnEnter}>
39
+ <div className="common-card-body">
40
+ {children}
41
+ </div>
42
+ {label &&
43
+ <label className={classList("common-card-label", labelClass)}>
44
+ {label}
45
+ </label>
46
+ }
47
+ </div>
48
+ }
@@ -0,0 +1,82 @@
1
+ import * as React from "react";
2
+ import { ControlProps } from "../util";
3
+
4
+ export interface LazyImageProps extends ControlProps {
5
+ src: string;
6
+ alt: string;
7
+ title?: string;
8
+ }
9
+
10
+ let observer: IntersectionObserver;
11
+
12
+ export const LazyImage = (props: LazyImageProps) => {
13
+ const {
14
+ id,
15
+ className,
16
+ role,
17
+ src,
18
+ alt,
19
+ title,
20
+ ariaLabel,
21
+ ariaHidden,
22
+ ariaDescribedBy,
23
+ } = props;
24
+
25
+ initObserver();
26
+
27
+ let imageRef: HTMLImageElement;
28
+
29
+ const handleImageRef = (ref: HTMLImageElement) => {
30
+ if (!ref) return;
31
+
32
+ if (imageRef) observer.unobserve(imageRef);
33
+ imageRef = ref;
34
+ observer.observe(ref);
35
+ }
36
+
37
+
38
+
39
+ return <div className="common-lazy-image-wrapper">
40
+ <img
41
+ id={id}
42
+ ref={handleImageRef}
43
+ className={className}
44
+ data-src={src}
45
+ alt={alt}
46
+ title={title}
47
+ role={role}
48
+ aria-label={ariaLabel}
49
+ aria-hidden={ariaHidden}
50
+ aria-describedby={ariaDescribedBy}
51
+ />
52
+ <div className="common-spinner" />
53
+ </div>
54
+ }
55
+
56
+ function initObserver() {
57
+ if (observer) return;
58
+
59
+ const config = {
60
+ // If the image gets within 50px in the Y axis, start the download.
61
+ rootMargin: '50px 0px',
62
+ threshold: 0.01
63
+ };
64
+ const onIntersection: IntersectionObserverCallback = (entries) => {
65
+ entries.forEach(entry => {
66
+ // Are we in viewport?
67
+ if (entry.intersectionRatio > 0) {
68
+ // Stop watching and load the image
69
+ observer.unobserve(entry.target);
70
+ const url = entry.target.getAttribute("data-src");
71
+ (entry.target as HTMLImageElement).src = url;
72
+
73
+ const image = entry.target as HTMLImageElement;
74
+ image.src = url;
75
+ image.onload = () => {
76
+ image.parentElement.classList.add("loaded");
77
+ }
78
+ }
79
+ })
80
+ }
81
+ observer = new IntersectionObserver(onIntersection, config);
82
+ }
@@ -0,0 +1,70 @@
1
+ import * as React from "react";
2
+ import { Button } from "../controls/Button";
3
+ import { Card } from "../controls/Card";
4
+ import { LazyImage } from "../controls/LazyImage";
5
+ import { classList } from "../util";
6
+
7
+ export interface ExtensionCardProps<U> {
8
+ title: string;
9
+ description: string;
10
+ imageUrl?: string;
11
+ learnMoreUrl?: string;
12
+ label?: string;
13
+ onClick?: (value: U) => void;
14
+ extension?: U;
15
+ loading?: boolean;
16
+ }
17
+
18
+ export const ExtensionCard = <U,>(props: ExtensionCardProps<U>) => {
19
+ const {
20
+ title,
21
+ description,
22
+ imageUrl,
23
+ learnMoreUrl,
24
+ label,
25
+ onClick,
26
+ extension,
27
+ loading
28
+ } = props;
29
+
30
+ const onCardClick = () => {
31
+ if (onClick) onClick(extension);
32
+ }
33
+
34
+ const id = pxt.Util.guidGen();
35
+
36
+ return <>
37
+ <Card
38
+ className={classList("common-extension-card", loading && "loading")}
39
+ onClick={onCardClick}
40
+ ariaLabelledBy={id + "-title"}
41
+ ariaDescribedBy={id + "-description"}
42
+ tabIndex={onClick && 0}
43
+ label={label}>
44
+ <div className="common-extension-card-contents">
45
+ {!loading && <>
46
+ <LazyImage src={imageUrl} alt={title} />
47
+ <div className="common-extension-card-title" id={id + "-title"}>
48
+ {title}
49
+ </div>
50
+ <div className="common-extension-card-description">
51
+ <div id={id + "-description"}>
52
+ {description}
53
+ </div>
54
+ </div>
55
+ {learnMoreUrl &&
56
+ <Button
57
+ className="link-button"
58
+ label={lf("Learn More")}
59
+ title={lf("Learn More")}
60
+ onClick={() => {}}
61
+ href={learnMoreUrl}
62
+ />
63
+ }
64
+ </>
65
+ }
66
+ </div>
67
+ <div className="common-spinner"/>
68
+ </Card>
69
+ </>
70
+ }
@@ -223,6 +223,12 @@
223
223
  text-decoration: underline;
224
224
  }
225
225
 
226
+ .common-button.link-button:focus {
227
+ outline: none;
228
+ border: none;
229
+ text-decoration: underline;
230
+ }
231
+
226
232
  /****************************************************
227
233
  * Circle Buttons *
228
234
  ****************************************************/
@@ -0,0 +1,53 @@
1
+ .common-card {
2
+ width: 18rem;
3
+ height: 20rem;
4
+ border-radius: 0.5rem;
5
+ border: 1px solid @inputBorderColor;
6
+ transition: border 0.1s ease;
7
+ position: relative;
8
+
9
+ .common-card-body {
10
+ overflow: hidden;
11
+ border-radius: 0.5rem;
12
+ }
13
+ }
14
+
15
+ .common-card[role="button"] {
16
+ cursor: pointer;
17
+
18
+ &:hover {
19
+ border: 1px solid @inputBorderColorFocus;
20
+ }
21
+ }
22
+
23
+ .common-card-label {
24
+ color: @white;
25
+ background-color: @orange;
26
+
27
+ border-top-left-radius: 0.25rem;
28
+ border-bottom-left-radius: 0.25rem;
29
+
30
+ position: absolute;
31
+ top: 1rem;
32
+ right: -1rem;
33
+
34
+ padding: 0.5rem 0.5rem 0.25rem 0.5rem;
35
+ min-width: 5rem;
36
+ font-size: 16px;
37
+ border-color: darken(@orange, 10%);
38
+ }
39
+
40
+ .common-card-label::after {
41
+ position: absolute;
42
+ content: "";
43
+ top: 100%;
44
+ left: auto;
45
+ right: 0;
46
+ background-color: transparent;
47
+ border-color: transparent;
48
+ border-style: solid;
49
+ border-width: 1.2em 1.2em 0 0;
50
+ border-top-color: inherit;
51
+ width: 0;
52
+ height: 0;
53
+ }
@@ -0,0 +1,29 @@
1
+ .common-lazy-image-wrapper {
2
+ display: flex;
3
+ justify-content: center;
4
+ align-items: center;
5
+
6
+ .common-spinner {
7
+ position: absolute;
8
+ width: 60px;
9
+ height: 60px;
10
+
11
+ opacity: 1;
12
+ transition: opacity 0.3s ease;
13
+ }
14
+
15
+ img {
16
+ opacity: 0;
17
+ transition: opacity 0.3s ease;
18
+ }
19
+ }
20
+
21
+ .common-lazy-image-wrapper.loaded {
22
+ .common-spinner {
23
+ opacity: 0;
24
+ }
25
+
26
+ img {
27
+ opacity: 1;
28
+ }
29
+ }
@@ -0,0 +1,82 @@
1
+ .common-extension-card {
2
+ background-color: @white;
3
+
4
+ .common-card-body {
5
+ display: flex;
6
+ flex-direction: column;
7
+ height: 100%;
8
+ align-items: center;
9
+ justify-content: center;
10
+ }
11
+
12
+ .common-extension-card-title {
13
+ font-weight: 600;
14
+ font-size: 18px;
15
+ padding: 0.5rem 1rem 0.25rem 1rem;
16
+ text-overflow: ellipsis;
17
+ overflow: hidden;
18
+ flex-shrink: 0;
19
+ }
20
+
21
+ .common-extension-card-description {
22
+ flex-grow: 1;
23
+ padding-left: 1rem;
24
+ padding-right: 1rem;
25
+ font-size: 16px;
26
+ overflow: hidden;
27
+
28
+ div {
29
+ text-overflow: ellipsis;
30
+ display: -webkit-box;
31
+ -webkit-box-orient: vertical;
32
+ -webkit-line-clamp: 3;
33
+ overflow: hidden;
34
+ }
35
+ }
36
+
37
+ img {
38
+ width: 100%;
39
+ height: 11rem;
40
+ object-fit: cover;
41
+ flex-shrink: 0;
42
+ }
43
+
44
+ .common-button.link-button {
45
+ text-align: right;
46
+ padding: 0.5rem 1rem;
47
+ background: @white;
48
+ margin: 0;
49
+ border-radius: 0;
50
+ border-top: solid 1px @inputBorderColor;
51
+ flex-shrink: 0;
52
+ }
53
+
54
+ .common-extension-card-contents {
55
+ display: flex;
56
+ flex-direction: column;
57
+ height: 100%;
58
+ width: 100%;
59
+
60
+ opacity: 1;
61
+ transition: opacity 0.3s ease;
62
+ }
63
+
64
+ .common-spinner {
65
+ opacity: 0;
66
+ transition: opacity 0.3s ease;
67
+
68
+ position: absolute;
69
+ width: 60px;
70
+ height: 60px;
71
+ }
72
+ }
73
+
74
+ .common-extension-card.loading {
75
+ .common-extension-card-contents {
76
+ opacity: 0;
77
+ }
78
+
79
+ .common-spinner {
80
+ opacity: 1;
81
+ }
82
+ }
@@ -1,12 +1,15 @@
1
1
  @import "profile/profile.less";
2
2
  @import "share/share.less";
3
+ @import "extensions/ExtensionCard.less";
3
4
  @import "controls/Button.less";
5
+ @import "controls/Card.less";
4
6
  @import "controls/Checkbox.less";
5
7
  @import "controls/DraggableGraph.less";
6
8
  @import "controls/Dropdown.less";
7
9
  @import "controls/EditorToggle.less";
8
10
  @import "controls/Icon.less";
9
11
  @import "controls/Input.less";
12
+ @import "controls/LazyImage.less";
10
13
  @import "controls/MenuDropdown.less";
11
14
  @import "controls/Modal.less";
12
15
  @import "controls/RadioButtonGroup.less";
package/theme/common.less CHANGED
@@ -1142,6 +1142,14 @@ Field editors
1142
1142
  }
1143
1143
  }
1144
1144
  }
1145
+
1146
+ .extension-cards {
1147
+ display: grid;
1148
+ grid-template-columns: repeat(auto-fit, 18rem);
1149
+ gap: 1rem;
1150
+ width: 100%;
1151
+ }
1152
+
1145
1153
  .import-extension-modal {
1146
1154
  .common-modal-body {
1147
1155
  display: flex;