lupine.components 1.0.17 → 1.0.19
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 +1 -1
- package/src/components/desktop-footer.tsx +17 -0
- package/src/components/desktop-header.tsx +52 -0
- package/src/components/menu-bar.tsx +1 -1
- package/src/components/menu-sidebar.tsx +1 -1
- package/src/components/mobile-components/icon-menu-item-props.ts +6 -0
- package/src/components/mobile-components/index.ts +7 -0
- package/src/components/mobile-components/mobile-footer-menu.tsx +9 -13
- package/src/{frames/header-with-back-frame.tsx → components/mobile-components/mobile-frame-with-header.tsx} +22 -13
- package/src/components/mobile-components/mobile-header-component.tsx +16 -4
- package/src/components/mobile-components/mobile-header-title-icon.tsx +105 -0
- package/src/components/mobile-components/mobile-side-menu.tsx +150 -0
- package/src/components/mobile-components/mobile-top-sys-icon.tsx +18 -0
- package/src/components/mobile-components/mobile-top-sys-menu.tsx +62 -0
- package/src/components/modal.tsx +2 -0
- package/src/components/popup-menu.tsx +30 -24
- package/src/components/progress.tsx +2 -2
- package/src/frames/index.ts +0 -2
- package/src/frames/responsive-frame.tsx +31 -33
- package/src/frames/slider-frame.tsx +20 -8
- package/src/frames/top-frame.tsx +4 -2
- package/src/lib/blob-utils.ts +23 -0
- package/src/lib/dom-utils.ts +32 -0
- package/src/lib/{dom/download.ts → download-link.ts} +1 -1
- package/src/lib/{dom/download-stream.ts → download-stream.ts} +3 -1
- package/src/lib/escape-html.ts +8 -0
- package/src/lib/find-parent-tag.ts +8 -0
- package/src/lib/index.ts +8 -1
- package/src/lib/path-utils.ts +37 -0
- package/src/frames/desktop-frame.tsx +0 -81
- package/src/lib/dom/index.ts +0 -71
- /package/src/lib/{dom/calculate-text-width.ts → calculate-text-width.ts} +0 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { CssProps, RefProps, VNode } from 'lupine.web';
|
|
2
2
|
import { stopPropagation } from '../lib';
|
|
3
3
|
|
|
4
|
-
export type
|
|
4
|
+
export type PopupMenuHookProps = {
|
|
5
|
+
openMenu?: (event?: MouseEvent) => void;
|
|
6
|
+
getValue?: () => string;
|
|
7
|
+
};
|
|
5
8
|
|
|
6
9
|
// defaultValue=<i class='ifc-icon co-cil-hamburger-menu'></i>
|
|
7
10
|
export const PopupMenuWithIcon = (props: PopupMenuProps) => {
|
|
8
|
-
const
|
|
11
|
+
const hook: PopupMenuHookProps = {};
|
|
9
12
|
const css: CssProps = {
|
|
10
13
|
cursor: 'pointer',
|
|
11
14
|
display: 'flex',
|
|
@@ -16,7 +19,7 @@ export const PopupMenuWithIcon = (props: PopupMenuProps) => {
|
|
|
16
19
|
return (
|
|
17
20
|
<div
|
|
18
21
|
onClick={() => {
|
|
19
|
-
|
|
22
|
+
hook.openMenu && hook.openMenu();
|
|
20
23
|
}}
|
|
21
24
|
css={css}
|
|
22
25
|
>
|
|
@@ -30,8 +33,8 @@ export const PopupMenuWithIcon = (props: PopupMenuProps) => {
|
|
|
30
33
|
handleSelected={props.handleSelected}
|
|
31
34
|
handleOpened={props.handleOpened}
|
|
32
35
|
handleClosed={props.handleClosed}
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
noUpdateLabel={props.noUpdateLabel}
|
|
37
|
+
hook={hook}
|
|
35
38
|
noTriangleIcon={props.noTriangleIcon}
|
|
36
39
|
></PopupMenu>
|
|
37
40
|
</div>
|
|
@@ -40,7 +43,7 @@ export const PopupMenuWithIcon = (props: PopupMenuProps) => {
|
|
|
40
43
|
|
|
41
44
|
export type PopupMenuProps = {
|
|
42
45
|
list: string[];
|
|
43
|
-
defaultValue: string
|
|
46
|
+
defaultValue: string;
|
|
44
47
|
tips?: string;
|
|
45
48
|
minWidth?: string;
|
|
46
49
|
maxWidth?: string;
|
|
@@ -48,8 +51,8 @@ export type PopupMenuProps = {
|
|
|
48
51
|
handleSelected?: Function;
|
|
49
52
|
handleOpened?: Function;
|
|
50
53
|
handleClosed?: Function;
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
noUpdateLabel?: boolean;
|
|
55
|
+
hook?: PopupMenuHookProps;
|
|
53
56
|
noTriangleIcon?: boolean;
|
|
54
57
|
align?: 'left' | 'right';
|
|
55
58
|
};
|
|
@@ -57,12 +60,12 @@ export type PopupMenuProps = {
|
|
|
57
60
|
export type PopupMenuWithButtonProps = { label: string } & PopupMenuProps;
|
|
58
61
|
|
|
59
62
|
export const PopupMenuWithButton = (props: PopupMenuWithButtonProps) => {
|
|
60
|
-
const
|
|
63
|
+
const hook: PopupMenuHookProps = {};
|
|
61
64
|
return (
|
|
62
65
|
<button
|
|
63
66
|
class='button-base'
|
|
64
67
|
onClick={() => {
|
|
65
|
-
|
|
68
|
+
hook.openMenu && hook.openMenu();
|
|
66
69
|
}}
|
|
67
70
|
css={{ '>div': { float: 'right', textAlign: 'left' } }}
|
|
68
71
|
>
|
|
@@ -77,8 +80,8 @@ export const PopupMenuWithButton = (props: PopupMenuWithButtonProps) => {
|
|
|
77
80
|
handleSelected={props.handleSelected}
|
|
78
81
|
handleOpened={props.handleOpened}
|
|
79
82
|
handleClosed={props.handleClosed}
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
noUpdateLabel={props.noUpdateLabel}
|
|
84
|
+
hook={hook}
|
|
82
85
|
noTriangleIcon={props.noTriangleIcon}
|
|
83
86
|
align={props.align}
|
|
84
87
|
></PopupMenu>
|
|
@@ -89,11 +92,11 @@ export const PopupMenuWithButton = (props: PopupMenuWithButtonProps) => {
|
|
|
89
92
|
export type PopupMenuWithLabelProps = { label: string } & PopupMenuProps;
|
|
90
93
|
|
|
91
94
|
export const PopupMenuWithLabel = (props: PopupMenuWithLabelProps) => {
|
|
92
|
-
const
|
|
95
|
+
const hook: PopupMenuHookProps = {};
|
|
93
96
|
return (
|
|
94
97
|
<div
|
|
95
98
|
onClick={() => {
|
|
96
|
-
|
|
99
|
+
hook.openMenu && hook.openMenu();
|
|
97
100
|
}}
|
|
98
101
|
css={{ cursor: 'pointer', '>div': { float: 'right', textAlign: 'left' } }}
|
|
99
102
|
>
|
|
@@ -108,8 +111,8 @@ export const PopupMenuWithLabel = (props: PopupMenuWithLabelProps) => {
|
|
|
108
111
|
handleSelected={props.handleSelected}
|
|
109
112
|
handleOpened={props.handleOpened}
|
|
110
113
|
handleClosed={props.handleClosed}
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
noUpdateLabel={props.noUpdateLabel}
|
|
115
|
+
hook={hook}
|
|
113
116
|
noTriangleIcon={props.noTriangleIcon}
|
|
114
117
|
align={props.align}
|
|
115
118
|
></PopupMenu>
|
|
@@ -127,12 +130,12 @@ export const PopupMenu = ({
|
|
|
127
130
|
handleSelected,
|
|
128
131
|
handleOpened,
|
|
129
132
|
handleClosed,
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
noUpdateLabel,
|
|
134
|
+
hook,
|
|
132
135
|
align = 'right',
|
|
133
136
|
noTriangleIcon,
|
|
134
137
|
}: PopupMenuProps) => {
|
|
135
|
-
const css:
|
|
138
|
+
const css: CssProps = {
|
|
136
139
|
'.popup-menu-item': {
|
|
137
140
|
padding: '0 0 1px 0',
|
|
138
141
|
display: 'inline-block',
|
|
@@ -209,7 +212,8 @@ export const PopupMenu = ({
|
|
|
209
212
|
|
|
210
213
|
let ref: RefProps = { id: '' };
|
|
211
214
|
let isShowing = false;
|
|
212
|
-
|
|
215
|
+
let selectedValue = defaultValue;
|
|
216
|
+
const openMenu = (event?: MouseEvent) => {
|
|
213
217
|
stopPropagation(event);
|
|
214
218
|
|
|
215
219
|
handleOpened && handleOpened();
|
|
@@ -224,8 +228,9 @@ export const PopupMenu = ({
|
|
|
224
228
|
listDom.classList.toggle('open', isShowing);
|
|
225
229
|
ref.$('.popup-menu-list .menu-focus').focus();
|
|
226
230
|
};
|
|
227
|
-
if (
|
|
228
|
-
|
|
231
|
+
if (hook) {
|
|
232
|
+
hook.openMenu = openMenu;
|
|
233
|
+
hook.getValue = () => selectedValue;
|
|
229
234
|
}
|
|
230
235
|
const itemClick = (event: any) => {
|
|
231
236
|
stopPropagation(event);
|
|
@@ -234,7 +239,8 @@ export const PopupMenu = ({
|
|
|
234
239
|
isShowing = false;
|
|
235
240
|
ref.$('.popup-menu-list').classList.remove('open');
|
|
236
241
|
if (event.target) {
|
|
237
|
-
|
|
242
|
+
selectedValue = event.target.innerText;
|
|
243
|
+
if (noUpdateLabel !== true) {
|
|
238
244
|
ref.$('.popup-menu-item .popup-menu-text').innerText = event.target.innerText;
|
|
239
245
|
}
|
|
240
246
|
if (handleSelected) {
|
|
@@ -252,7 +258,7 @@ export const PopupMenu = ({
|
|
|
252
258
|
};
|
|
253
259
|
|
|
254
260
|
return (
|
|
255
|
-
<div ref={ref} css={css} onClick={
|
|
261
|
+
<div ref={ref} css={css} onClick={openMenu} title={tips}>
|
|
256
262
|
<div class='popup-menu-item'>
|
|
257
263
|
<span class={'popup-menu-text' + (noTriangleIcon !== true ? ' triangle-icon' : '')}>
|
|
258
264
|
{defaultValue || ' '}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RefProps } from 'lupine.web';
|
|
1
|
+
import { CssProps, RefProps } from 'lupine.web';
|
|
2
2
|
import { HtmlVar } from './html-var';
|
|
3
3
|
|
|
4
4
|
export type ProgressHookProps = {
|
|
@@ -10,7 +10,7 @@ export type ProgressProps = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export const Progress = (props: ProgressProps) => {
|
|
13
|
-
const css:
|
|
13
|
+
const css: CssProps = {
|
|
14
14
|
position: 'fixed',
|
|
15
15
|
display: 'flex',
|
|
16
16
|
bottom: '0',
|
package/src/frames/index.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/* ResponsiveFrame for desktop and mobile
|
|
2
|
+
sliderFrameHook is for slider frame from right or bottom used in side-menu
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { VNode, CssProps, MediaQueryRange, SliderFrameHookProps, SliderFrame } from 'lupine.components';
|
|
6
|
+
import { MobileFooterMenu } from '../components/mobile-components/mobile-footer-menu';
|
|
7
|
+
import { DesktopFooter } from '../components/desktop-footer';
|
|
8
|
+
import { DesktopHeader } from '../components/desktop-header';
|
|
3
9
|
import { MobileHeaderComponent } from '../components/mobile-components/mobile-header-component';
|
|
10
|
+
import { MobileSideMenu } from '../components/mobile-components/mobile-side-menu';
|
|
11
|
+
import { IconMenuItemProps } from '../components/mobile-components/icon-menu-item-props';
|
|
4
12
|
|
|
5
|
-
export
|
|
6
|
-
placeholderClassname: string
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
export interface ResponsiveFrameProps {
|
|
14
|
+
placeholderClassname: string;
|
|
15
|
+
mainContent: VNode<any>;
|
|
16
|
+
desktopHeaderTitle: string;
|
|
17
|
+
desktopFooterTitle: string;
|
|
18
|
+
desktopTopMenu: IconMenuItemProps[];
|
|
19
|
+
mobileBottomMenu: IconMenuItemProps[];
|
|
20
|
+
mobileSideMenuContent: VNode<any>;
|
|
21
|
+
sliderFrameHook: SliderFrameHookProps;
|
|
22
|
+
}
|
|
23
|
+
export const ResponsiveFrame = async (props: ResponsiveFrameProps) => {
|
|
13
24
|
const cssContainer: CssProps = {
|
|
14
25
|
display: 'flex',
|
|
15
26
|
flexDirection: 'column',
|
|
@@ -20,22 +31,16 @@ export const ResponsiveFrame = async (
|
|
|
20
31
|
display: 'flex',
|
|
21
32
|
flexDirection: 'column',
|
|
22
33
|
width: '100vw',
|
|
23
|
-
// height: '72px',
|
|
24
|
-
// position: 'fixed',
|
|
25
|
-
// left: 0,
|
|
26
|
-
// top: 0,
|
|
27
|
-
// zIndex: 'var(--layer-menu)',
|
|
28
34
|
backgroundColor: 'var(--activatable-bg-color-normal)',
|
|
29
35
|
},
|
|
30
36
|
'.frame-content': {
|
|
31
37
|
display: 'flex',
|
|
32
38
|
flex: '1',
|
|
33
39
|
flexDirection: 'column',
|
|
34
|
-
// paddingTop: '100px',
|
|
35
40
|
overflowY: 'auto',
|
|
36
41
|
scrollbarWidth: 'none',
|
|
37
42
|
'&::-webkit-scrollbar': {
|
|
38
|
-
|
|
43
|
+
display: 'none',
|
|
39
44
|
},
|
|
40
45
|
},
|
|
41
46
|
'.content-block': {
|
|
@@ -45,39 +50,32 @@ export const ResponsiveFrame = async (
|
|
|
45
50
|
overflowY: 'auto',
|
|
46
51
|
scrollbarWidth: 'none',
|
|
47
52
|
},
|
|
53
|
+
'.content-block::-webkit-scrollbar': {
|
|
54
|
+
display: 'none',
|
|
55
|
+
},
|
|
48
56
|
'.content-block .padding-block': {
|
|
49
57
|
padding: '0 16px',
|
|
50
58
|
},
|
|
51
|
-
// '.frame-footer': {
|
|
52
|
-
// paddingTop: '57px', // 应该和底部菜单的高度一致
|
|
53
|
-
// },
|
|
54
59
|
[MediaQueryRange.TabletBelow]: {
|
|
55
|
-
// .header-box,
|
|
56
60
|
'.frame-footer .footer-box, .frame-top-menu .desktop-menu-box': {
|
|
57
61
|
display: 'none',
|
|
58
62
|
},
|
|
59
|
-
// '.content-block': {
|
|
60
|
-
// paddingBottom: '16px',
|
|
61
|
-
// },
|
|
62
|
-
'.metronome-page-box, .gauge-box': {
|
|
63
|
-
boxShadow: '#313131 2.02px 2.02px 5.08px 1px !important',
|
|
64
|
-
},
|
|
65
63
|
},
|
|
66
64
|
};
|
|
67
65
|
|
|
68
66
|
return (
|
|
69
67
|
<div css={cssContainer} class='responsive-frame'>
|
|
68
|
+
<SliderFrame hook={props.sliderFrameHook} />
|
|
70
69
|
<div class='frame-top-menu'>
|
|
71
|
-
|
|
70
|
+
<DesktopHeader title={props.desktopHeaderTitle} items={props.desktopTopMenu}></DesktopHeader>
|
|
72
71
|
<MobileHeaderComponent></MobileHeaderComponent>
|
|
73
72
|
</div>
|
|
74
73
|
<div class='frame-content'>
|
|
75
|
-
<
|
|
74
|
+
<MobileSideMenu>{props.mobileSideMenuContent}</MobileSideMenu>
|
|
75
|
+
<div class={'content-block ' + props.placeholderClassname}>{props.mainContent}</div>
|
|
76
76
|
<div class='frame-footer'>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
</div>
|
|
80
|
-
<MobileFooterMenu items={bottomMenu}></MobileFooterMenu>
|
|
77
|
+
<DesktopFooter title={props.desktopFooterTitle}></DesktopFooter>
|
|
78
|
+
<MobileFooterMenu items={props.mobileBottomMenu}></MobileFooterMenu>
|
|
81
79
|
</div>
|
|
82
80
|
</div>
|
|
83
81
|
</div>
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/* frame component to show pages (sliders) from right or bottom
|
|
2
|
+
const sliderFrameHook: SliderFrameHookProps = {};
|
|
3
|
+
const onClick = (event: Event) => {
|
|
4
|
+
sliderFrameHook.load!(<... />);
|
|
5
|
+
or
|
|
6
|
+
sliderFrameHook.close!(event);
|
|
7
|
+
};
|
|
8
|
+
return (
|
|
9
|
+
<div onClick={onClick}>
|
|
10
|
+
<SliderFrame hook={sliderFrameHook} />
|
|
11
|
+
...
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
*/
|
|
15
|
+
import { VNode, CssProps, HtmlVar, RefProps, stopPropagation, MediaQueryRange } from 'lupine.components';
|
|
5
16
|
|
|
6
17
|
export type SliderFramePosition = 'desktop-slide-left' | 'desktop-slide-right';
|
|
7
18
|
export type SliderFrameHookProps = {
|
|
@@ -69,20 +80,21 @@ export const SliderFrame = (props: SliderFrameProps) => {
|
|
|
69
80
|
},
|
|
70
81
|
'&.desktop-slide-right': {
|
|
71
82
|
[MediaQueryRange.TabletAbove]: {
|
|
72
|
-
top: '
|
|
83
|
+
top: '59px',
|
|
73
84
|
left: '30%',
|
|
74
85
|
transform: 'translateX(0)',
|
|
75
|
-
|
|
86
|
+
// notice: here is connected with mobile-header-title-icon.tsx
|
|
87
|
+
'.mobile-header-title-icon-top': {
|
|
76
88
|
width: '100%',
|
|
77
89
|
boxShadow: 'unset',
|
|
78
90
|
},
|
|
79
91
|
'.header-back-content': {
|
|
80
92
|
width: '100%',
|
|
81
93
|
},
|
|
82
|
-
'.
|
|
94
|
+
'.mhti-title': {
|
|
83
95
|
fontSize: '15px',
|
|
84
96
|
},
|
|
85
|
-
'.
|
|
97
|
+
'.mhti-left, .mhti-right': {
|
|
86
98
|
display: 'none',
|
|
87
99
|
},
|
|
88
100
|
'&.d-none': {
|
package/src/frames/top-frame.tsx
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/* most top-frame for all other pages
|
|
2
|
+
place to set safe-area-inset-top and other common styles
|
|
3
|
+
*/
|
|
4
|
+
import { VNode, CssProps } from 'lupine.components';
|
|
2
5
|
|
|
3
6
|
export const TopFrame = async (placeholderClassname: string, vnode: VNode<any>) => {
|
|
4
7
|
const cssContainer: CssProps = {
|
|
@@ -18,7 +21,6 @@ export const TopFrame = async (placeholderClassname: string, vnode: VNode<any>)
|
|
|
18
21
|
},
|
|
19
22
|
};
|
|
20
23
|
|
|
21
|
-
// console.log('Web version: ', getWebVersion());
|
|
22
24
|
return (
|
|
23
25
|
<div css={cssContainer}>
|
|
24
26
|
{/* Can't put css on this placeholder node! */}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const blobToBase64 = (blob: Blob): Promise<string> => {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const reader = new FileReader();
|
|
4
|
+
reader.onloadend = () => resolve(reader.result as string); // data:audio/mpeg;base64,...
|
|
5
|
+
reader.onerror = reject;
|
|
6
|
+
reader.readAsDataURL(blob);
|
|
7
|
+
});
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const blobFromBase64 = (base64: string) => {
|
|
11
|
+
const [header, base64Data] = base64.split(','); // remove data:... prefix
|
|
12
|
+
const mimeMatch = header.match(/data:(.*);base64/);
|
|
13
|
+
const mimeType = mimeMatch ? mimeMatch[1] : 'application/octet-stream';
|
|
14
|
+
const byteCharacters = atob(base64Data);
|
|
15
|
+
const byteNumbers = Array.from(byteCharacters).map((c) => c.charCodeAt(0));
|
|
16
|
+
const byteArray = new Uint8Array(byteNumbers);
|
|
17
|
+
return new Blob([byteArray], { type: mimeType });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const base64ToUrl = (base64: string) => {
|
|
21
|
+
const blob = blobFromBase64(base64);
|
|
22
|
+
return URL.createObjectURL(blob);
|
|
23
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// todo: tree-shaking
|
|
2
|
+
export class DomUtils {
|
|
3
|
+
public static getValue(cssSelector: string) {
|
|
4
|
+
return (document.querySelector(cssSelector) as HTMLInputElement)?.value;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
public static setValue(cssSelector: string, value: string) {
|
|
8
|
+
const dom = document.querySelector(cssSelector) as HTMLInputElement;
|
|
9
|
+
if (dom) dom.value = value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public static getChecked(cssSelector: string) {
|
|
13
|
+
return (document.querySelector(cssSelector) as HTMLInputElement)?.checked;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public static setChecked(cssSelector: string, checked: boolean) {
|
|
17
|
+
const dom = document.querySelector(cssSelector) as HTMLInputElement;
|
|
18
|
+
if (dom) dom.checked = checked;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static joinValues(values: (string | undefined)[], joinChar = ' ') {
|
|
22
|
+
return values.filter(Boolean).join(joinChar);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static byId(id: string) {
|
|
26
|
+
return document.querySelector(`#${id}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static bySelector(selector: string) {
|
|
30
|
+
return document.querySelector(selector);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
downloadStream(blob, filename);
|
|
5
5
|
*/
|
|
6
6
|
export const downloadStream = (blob: Blob, filename?: string) => {
|
|
7
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
7
8
|
const dom = document.createElement('a');
|
|
8
|
-
dom.setAttribute('href',
|
|
9
|
+
dom.setAttribute('href', blobUrl);
|
|
9
10
|
dom.setAttribute('download', filename || 'true');
|
|
10
11
|
dom.style.display = 'none';
|
|
11
12
|
document.body.appendChild(dom);
|
|
12
13
|
dom.click();
|
|
13
14
|
setTimeout(() => {
|
|
14
15
|
document.body.removeChild(dom);
|
|
16
|
+
URL.revokeObjectURL(blobUrl);
|
|
15
17
|
}, 3000);
|
|
16
18
|
return dom;
|
|
17
19
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const findParentTag = (dom: HTMLElement, tag: string) => {
|
|
2
|
+
const tagUpper = tag.toUpperCase();
|
|
3
|
+
let parent = dom.parentElement;
|
|
4
|
+
while (parent && parent.tagName !== tagUpper && parent.tagName !== 'BODY') {
|
|
5
|
+
parent = parent.parentElement;
|
|
6
|
+
}
|
|
7
|
+
return parent;
|
|
8
|
+
};
|
package/src/lib/index.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './blob-utils';
|
|
2
|
+
export * from './calculate-text-width';
|
|
2
3
|
export * from './date-utils';
|
|
3
4
|
export * from './deep-merge';
|
|
4
5
|
export * from './document-ready';
|
|
6
|
+
export * from './dom-utils';
|
|
5
7
|
export * from './download-file';
|
|
8
|
+
export * from './download-link';
|
|
9
|
+
export * from './download-stream';
|
|
6
10
|
export * from './drag-util';
|
|
7
11
|
export * from './dynamical-load';
|
|
12
|
+
export * from './escape-html';
|
|
13
|
+
export * from './find-parent-tag';
|
|
8
14
|
export * from './format-bytes';
|
|
9
15
|
export * from './lite-dom';
|
|
10
16
|
export * from './message-hub';
|
|
11
17
|
export * from './observable';
|
|
18
|
+
export * from './path-utils';
|
|
12
19
|
export * from './promise-timeout';
|
|
13
20
|
export * from './simple-storage';
|
|
14
21
|
export * from './stop-propagation';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const pathUtils = {
|
|
2
|
+
join(...parts: string[]): string {
|
|
3
|
+
let joined = parts.filter(Boolean).join('/');
|
|
4
|
+
joined = joined.replace(/\/+/g, '/'); // merge duplicate slashes
|
|
5
|
+
const isAbsolute = parts[0]?.startsWith('/');
|
|
6
|
+
return isAbsolute ? '/' + joined.replace(/^\/+/, '') : joined.replace(/^\/+/, '');
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
dirname(p: string): string {
|
|
10
|
+
if (!p) return '.';
|
|
11
|
+
p = p.replace(/\/+$/, '');
|
|
12
|
+
const idx = p.lastIndexOf('/');
|
|
13
|
+
if (idx === -1) return '.';
|
|
14
|
+
if (idx === 0) return '/';
|
|
15
|
+
return p.slice(0, idx);
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// get filename, remove ext if exists
|
|
19
|
+
basename(p: string, ext?: string): string {
|
|
20
|
+
if (!p) return '';
|
|
21
|
+
p = p.replace(/\/+$/, '');
|
|
22
|
+
const idx = p.lastIndexOf('/');
|
|
23
|
+
let base = idx === -1 ? p : p.slice(idx + 1);
|
|
24
|
+
if (ext && base.endsWith(ext)) {
|
|
25
|
+
base = base.slice(0, -ext.length);
|
|
26
|
+
}
|
|
27
|
+
return base;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// .hidden's ext is empty (Node.js behavior)
|
|
31
|
+
extname(p: string): string {
|
|
32
|
+
if (!p) return '';
|
|
33
|
+
const base = pathUtils.basename(p);
|
|
34
|
+
const idx = base.lastIndexOf('.');
|
|
35
|
+
return idx > 0 ? base.slice(idx) : '';
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { VNode, CssProps } from 'lupine.web';
|
|
2
|
-
import { MediaQueryMaxWidth } from '../styles';
|
|
3
|
-
import { MenuBar } from '../components';
|
|
4
|
-
|
|
5
|
-
export interface MenuBarMenuProps {
|
|
6
|
-
id: string;
|
|
7
|
-
text: string;
|
|
8
|
-
url: string;
|
|
9
|
-
}
|
|
10
|
-
export const DesktopFrame = async (
|
|
11
|
-
placeholderClassname: string,
|
|
12
|
-
vnode: VNode<any>,
|
|
13
|
-
title: string,
|
|
14
|
-
footerTitle: string,
|
|
15
|
-
logoUrl: string,
|
|
16
|
-
menu: MenuBarMenuProps[]
|
|
17
|
-
) => {
|
|
18
|
-
const cssContainer: CssProps = {
|
|
19
|
-
display: 'flex',
|
|
20
|
-
'flex-direction': 'column',
|
|
21
|
-
width: '100%',
|
|
22
|
-
'min-height': '100%',
|
|
23
|
-
'overflow-y': 'auto',
|
|
24
|
-
'.content-block': {
|
|
25
|
-
display: 'flex',
|
|
26
|
-
flex: '1',
|
|
27
|
-
margin: 'auto',
|
|
28
|
-
maxWidth: MediaQueryMaxWidth.DesktopMax,
|
|
29
|
-
width: '100%',
|
|
30
|
-
},
|
|
31
|
-
'.top-footer': {
|
|
32
|
-
paddingTop: '16px',
|
|
33
|
-
},
|
|
34
|
-
'.header-box': {
|
|
35
|
-
display: 'flex',
|
|
36
|
-
'flex-direction': 'column',
|
|
37
|
-
width: '100%',
|
|
38
|
-
'min-height': '100%',
|
|
39
|
-
'overflow-y': 'auto',
|
|
40
|
-
'.content-block': {
|
|
41
|
-
display: 'flex',
|
|
42
|
-
flex: '1',
|
|
43
|
-
margin: 'auto',
|
|
44
|
-
maxWidth: MediaQueryMaxWidth.DesktopMax,
|
|
45
|
-
width: '100%',
|
|
46
|
-
},
|
|
47
|
-
'.top-footer': {
|
|
48
|
-
paddingTop: '16px',
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
'.footer-box': {
|
|
52
|
-
display: 'flex',
|
|
53
|
-
padding: '0 32px 16px',
|
|
54
|
-
'.footer-cp': {
|
|
55
|
-
padding: '1px 15px',
|
|
56
|
-
margin: 'auto',
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<div css={cssContainer}>
|
|
63
|
-
<div class='header-box'>
|
|
64
|
-
<div class='logo-box'>
|
|
65
|
-
<img class='logo' src={logoUrl} />
|
|
66
|
-
</div>
|
|
67
|
-
<div class='header-title'>
|
|
68
|
-
{title}
|
|
69
|
-
{/* <div class='header-subtitle pt-s'>{'ver: ' + getWebVersion()}</div> */}
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
<MenuBar items={menu} maxWidthMobileMenu={'800px'} maxWidth={MediaQueryMaxWidth.DesktopMax}></MenuBar>
|
|
73
|
-
<div class={'content-block ' + placeholderClassname}>{vnode}</div>
|
|
74
|
-
<div class='top-footer'>
|
|
75
|
-
<div class='footer-box'>
|
|
76
|
-
<div class='footer-cp'>{footerTitle}</div>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
};
|