@teambit/preview.ui.preview-placeholder 0.0.562 → 0.0.564
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/dist/preview-placeholder.js +82 -5
- package/dist/preview-placeholder.js.map +1 -1
- package/dist/preview-placeholder.module.scss +79 -6
- package/package.json +4 -5
- package/preview-placeholder.module.scss +79 -6
- package/preview-placeholder.tsx +129 -15
- /package/dist/{preview-1768941159169.js → preview-1774038463160.js} +0 -0
|
@@ -44,9 +44,83 @@ const react_1 = __importStar(require("react"));
|
|
|
44
44
|
const compositions_1 = require("@teambit/compositions");
|
|
45
45
|
const design_ui_heading_1 = require("@teambit/design.ui.heading");
|
|
46
46
|
const toolbox_string_capitalize_1 = require("@teambit/toolbox.string.capitalize");
|
|
47
|
-
const base_ui_loaders_skeleton_1 = require("@teambit/base-ui.loaders.skeleton");
|
|
48
47
|
const docs_1 = require("@teambit/docs");
|
|
49
48
|
const preview_placeholder_module_scss_1 = __importDefault(require("./preview-placeholder.module.scss"));
|
|
49
|
+
// ── BrowserSkeleton ─────────────────────────────────────────────────────────
|
|
50
|
+
function BrowserSkeleton() {
|
|
51
|
+
return (react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserSkeleton },
|
|
52
|
+
react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserToolbar },
|
|
53
|
+
react_1.default.createElement("span", { className: preview_placeholder_module_scss_1.default.browserDot }),
|
|
54
|
+
react_1.default.createElement("span", { className: preview_placeholder_module_scss_1.default.browserDot }),
|
|
55
|
+
react_1.default.createElement("span", { className: preview_placeholder_module_scss_1.default.browserDot }),
|
|
56
|
+
react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserUrlBar })),
|
|
57
|
+
react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserBody },
|
|
58
|
+
react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserLine1 }),
|
|
59
|
+
react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserLine2 }),
|
|
60
|
+
react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserLine3 }),
|
|
61
|
+
react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.browserLine4 }))));
|
|
62
|
+
}
|
|
63
|
+
// ── Prefetch helper ─────────────────────────────────────────────────────────
|
|
64
|
+
const prefetchedAssets = new Set();
|
|
65
|
+
function prefetchPreviewAssets(url) {
|
|
66
|
+
if (prefetchedAssets.has(url))
|
|
67
|
+
return;
|
|
68
|
+
prefetchedAssets.add(url);
|
|
69
|
+
fetch(url)
|
|
70
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
71
|
+
.then((data) => {
|
|
72
|
+
if (!(data === null || data === void 0 ? void 0 : data.files))
|
|
73
|
+
return;
|
|
74
|
+
for (const file of data.files) {
|
|
75
|
+
if (document.querySelector(`link[href*="${file}"]`))
|
|
76
|
+
continue;
|
|
77
|
+
const link = document.createElement('link');
|
|
78
|
+
link.rel = 'prefetch';
|
|
79
|
+
link.href = file;
|
|
80
|
+
document.head.appendChild(link);
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
.catch(() => { });
|
|
84
|
+
}
|
|
85
|
+
// ── ViewportGate ────────────────────────────────────────────────────────────
|
|
86
|
+
// Defers iframe mounting until near the viewport. Prefetches assets ahead.
|
|
87
|
+
// Does NOT own skeleton state — parent handles that via onLoad.
|
|
88
|
+
function ViewportGate({ previewAssetsUrl, rootMargin = '0px 0px 50% 0px', children, }) {
|
|
89
|
+
const sentinelRef = (0, react_1.useRef)(null);
|
|
90
|
+
const [visible, setVisible] = (0, react_1.useState)(false);
|
|
91
|
+
(0, react_1.useEffect)(() => {
|
|
92
|
+
const el = sentinelRef.current;
|
|
93
|
+
if (!el)
|
|
94
|
+
return;
|
|
95
|
+
// Prefetch observer — warm browser cache 4 viewports ahead
|
|
96
|
+
const prefetchObs = previewAssetsUrl
|
|
97
|
+
? new IntersectionObserver(([e]) => {
|
|
98
|
+
if (e.isIntersecting) {
|
|
99
|
+
prefetchPreviewAssets(previewAssetsUrl);
|
|
100
|
+
prefetchObs.disconnect();
|
|
101
|
+
}
|
|
102
|
+
}, { rootMargin: '0px 0px 150% 0px' })
|
|
103
|
+
: null;
|
|
104
|
+
// Mount observer — mount iframe when 2 viewports away
|
|
105
|
+
const mountObs = new IntersectionObserver(([e]) => {
|
|
106
|
+
if (e.isIntersecting) {
|
|
107
|
+
mountObs.disconnect();
|
|
108
|
+
prefetchObs === null || prefetchObs === void 0 ? void 0 : prefetchObs.disconnect();
|
|
109
|
+
if (previewAssetsUrl)
|
|
110
|
+
prefetchPreviewAssets(previewAssetsUrl);
|
|
111
|
+
setVisible(true);
|
|
112
|
+
}
|
|
113
|
+
}, { rootMargin });
|
|
114
|
+
prefetchObs === null || prefetchObs === void 0 ? void 0 : prefetchObs.observe(el);
|
|
115
|
+
mountObs.observe(el);
|
|
116
|
+
return () => {
|
|
117
|
+
prefetchObs === null || prefetchObs === void 0 ? void 0 : prefetchObs.disconnect();
|
|
118
|
+
mountObs.disconnect();
|
|
119
|
+
};
|
|
120
|
+
}, []);
|
|
121
|
+
return (react_1.default.createElement("div", { ref: sentinelRef, className: preview_placeholder_module_scss_1.default.viewportGate }, visible ? children : null));
|
|
122
|
+
}
|
|
123
|
+
// ── PreviewPlaceholder ──────────────────────────────────────────────────────
|
|
50
124
|
function getCompositions(component) {
|
|
51
125
|
const entry = component.get(compositions_1.CompositionsAspect.id);
|
|
52
126
|
if (!entry)
|
|
@@ -83,6 +157,7 @@ function PreviewPlaceholder(_a) {
|
|
|
83
157
|
const serverUrl = (_c = component === null || component === void 0 ? void 0 : component.server) === null || _c === void 0 ? void 0 : _c.url;
|
|
84
158
|
const prevServerUrlRef = (0, react_1.useRef)(serverUrl);
|
|
85
159
|
const [forceRender, setForceRender] = react_1.default.useState(0);
|
|
160
|
+
const [previewLoaded, setPreviewLoaded] = (0, react_1.useState)(false);
|
|
86
161
|
(0, react_1.useEffect)(() => {
|
|
87
162
|
if (prevServerUrlRef.current !== serverUrl && shouldShowPreview) {
|
|
88
163
|
prevServerUrlRef.current = serverUrl;
|
|
@@ -107,10 +182,12 @@ function PreviewPlaceholder(_a) {
|
|
|
107
182
|
const name = component.id.toString();
|
|
108
183
|
if (!serverUrl || (!shouldShowPreview && component.buildStatus === 'pending'))
|
|
109
184
|
return (react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.previewPlaceholder, "data-tip": "", "data-for": name },
|
|
110
|
-
react_1.default.createElement(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
185
|
+
react_1.default.createElement(BrowserSkeleton, null)));
|
|
186
|
+
return (react_1.default.createElement("div", { key: `${name}-${serverUrl}-${forceRender}`, className: preview_placeholder_module_scss_1.default.previewCard },
|
|
187
|
+
react_1.default.createElement(ViewportGate, { previewAssetsUrl: `/api/${name}/~aspect/preview-assets` },
|
|
188
|
+
react_1.default.createElement(compositions_1.ComponentComposition, { component: component, composition: selectedPreview, pubsub: false, includeEnv: true, loading: 'lazy', viewport: 1280, queryParams: 'disableCta=true', onLoad: () => setPreviewLoaded(true) })),
|
|
189
|
+
!previewLoaded && (react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.skeletonOverlay },
|
|
190
|
+
react_1.default.createElement(BrowserSkeleton, null))),
|
|
114
191
|
react_1.default.createElement("div", { className: preview_placeholder_module_scss_1.default.previewOverlay })));
|
|
115
192
|
}
|
|
116
193
|
const PREVIEW_COMPOSITION_SUFFIX = 'Preview';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview-placeholder.js","sourceRoot":"","sources":["../preview-placeholder.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"preview-placeholder.js","sourceRoot":"","sources":["../preview-placeholder.tsx"],"names":[],"mappings":"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"}
|
|
@@ -78,17 +78,90 @@
|
|
|
78
78
|
cursor: pointer;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
// ── Viewport gate & skeleton overlay ────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
.previewCard {
|
|
84
|
+
position: relative;
|
|
85
|
+
width: 100%;
|
|
86
|
+
height: 100%;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.viewportGate {
|
|
82
90
|
width: 100%;
|
|
83
91
|
height: 100%;
|
|
84
|
-
position: relative;
|
|
85
|
-
color: var(--skeleton-color, #e0e0e0);
|
|
86
|
-
opacity: 0.7;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
|
-
.
|
|
94
|
+
.skeletonOverlay {
|
|
95
|
+
position: absolute;
|
|
96
|
+
top: 0;
|
|
97
|
+
left: 0;
|
|
98
|
+
width: 100%;
|
|
99
|
+
height: 100%;
|
|
100
|
+
z-index: 10;
|
|
101
|
+
background: var(--surface-color, #ffffff);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Browser-chrome skeleton ─────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
.browserSkeleton {
|
|
90
107
|
width: 100%;
|
|
91
108
|
height: 100%;
|
|
92
109
|
min-height: 200px;
|
|
93
|
-
display:
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.browserToolbar {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 5px;
|
|
119
|
+
padding: 7px 10px;
|
|
120
|
+
border-bottom: 1px solid currentColor;
|
|
121
|
+
opacity: 0.08;
|
|
122
|
+
flex-shrink: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.browserDot {
|
|
126
|
+
width: 7px;
|
|
127
|
+
height: 7px;
|
|
128
|
+
border-radius: 50%;
|
|
129
|
+
background: currentColor;
|
|
130
|
+
flex-shrink: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.browserUrlBar {
|
|
134
|
+
flex: 1;
|
|
135
|
+
height: 14px;
|
|
136
|
+
margin-left: 8px;
|
|
137
|
+
border-radius: 10px;
|
|
138
|
+
background: currentColor;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.browserBody {
|
|
142
|
+
flex: 1;
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
gap: 10px;
|
|
146
|
+
padding: 20px 14px;
|
|
147
|
+
opacity: 0.06;
|
|
148
|
+
|
|
149
|
+
> * {
|
|
150
|
+
height: 10px;
|
|
151
|
+
border-radius: 6px;
|
|
152
|
+
background: currentColor;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.browserLine1 {
|
|
157
|
+
width: 55%;
|
|
158
|
+
}
|
|
159
|
+
.browserLine2 {
|
|
160
|
+
width: 75%;
|
|
161
|
+
}
|
|
162
|
+
.browserLine3 {
|
|
163
|
+
width: 40%;
|
|
164
|
+
}
|
|
165
|
+
.browserLine4 {
|
|
166
|
+
width: 65%;
|
|
94
167
|
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teambit/preview.ui.preview-placeholder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.564",
|
|
4
4
|
"homepage": "https://bit.cloud/teambit/preview/ui/preview-placeholder",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"componentId": {
|
|
7
7
|
"scope": "teambit.preview",
|
|
8
8
|
"name": "ui/preview-placeholder",
|
|
9
|
-
"version": "0.0.
|
|
9
|
+
"version": "0.0.564"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"core-js": "^3.0.0",
|
|
13
|
-
"@teambit/base-ui.loaders.skeleton": "1.0.1",
|
|
14
|
-
"@teambit/design.ui.heading": "1.0.26",
|
|
15
13
|
"@teambit/component-descriptor": "0.0.450",
|
|
16
|
-
"@teambit/
|
|
14
|
+
"@teambit/design.ui.heading": "1.0.26",
|
|
15
|
+
"@teambit/toolbox.string.capitalize": "0.0.508"
|
|
17
16
|
},
|
|
18
17
|
"devDependencies": {
|
|
19
18
|
"@types/react": "^17.0.8",
|
|
@@ -78,17 +78,90 @@
|
|
|
78
78
|
cursor: pointer;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
// ── Viewport gate & skeleton overlay ────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
.previewCard {
|
|
84
|
+
position: relative;
|
|
85
|
+
width: 100%;
|
|
86
|
+
height: 100%;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.viewportGate {
|
|
82
90
|
width: 100%;
|
|
83
91
|
height: 100%;
|
|
84
|
-
position: relative;
|
|
85
|
-
color: var(--skeleton-color, #e0e0e0);
|
|
86
|
-
opacity: 0.7;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
|
-
.
|
|
94
|
+
.skeletonOverlay {
|
|
95
|
+
position: absolute;
|
|
96
|
+
top: 0;
|
|
97
|
+
left: 0;
|
|
98
|
+
width: 100%;
|
|
99
|
+
height: 100%;
|
|
100
|
+
z-index: 10;
|
|
101
|
+
background: var(--surface-color, #ffffff);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Browser-chrome skeleton ─────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
.browserSkeleton {
|
|
90
107
|
width: 100%;
|
|
91
108
|
height: 100%;
|
|
92
109
|
min-height: 200px;
|
|
93
|
-
display:
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
overflow: hidden;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.browserToolbar {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 5px;
|
|
119
|
+
padding: 7px 10px;
|
|
120
|
+
border-bottom: 1px solid currentColor;
|
|
121
|
+
opacity: 0.08;
|
|
122
|
+
flex-shrink: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.browserDot {
|
|
126
|
+
width: 7px;
|
|
127
|
+
height: 7px;
|
|
128
|
+
border-radius: 50%;
|
|
129
|
+
background: currentColor;
|
|
130
|
+
flex-shrink: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.browserUrlBar {
|
|
134
|
+
flex: 1;
|
|
135
|
+
height: 14px;
|
|
136
|
+
margin-left: 8px;
|
|
137
|
+
border-radius: 10px;
|
|
138
|
+
background: currentColor;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.browserBody {
|
|
142
|
+
flex: 1;
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
gap: 10px;
|
|
146
|
+
padding: 20px 14px;
|
|
147
|
+
opacity: 0.06;
|
|
148
|
+
|
|
149
|
+
> * {
|
|
150
|
+
height: 10px;
|
|
151
|
+
border-radius: 6px;
|
|
152
|
+
background: currentColor;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.browserLine1 {
|
|
157
|
+
width: 55%;
|
|
158
|
+
}
|
|
159
|
+
.browserLine2 {
|
|
160
|
+
width: 75%;
|
|
161
|
+
}
|
|
162
|
+
.browserLine3 {
|
|
163
|
+
width: 40%;
|
|
164
|
+
}
|
|
165
|
+
.browserLine4 {
|
|
166
|
+
width: 65%;
|
|
94
167
|
}
|
package/preview-placeholder.tsx
CHANGED
|
@@ -1,14 +1,121 @@
|
|
|
1
1
|
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
-
import React, { useMemo, useEffect, useRef } from 'react';
|
|
2
|
+
import React, { useMemo, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { CompositionsAspect, ComponentComposition, Composition } from '@teambit/compositions';
|
|
4
4
|
import { H3, H5 } from '@teambit/design.ui.heading';
|
|
5
5
|
import { capitalize } from '@teambit/toolbox.string.capitalize';
|
|
6
6
|
import type { ComponentModel } from '@teambit/component';
|
|
7
7
|
import type { ComponentDescriptor } from '@teambit/component-descriptor';
|
|
8
|
-
import { BlockSkeleton } from '@teambit/base-ui.loaders.skeleton';
|
|
9
8
|
import { DocsAspect } from '@teambit/docs';
|
|
10
9
|
import styles from './preview-placeholder.module.scss';
|
|
11
10
|
|
|
11
|
+
// ── BrowserSkeleton ─────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function BrowserSkeleton() {
|
|
14
|
+
return (
|
|
15
|
+
<div className={styles.browserSkeleton}>
|
|
16
|
+
<div className={styles.browserToolbar}>
|
|
17
|
+
<span className={styles.browserDot} />
|
|
18
|
+
<span className={styles.browserDot} />
|
|
19
|
+
<span className={styles.browserDot} />
|
|
20
|
+
<div className={styles.browserUrlBar} />
|
|
21
|
+
</div>
|
|
22
|
+
<div className={styles.browserBody}>
|
|
23
|
+
<div className={styles.browserLine1} />
|
|
24
|
+
<div className={styles.browserLine2} />
|
|
25
|
+
<div className={styles.browserLine3} />
|
|
26
|
+
<div className={styles.browserLine4} />
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Prefetch helper ─────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
const prefetchedAssets = new Set<string>();
|
|
35
|
+
|
|
36
|
+
function prefetchPreviewAssets(url: string) {
|
|
37
|
+
if (prefetchedAssets.has(url)) return;
|
|
38
|
+
prefetchedAssets.add(url);
|
|
39
|
+
|
|
40
|
+
fetch(url)
|
|
41
|
+
.then((res) => (res.ok ? res.json() : null))
|
|
42
|
+
.then((data) => {
|
|
43
|
+
if (!data?.files) return;
|
|
44
|
+
for (const file of data.files) {
|
|
45
|
+
if (document.querySelector(`link[href*="${file}"]`)) continue;
|
|
46
|
+
const link = document.createElement('link');
|
|
47
|
+
link.rel = 'prefetch';
|
|
48
|
+
link.href = file;
|
|
49
|
+
document.head.appendChild(link);
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.catch(() => {});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── ViewportGate ────────────────────────────────────────────────────────────
|
|
56
|
+
// Defers iframe mounting until near the viewport. Prefetches assets ahead.
|
|
57
|
+
// Does NOT own skeleton state — parent handles that via onLoad.
|
|
58
|
+
|
|
59
|
+
function ViewportGate({
|
|
60
|
+
previewAssetsUrl,
|
|
61
|
+
rootMargin = '0px 0px 50% 0px',
|
|
62
|
+
children,
|
|
63
|
+
}: {
|
|
64
|
+
previewAssetsUrl?: string;
|
|
65
|
+
rootMargin?: string;
|
|
66
|
+
children: ReactNode;
|
|
67
|
+
}) {
|
|
68
|
+
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
69
|
+
const [visible, setVisible] = useState(false);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const el = sentinelRef.current;
|
|
73
|
+
if (!el) return;
|
|
74
|
+
|
|
75
|
+
// Prefetch observer — warm browser cache 4 viewports ahead
|
|
76
|
+
const prefetchObs = previewAssetsUrl
|
|
77
|
+
? new IntersectionObserver(
|
|
78
|
+
([e]) => {
|
|
79
|
+
if (e.isIntersecting) {
|
|
80
|
+
prefetchPreviewAssets(previewAssetsUrl);
|
|
81
|
+
prefetchObs!.disconnect();
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{ rootMargin: '0px 0px 150% 0px' }
|
|
85
|
+
)
|
|
86
|
+
: null;
|
|
87
|
+
|
|
88
|
+
// Mount observer — mount iframe when 2 viewports away
|
|
89
|
+
const mountObs = new IntersectionObserver(
|
|
90
|
+
([e]) => {
|
|
91
|
+
if (e.isIntersecting) {
|
|
92
|
+
mountObs.disconnect();
|
|
93
|
+
prefetchObs?.disconnect();
|
|
94
|
+
if (previewAssetsUrl) prefetchPreviewAssets(previewAssetsUrl);
|
|
95
|
+
setVisible(true);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{ rootMargin }
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
prefetchObs?.observe(el);
|
|
102
|
+
mountObs.observe(el);
|
|
103
|
+
|
|
104
|
+
return () => {
|
|
105
|
+
prefetchObs?.disconnect();
|
|
106
|
+
mountObs.disconnect();
|
|
107
|
+
};
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div ref={sentinelRef} className={styles.viewportGate}>
|
|
112
|
+
{visible ? children : null}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── PreviewPlaceholder ──────────────────────────────────────────────────────
|
|
118
|
+
|
|
12
119
|
export function getCompositions(component: ComponentDescriptor) {
|
|
13
120
|
const entry: any = component.get(CompositionsAspect.id);
|
|
14
121
|
if (!entry) return [];
|
|
@@ -53,6 +160,7 @@ export function PreviewPlaceholder({
|
|
|
53
160
|
|
|
54
161
|
const prevServerUrlRef = useRef(serverUrl);
|
|
55
162
|
const [forceRender, setForceRender] = React.useState(0);
|
|
163
|
+
const [previewLoaded, setPreviewLoaded] = useState(false);
|
|
56
164
|
|
|
57
165
|
useEffect(() => {
|
|
58
166
|
if (prevServerUrlRef.current !== serverUrl && shouldShowPreview) {
|
|
@@ -87,23 +195,29 @@ export function PreviewPlaceholder({
|
|
|
87
195
|
if (!serverUrl || (!shouldShowPreview && component.buildStatus === 'pending'))
|
|
88
196
|
return (
|
|
89
197
|
<div className={styles.previewPlaceholder} data-tip="" data-for={name}>
|
|
90
|
-
<
|
|
91
|
-
<BlockSkeleton lines={12} className={styles.skeletonBlock} />
|
|
92
|
-
</div>
|
|
198
|
+
<BrowserSkeleton />
|
|
93
199
|
</div>
|
|
94
200
|
);
|
|
95
201
|
|
|
96
202
|
return (
|
|
97
|
-
<div key={`${name}-${serverUrl}-${forceRender}`}>
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
203
|
+
<div key={`${name}-${serverUrl}-${forceRender}`} className={styles.previewCard}>
|
|
204
|
+
<ViewportGate previewAssetsUrl={`/api/${name}/~aspect/preview-assets`}>
|
|
205
|
+
<ComponentComposition
|
|
206
|
+
component={component}
|
|
207
|
+
composition={selectedPreview}
|
|
208
|
+
pubsub={false}
|
|
209
|
+
includeEnv={true}
|
|
210
|
+
loading={'lazy'}
|
|
211
|
+
viewport={1280}
|
|
212
|
+
queryParams={'disableCta=true'}
|
|
213
|
+
onLoad={() => setPreviewLoaded(true)}
|
|
214
|
+
/>
|
|
215
|
+
</ViewportGate>
|
|
216
|
+
{!previewLoaded && (
|
|
217
|
+
<div className={styles.skeletonOverlay}>
|
|
218
|
+
<BrowserSkeleton />
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
107
221
|
<div className={styles.previewOverlay} />
|
|
108
222
|
</div>
|
|
109
223
|
);
|
|
File without changes
|