@blocklet/launcher-layout 2.1.108 → 2.2.0

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.
@@ -0,0 +1,115 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import styled from '@emotion/styled';
3
+ import PropTypes from 'prop-types';
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ import { jsxs as _jsxs } from "react/jsx-runtime";
6
+ function CompactLayout({
7
+ children,
8
+ bottom,
9
+ onBottomFix
10
+ }) {
11
+ const mainCon = useRef(null);
12
+ const childsCon = useRef(null);
13
+ const compactCon = useRef(null);
14
+ const isFix = useRef(null);
15
+ const [scrollMode, setScrollMode] = useState(false);
16
+ useEffect(() => {
17
+ let inTimer;
18
+ const fixSize = () => {
19
+ if (inTimer) {
20
+ return;
21
+ }
22
+ inTimer = setTimeout(() => {
23
+ if (childsCon.current) {
24
+ if (childsCon.current.scrollHeight > mainCon.current.clientHeight - compactCon.current.clientHeight) {
25
+ if (isFix.current !== 'fix') {
26
+ onBottomFix('fix');
27
+ isFix.current = 'fix';
28
+ }
29
+ setScrollMode(true);
30
+ } else {
31
+ if (isFix.current !== 'scroll') {
32
+ onBottomFix('scroll');
33
+ isFix.current = 'scroll';
34
+ }
35
+ setScrollMode(false);
36
+ }
37
+ }
38
+ inTimer = null;
39
+ }, 50);
40
+ };
41
+ let resizeObs;
42
+ if (window.ResizeObserver) {
43
+ resizeObs = new ResizeObserver(fixSize);
44
+ } else {
45
+ window.addEventListener('resize', fixSize);
46
+ }
47
+ resizeObs.observe(mainCon.current);
48
+ resizeObs.observe(childsCon.current);
49
+ return () => {
50
+ if (resizeObs) {
51
+ resizeObs.disconnect();
52
+ } else {
53
+ window.removeEventListener('resize', fixSize);
54
+ }
55
+ if (inTimer) {
56
+ clearTimeout(inTimer);
57
+ }
58
+ };
59
+ }, [onBottomFix]);
60
+ return /*#__PURE__*/_jsxs(Container, {
61
+ ref: mainCon,
62
+ className: `${scrollMode ? 'scroll-mode' : ''}`,
63
+ children: [/*#__PURE__*/_jsx("div", {
64
+ className: "compact-context",
65
+ ref: childsCon,
66
+ children: children
67
+ }), /*#__PURE__*/_jsx("div", {
68
+ className: "fix-container",
69
+ ref: compactCon,
70
+ children: bottom
71
+ })]
72
+ });
73
+ }
74
+ const Container = styled.div`
75
+ display: flex;
76
+ flex-direction: column;
77
+ width: 100%;
78
+ height: 100%;
79
+ animation: fadein ease 0.5s;
80
+
81
+ .fix-container {
82
+ margin-top: auto;
83
+ width: 100%;
84
+ }
85
+
86
+ &.scroll-mode {
87
+ .compact-context {
88
+ flex: 1;
89
+ overflow-y: auto;
90
+ }
91
+ .fix-container {
92
+ margin-top: auto;
93
+ flex-shrink: 0;
94
+ }
95
+ }
96
+
97
+ @keyframes fadein {
98
+ 0% {
99
+ opacity: 0;
100
+ }
101
+ 100% {
102
+ opacity: 1;
103
+ }
104
+ }
105
+ `;
106
+ CompactLayout.propTypes = {
107
+ children: PropTypes.any,
108
+ bottom: PropTypes.element.isRequired,
109
+ onBottomFix: PropTypes.func
110
+ };
111
+ CompactLayout.defaultProps = {
112
+ children: [],
113
+ onBottomFix: () => {}
114
+ };
115
+ export default CompactLayout;
package/es/content.js ADDED
@@ -0,0 +1,72 @@
1
+ import styled from '@emotion/styled';
2
+ import PropTypes from 'prop-types';
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ function Content({
5
+ children
6
+ }) {
7
+ return /*#__PURE__*/_jsx(Container, {
8
+ children: children
9
+ });
10
+ }
11
+ Content.propTypes = {
12
+ children: PropTypes.any.isRequired
13
+ };
14
+ export default Content;
15
+ const Container = styled.div`
16
+ display: flex;
17
+ flex-direction: column;
18
+ height: 100%;
19
+ width: 100%;
20
+
21
+ .center {
22
+ display: flex;
23
+ justify-content: center;
24
+ }
25
+
26
+ .page-title {
27
+ text-align: center;
28
+ }
29
+
30
+ .toolbar {
31
+ display: flex;
32
+ justify-content: space-between;
33
+ align-items: center;
34
+
35
+ .toolbar_title {
36
+ color: ${props => props.theme.palette.grey['900']};
37
+ font-size: 16px;
38
+ }
39
+ }
40
+
41
+ .page-footer {
42
+ display: flex;
43
+ justify-content: center;
44
+ align-items: center;
45
+
46
+ width: 100%;
47
+ background: ${props => props.theme.palette.common.white};
48
+
49
+ .create-button {
50
+ margin-right: 32px;
51
+ }
52
+
53
+ & > button,
54
+ .button {
55
+ margin: 0 8px;
56
+ }
57
+
58
+ ${props => props.theme.breakpoints.up('md')} {
59
+ padding: 24px;
60
+ & > button,
61
+ .button {
62
+ margin: 0 12px;
63
+ min-width: 200px;
64
+ }
65
+ }
66
+
67
+ ${props => props.theme.breakpoints.down('md')} {
68
+ margin-top: auto;
69
+ padding: 16px;
70
+ }
71
+ }
72
+ `;
@@ -0,0 +1,137 @@
1
+ import { createContext, useContext, useEffect, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ const StepContext = /*#__PURE__*/createContext();
5
+ const {
6
+ Provider
7
+ } = StepContext;
8
+ const matchPath = (path, exact, currentPathname = window.location.pathname.replace(/\/$/, '/')) => {
9
+ if (!path) {
10
+ return false;
11
+ }
12
+ if (exact) {
13
+ return path === currentPathname;
14
+ }
15
+ return new RegExp(path).test(currentPathname);
16
+ };
17
+ function StepProvider({
18
+ children,
19
+ steps,
20
+ mode
21
+ }) {
22
+ const [activeStepKey, setActiveStepKey] = useState('');
23
+ if (!activeStepKey && steps.length) {
24
+ setActiveStepKey(steps[0].key);
25
+ }
26
+ const flatSteps = steps.map(item => {
27
+ if (item.children) {
28
+ return [{
29
+ key: item.key,
30
+ hasChild: true
31
+ }, item.children.map(childItem => {
32
+ return {
33
+ parentKey: item.key,
34
+ key: childItem.key
35
+ };
36
+ })];
37
+ }
38
+ return {
39
+ key: item.key
40
+ };
41
+ }).flat(Infinity);
42
+ const totalSteps = flatSteps.filter(e => !e.hasChild);
43
+
44
+ // 根据 key 获取当前 step 处于的状态;before已超过该步骤;current处于当前步骤;after还未到这个步骤;
45
+ const getStepStatus = key => {
46
+ let activeParentStepKey;
47
+ const activeIndex = flatSteps.findIndex(e => {
48
+ if (e.key === activeStepKey) {
49
+ activeParentStepKey = e.parentKey;
50
+ return true;
51
+ }
52
+ return false;
53
+ });
54
+ if (activeParentStepKey === key) {
55
+ return 'current';
56
+ }
57
+ const targetIndex = flatSteps.findIndex(e => e.key === key);
58
+ if (targetIndex !== -1) {
59
+ if (targetIndex === activeIndex) {
60
+ return 'current';
61
+ }
62
+ if (targetIndex < activeIndex) {
63
+ return 'before';
64
+ }
65
+ }
66
+ return 'after';
67
+ };
68
+
69
+ // can use this to set the activeStep by key
70
+ function setActiveStepByKey(key) {
71
+ setActiveStepKey(key);
72
+ }
73
+
74
+ // TODO: setIndex模式很难定位到child step,尽量使用key方式去定位
75
+ function setActiveStepByIndex(index) {
76
+ if (typeof index === 'function') {
77
+ const newIndex = index(totalSteps.findIndex(e => e.key === activeStepKey));
78
+ setActiveStepKey(totalSteps[newIndex].key);
79
+ } else {
80
+ setActiveStepKey(totalSteps[index].key);
81
+ }
82
+ }
83
+ const activeStep = totalSteps.findIndex(e => e.key === activeStepKey);
84
+ useEffect(() => {
85
+ if (mode === 'history') {
86
+ let stepKey = '';
87
+ steps.some(step => {
88
+ if (step.children && step.children.length) {
89
+ return step.children.some(childStep => {
90
+ if (matchPath(childStep.path, childStep.exact)) {
91
+ stepKey = childStep.key;
92
+ return true;
93
+ }
94
+ return false;
95
+ });
96
+ }
97
+ if (matchPath(step.path, step.exact)) {
98
+ stepKey = step.key;
99
+ return true;
100
+ }
101
+ return false;
102
+ });
103
+ if (stepKey) {
104
+ setActiveStepKey(stepKey);
105
+ }
106
+ }
107
+ return () => {};
108
+ }, [steps, mode]);
109
+ const value = {
110
+ steps,
111
+ totalStepsCount: totalSteps.length,
112
+ getStepStatus,
113
+ setActiveStepByKey,
114
+ setActiveStepByIndex,
115
+ activeStep,
116
+ // judge node can be clicked to back if the mode is 'memory'
117
+ canBackToStep: nodeIndex => {
118
+ return mode === 'memory' && activeStep !== 0 && nodeIndex < activeStep;
119
+ }
120
+ };
121
+ return /*#__PURE__*/_jsx(Provider, {
122
+ value: value,
123
+ children: children
124
+ });
125
+ }
126
+ function useStepContext() {
127
+ return useContext(StepContext);
128
+ }
129
+ StepProvider.propTypes = {
130
+ children: PropTypes.any.isRequired,
131
+ steps: PropTypes.array.isRequired,
132
+ mode: PropTypes.string
133
+ };
134
+ StepProvider.defaultProps = {
135
+ mode: 'history'
136
+ };
137
+ export { StepProvider, useStepContext };
package/es/header.js ADDED
@@ -0,0 +1,337 @@
1
+ import Img from '@arcblock/ux/lib/Img';
2
+ import { getBlockletDisplayName } from '@blocklet/launcher-util/es/util';
3
+ import styled from '@emotion/styled';
4
+ import Skeleton from '@mui/material/Skeleton';
5
+ import useMediaQuery from '@mui/material/useMediaQuery';
6
+ import isEmpty from 'is-empty';
7
+ import PropTypes from 'prop-types';
8
+ import { useEffect, useRef, useState } from 'react';
9
+ import joinUrl from 'url-join';
10
+ import defaultBlockletLogo from './assets/blocklet-default-logo.png';
11
+ import { getLaunchingText } from './locale';
12
+ import { jsx as _jsx } from "react/jsx-runtime";
13
+ import { jsxs as _jsxs } from "react/jsx-runtime";
14
+ function AppHeader({
15
+ blockletMeta,
16
+ subTitle,
17
+ logoUrl,
18
+ locale,
19
+ navLogo,
20
+ loading
21
+ }) {
22
+ const isMobile = useMediaQuery(theme => theme.breakpoints.down('md'));
23
+ const blockletSize = isMobile ? 18 : 48;
24
+ const appNameRef = useRef(null);
25
+ const [appNameSize, setAppNameSize] = useState('');
26
+ useEffect(() => {
27
+ if (appNameRef.current && appNameRef.current.offsetHeight > 34) {
28
+ if (appNameSize === '') {
29
+ setAppNameSize('middle');
30
+ } else if (appNameSize === 'middle') {
31
+ setAppNameSize('small');
32
+ }
33
+ }
34
+ }, [appNameSize]);
35
+ return /*#__PURE__*/_jsxs(Container, {
36
+ className: appNameSize === 'small' ? 'center-mode' : '',
37
+ children: [isMobile ? /*#__PURE__*/_jsx("div", {
38
+ className: "app-icon",
39
+ children: navLogo
40
+ }) : /*#__PURE__*/_jsxs("div", {
41
+ className: "app-icon",
42
+ children: [loading && /*#__PURE__*/_jsx(AppIconSkeleton, {
43
+ blockletSize: blockletSize,
44
+ isMobile: isMobile
45
+ }), !loading && /*#__PURE__*/_jsx(AppIcon, {
46
+ blockletSize: blockletSize,
47
+ logoUrl: logoUrl
48
+ })]
49
+ }), /*#__PURE__*/_jsxs("div", {
50
+ className: "header-title",
51
+ children: [isMobile && subTitle && /*#__PURE__*/_jsx("div", {
52
+ className: "header-title-sub",
53
+ children: subTitle
54
+ }), /*#__PURE__*/_jsxs("div", {
55
+ className: "app-name-content",
56
+ children: [!isMobile && /*#__PURE__*/_jsx("div", {
57
+ className: "launching-context",
58
+ children: subTitle || getLaunchingText(locale)
59
+ }), /*#__PURE__*/_jsx("div", {
60
+ className: `header-title-name ${appNameSize === 'middle' || appNameSize === 'small' ? 'middle-size' : ''}`,
61
+ ref: appNameRef,
62
+ children: /*#__PURE__*/_jsx(AppTitle, {
63
+ logoUrl: logoUrl,
64
+ isMobile: isMobile,
65
+ blockletMeta: blockletMeta,
66
+ blockletSize: blockletSize,
67
+ loading: loading
68
+ })
69
+ })]
70
+ })]
71
+ })]
72
+ });
73
+ }
74
+ AppHeader.propTypes = {
75
+ blockletMeta: PropTypes.object,
76
+ subTitle: PropTypes.any,
77
+ logoUrl: PropTypes.string,
78
+ locale: PropTypes.string,
79
+ navLogo: PropTypes.any,
80
+ loading: PropTypes.bool
81
+ };
82
+ AppHeader.defaultProps = {
83
+ subTitle: '',
84
+ logoUrl: '',
85
+ locale: '',
86
+ navLogo: '',
87
+ loading: false,
88
+ blockletMeta: {}
89
+ };
90
+
91
+ // eslint-disable-next-line react/prop-types
92
+ function AppIconSkeleton({
93
+ blockletSize,
94
+ isMobile
95
+ }) {
96
+ if (isMobile) {
97
+ return /*#__PURE__*/_jsx(Skeleton, {
98
+ variant: "rectangular",
99
+ width: 100,
100
+ height: 16,
101
+ alt: "icon",
102
+ style: {
103
+ marginTop: 4
104
+ }
105
+ });
106
+ }
107
+ return /*#__PURE__*/_jsx(Skeleton, {
108
+ variant: "rectangular",
109
+ width: blockletSize,
110
+ height: blockletSize
111
+ });
112
+ }
113
+
114
+ // eslint-disable-next-line react/prop-types
115
+ function AppIcon({
116
+ logoUrl,
117
+ blockletSize
118
+ }) {
119
+ return /*#__PURE__*/_jsx(Img, {
120
+ // force update
121
+ width: blockletSize,
122
+ height: blockletSize,
123
+ src: logoUrl,
124
+ alt: "blocklet icon",
125
+ fallback: defaultBlockletLogo,
126
+ style: {
127
+ verticalAlign: 'text-bottom'
128
+ }
129
+ }, `layout-app-icon-${logoUrl}`);
130
+ }
131
+
132
+ // eslint-disable-next-line react/prop-types
133
+ function AppTitle({
134
+ isMobile,
135
+ logoUrl,
136
+ blockletMeta,
137
+ blockletSize,
138
+ loading
139
+ }) {
140
+ const name = getBlockletDisplayName(blockletMeta);
141
+ if (loading) {
142
+ return /*#__PURE__*/_jsx(Skeleton, {
143
+ variant: "rectangular",
144
+ width: 100,
145
+ height: 16,
146
+ alt: "icon",
147
+ style: {
148
+ marginTop: 4
149
+ }
150
+ });
151
+ }
152
+ if (isEmpty(blockletMeta)) {
153
+ return /*#__PURE__*/_jsx(Skeleton, {
154
+ variant: "rectangular",
155
+ animation: false,
156
+ width: 100,
157
+ height: 16,
158
+ alt: "icon",
159
+ style: {
160
+ marginTop: 4
161
+ }
162
+ });
163
+ }
164
+ if (isMobile) {
165
+ return /*#__PURE__*/_jsxs(MobileTitleStyle, {
166
+ children: [/*#__PURE__*/_jsx(AppIcon, {
167
+ blockletSize: blockletSize,
168
+ logoUrl: logoUrl
169
+ }), /*#__PURE__*/_jsx("span", {
170
+ className: "ellipsis-title",
171
+ children: getBlockletDisplayName(blockletMeta)
172
+ })]
173
+ });
174
+ }
175
+ return /*#__PURE__*/_jsx(AppLink, {
176
+ target: "_blank",
177
+ href: joinUrl(blockletMeta?.registryUrl || '', `/blocklets/${blockletMeta?.did}`),
178
+ title: name,
179
+ children: name
180
+ });
181
+ }
182
+ const MobileTitleStyle = styled.div`
183
+ .ellipsis-title {
184
+ flex: 1;
185
+ position: absolute;
186
+ left: 0;
187
+ top: 0;
188
+ width: 100%;
189
+ height: 100%;
190
+ white-space: nowrap;
191
+ overflow: hidden;
192
+ text-overflow: ellipsis;
193
+
194
+ ${props => props.theme.breakpoints.down('md')} {
195
+ left: 26px;
196
+ width: calc(100% - 26px);
197
+ }
198
+ }
199
+ `;
200
+ AppTitle.propTypes = {
201
+ blockletMeta: PropTypes.object
202
+ };
203
+ AppTitle.defaultProps = {
204
+ blockletMeta: {}
205
+ };
206
+ export default AppHeader;
207
+ const AppLink = styled.a`
208
+ color: ${props => props.theme.palette.common.black};
209
+ text-decoration: none;
210
+ overflow: hidden;
211
+ text-overflow: ellipsis;
212
+ display: -webkit-box;
213
+ -webkit-line-clamp: 2;
214
+ -webkit-box-orient: vertical;
215
+ font-family: Lato, Avenir, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
216
+ 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
217
+ text-decoration: none;
218
+ font-weight: 700;
219
+ -webkit-font-smoothing: antialiased;
220
+
221
+ ${props => props.theme.breakpoints.down('md')} {
222
+ display: block;
223
+ max-width: calc(100vw - 150px);
224
+ white-space: nowrap;
225
+ overflow: hidden;
226
+ text-overflow: ellipsis;
227
+ }
228
+ `;
229
+ const Container = styled.div`
230
+ display: flex;
231
+ justify-content: flex-start;
232
+ align-items: flex-start;
233
+ width: 100%;
234
+ ${props => props.theme.breakpoints.up('md')} {
235
+ min-height: 70px;
236
+ }
237
+ ${props => props.theme.breakpoints.down('md')} {
238
+ align-items: center;
239
+ }
240
+ .app-name-content {
241
+ width: 100%;
242
+ display: flex;
243
+ align-items: center;
244
+ justify-content: center;
245
+ ${props => props.theme.breakpoints.up('md')} {
246
+ align-items: flex-start;
247
+ flex-direction: column;
248
+ }
249
+ ${props => props.theme.breakpoints.down('md')} {
250
+ height: 22px;
251
+ }
252
+ }
253
+ .launching-context {
254
+ flex-shrink: 0;
255
+ font-size: 14px;
256
+ font-weight: 600;
257
+ color: ${props => props.theme.palette.grey[500]};
258
+ ${props => props.theme.breakpoints.up('md')} {
259
+ margin-bottom: 4px;
260
+ }
261
+ ${props => props.theme.breakpoints.down('md')} {
262
+ font-size: 14px;
263
+ line-height: 15px;
264
+ &:after {
265
+ display: inline-block;
266
+ margin: 0 4px;
267
+ content: ':';
268
+ }
269
+ }
270
+ }
271
+
272
+ &.center-mode {
273
+ align-items: flex-start;
274
+ .launching-context {
275
+ ${props => props.theme.breakpoints.up('sm')} {
276
+ margin-bottom: 0;
277
+ }
278
+ }
279
+ }
280
+
281
+ .app-icon {
282
+ flex-shrink: 0;
283
+ > * {
284
+ display: block;
285
+ vertical-align: middle;
286
+ }
287
+ }
288
+
289
+ .header-title {
290
+ flex: 1;
291
+ display: flex;
292
+ flex-direction: column;
293
+ justify-content: space-around;
294
+ align-items: flex-start;
295
+ margin-left: 24px;
296
+ ${props => props.theme.breakpoints.down('md')} {
297
+ margin-left: 16px;
298
+ }
299
+ .header-title-name {
300
+ position: relative;
301
+ display: flex;
302
+ justify-content: center;
303
+ align-items: center;
304
+ flex: 1;
305
+ height: 100%;
306
+ color: ${props => props.theme.palette.common.black};
307
+ font-weight: 700;
308
+ ${props => props.theme.breakpoints.up('sm')} {
309
+ font-size: 18px;
310
+
311
+ &.middle-size {
312
+ font-size: 16px;
313
+ }
314
+ }
315
+ ${props => props.theme.breakpoints.down('md')} {
316
+ display: block;
317
+ max-width: calc(100vw - 100px);
318
+ font-size: 16px;
319
+ white-space: nowrap;
320
+ overflow: hidden;
321
+ text-overflow: ellipsis;
322
+ }
323
+ }
324
+
325
+ .header-title-sub {
326
+ color: ${props => props.theme.palette.grey[700]};
327
+ font-size: 12px;
328
+ line-height: 16px;
329
+
330
+ ${props => props.theme.breakpoints.down('md')} {
331
+ white-space: nowrap;
332
+ overflow: hidden;
333
+ text-overflow: ellipsis;
334
+ }
335
+ }
336
+ }
337
+ `;