@datalayer/core 1.1.49 → 1.1.56
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/README.md +19 -42
- package/lib/client/auth/AuthenticationManager.js +6 -4
- package/lib/client/auth/strategies.js +2 -0
- package/lib/components/animation/AnimatedText.d.ts +98 -0
- package/lib/components/animation/AnimatedText.js +220 -0
- package/lib/components/animation/index.d.ts +1 -0
- package/lib/components/{tokens → animation}/index.js +1 -1
- package/lib/components/auth/{LoginToken.d.ts → SigInAPIKey.d.ts} +3 -3
- package/lib/components/auth/{LoginToken.js → SigInAPIKey.js} +5 -5
- package/lib/components/auth/{Login.d.ts → SignIn.d.ts} +3 -3
- package/lib/components/auth/{Login.js → SignIn.js} +13 -13
- package/lib/components/auth/{Login.stories.d.ts → SignIn.stories.d.ts} +2 -2
- package/lib/components/auth/{Login.stories.js → SignIn.stories.js} +9 -9
- package/lib/components/auth/index.d.ts +2 -2
- package/lib/components/auth/index.js +2 -2
- package/lib/components/avatars/UserAvatar.d.ts +7 -1
- package/lib/components/avatars/UserAvatar.js +19 -6
- package/lib/components/avatars/UserProfileAvatar.js +2 -6
- package/lib/components/billing/BillingEntitySelect.js +10 -2
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +31 -0
- package/lib/components/collaboration/LiveEditorCollaborators.js +107 -0
- package/lib/components/collaboration/index.d.ts +1 -0
- package/lib/components/collaboration/index.js +9 -0
- package/lib/components/display/DatalayerBox.d.ts +3 -1
- package/lib/components/display/DatalayerBox.js +7 -7
- package/lib/components/display/JupyterDialog.d.ts +2 -0
- package/lib/components/display/JupyterDialog.js +10 -4
- package/lib/components/iam/ExternalTokenSilentLogin.js +8 -4
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +2 -0
- package/lib/components/labels/StatusLabels.d.ts +42 -0
- package/lib/components/labels/StatusLabels.js +61 -0
- package/lib/components/labels/index.d.ts +1 -0
- package/lib/components/labels/index.js +1 -0
- package/lib/components/principal/Principal.d.ts +2 -0
- package/lib/components/principal/Principal.js +11 -1
- package/lib/components/principal/PrincipalAppearance.d.ts +136 -0
- package/lib/components/principal/PrincipalAppearance.js +203 -0
- package/lib/components/principal/PrincipalAvatar.d.ts +5 -2
- package/lib/components/principal/PrincipalAvatar.js +16 -3
- package/lib/components/principal/PrincipalBadge.js +7 -0
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +2 -1
- package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
- package/lib/components/principal/index.d.ts +1 -0
- package/lib/components/principal/index.js +1 -0
- package/lib/components/progress/ConsumptionBar.d.ts +9 -0
- package/lib/components/progress/ConsumptionBar.js +10 -2
- package/lib/components/sharing/ShareAccessComponent.js +6 -1
- package/lib/config/Configuration.d.ts +17 -8
- package/lib/config/Configuration.js +12 -5
- package/lib/hooks/useCache.d.ts +2 -0
- package/lib/hooks/useCache.js +35 -11
- package/lib/hooks/usePrincipalCacheStore.d.ts +2 -0
- package/lib/hooks/usePrincipalCacheStore.js +2 -0
- package/lib/models/IAM.d.ts +10 -0
- package/lib/models/Organization.d.ts +2 -0
- package/lib/models/Organization.js +2 -0
- package/lib/models/Profile.d.ts +2 -0
- package/lib/models/Team.d.ts +2 -0
- package/lib/models/Team.js +2 -0
- package/lib/models/User.d.ts +4 -0
- package/lib/models/User.js +4 -0
- package/lib/models/UserDTO.d.ts +6 -0
- package/lib/models/UserDTO.js +8 -0
- package/lib/state/substates/CoreState.js +5 -4
- package/lib/state/substates/IAMState.js +6 -0
- package/lib/state/substates/LayoutState.js +62 -7
- package/lib/state/substates/OrganizationState.d.ts +6 -0
- package/lib/state/substates/OrganizationState.js +5 -0
- package/lib/state/substates/ProfileState.d.ts +34 -0
- package/lib/state/substates/ProfileState.js +68 -0
- package/lib/state/substates/TeamState.d.ts +6 -0
- package/lib/state/substates/TeamState.js +3 -0
- package/lib/state/substates/index.d.ts +1 -0
- package/lib/state/substates/index.js +1 -0
- package/lib/utils/Jwt.d.ts +0 -6
- package/lib/utils/Jwt.js +8 -1
- package/lib/views/iam/SignInSimple.d.ts +1 -1
- package/lib/views/iam/SignInSimple.js +131 -97
- package/lib/views/profile/UserBadge.js +6 -3
- package/package.json +3 -3
- package/lib/components/tokens/index.d.ts +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/sponsors/datalayer)
|
|
4
4
|
|
|
5
|
-
# ☰
|
|
5
|
+
# ☰ ⭕ Datalayer Core
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<strong>Python and Typescript libraries for Datalayer</strong>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
## Overview
|
|
20
20
|
|
|
21
|
-
Datalayer Core is the foundational package that powers the [Datalayer AI Platform](https://datalayer.
|
|
21
|
+
Datalayer Core is the foundational package that powers the [Datalayer AI Platform](https://datalayer.ai). It provides a TypesScript and Python packages as a Command Line Interface (CLI) for AI engineers, data scientists, and researchers to seamlessly integrate scalable compute runtimes into their workflows.
|
|
22
22
|
|
|
23
23
|
This package serves as the base foundation used by many other Datalayer packages, containing core application classes, configuration, and unified APIs for authentication, runtime management, and code execution in cloud-based environments.
|
|
24
24
|
|
|
@@ -66,8 +66,6 @@ npm install
|
|
|
66
66
|
|
|
67
67
|
## Quick Start with Python
|
|
68
68
|
|
|
69
|
-
### 1. Authentication
|
|
70
|
-
|
|
71
69
|
Set your Datalayer token as an environment variable:
|
|
72
70
|
|
|
73
71
|
```bash
|
|
@@ -89,31 +87,19 @@ if client.authenticate():
|
|
|
89
87
|
print("Successfully authenticated!")
|
|
90
88
|
```
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Runtime execution, snapshot workflows, and evals CLI are now documented and maintained in:
|
|
90
|
+
## Architecture
|
|
95
91
|
|
|
96
|
-
|
|
97
|
-
- [agent-runtimes CLI docs](https://agent-runtimes.datalayer.tech/cli)
|
|
92
|
+
Datalayer Core serves as the foundation for the entire Datalayer ecosystem:
|
|
98
93
|
|
|
99
|
-
|
|
94
|
+
- **Base Classes**: Core application classes inherited by other Datalayer packages
|
|
95
|
+
- **Configuration Management**: Centralized configuration system for all Datalayer components
|
|
96
|
+
- **Authentication Layer**: Unified authentication across all Datalayer services
|
|
97
|
+
- **Runtime Abstraction**: Common interface for different types of compute runtimes
|
|
98
|
+
- **Resource Management**: Automatic cleanup and lifecycle management
|
|
100
99
|
|
|
101
100
|
## Examples
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
- [Agent Runtimes examples README](https://github.com/datalayer/agent-runtimes/blob/main/examples/README.md)
|
|
106
|
-
- [Agent Runtimes examples directory](https://github.com/datalayer/agent-runtimes/tree/main/examples)
|
|
107
|
-
|
|
108
|
-
## Platform Integration
|
|
109
|
-
|
|
110
|
-
Datalayer adds AI capabilities and scalable compute runtimes to your development workflows. The platform is designed to seamlessly integrate into your existing processes and supercharge your computations with the processing power you need.
|
|
111
|
-
|
|
112
|
-
Key platform features accessible through this Client and CLI:
|
|
113
|
-
|
|
114
|
-
- **Remote Runtimes**: Execute code on powerful remote machines with CPU, RAM, and GPU resources
|
|
115
|
-
- **Multiple Interfaces**: Access and consume runtimes through Python Client, CLI, or other integrated tools
|
|
116
|
-
- **Scalable Compute**: Dynamically scale your computational resources based on workload requirements
|
|
102
|
+
- [OTEL example README](./examples/otel/README.md)
|
|
117
103
|
|
|
118
104
|
## Documentation
|
|
119
105
|
|
|
@@ -193,16 +179,6 @@ This Client is designed to be simple and extensible. We welcome contributions! P
|
|
|
193
179
|
|
|
194
180
|
For issues and enhancement requests, please use the [GitHub issue tracker](https://github.com/datalayer/core/issues).
|
|
195
181
|
|
|
196
|
-
## Architecture
|
|
197
|
-
|
|
198
|
-
Datalayer Core serves as the foundation for the entire Datalayer ecosystem:
|
|
199
|
-
|
|
200
|
-
- **Base Classes**: Core application classes inherited by other Datalayer packages
|
|
201
|
-
- **Configuration Management**: Centralized configuration system for all Datalayer components
|
|
202
|
-
- **Authentication Layer**: Unified authentication across all Datalayer services
|
|
203
|
-
- **Runtime Abstraction**: Common interface for different types of compute runtimes
|
|
204
|
-
- **Resource Management**: Automatic cleanup and lifecycle management
|
|
205
|
-
|
|
206
182
|
## Use Cases
|
|
207
183
|
|
|
208
184
|
- **AI/ML Development**: Scale your machine learning workflows with cloud compute using Client or CLI
|
|
@@ -219,15 +195,16 @@ This project is licensed under the [BSD 3-Clause License](https://github.com/dat
|
|
|
219
195
|
|
|
220
196
|
- **Documentation**: [Datalayer Platform Documentation](https://datalayer.ai/docs/)
|
|
221
197
|
- **Issues**: [GitHub Issues](https://github.com/datalayer/core/issues)
|
|
222
|
-
- **Community**: [Datalayer Platform](https://datalayer.
|
|
198
|
+
- **Community**: [Datalayer Platform](https://datalayer.ai)
|
|
223
199
|
|
|
224
200
|
---
|
|
225
201
|
|
|
226
|
-
<
|
|
227
|
-
<img src="https://assets.datalayer.tech/datalayer-25.svg" alt="Datalayer Logo" width="200"></img>
|
|
228
|
-
</p>
|
|
202
|
+
<div align="center">
|
|
229
203
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
204
|
+
**If this project is helpful to you, please give us a ⭐️**
|
|
205
|
+
|
|
206
|
+
Made with ❤️ by [Datalayer](https://datalayer.ai)
|
|
207
|
+
|
|
208
|
+
<img src="https://assets.datalayer.tech/datalayer-25.svg" alt="Datalayer Logo" width="200"></img>
|
|
209
|
+
|
|
210
|
+
</div>
|
|
@@ -25,15 +25,17 @@ export class AuthenticationManager {
|
|
|
25
25
|
*/
|
|
26
26
|
constructor(iamUrl, storage) {
|
|
27
27
|
this.iamUrl = iamUrl;
|
|
28
|
-
// Extract
|
|
29
|
-
|
|
30
|
-
//
|
|
28
|
+
// Extract the service URL (remove /api/iam/v1 if present)
|
|
29
|
+
// The service URL the credentials are stored under: the IAM one, without
|
|
30
|
+
// the path of its API.
|
|
31
|
+
const serviceUrl = iamUrl.replace('/api/iam/v1', '');
|
|
32
|
+
// CRITICAL: Pass the service URL to storage for keyring compatibility
|
|
31
33
|
if (!storage) {
|
|
32
34
|
if (typeof window !== 'undefined') {
|
|
33
35
|
this.storage = new BrowserStorage();
|
|
34
36
|
}
|
|
35
37
|
else {
|
|
36
|
-
this.storage = new NodeStorage(
|
|
38
|
+
this.storage = new NodeStorage(serviceUrl);
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
else {
|
|
@@ -39,6 +39,8 @@ class BaseAuthStrategy {
|
|
|
39
39
|
first_name_t: response.profile.first_name_t || response.profile.first_name || '',
|
|
40
40
|
last_name_t: response.profile.last_name_t || response.profile.last_name || '',
|
|
41
41
|
avatar_url_s: response.profile.avatar_url_s || response.profile.avatar_url,
|
|
42
|
+
avatar_icon_s: response.profile.avatar_icon_s,
|
|
43
|
+
banner_s: response.profile.banner_s,
|
|
42
44
|
};
|
|
43
45
|
return new UserDTO(userData, undefined);
|
|
44
46
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One piece of an animated line.
|
|
3
|
+
*
|
|
4
|
+
* A string is written as it is. An object cycles through its words, and every
|
|
5
|
+
* cycling piece of the same line advances together — so a sentence can name
|
|
6
|
+
* two things that change at once and still read as one sentence rather than
|
|
7
|
+
* two independent slot machines.
|
|
8
|
+
*/
|
|
9
|
+
export type AnimatedTextPart = string | AnimatedTextWords;
|
|
10
|
+
export type AnimatedTextWords = {
|
|
11
|
+
/** The words this piece cycles through. */
|
|
12
|
+
words: string[];
|
|
13
|
+
/**
|
|
14
|
+
* The colour of the words, as a Primer foreground token name — `accent`,
|
|
15
|
+
* `success`, `attention`, `severe`, `done`, `muted` — or any CSS colour.
|
|
16
|
+
* Given a token name the colour follows the theme, light or dark, because
|
|
17
|
+
* it resolves to the CSS variable Primer sets.
|
|
18
|
+
*/
|
|
19
|
+
color?: AnimatedTextColor;
|
|
20
|
+
/** Overrides the background of this piece alone. */
|
|
21
|
+
background?: string;
|
|
22
|
+
};
|
|
23
|
+
export type AnimatedTextColor = 'accent' | 'success' | 'attention' | 'severe' | 'done' | 'muted' | 'default' | (string & {});
|
|
24
|
+
export interface AnimatedTextProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children' | 'color'> {
|
|
25
|
+
/** Text before the cycling words. Ignored when `parts` is given. */
|
|
26
|
+
prefix?: string;
|
|
27
|
+
/** The words to cycle through. Ignored when `parts` is given. */
|
|
28
|
+
words?: string[];
|
|
29
|
+
/** Text after the cycling words. Ignored when `parts` is given. */
|
|
30
|
+
suffix?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The line, as a sequence of fixed and cycling pieces. Every cycling piece
|
|
33
|
+
* advances on the same tick, so several of them stay in step.
|
|
34
|
+
*/
|
|
35
|
+
parts?: AnimatedTextPart[];
|
|
36
|
+
/** Colour applied to cycling pieces that do not name their own. */
|
|
37
|
+
color?: AnimatedTextColor;
|
|
38
|
+
/**
|
|
39
|
+
* The colours to draw cycling words in when neither the piece nor `color`
|
|
40
|
+
* names one. Each word takes a different colour, and changes colour as it
|
|
41
|
+
* changes word.
|
|
42
|
+
*/
|
|
43
|
+
palette?: string[];
|
|
44
|
+
/**
|
|
45
|
+
* What sits behind the cycling words. White by default so a coloured word
|
|
46
|
+
* stays readable over artwork; pass `'transparent'` for a plain background
|
|
47
|
+
* where the plate is not wanted.
|
|
48
|
+
*/
|
|
49
|
+
background?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Hold every cycling piece at the width of the longest word it can show,
|
|
52
|
+
* instead of letting it size itself to whichever word is currently up.
|
|
53
|
+
*
|
|
54
|
+
* Off by default, because it changes the look: a piece is then as wide as
|
|
55
|
+
* its longest word even when a short one is showing, which is right for a
|
|
56
|
+
* line that reads as a slot being filled and wrong for one meant to read as
|
|
57
|
+
* an ordinary sentence.
|
|
58
|
+
*
|
|
59
|
+
* Turn it on when the line is set large, or is close to the width it has.
|
|
60
|
+
* Words of different lengths otherwise shove the rest of the sentence
|
|
61
|
+
* sideways on every tick, and the longest combination can take a line that
|
|
62
|
+
* fits on one row and drop it onto two — which, since a theme may set any
|
|
63
|
+
* typeface, is not something a caller can rule out by measuring.
|
|
64
|
+
*
|
|
65
|
+
* The reservation is made by the browser, not by a measurement: all of the
|
|
66
|
+
* words are laid into one grid cell and the ones not showing are hidden, so
|
|
67
|
+
* the piece is exactly as wide as the widest of them in the font and size it
|
|
68
|
+
* actually ends up with.
|
|
69
|
+
*/
|
|
70
|
+
reserveWidth?: boolean;
|
|
71
|
+
intervalMs?: number;
|
|
72
|
+
transitionMs?: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A line of text in which one or more words change, in step.
|
|
76
|
+
*
|
|
77
|
+
* ```tsx
|
|
78
|
+
* <AnimatedText
|
|
79
|
+
* parts={[
|
|
80
|
+
* 'Bring ',
|
|
81
|
+
* { words: ['Claude', 'Codex', 'Cursor'], color: 'accent' },
|
|
82
|
+
* '. Datalayer gives it a secure endpoint to your ',
|
|
83
|
+
* { words: ['Notebooks', 'Data', 'Code Sandboxes'], color: 'success' },
|
|
84
|
+
* '.',
|
|
85
|
+
* ]}
|
|
86
|
+
* />
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* Set large, or set close to the width available, pass `reserveWidth` so the
|
|
90
|
+
* cycling pieces hold the width of their longest word and the line neither
|
|
91
|
+
* shifts on each tick nor wraps on its longest combination:
|
|
92
|
+
*
|
|
93
|
+
* ```tsx
|
|
94
|
+
* <AnimatedText reserveWidth parts={[...]} background="transparent" />
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare function AnimatedText({ prefix, words, suffix, parts, color, palette, background, reserveWidth, intervalMs, transitionMs, style, ...rest }: AnimatedTextProps): JSX.Element;
|
|
98
|
+
export default AnimatedText;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
7
|
+
/** The Primer foreground tokens, which carry the theme with them. */
|
|
8
|
+
const THEME_COLORS = {
|
|
9
|
+
accent: 'var(--fgColor-accent, var(--color-accent-fg))',
|
|
10
|
+
success: 'var(--fgColor-success, var(--color-success-fg))',
|
|
11
|
+
attention: 'var(--fgColor-attention, var(--color-attention-fg))',
|
|
12
|
+
severe: 'var(--fgColor-severe, var(--color-severe-fg))',
|
|
13
|
+
done: 'var(--fgColor-done, var(--color-done-fg))',
|
|
14
|
+
muted: 'var(--fgColor-muted, var(--color-fg-muted))',
|
|
15
|
+
default: 'inherit',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The colours the cycling words are drawn in when none is named.
|
|
19
|
+
*
|
|
20
|
+
* Fixed values rather than Primer's theme tokens, because the plate behind
|
|
21
|
+
* the words is white in either theme: a token would resolve to its light-mode
|
|
22
|
+
* variant in the dark and wash out. Every one of these sits between 4.8 and
|
|
23
|
+
* 5.4 contrast on white — comfortably readable, and none of them the pale or
|
|
24
|
+
* near-black ends of the palette.
|
|
25
|
+
*/
|
|
26
|
+
const MEDIUM_PALETTE = [
|
|
27
|
+
'#0969da', // blue
|
|
28
|
+
'#1a7f37', // green
|
|
29
|
+
'#8250df', // purple
|
|
30
|
+
'#bc4c00', // orange
|
|
31
|
+
'#cf222e', // red
|
|
32
|
+
'#1b7c83', // teal
|
|
33
|
+
'#bf3989', // pink
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* What sits behind the cycling words.
|
|
37
|
+
*
|
|
38
|
+
* A coloured word over a photograph or an illustrated hero is legible only by
|
|
39
|
+
* luck: the contrast changes with whatever the artwork happens to be doing
|
|
40
|
+
* behind that glyph. A solid plate underneath makes it legible by
|
|
41
|
+
* construction, whatever it is laid over.
|
|
42
|
+
*/
|
|
43
|
+
const DEFAULT_BACKGROUND = '#ffffff';
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a colour: a known token becomes the themed CSS variable, anything
|
|
46
|
+
* else is passed through so a caller can still give a literal colour.
|
|
47
|
+
*/
|
|
48
|
+
function resolveColor(color) {
|
|
49
|
+
if (!color) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return THEME_COLORS[color] ?? color;
|
|
53
|
+
}
|
|
54
|
+
/** Whether this reader has asked for less movement. */
|
|
55
|
+
function usePrefersReducedMotion() {
|
|
56
|
+
const [reduced, setReduced] = useState(false);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (typeof window === 'undefined' || !window.matchMedia) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const query = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
62
|
+
const update = () => setReduced(query.matches);
|
|
63
|
+
update();
|
|
64
|
+
query.addEventListener('change', update);
|
|
65
|
+
return () => query.removeEventListener('change', update);
|
|
66
|
+
}, []);
|
|
67
|
+
return reduced;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A line of text in which one or more words change, in step.
|
|
71
|
+
*
|
|
72
|
+
* ```tsx
|
|
73
|
+
* <AnimatedText
|
|
74
|
+
* parts={[
|
|
75
|
+
* 'Bring ',
|
|
76
|
+
* { words: ['Claude', 'Codex', 'Cursor'], color: 'accent' },
|
|
77
|
+
* '. Datalayer gives it a secure endpoint to your ',
|
|
78
|
+
* { words: ['Notebooks', 'Data', 'Code Sandboxes'], color: 'success' },
|
|
79
|
+
* '.',
|
|
80
|
+
* ]}
|
|
81
|
+
* />
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* Set large, or set close to the width available, pass `reserveWidth` so the
|
|
85
|
+
* cycling pieces hold the width of their longest word and the line neither
|
|
86
|
+
* shifts on each tick nor wraps on its longest combination:
|
|
87
|
+
*
|
|
88
|
+
* ```tsx
|
|
89
|
+
* <AnimatedText reserveWidth parts={[...]} background="transparent" />
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function AnimatedText({ prefix, words, suffix, parts, color, palette = MEDIUM_PALETTE, background = DEFAULT_BACKGROUND, reserveWidth = false, intervalMs = 1800, transitionMs = 240, style, ...rest }) {
|
|
93
|
+
// The words still change for a reader who asked for less movement — that is
|
|
94
|
+
// what this component is for — but they do it without travelling, and the
|
|
95
|
+
// fade is dropped rather than merely shortened.
|
|
96
|
+
const reducedMotion = usePrefersReducedMotion();
|
|
97
|
+
// The older three-prop form is the same thing with one cycling piece.
|
|
98
|
+
const resolvedParts = useMemo(() => {
|
|
99
|
+
if (parts && parts.length > 0) {
|
|
100
|
+
return parts;
|
|
101
|
+
}
|
|
102
|
+
return [prefix ?? '', { words: words ?? [] }, suffix ?? ''];
|
|
103
|
+
}, [parts, prefix, suffix, words]);
|
|
104
|
+
const cyclingParts = useMemo(() => resolvedParts.filter((part) => typeof part !== 'string' &&
|
|
105
|
+
part.words.filter(word => word.trim().length > 0).length > 0), [resolvedParts]);
|
|
106
|
+
// One index for the whole line: what makes several cycling pieces change
|
|
107
|
+
// together rather than drifting apart.
|
|
108
|
+
const [step, setStep] = useState(0);
|
|
109
|
+
const [visible, setVisible] = useState(true);
|
|
110
|
+
// The tick has to run until every piece has come back round, so a line
|
|
111
|
+
// pairing three words with four does not repeat after three.
|
|
112
|
+
const cycleLength = useMemo(() => {
|
|
113
|
+
const lengths = cyclingParts.map(part => part.words.filter(word => word.trim().length > 0).length);
|
|
114
|
+
return lengths.reduce((total, length) => lowestCommonMultiple(total, length), 1);
|
|
115
|
+
}, [cyclingParts]);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (cycleLength <= 1) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const timer = setInterval(() => {
|
|
121
|
+
setVisible(false);
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
setStep(previous => (previous + 1) % cycleLength);
|
|
124
|
+
setVisible(true);
|
|
125
|
+
}, transitionMs);
|
|
126
|
+
}, intervalMs);
|
|
127
|
+
return () => {
|
|
128
|
+
clearInterval(timer);
|
|
129
|
+
};
|
|
130
|
+
}, [cycleLength, intervalMs, transitionMs]);
|
|
131
|
+
/**
|
|
132
|
+
* The colour of the cycling piece at `ordinal`, for the current step.
|
|
133
|
+
*
|
|
134
|
+
* Derived rather than random so a re-render does not repaint the line, and
|
|
135
|
+
* offset by the ordinal so two words showing at once never take the same
|
|
136
|
+
* colour. The palette length being prime keeps the pairing from settling
|
|
137
|
+
* into a short repeating pattern.
|
|
138
|
+
*/
|
|
139
|
+
const paletteColor = (ordinal) => {
|
|
140
|
+
if (palette.length === 0) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
return palette[(step * 2 + ordinal * 3) % palette.length];
|
|
144
|
+
};
|
|
145
|
+
/** How a word arrives and leaves, and how far it travels doing it. */
|
|
146
|
+
const motion = reducedMotion
|
|
147
|
+
? { transition: undefined }
|
|
148
|
+
: {
|
|
149
|
+
transition: `opacity ${transitionMs}ms ease, transform ${transitionMs}ms ease`,
|
|
150
|
+
};
|
|
151
|
+
const offset = reducedMotion ? 'translateY(0)' : 'translateY(0.25em)';
|
|
152
|
+
/** The plate the words of a piece are drawn on, and the colour they take. */
|
|
153
|
+
const pieceStyle = (part, ordinal) => {
|
|
154
|
+
const plate = part.background ?? background;
|
|
155
|
+
const hasPlate = plate !== 'transparent' && plate !== 'none';
|
|
156
|
+
return {
|
|
157
|
+
// An explicitly named colour always wins; otherwise the palette.
|
|
158
|
+
color: resolveColor(part.color ?? color) ?? paletteColor(ordinal),
|
|
159
|
+
backgroundColor: hasPlate ? plate : undefined,
|
|
160
|
+
// Enough to keep the plate clear of the glyphs without the words
|
|
161
|
+
// drifting apart from the fixed text around them.
|
|
162
|
+
padding: hasPlate ? '0 0.25em' : undefined,
|
|
163
|
+
borderRadius: hasPlate ? '0.25em' : undefined,
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
const wordStyle = (part, ordinal) => ({
|
|
167
|
+
...pieceStyle(part, ordinal),
|
|
168
|
+
display: 'inline-block',
|
|
169
|
+
...motion,
|
|
170
|
+
opacity: visible ? 1 : 0,
|
|
171
|
+
transform: visible ? 'translateY(0)' : offset,
|
|
172
|
+
});
|
|
173
|
+
/**
|
|
174
|
+
* The piece as a slot: a grid of one cell, holding every word it can show.
|
|
175
|
+
*
|
|
176
|
+
* The cell takes the width of its widest occupant, so the slot is the same
|
|
177
|
+
* width whichever word is up — which is the whole point of `reserveWidth`,
|
|
178
|
+
* and why the words have to be in the document rather than measured.
|
|
179
|
+
*/
|
|
180
|
+
const slotStyle = (part, ordinal) => ({
|
|
181
|
+
...pieceStyle(part, ordinal),
|
|
182
|
+
display: 'inline-grid',
|
|
183
|
+
justifyItems: 'center',
|
|
184
|
+
});
|
|
185
|
+
const wordInSlotStyle = (showing) => ({
|
|
186
|
+
gridArea: '1 / 1',
|
|
187
|
+
whiteSpace: 'nowrap',
|
|
188
|
+
// Hidden rather than merely transparent: the words not showing should be
|
|
189
|
+
// out of the accessibility tree and out of the way of the pointer, while
|
|
190
|
+
// still holding the cell open.
|
|
191
|
+
visibility: showing ? 'visible' : 'hidden',
|
|
192
|
+
...motion,
|
|
193
|
+
opacity: showing && visible ? 1 : 0,
|
|
194
|
+
transform: showing && visible ? 'translateY(0)' : offset,
|
|
195
|
+
});
|
|
196
|
+
return (_jsx("span", { ...rest, style: style, children: resolvedParts.map((part, index) => {
|
|
197
|
+
if (typeof part === 'string') {
|
|
198
|
+
return _jsx("span", { children: part }, `fixed-${index}`);
|
|
199
|
+
}
|
|
200
|
+
const available = part.words.filter(word => word.trim().length > 0);
|
|
201
|
+
if (available.length === 0) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const ordinal = cyclingParts.indexOf(part);
|
|
205
|
+
const current = step % available.length;
|
|
206
|
+
return (_jsx("span", { "aria-live": "polite", style: reserveWidth ? slotStyle(part, ordinal) : wordStyle(part, ordinal), children: reserveWidth
|
|
207
|
+
? available.map((word, wordIndex) => (_jsx("span", { "aria-hidden": wordIndex === current ? undefined : true, style: wordInSlotStyle(wordIndex === current), children: word }, word)))
|
|
208
|
+
: available[current] }, `words-${index}`));
|
|
209
|
+
}) }));
|
|
210
|
+
}
|
|
211
|
+
function greatestCommonDivisor(a, b) {
|
|
212
|
+
return b === 0 ? a : greatestCommonDivisor(b, a % b);
|
|
213
|
+
}
|
|
214
|
+
function lowestCommonMultiple(a, b) {
|
|
215
|
+
if (a === 0 || b === 0) {
|
|
216
|
+
return Math.max(a, b);
|
|
217
|
+
}
|
|
218
|
+
return Math.abs(a * b) / greatestCommonDivisor(a, b);
|
|
219
|
+
}
|
|
220
|
+
export default AnimatedText;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './AnimatedText';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface ISigninAPIKeyProps {
|
|
2
2
|
/**
|
|
3
3
|
* Home page route to navigate to after successful login
|
|
4
4
|
*/
|
|
@@ -16,5 +16,5 @@ export interface ILoginTokenProps {
|
|
|
16
16
|
* Component for token-based authentication
|
|
17
17
|
* Provides a simple button that expands to show a token input form
|
|
18
18
|
*/
|
|
19
|
-
export declare const
|
|
20
|
-
export default
|
|
19
|
+
export declare const SigInAPIKey: (props: ISigninAPIKeyProps) => JSX.Element;
|
|
20
|
+
export default SigInAPIKey;
|
|
@@ -14,7 +14,7 @@ import { useIAMStore } from '../../state/index.js';
|
|
|
14
14
|
* Component for token-based authentication
|
|
15
15
|
* Provides a simple button that expands to show a token input form
|
|
16
16
|
*/
|
|
17
|
-
export const
|
|
17
|
+
export const SigInAPIKey = (props) => {
|
|
18
18
|
const { homeRoute, disabled = false, style } = props;
|
|
19
19
|
const [showForm, setShowForm] = useState(false);
|
|
20
20
|
const [token, setToken] = useState('');
|
|
@@ -35,7 +35,7 @@ export const LoginToken = (props) => {
|
|
|
35
35
|
setToken('');
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
|
-
console.error('Token
|
|
38
|
+
console.error('Token sign-in failed:', error);
|
|
39
39
|
enqueueToast('Failed to authenticate with provided token', {
|
|
40
40
|
variant: 'error',
|
|
41
41
|
});
|
|
@@ -45,7 +45,7 @@ export const LoginToken = (props) => {
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
if (!showForm) {
|
|
48
|
-
return (_jsx(Button, { onClick: () => setShowForm(true), style: style, disabled: disabled, children: "
|
|
48
|
+
return (_jsx(Button, { onClick: () => setShowForm(true), style: style, disabled: disabled, children: "Sign in with API Key" }));
|
|
49
49
|
}
|
|
50
50
|
return (_jsxs(Box, { style: style, children: [_jsxs(FormControl, { children: [_jsx(FormControl.Label, { children: "Your Authentication Token" }), _jsx(TextInput, { block: true, placeholder: "Paste your authentication token here", value: token, disabled: disabled || loading, onChange: e => setToken(e.target.value), onKeyDown: e => {
|
|
51
51
|
if (e.key === 'Enter') {
|
|
@@ -55,9 +55,9 @@ export const LoginToken = (props) => {
|
|
|
55
55
|
setShowForm(false);
|
|
56
56
|
setToken('');
|
|
57
57
|
}
|
|
58
|
-
} }), _jsx(FormControl.Caption, { children: "Enter your Datalayer authentication token to log in" })] }), _jsxs(Box, { mt: 3, display: "flex", justifyContent: "flex-start", children: [_jsx(Button, { variant: "primary", onClick: handleLogin, disabled: disabled || loading || !token.trim(), sx: { mr: 2 }, children: loading ? 'Authenticating...' : '
|
|
58
|
+
} }), _jsx(FormControl.Caption, { children: "Enter your Datalayer authentication token to log in" })] }), _jsxs(Box, { mt: 3, display: "flex", justifyContent: "flex-start", children: [_jsx(Button, { variant: "primary", onClick: handleLogin, disabled: disabled || loading || !token.trim(), sx: { mr: 2 }, children: loading ? 'Authenticating...' : 'Sign In' }), _jsx(Button, { variant: "default", disabled: disabled || loading, onClick: () => {
|
|
59
59
|
setShowForm(false);
|
|
60
60
|
setToken('');
|
|
61
61
|
}, children: "Cancel" })] })] }));
|
|
62
62
|
};
|
|
63
|
-
export default
|
|
63
|
+
export default SigInAPIKey;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface ISigninProps {
|
|
2
2
|
/**
|
|
3
3
|
* Page heading
|
|
4
4
|
*/
|
|
@@ -40,5 +40,5 @@ export interface ILoginProps {
|
|
|
40
40
|
*/
|
|
41
41
|
showTokenLogin?: boolean;
|
|
42
42
|
}
|
|
43
|
-
export declare const
|
|
44
|
-
export default
|
|
43
|
+
export declare const SignIn: (props: ISigninProps) => JSX.Element;
|
|
44
|
+
export default SignIn;
|
|
@@ -14,8 +14,8 @@ import { useIAMStore } from '../../state/index.js';
|
|
|
14
14
|
import { CenteredSpinner } from '../display/index.js';
|
|
15
15
|
import { isInsideJupyterLab, validateLength } from '../../utils/index.js';
|
|
16
16
|
import { useNavigate, useCache, useToast, useIAM } from '../../hooks/index.js';
|
|
17
|
-
import {
|
|
18
|
-
export const
|
|
17
|
+
import { SigInAPIKey } from './SigInAPIKey.js';
|
|
18
|
+
export const SignIn = (props) => {
|
|
19
19
|
const { heading, homeRoute, loginRoute, showEmailLogin = true, showGitHubLogin = true, showGoogleLogin = true, showTokenLogin = true, } = props;
|
|
20
20
|
const { useLogin, useOAuth2AuthorizationURL } = useCache({ loginRoute });
|
|
21
21
|
const loginMutation = useLogin();
|
|
@@ -62,7 +62,7 @@ export const Login = (props) => {
|
|
|
62
62
|
addIAMProviderAuthorizationURL(iamProvider.name, authUrl);
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
|
-
console.error(`Failed to get the
|
|
65
|
+
console.error(`Failed to get the sign-in URL from Datalayer IAM for provider ${iamProvider.name}.`);
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
});
|
|
@@ -82,7 +82,7 @@ export const Login = (props) => {
|
|
|
82
82
|
setLoadingWithToken(1);
|
|
83
83
|
loginAndNavigate(externalToken, logout, checkIAMToken, navigate, homeRoute)
|
|
84
84
|
.catch(error => {
|
|
85
|
-
console.debug('Failed to
|
|
85
|
+
console.debug('Failed to sign in with token from cookie..', error);
|
|
86
86
|
enqueueToast('Failed to check authentication.', { variant: 'error' });
|
|
87
87
|
})
|
|
88
88
|
.finally(() => {
|
|
@@ -127,14 +127,14 @@ export const Login = (props) => {
|
|
|
127
127
|
navigate(homeRoute);
|
|
128
128
|
}
|
|
129
129
|
else {
|
|
130
|
-
enqueueToast('Failed to
|
|
130
|
+
enqueueToast('Failed to sign in. Check your username and password.', {
|
|
131
131
|
variant: 'warning',
|
|
132
132
|
});
|
|
133
133
|
setValidationResult({
|
|
134
134
|
handle: '',
|
|
135
135
|
password: 'Invalid credentials',
|
|
136
136
|
});
|
|
137
|
-
console.debug(`Failed to
|
|
137
|
+
console.debug(`Failed to sign in: ${resp.message}`, resp.errors ?? '');
|
|
138
138
|
}
|
|
139
139
|
},
|
|
140
140
|
onSettled: () => {
|
|
@@ -183,22 +183,22 @@ export const Login = (props) => {
|
|
|
183
183
|
: 'Your password may not be empty.',
|
|
184
184
|
});
|
|
185
185
|
}, [formValues]);
|
|
186
|
-
return (_jsxs(PageLayout, { containerWidth: "medium", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [_jsx(PageLayout.Header, { children: _jsx(Heading, { children: heading || '
|
|
186
|
+
return (_jsxs(PageLayout, { containerWidth: "medium", padding: "normal", style: { overflow: 'visible', minHeight: 'calc(100vh - 45px)' }, children: [_jsx(PageLayout.Header, { children: _jsx(Heading, { children: heading || 'Sign in to Datalayer' }) }), _jsx(PageLayout.Content, { children: loadingWithToken < 0 ? (_jsx(_Fragment, { children: _jsxs(Box, { display: "flex", children: [showEmailLogin && (_jsxs(Box, { sx: { label: { marginTop: 2 }, paddingRight: '10%' }, children: [_jsx(Box, { mt: 5, children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your username" }), _jsx(TextInput, { autoFocus: true, placeholder: "Your username", value: formValues.handle, disabled: loading, onChange: handleHandleChange, onKeyDown: handleKeyDown }), validationResult.handle && (_jsx(FormControl.Validation, { variant: validationResult.handle ? 'error' : 'success', children: validationResult.handle }))] }) }), _jsx(Box, { children: _jsxs(FormControl, { required: true, children: [_jsx(FormControl.Label, { children: "Your password" }), _jsx(TextInput, { placeholder: "Your password", type: passwordVisibility ? 'text' : 'password', value: formValues.password, disabled: loading, onChange: handlePasswordChange, onKeyDown: handleKeyDown, trailingAction: _jsx(TextInput.Action, { onClick: () => {
|
|
187
187
|
setPasswordVisibility(!passwordVisibility);
|
|
188
188
|
}, disabled: loading, icon: passwordVisibility ? EyeClosedIcon : EyeIcon, "aria-label": passwordVisibility
|
|
189
189
|
? 'Hide password'
|
|
190
190
|
: 'Show password', sx: { color: 'var(--fgColor-muted)' } }), sx: { overflow: 'visible' } }), validationResult.password && (_jsx(FormControl.Validation, { variant: validationResult.password ? 'error' : 'success', children: validationResult.password }))] }) }), _jsxs(Box, { mt: 5, children: [_jsx(Button, { variant: "primary", disabled: loading ||
|
|
191
191
|
validationResult.handle !== '' ||
|
|
192
192
|
validationResult.password !== '', onClick: submit, children: loading
|
|
193
|
-
? '
|
|
193
|
+
? 'Sign In…'
|
|
194
194
|
: heading
|
|
195
|
-
? '
|
|
196
|
-
: '
|
|
195
|
+
? 'Sign in with Datalayer'
|
|
196
|
+
: 'Sign In' }), _jsx(Box, { pt: 6 }), _jsx(Link, { href: "https://datalayer.ai/password", target: "_blank", children: "Forgot password?" })] })] })), _jsx(Box, { children: _jsxs(Box, { display: "flex", flexDirection: "column", sx: { margin: 'auto' }, children: [showGitHubLogin &&
|
|
197
197
|
iamProvidersAuthorizationURL[IAMProvidersSpecs.GitHub.name] && (_jsx(Button, { leadingVisual: pendingOAuthProvider === IAMProvidersSpecs.GitHub.name
|
|
198
198
|
? () => _jsx(Spinner, { size: "small" })
|
|
199
|
-
: MarkGithubIcon, onClick: () => startOAuthSignIn(IAMProvidersSpecs.GitHub), disabled: socialButtonsDisabled, style: { margin: '10px 0' }, children: "
|
|
199
|
+
: MarkGithubIcon, onClick: () => startOAuthSignIn(IAMProvidersSpecs.GitHub), disabled: socialButtonsDisabled, style: { margin: '10px 0' }, children: "Sign in with GitHub" })), showGoogleLogin &&
|
|
200
200
|
iamProvidersAuthorizationURL[IAMProvidersSpecs.Google.name] && (_jsx(Button, { leadingVisual: pendingOAuthProvider === IAMProvidersSpecs.Google.name
|
|
201
201
|
? () => _jsx(Spinner, { size: "small" })
|
|
202
|
-
: GoogleIcon, onClick: () => startOAuthSignIn(IAMProvidersSpecs.Google), disabled: socialButtonsDisabled, style: { margin: '10px 0' }, children: "
|
|
202
|
+
: GoogleIcon, onClick: () => startOAuthSignIn(IAMProvidersSpecs.Google), disabled: socialButtonsDisabled, style: { margin: '10px 0' }, children: "Sign in with Google" })), showTokenLogin && (_jsx(SigInAPIKey, { homeRoute: homeRoute, disabled: loading, style: { margin: '10px 0' } }))] }) })] }) })) : loadingWithToken ? (_jsx(CenteredSpinner, { message: "Checking authentication\u2026" })) : (_jsx(_Fragment, {})) })] }));
|
|
203
203
|
};
|
|
204
|
-
export default
|
|
204
|
+
export default SignIn;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StoryObj } from '@storybook/react-vite';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* SignIn component provides a comprehensive authentication interface with multiple methods:
|
|
4
4
|
* - Email/password authentication
|
|
5
5
|
* - OAuth (GitHub, LinkedIn)
|
|
6
6
|
* - Direct token input
|
|
@@ -9,7 +9,7 @@ import type { StoryObj } from '@storybook/react-vite';
|
|
|
9
9
|
*/
|
|
10
10
|
declare const meta: {
|
|
11
11
|
title: string;
|
|
12
|
-
component: (props: import("./
|
|
12
|
+
component: (props: import("./SignIn").ISigninProps) => JSX.Element;
|
|
13
13
|
parameters: {
|
|
14
14
|
layout: string;
|
|
15
15
|
};
|