@datalayer/core 1.0.11 → 1.0.13
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/hooks/index.d.ts +3 -0
- package/lib/hooks/index.js +3 -0
- package/lib/hooks/useHighZIndexPortal.d.ts +4 -0
- package/lib/hooks/useHighZIndexPortal.js +36 -0
- package/lib/hooks/useKeyboardShortcuts.d.ts +23 -0
- package/lib/hooks/useKeyboardShortcuts.js +124 -0
- package/lib/utils/DownloadFile.d.ts +13 -0
- package/lib/utils/DownloadFile.js +20 -0
- package/lib/views/iam/SignInSimple.js +9 -2
- package/package.json +4 -4
package/lib/hooks/index.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export * from './useIAM';
|
|
|
11
11
|
export * from './useId';
|
|
12
12
|
export * from './useIsomorphicLayoutEffect';
|
|
13
13
|
export * from './useJupyterLabTheme';
|
|
14
|
+
export * from './useHighZIndexPortal';
|
|
15
|
+
export * from './useKeyboardShortcuts';
|
|
14
16
|
export * from './useKeyboardEscape';
|
|
15
17
|
export * from './useLocation';
|
|
16
18
|
export * from './useLocationHandles';
|
|
@@ -25,5 +27,6 @@ export * from './useUpload';
|
|
|
25
27
|
export * from './useUser';
|
|
26
28
|
export * from './useProjects';
|
|
27
29
|
export * from './useProjectStore';
|
|
30
|
+
export * from './useMobile';
|
|
28
31
|
export * from './useVisibilityObserver';
|
|
29
32
|
export * from './useWindowSize';
|
package/lib/hooks/index.js
CHANGED
|
@@ -16,6 +16,8 @@ export * from './useIAM';
|
|
|
16
16
|
export * from './useId';
|
|
17
17
|
export * from './useIsomorphicLayoutEffect';
|
|
18
18
|
export * from './useJupyterLabTheme';
|
|
19
|
+
export * from './useHighZIndexPortal';
|
|
20
|
+
export * from './useKeyboardShortcuts';
|
|
19
21
|
export * from './useKeyboardEscape';
|
|
20
22
|
export * from './useLocation';
|
|
21
23
|
export * from './useLocationHandles';
|
|
@@ -30,5 +32,6 @@ export * from './useUpload';
|
|
|
30
32
|
export * from './useUser';
|
|
31
33
|
export * from './useProjects';
|
|
32
34
|
export * from './useProjectStore';
|
|
35
|
+
export * from './useMobile';
|
|
33
36
|
export * from './useVisibilityObserver';
|
|
34
37
|
export * from './useWindowSize';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { useEffect } from 'react';
|
|
6
|
+
const PRIMER_PORTAL_ROOT_ID = 'primer-portal-root';
|
|
7
|
+
/**
|
|
8
|
+
* Ensure Primer portal root has a high z-index so overlays render above chat.
|
|
9
|
+
*/
|
|
10
|
+
export function useHighZIndexPortal() {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const setPortalZIndex = () => {
|
|
13
|
+
const portalRoot = document.getElementById(PRIMER_PORTAL_ROOT_ID);
|
|
14
|
+
if (portalRoot) {
|
|
15
|
+
portalRoot.style.zIndex = '9999';
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
};
|
|
20
|
+
if (setPortalZIndex()) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const observer = new MutationObserver(() => {
|
|
24
|
+
if (setPortalZIndex()) {
|
|
25
|
+
observer.disconnect();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
observer.observe(document.body, {
|
|
29
|
+
childList: true,
|
|
30
|
+
subtree: true,
|
|
31
|
+
});
|
|
32
|
+
return () => {
|
|
33
|
+
observer.disconnect();
|
|
34
|
+
};
|
|
35
|
+
}, []);
|
|
36
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface KeyboardShortcut {
|
|
2
|
+
key: string;
|
|
3
|
+
ctrlOrCmd?: boolean;
|
|
4
|
+
shift?: boolean;
|
|
5
|
+
alt?: boolean;
|
|
6
|
+
handler: () => void;
|
|
7
|
+
description?: string;
|
|
8
|
+
allowInInput?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface UseKeyboardShortcutsOptions {
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
shortcuts: KeyboardShortcut[];
|
|
13
|
+
}
|
|
14
|
+
export declare function useKeyboardShortcuts({ enabled, shortcuts, }: UseKeyboardShortcutsOptions): void;
|
|
15
|
+
export declare function useChatKeyboardShortcuts({ onToggle, onNewChat, onClear, onFocusInput, enabled, }: {
|
|
16
|
+
onToggle?: () => void;
|
|
17
|
+
onNewChat?: () => void;
|
|
18
|
+
onClear?: () => void;
|
|
19
|
+
onFocusInput?: () => void;
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
}): void;
|
|
22
|
+
export declare function getShortcutDisplay(shortcut: KeyboardShortcut): string;
|
|
23
|
+
export default useKeyboardShortcuts;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { useEffect, useCallback } from 'react';
|
|
6
|
+
function isMacOS() {
|
|
7
|
+
if (typeof navigator === 'undefined')
|
|
8
|
+
return false;
|
|
9
|
+
return /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
10
|
+
}
|
|
11
|
+
export function useKeyboardShortcuts({ enabled = true, shortcuts, }) {
|
|
12
|
+
const handleKeyDown = useCallback((event) => {
|
|
13
|
+
if (!enabled)
|
|
14
|
+
return;
|
|
15
|
+
const target = event.target;
|
|
16
|
+
const isInInput = target.tagName === 'INPUT' ||
|
|
17
|
+
target.tagName === 'SELECT' ||
|
|
18
|
+
target.tagName === 'TEXTAREA' ||
|
|
19
|
+
target.isContentEditable;
|
|
20
|
+
for (const shortcut of shortcuts) {
|
|
21
|
+
if (isInInput && !shortcut.allowInInput) {
|
|
22
|
+
if (shortcut.key !== 'Escape') {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (event.key.toLowerCase() !== shortcut.key.toLowerCase()) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const ctrlOrCmdPressed = isMacOS() ? event.metaKey : event.ctrlKey;
|
|
30
|
+
if (shortcut.ctrlOrCmd && !ctrlOrCmdPressed) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (!shortcut.ctrlOrCmd &&
|
|
34
|
+
ctrlOrCmdPressed &&
|
|
35
|
+
shortcut.key !== 'Escape') {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (shortcut.shift && !event.shiftKey) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (shortcut.alt && !event.altKey) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
event.stopPropagation();
|
|
46
|
+
shortcut.handler();
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}, [enabled, shortcuts]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!enabled)
|
|
52
|
+
return;
|
|
53
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
54
|
+
return () => {
|
|
55
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
56
|
+
};
|
|
57
|
+
}, [enabled, handleKeyDown]);
|
|
58
|
+
}
|
|
59
|
+
export function useChatKeyboardShortcuts({ onToggle, onNewChat, onClear, onFocusInput, enabled = true, }) {
|
|
60
|
+
const shortcuts = [];
|
|
61
|
+
if (onToggle) {
|
|
62
|
+
shortcuts.push({
|
|
63
|
+
key: 'k',
|
|
64
|
+
ctrlOrCmd: true,
|
|
65
|
+
handler: onToggle,
|
|
66
|
+
description: 'Toggle chat sidebar',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (onNewChat) {
|
|
70
|
+
shortcuts.push({
|
|
71
|
+
key: 'n',
|
|
72
|
+
ctrlOrCmd: true,
|
|
73
|
+
shift: true,
|
|
74
|
+
handler: onNewChat,
|
|
75
|
+
description: 'Start new chat',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (onClear) {
|
|
79
|
+
shortcuts.push({
|
|
80
|
+
key: 'Backspace',
|
|
81
|
+
ctrlOrCmd: true,
|
|
82
|
+
shift: true,
|
|
83
|
+
handler: onClear,
|
|
84
|
+
description: 'Clear chat messages',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (onFocusInput) {
|
|
88
|
+
shortcuts.push({
|
|
89
|
+
key: '/',
|
|
90
|
+
handler: onFocusInput,
|
|
91
|
+
description: 'Focus chat input',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (onToggle) {
|
|
95
|
+
shortcuts.push({
|
|
96
|
+
key: 'Escape',
|
|
97
|
+
handler: onToggle,
|
|
98
|
+
description: 'Close chat sidebar',
|
|
99
|
+
allowInInput: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
useKeyboardShortcuts({ enabled, shortcuts });
|
|
103
|
+
}
|
|
104
|
+
export function getShortcutDisplay(shortcut) {
|
|
105
|
+
const parts = [];
|
|
106
|
+
const isMac = isMacOS();
|
|
107
|
+
if (shortcut.ctrlOrCmd) {
|
|
108
|
+
parts.push(isMac ? '⌘' : 'Ctrl');
|
|
109
|
+
}
|
|
110
|
+
if (shortcut.shift) {
|
|
111
|
+
parts.push(isMac ? '⇧' : 'Shift');
|
|
112
|
+
}
|
|
113
|
+
if (shortcut.alt) {
|
|
114
|
+
parts.push(isMac ? '⌥' : 'Alt');
|
|
115
|
+
}
|
|
116
|
+
let keyDisplay = shortcut.key.toUpperCase();
|
|
117
|
+
if (shortcut.key === 'Escape')
|
|
118
|
+
keyDisplay = 'Esc';
|
|
119
|
+
if (shortcut.key === 'Backspace')
|
|
120
|
+
keyDisplay = isMac ? '⌫' : 'Del';
|
|
121
|
+
parts.push(keyDisplay);
|
|
122
|
+
return parts.join(isMac ? '' : '+');
|
|
123
|
+
}
|
|
124
|
+
export default useKeyboardShortcuts;
|
|
@@ -7,4 +7,17 @@
|
|
|
7
7
|
* .zip ZIP archive application/zip
|
|
8
8
|
*/
|
|
9
9
|
export declare const downloadFile: (data: any, filename: string, mime?: any, bom?: any) => void;
|
|
10
|
+
export type TextDownloadPayload = {
|
|
11
|
+
content: string;
|
|
12
|
+
filename: string;
|
|
13
|
+
mime: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Create a generic markdown text payload for file downloads.
|
|
17
|
+
*/
|
|
18
|
+
export declare const createMarkdownDownloadPayload: (content: string, fileStem: string) => TextDownloadPayload;
|
|
19
|
+
/**
|
|
20
|
+
* Download a text payload produced by a helper such as createMarkdownDownloadPayload.
|
|
21
|
+
*/
|
|
22
|
+
export declare const downloadTextPayload: (payload: TextDownloadPayload, withBom?: boolean) => void;
|
|
10
23
|
export default downloadFile;
|
|
@@ -35,4 +35,24 @@ export const downloadFile = (data, filename, mime, bom) => {
|
|
|
35
35
|
window.URL.revokeObjectURL(blobURL);
|
|
36
36
|
}, 200);
|
|
37
37
|
};
|
|
38
|
+
const sanitizeFileStem = (value) => value
|
|
39
|
+
.trim()
|
|
40
|
+
.replace(/\s+/g, '-')
|
|
41
|
+
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
42
|
+
.replace(/-+/g, '-')
|
|
43
|
+
.replace(/^-+|-+$/g, '') || 'download';
|
|
44
|
+
/**
|
|
45
|
+
* Create a generic markdown text payload for file downloads.
|
|
46
|
+
*/
|
|
47
|
+
export const createMarkdownDownloadPayload = (content, fileStem) => ({
|
|
48
|
+
content,
|
|
49
|
+
filename: `${sanitizeFileStem(fileStem)}.md`,
|
|
50
|
+
mime: 'text/markdown;charset=utf-8',
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Download a text payload produced by a helper such as createMarkdownDownloadPayload.
|
|
54
|
+
*/
|
|
55
|
+
export const downloadTextPayload = (payload, withBom = true) => {
|
|
56
|
+
downloadFile(payload.content, payload.filename, payload.mime, withBom ? '\uFEFF' : undefined);
|
|
57
|
+
};
|
|
38
58
|
export default downloadFile;
|
|
@@ -12,12 +12,19 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
12
12
|
*
|
|
13
13
|
* @module views/signin
|
|
14
14
|
*/
|
|
15
|
-
import { useState, useCallback, useRef } from 'react';
|
|
15
|
+
import { useState, useCallback, useRef, useMemo } from 'react';
|
|
16
16
|
import { Box, Button, FormControl, Heading, Text, Textarea, TextInput, } from '@primer/react';
|
|
17
17
|
import { Dialog } from '@primer/react/experimental';
|
|
18
18
|
import { EyeIcon, EyeClosedIcon, KeyIcon, TelescopeIcon, } from '@primer/octicons-react';
|
|
19
|
+
import { coreStore } from '../../state';
|
|
19
20
|
// ── Component ────────────────────────────────────────────────────────
|
|
20
|
-
export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl
|
|
21
|
+
export const SignInSimple = ({ onSignIn, onApiKeySignIn, loginUrl: loginUrlProp, title = 'Datalayer OTEL', description = 'Sign in to access the observability dashboard.', leadingIcon = _jsx(TelescopeIcon, { size: 24 }), }) => {
|
|
22
|
+
const loginUrl = useMemo(() => {
|
|
23
|
+
if (loginUrlProp)
|
|
24
|
+
return loginUrlProp;
|
|
25
|
+
const iamRunUrl = coreStore.getState().configuration?.iamRunUrl;
|
|
26
|
+
return iamRunUrl ? `${iamRunUrl}/api/iam/v1/login` : '/api/iam/v1/login';
|
|
27
|
+
}, [loginUrlProp]);
|
|
21
28
|
const [handle, setHandle] = useState('');
|
|
22
29
|
const [password, setPassword] = useState('');
|
|
23
30
|
const [showPassword, setShowPassword] = useState(false);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalayer/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
".",
|
|
@@ -103,9 +103,9 @@
|
|
|
103
103
|
},
|
|
104
104
|
"dependencies": {
|
|
105
105
|
"@datalayer/icons-react": "^1.0.6",
|
|
106
|
-
"@datalayer/jupyter-lexical": "^1.0.
|
|
107
|
-
"@datalayer/jupyter-react": "^2.0.
|
|
108
|
-
"@datalayer/primer-addons": "^1.0.
|
|
106
|
+
"@datalayer/jupyter-lexical": "^1.0.16",
|
|
107
|
+
"@datalayer/jupyter-react": "^2.0.7",
|
|
108
|
+
"@datalayer/primer-addons": "^1.0.12",
|
|
109
109
|
"@datalayer/primer-rjsf": "^1.0.1",
|
|
110
110
|
"@fluentui/react": "^8.125.3",
|
|
111
111
|
"@jupyter-widgets/base-manager": "^1.0.12",
|