@lark-apaas/client-toolkit 1.2.0-alpha.4 → 1.2.0-alpha.6
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/lib/apis/components/Avatar.d.ts +4 -0
- package/lib/apis/components/Avatar.js +2 -0
- package/lib/apis/components/NavLink.d.ts +9 -0
- package/lib/apis/components/NavLink.js +50 -0
- package/lib/apis/components/TruncatedTitle.d.ts +5 -0
- package/lib/apis/components/TruncatedTitle.js +2 -0
- package/lib/components/AppContainer/index.js +2 -0
- package/lib/components/AppContainer/utils/observable.d.ts +1 -0
- package/lib/components/AppContainer/utils/observable.js +16 -0
- package/lib/hooks/useAppInfo.js +5 -3
- package/lib/hooks/useCurrentUserProfile.js +15 -8
- package/lib/logger/logger.js +20 -0
- package/lib/utils/axiosConfig.js +6 -4
- package/lib/utils/getAxiosForBackend.js +55 -1
- package/lib/utils/safeStringify.d.ts +4 -0
- package/lib/utils/safeStringify.js +42 -0
- package/package.json +4 -3
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type NavLinkProps } from 'react-router-dom';
|
|
3
|
+
/**
|
|
4
|
+
* Enhanced NavLink component that extends react-router-dom's NavLink
|
|
5
|
+
* with support for hash links (anchor navigation) and smooth scrolling.
|
|
6
|
+
*/
|
|
7
|
+
declare const NavLink: React.ForwardRefExoticComponent<NavLinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
8
|
+
export { NavLink };
|
|
9
|
+
export type { NavLinkProps };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { NavLink, useLocation, useNavigate } from "react-router-dom";
|
|
4
|
+
const NavLink_NavLink = /*#__PURE__*/ forwardRef(({ to, children, className, style, ...props }, ref)=>{
|
|
5
|
+
const isHashLink = 'string' == typeof to && to.startsWith('#');
|
|
6
|
+
const location = useLocation();
|
|
7
|
+
const navigate = useNavigate();
|
|
8
|
+
if (isHashLink) {
|
|
9
|
+
const handleClick = (e)=>{
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
const element = document.querySelector(to);
|
|
12
|
+
if (element) element.scrollIntoView({
|
|
13
|
+
behavior: 'smooth'
|
|
14
|
+
});
|
|
15
|
+
navigate(to);
|
|
16
|
+
};
|
|
17
|
+
const isActive = location.hash === to;
|
|
18
|
+
const renderProps = {
|
|
19
|
+
isActive,
|
|
20
|
+
isPending: false,
|
|
21
|
+
isTransitioning: false
|
|
22
|
+
};
|
|
23
|
+
const resolvedClassName = 'function' == typeof className ? className(renderProps) : className;
|
|
24
|
+
const resolvedStyle = 'function' == typeof style ? style(renderProps) : style;
|
|
25
|
+
const { caseSensitive, end, replace, state, preventScrollReset, relative, viewTransition, ...restProps } = props;
|
|
26
|
+
return /*#__PURE__*/ jsx("a", {
|
|
27
|
+
href: to,
|
|
28
|
+
onClick: handleClick,
|
|
29
|
+
ref: ref,
|
|
30
|
+
className: resolvedClassName,
|
|
31
|
+
style: resolvedStyle,
|
|
32
|
+
...restProps,
|
|
33
|
+
children: 'function' == typeof children ? children(renderProps) : children
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return /*#__PURE__*/ jsx(NavLink, {
|
|
37
|
+
to: to,
|
|
38
|
+
ref: ref,
|
|
39
|
+
className: className,
|
|
40
|
+
style: style,
|
|
41
|
+
onClick: ()=>(document.getElementById('rootContainer') || window)?.scrollTo({
|
|
42
|
+
top: 0,
|
|
43
|
+
behavior: 'smooth'
|
|
44
|
+
}),
|
|
45
|
+
...props,
|
|
46
|
+
children: children
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
NavLink_NavLink.displayName = 'NavLink';
|
|
50
|
+
export { NavLink_NavLink as NavLink };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TruncatedTitle component - displays truncated text with a tooltip when overflowed.
|
|
3
|
+
* This is an alias for OverflowTooltipText for semantic naming in layout components.
|
|
4
|
+
*/
|
|
5
|
+
export { OverflowTooltipText, OverflowTooltipText as TruncatedTitle, type OverflowTooltipTextProps, type OverflowTooltipTextProps as TruncatedTitleProps, } from '../../components/ui/overflow-tooltip-text';
|
|
@@ -17,8 +17,10 @@ import safety from "./safety.js";
|
|
|
17
17
|
import { getAppId } from "../../utils/getAppId.js";
|
|
18
18
|
import { ServerLogPoller } from "../../server-log/index.js";
|
|
19
19
|
import QueryProvider from "../QueryProvider/index.js";
|
|
20
|
+
import { initObservable } from "./utils/observable.js";
|
|
20
21
|
registerDayjsPlugins();
|
|
21
22
|
initAxiosConfig();
|
|
23
|
+
initObservable();
|
|
22
24
|
const isMiaodaPreview = window.IS_MIAODA_PREVIEW;
|
|
23
25
|
const readCssVarColor = (varName, fallback)=>{
|
|
24
26
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const initObservable: () => void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AppEnv, observable } from "@lark-apaas/observable-web";
|
|
2
|
+
const initObservable = ()=>{
|
|
3
|
+
try {
|
|
4
|
+
observable.start({
|
|
5
|
+
serviceName: "app",
|
|
6
|
+
env: 'development' === process.env.NODE_ENV ? AppEnv.Dev : AppEnv.Prod,
|
|
7
|
+
collectorUrl: {
|
|
8
|
+
log: `/spark/app/${window.appId}/runtime/api/v1/observability/logs/collect`,
|
|
9
|
+
metric: `/spark/app/${window.appId}/runtime/api/v1/observability/metrics/collect`
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error('Failed to start WebObservableSdk:', error);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
export { initObservable };
|
package/lib/hooks/useAppInfo.js
CHANGED
|
@@ -23,17 +23,19 @@ const useAppInfo = ()=>{
|
|
|
23
23
|
if (info.description) {
|
|
24
24
|
const meta = document.querySelector("meta[property='og:description']");
|
|
25
25
|
if (meta) meta.content = info.description;
|
|
26
|
+
const metaDom = document.querySelector("meta[name='description']");
|
|
27
|
+
if (metaDom) metaDom.content = info.description;
|
|
26
28
|
}
|
|
27
29
|
};
|
|
28
|
-
const handleMetaInfoChanged = async (info)=>{
|
|
29
|
-
if (!info) info = await getAppInfo(
|
|
30
|
+
const handleMetaInfoChanged = async (info, refresh = true)=>{
|
|
31
|
+
if (!info) info = await getAppInfo(refresh);
|
|
30
32
|
updateDomInfo(info);
|
|
31
33
|
setAppInfo((prev)=>({
|
|
32
34
|
...prev,
|
|
33
35
|
...info
|
|
34
36
|
}));
|
|
35
37
|
};
|
|
36
|
-
handleMetaInfoChanged();
|
|
38
|
+
handleMetaInfoChanged(null, false);
|
|
37
39
|
const onUpdate = (e)=>{
|
|
38
40
|
const info = e.detail;
|
|
39
41
|
handleMetaInfoChanged(info);
|
|
@@ -3,6 +3,11 @@ import { logger } from "../logger/index.js";
|
|
|
3
3
|
import { getCurrentUserProfile } from "../integrations/getCurrentUserProfile.js";
|
|
4
4
|
import { getDataloom } from "../integrations/dataloom.js";
|
|
5
5
|
import { isSparkRuntime } from "../utils/utils.js";
|
|
6
|
+
function getNameFromArray(nameArray) {
|
|
7
|
+
if (!nameArray || 0 === nameArray.length) return;
|
|
8
|
+
const chineseName = nameArray.find((item)=>2052 === item.language_code);
|
|
9
|
+
return chineseName?.text ?? nameArray[0]?.text;
|
|
10
|
+
}
|
|
6
11
|
function getCompatibilityUserProfile() {
|
|
7
12
|
const userInfo = getCurrentUserProfile();
|
|
8
13
|
return {
|
|
@@ -18,34 +23,36 @@ const useCurrentUserProfile = ()=>{
|
|
|
18
23
|
if (isSparkRuntime()) {
|
|
19
24
|
(async ()=>{
|
|
20
25
|
const dataloom = await getDataloom();
|
|
21
|
-
const result = await dataloom
|
|
26
|
+
const result = await dataloom?.service?.session?.getUserInfo();
|
|
22
27
|
const userInfo = result?.data?.user_info;
|
|
28
|
+
const userName = getNameFromArray(userInfo?.name);
|
|
23
29
|
setUserInfo({
|
|
24
30
|
user_id: userInfo?.user_id?.toString(),
|
|
25
31
|
email: userInfo?.email,
|
|
26
|
-
name:
|
|
32
|
+
name: userName,
|
|
27
33
|
avatar: userInfo?.avatar?.image?.large,
|
|
28
|
-
userName:
|
|
34
|
+
userName: userName,
|
|
29
35
|
userAvatar: userInfo?.avatar?.image?.large
|
|
30
36
|
});
|
|
31
37
|
})();
|
|
32
38
|
handleMetaInfoChanged = async ()=>{
|
|
33
39
|
const dataloom = await getDataloom();
|
|
34
|
-
const result = await dataloom
|
|
40
|
+
const result = await dataloom?.service?.session?.getUserInfo();
|
|
35
41
|
const userInfo = result?.data?.user_info;
|
|
42
|
+
const userName = getNameFromArray(userInfo?.name);
|
|
36
43
|
const newUserInfo = {
|
|
37
44
|
user_id: userInfo?.user_id?.toString(),
|
|
38
45
|
email: userInfo?.email,
|
|
39
|
-
name:
|
|
46
|
+
name: userName,
|
|
40
47
|
avatar: userInfo?.avatar?.image?.large,
|
|
41
|
-
userName:
|
|
48
|
+
userName: userName,
|
|
42
49
|
userAvatar: userInfo?.avatar?.image?.large
|
|
43
50
|
};
|
|
44
|
-
logger.info('MiaoDaMetaInfoChanged', newUserInfo);
|
|
51
|
+
if ('development' === process.env.NODE_ENV) logger.info('MiaoDaMetaInfoChanged', newUserInfo);
|
|
45
52
|
setUserInfo(newUserInfo);
|
|
46
53
|
};
|
|
47
54
|
} else handleMetaInfoChanged = ()=>{
|
|
48
|
-
logger.info('MiaoDaMetaInfoChanged', getCompatibilityUserProfile());
|
|
55
|
+
if ('development' === process.env.NODE_ENV) logger.info('MiaoDaMetaInfoChanged', getCompatibilityUserProfile());
|
|
49
56
|
setUserInfo(getCompatibilityUserProfile());
|
|
50
57
|
};
|
|
51
58
|
window.addEventListener('MiaoDaMetaInfoChanged', handleMetaInfoChanged);
|
package/lib/logger/logger.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { observable } from "@lark-apaas/observable-web";
|
|
1
2
|
import { interceptors } from "./selected-logs.js";
|
|
2
3
|
import { interceptErrors } from "./intercept-global-error.js";
|
|
4
|
+
import { mapLogLevel, processLogParams } from "../utils/safeStringify.js";
|
|
5
|
+
const shouldReportToObservable = 'production' === process.env.NODE_ENV;
|
|
3
6
|
const LOG_LEVELS = [
|
|
4
7
|
'debug',
|
|
5
8
|
'info',
|
|
@@ -45,18 +48,35 @@ let logger = {
|
|
|
45
48
|
},
|
|
46
49
|
info (message, ...args) {
|
|
47
50
|
if (shouldLog('info')) console.log(...getFormattedPrefix('info'), message, ...args);
|
|
51
|
+
if (shouldReportToObservable) observable.log('INFO', processLogParams([
|
|
52
|
+
message,
|
|
53
|
+
...args
|
|
54
|
+
]));
|
|
48
55
|
},
|
|
49
56
|
warn (message, ...args) {
|
|
50
57
|
if (shouldLog('warn')) console.log(...getFormattedPrefix('warn'), message, ...args);
|
|
58
|
+
if (shouldReportToObservable) observable.log('WARN', processLogParams([
|
|
59
|
+
message,
|
|
60
|
+
...args
|
|
61
|
+
]));
|
|
51
62
|
},
|
|
52
63
|
error (message, ...args) {
|
|
53
64
|
if (shouldLog('error')) console.error(...getFormattedPrefix('error'), message, ...args);
|
|
65
|
+
if (shouldReportToObservable) observable.log('ERROR', processLogParams([
|
|
66
|
+
message,
|
|
67
|
+
...args
|
|
68
|
+
]));
|
|
54
69
|
},
|
|
55
70
|
success (message, ...args) {
|
|
56
71
|
if (shouldLog('success')) console.log(...getFormattedPrefix('success'), message, ...args);
|
|
72
|
+
if (shouldReportToObservable) observable.log('INFO', processLogParams([
|
|
73
|
+
message,
|
|
74
|
+
...args
|
|
75
|
+
]));
|
|
57
76
|
},
|
|
58
77
|
log ({ level, args }) {
|
|
59
78
|
if (shouldLog(level)) console.log(...getFormattedPrefix(level), ...args);
|
|
79
|
+
if (shouldReportToObservable && "debug" !== level) observable.log(mapLogLevel(level), processLogParams(args));
|
|
60
80
|
}
|
|
61
81
|
};
|
|
62
82
|
if ('production' !== process.env.NODE_ENV) window.__RUNTIME_LOGGER__ = {
|
package/lib/utils/axiosConfig.js
CHANGED
|
@@ -52,7 +52,7 @@ async function logResponse(ok, responseOrError) {
|
|
|
52
52
|
logTraceID
|
|
53
53
|
};
|
|
54
54
|
if (stacktrace) logMeta.stacktrace = stacktrace;
|
|
55
|
-
logger.log({
|
|
55
|
+
if ('development' === process.env.NODE_ENV) logger.log({
|
|
56
56
|
level: ok,
|
|
57
57
|
args: [
|
|
58
58
|
parts.join(''),
|
|
@@ -131,15 +131,17 @@ function initAxiosConfig(axiosInstance) {
|
|
|
131
131
|
if (!axiosInstance) axiosInstance = axios;
|
|
132
132
|
axiosInstance.interceptors.request.use((config)=>{
|
|
133
133
|
const requestUUID = crypto.randomUUID();
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
if ('production' !== process.env.NODE_ENV) {
|
|
135
|
+
const stacktrace = getStacktrace();
|
|
136
|
+
requestStacktraceMap.set(requestUUID, stacktrace);
|
|
137
|
+
}
|
|
136
138
|
config._requestUUID = requestUUID;
|
|
137
139
|
config._startTime = Date.now();
|
|
138
140
|
const csrfToken = window.csrfToken;
|
|
139
141
|
if (csrfToken) config.headers['X-Suda-Csrf-Token'] = csrfToken;
|
|
140
142
|
return config;
|
|
141
143
|
}, (error)=>Promise.reject(error));
|
|
142
|
-
axiosInstance.interceptors.response.use((response)=>{
|
|
144
|
+
'production' !== process.env.NODE_ENV && axiosInstance.interceptors.response.use((response)=>{
|
|
143
145
|
logResponse('success', response);
|
|
144
146
|
return response;
|
|
145
147
|
}, (error)=>{
|
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import { initAxiosConfig } from "./axiosConfig.js";
|
|
3
3
|
let axiosInstance;
|
|
4
|
+
function showToast(message, duration = 3000) {
|
|
5
|
+
return new Promise((resolve)=>{
|
|
6
|
+
const toast = document.createElement('div');
|
|
7
|
+
const iconSvg = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
8
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1.333a6.667 6.667 0 1 1 0 13.334A6.667 6.667 0 0 1 8 1.333ZM8 10a.667.667 0 1 0 0 1.333A.667.667 0 0 0 8 10Zm0-5.333a.667.667 0 0 0-.667.666v3.334a.667.667 0 0 0 1.334 0V5.333A.667.667 0 0 0 8 4.667Z" fill="#ff811a"/>
|
|
9
|
+
</svg>`;
|
|
10
|
+
const iconWrapper = document.createElement('span');
|
|
11
|
+
iconWrapper.innerHTML = iconSvg;
|
|
12
|
+
Object.assign(iconWrapper.style, {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
marginRight: '8px',
|
|
16
|
+
flexShrink: '0'
|
|
17
|
+
});
|
|
18
|
+
const textWrapper = document.createElement('span');
|
|
19
|
+
textWrapper.textContent = message;
|
|
20
|
+
toast.appendChild(iconWrapper);
|
|
21
|
+
toast.appendChild(textWrapper);
|
|
22
|
+
Object.assign(toast.style, {
|
|
23
|
+
position: 'fixed',
|
|
24
|
+
top: '20%',
|
|
25
|
+
left: '50%',
|
|
26
|
+
transform: 'translate(-50%, -50%)',
|
|
27
|
+
display: 'flex',
|
|
28
|
+
alignItems: 'center',
|
|
29
|
+
padding: '12px 16px',
|
|
30
|
+
backgroundColor: '#fff',
|
|
31
|
+
color: '#1f2329',
|
|
32
|
+
border: '1px solid #dee0e3',
|
|
33
|
+
borderRadius: '6px',
|
|
34
|
+
fontSize: '14px',
|
|
35
|
+
lineHeight: '1.5',
|
|
36
|
+
zIndex: '99999',
|
|
37
|
+
maxWidth: '80vw',
|
|
38
|
+
wordBreak: 'break-word',
|
|
39
|
+
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.03), 0 3px 6px rgba(0, 0, 0, 0.05), 0 6px 18px rgba(0, 0, 0, 0.03)',
|
|
40
|
+
opacity: '0',
|
|
41
|
+
transition: 'opacity 0.3s ease'
|
|
42
|
+
});
|
|
43
|
+
document.body.appendChild(toast);
|
|
44
|
+
requestAnimationFrame(()=>{
|
|
45
|
+
toast.style.opacity = '1';
|
|
46
|
+
});
|
|
47
|
+
setTimeout(()=>{
|
|
48
|
+
toast.style.opacity = '0';
|
|
49
|
+
setTimeout(()=>{
|
|
50
|
+
document.body.removeChild(toast);
|
|
51
|
+
resolve();
|
|
52
|
+
}, 300);
|
|
53
|
+
}, duration);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
4
56
|
function getAxiosForBackend() {
|
|
5
57
|
if (!axiosInstance) {
|
|
6
58
|
axiosInstance = axios.create({
|
|
@@ -9,7 +61,9 @@ function getAxiosForBackend() {
|
|
|
9
61
|
axiosInstance.interceptors.response.use(null, (err)=>{
|
|
10
62
|
if (err.config.meta?.autoJumpToLogin !== false) {
|
|
11
63
|
const loginUrl = err.response?.headers?.['x-login-url'];
|
|
12
|
-
if (loginUrl)
|
|
64
|
+
if (loginUrl) showToast('需要登录后才能执行操作,将自动跳转登录页', 3000).then(()=>{
|
|
65
|
+
window.location.href = loginUrl;
|
|
66
|
+
});
|
|
13
67
|
}
|
|
14
68
|
return Promise.reject(err);
|
|
15
69
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
function safeStringify(obj) {
|
|
2
|
+
const seen = new Set();
|
|
3
|
+
try {
|
|
4
|
+
return JSON.stringify(obj, (key, value)=>{
|
|
5
|
+
if ('object' == typeof value && null !== value) {
|
|
6
|
+
if (seen.has(value)) return '[Circular]';
|
|
7
|
+
seen.add(value);
|
|
8
|
+
}
|
|
9
|
+
if ('bigint' == typeof value) return value.toString();
|
|
10
|
+
if (value instanceof Date) return value.toISOString();
|
|
11
|
+
if (value instanceof Map) return Object.fromEntries(value);
|
|
12
|
+
if (value instanceof Set) return Array.from(value);
|
|
13
|
+
if (void 0 === value) return 'undefined';
|
|
14
|
+
if ('symbol' == typeof value) return value.toString();
|
|
15
|
+
return value;
|
|
16
|
+
});
|
|
17
|
+
} catch {
|
|
18
|
+
return '';
|
|
19
|
+
} finally{
|
|
20
|
+
seen.clear();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function processLogParams(args) {
|
|
24
|
+
if (1 === args.length) return safeStringify(args[0]);
|
|
25
|
+
const obj = {};
|
|
26
|
+
for(let i = 0; i < args.length; i++)obj[i.toString()] = args[i];
|
|
27
|
+
return safeStringify(obj);
|
|
28
|
+
}
|
|
29
|
+
function mapLogLevel(level) {
|
|
30
|
+
switch(level){
|
|
31
|
+
case "error":
|
|
32
|
+
return "ERROR";
|
|
33
|
+
case "info":
|
|
34
|
+
case "success":
|
|
35
|
+
return "INFO";
|
|
36
|
+
case "warn":
|
|
37
|
+
return "WARN";
|
|
38
|
+
default:
|
|
39
|
+
return "INFO";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export { mapLogLevel, processLogParams, safeStringify };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/client-toolkit",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.6",
|
|
4
4
|
"types": "./lib/index.d.ts",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -81,8 +81,9 @@
|
|
|
81
81
|
"@ant-design/colors": "^7.2.1",
|
|
82
82
|
"@ant-design/cssinjs": "^1.24.0",
|
|
83
83
|
"@data-loom/js": "^0.4.3",
|
|
84
|
-
"@lark-apaas/client-capability": "0.0.1-alpha.
|
|
85
|
-
"@lark-apaas/miaoda-inspector": "^1.0.
|
|
84
|
+
"@lark-apaas/client-capability": "0.0.1-alpha.3",
|
|
85
|
+
"@lark-apaas/miaoda-inspector": "^1.0.8",
|
|
86
|
+
"@lark-apaas/observable-web": "^1.0.0",
|
|
86
87
|
"@radix-ui/react-avatar": "^1.1.10",
|
|
87
88
|
"@radix-ui/react-popover": "^1.1.15",
|
|
88
89
|
"@radix-ui/react-slot": "^1.2.3",
|