@meshxdata/fops 0.1.54 → 0.1.57
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/CHANGELOG.md +184 -1
- package/package.json +1 -2
- package/src/commands/index.js +2 -0
- package/src/commands/k3s-cmd.js +124 -0
- package/src/commands/lifecycle.js +7 -0
- package/src/plugins/builtins/docker-compose.js +7 -13
- package/src/plugins/bundled/fops-plugin-azure/lib/azure-openai.js +0 -3
- package/src/plugins/bundled/fops-plugin-azure/lib/commands/vm-cmds.js +5 -2
- package/src/plugins/bundled/fops-plugin-cloud/api.js +14 -0
- package/src/project.js +12 -7
- package/src/plugins/bundled/fops-plugin-cloud/ui/postcss.config.cjs +0 -5
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/App.jsx +0 -32
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/api/client.js +0 -114
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/api/queries.js +0 -111
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/components/LogPanel.jsx +0 -162
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/components/ThemeToggle.jsx +0 -46
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/css/additional-styles/utility-patterns.css +0 -147
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/css/style.css +0 -138
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/favicon.svg +0 -15
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/lib/utils.ts +0 -19
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/main.jsx +0 -25
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Audit.jsx +0 -164
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Costs.jsx +0 -305
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/CreateResource.jsx +0 -285
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Fleet.jsx +0 -307
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/pages/Resources.jsx +0 -229
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/partials/Header.jsx +0 -132
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/partials/Sidebar.jsx +0 -174
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/partials/SidebarLinkGroup.jsx +0 -21
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/AuthContext.jsx +0 -170
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/Info.jsx +0 -49
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/ThemeContext.jsx +0 -37
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/Transition.jsx +0 -116
- package/src/plugins/bundled/fops-plugin-cloud/ui/src/utils/Utils.js +0 -63
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useEffect, useState } from "react";
|
|
2
|
-
import { Auth0Provider, useAuth0 } from "@auth0/auth0-react";
|
|
3
|
-
import { setTokenGetter } from "../api/client";
|
|
4
|
-
|
|
5
|
-
const AuthContext = createContext({
|
|
6
|
-
user: null,
|
|
7
|
-
roles: [],
|
|
8
|
-
permissions: [],
|
|
9
|
-
isAdmin: false,
|
|
10
|
-
canWrite: false,
|
|
11
|
-
canDeploy: false,
|
|
12
|
-
getToken: async () => "",
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Inner provider that fetches user profile + roles from the API
|
|
17
|
-
* once authenticated.
|
|
18
|
-
*/
|
|
19
|
-
function AuthGate({ children }) {
|
|
20
|
-
const { isAuthenticated, isLoading, loginWithRedirect, getAccessTokenSilently, user } = useAuth0();
|
|
21
|
-
const [profile, setProfile] = useState(null);
|
|
22
|
-
const [loadingProfile, setLoadingProfile] = useState(true);
|
|
23
|
-
|
|
24
|
-
// Wire token getter into the API client so all fetches are authenticated
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (isAuthenticated) setTokenGetter(() => getAccessTokenSilently());
|
|
27
|
-
}, [isAuthenticated, getAccessTokenSilently]);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (!isAuthenticated) return;
|
|
31
|
-
let cancelled = false;
|
|
32
|
-
|
|
33
|
-
(async () => {
|
|
34
|
-
try {
|
|
35
|
-
const token = await getAccessTokenSilently();
|
|
36
|
-
const res = await fetch("/cloud/api/me", {
|
|
37
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
38
|
-
});
|
|
39
|
-
if (res.ok && !cancelled) {
|
|
40
|
-
setProfile(await res.json());
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
// token or fetch failed — will show login
|
|
44
|
-
} finally {
|
|
45
|
-
if (!cancelled) setLoadingProfile(false);
|
|
46
|
-
}
|
|
47
|
-
})();
|
|
48
|
-
|
|
49
|
-
return () => { cancelled = true; };
|
|
50
|
-
}, [isAuthenticated, getAccessTokenSilently]);
|
|
51
|
-
|
|
52
|
-
if (isLoading) {
|
|
53
|
-
return (
|
|
54
|
-
<div className="flex items-center justify-center h-screen bg-gray-100 dark:bg-gray-900">
|
|
55
|
-
<div className="text-gray-500 dark:text-gray-400 text-sm">Loading...</div>
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!isAuthenticated) {
|
|
61
|
-
return (
|
|
62
|
-
<div className="flex items-center justify-center h-screen bg-gray-100 dark:bg-gray-900">
|
|
63
|
-
<div className="text-center">
|
|
64
|
-
<h1 className="text-2xl font-bold text-gray-800 dark:text-gray-100 mb-2">meshXcloud</h1>
|
|
65
|
-
<p className="text-gray-500 dark:text-gray-400 mb-6">Sign in to access the cloud panel</p>
|
|
66
|
-
<button
|
|
67
|
-
onClick={() => loginWithRedirect()}
|
|
68
|
-
className="px-6 py-2.5 bg-violet-500 hover:bg-violet-600 text-white rounded-lg font-medium transition-colors"
|
|
69
|
-
>
|
|
70
|
-
Sign in with Auth0
|
|
71
|
-
</button>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (loadingProfile) {
|
|
78
|
-
return (
|
|
79
|
-
<div className="flex items-center justify-center h-screen bg-gray-100 dark:bg-gray-900">
|
|
80
|
-
<div className="text-gray-500 dark:text-gray-400 text-sm">Loading profile...</div>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const roles = profile?.roles || [];
|
|
86
|
-
const permissions = profile?.permissions || [];
|
|
87
|
-
const isAdmin = roles.includes("admin");
|
|
88
|
-
|
|
89
|
-
const value = {
|
|
90
|
-
user: { ...user, ...profile },
|
|
91
|
-
roles,
|
|
92
|
-
permissions,
|
|
93
|
-
isAdmin,
|
|
94
|
-
canWrite: isAdmin || permissions.includes("cloud:write") || permissions.includes("cloud:admin"),
|
|
95
|
-
canDeploy: isAdmin || permissions.includes("cloud:deploy") || permissions.includes("cloud:admin"),
|
|
96
|
-
getToken: getAccessTokenSilently,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Passthrough provider when auth is disabled — grants full admin access.
|
|
104
|
-
*/
|
|
105
|
-
function NoAuthProvider({ children }) {
|
|
106
|
-
const value = {
|
|
107
|
-
user: { sub: "local", name: "Local User", email: "" },
|
|
108
|
-
roles: ["admin"],
|
|
109
|
-
permissions: ["cloud:admin"],
|
|
110
|
-
isAdmin: true,
|
|
111
|
-
canWrite: true,
|
|
112
|
-
canDeploy: true,
|
|
113
|
-
getToken: async () => "",
|
|
114
|
-
};
|
|
115
|
-
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Top-level wrapper that fetches Auth0 config from the API.
|
|
120
|
-
* If auth is disabled (CLOUD_AUTH != 1), renders children directly
|
|
121
|
-
* with full permissions. Otherwise, wraps in Auth0Provider + AuthGate.
|
|
122
|
-
*/
|
|
123
|
-
export default function AuthProvider({ children }) {
|
|
124
|
-
const [config, setConfig] = useState(null);
|
|
125
|
-
const [error, setError] = useState(null);
|
|
126
|
-
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
fetch("/cloud/api/auth-config")
|
|
129
|
-
.then((r) => r.json())
|
|
130
|
-
.then(setConfig)
|
|
131
|
-
.catch((e) => setError(e.message));
|
|
132
|
-
}, []);
|
|
133
|
-
|
|
134
|
-
if (error) {
|
|
135
|
-
return (
|
|
136
|
-
<div className="flex items-center justify-center h-screen bg-gray-100 dark:bg-gray-900">
|
|
137
|
-
<div className="text-red-500 text-sm">Auth config error: {error}</div>
|
|
138
|
-
</div>
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (!config) {
|
|
143
|
-
return (
|
|
144
|
-
<div className="flex items-center justify-center h-screen bg-gray-100 dark:bg-gray-900">
|
|
145
|
-
<div className="text-gray-500 dark:text-gray-400 text-sm">Initializing...</div>
|
|
146
|
-
</div>
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Auth disabled — bypass Auth0 entirely
|
|
151
|
-
if (!config.enabled) {
|
|
152
|
-
return <NoAuthProvider>{children}</NoAuthProvider>;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return (
|
|
156
|
-
<Auth0Provider
|
|
157
|
-
domain={config.domain}
|
|
158
|
-
clientId={config.clientId}
|
|
159
|
-
authorizationParams={{
|
|
160
|
-
redirect_uri: window.location.origin + "/cloud",
|
|
161
|
-
audience: config.audience,
|
|
162
|
-
}}
|
|
163
|
-
cacheLocation="localstorage"
|
|
164
|
-
>
|
|
165
|
-
<AuthGate>{children}</AuthGate>
|
|
166
|
-
</Auth0Provider>
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export const useAuthContext = () => useContext(AuthContext);
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import Transition from './Transition';
|
|
3
|
-
|
|
4
|
-
function Info({
|
|
5
|
-
children,
|
|
6
|
-
className,
|
|
7
|
-
containerClassName
|
|
8
|
-
}) {
|
|
9
|
-
|
|
10
|
-
const [infoOpen, setInfoOpen] = useState(false);
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<div
|
|
14
|
-
className={`relative ${className}`}
|
|
15
|
-
onMouseEnter={() => setInfoOpen(true)}
|
|
16
|
-
onMouseLeave={() => setInfoOpen(false)}
|
|
17
|
-
onFocus={() => setInfoOpen(true)}
|
|
18
|
-
onBlur={() => setInfoOpen(false)}
|
|
19
|
-
>
|
|
20
|
-
<button
|
|
21
|
-
className="block"
|
|
22
|
-
aria-haspopup="true"
|
|
23
|
-
aria-expanded={infoOpen}
|
|
24
|
-
onClick={(e) => e.preventDefault()}
|
|
25
|
-
>
|
|
26
|
-
<svg className="w-4 h-4 fill-current text-gray-400" viewBox="0 0 16 16">
|
|
27
|
-
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
|
28
|
-
</svg>
|
|
29
|
-
</button>
|
|
30
|
-
<div className="z-10 absolute bottom-full left-1/2 transform -translate-x-1/2">
|
|
31
|
-
<Transition
|
|
32
|
-
show={infoOpen}
|
|
33
|
-
tag="div"
|
|
34
|
-
className={`bg-white border border-gray-200 p-3 rounded-sm shadow-lg overflow-hidden mb-2 ${containerClassName}`}
|
|
35
|
-
enter="transition ease-out duration-200 transform"
|
|
36
|
-
enterStart="opacity-0 -translate-y-2"
|
|
37
|
-
enterEnd="opacity-100 translate-y-0"
|
|
38
|
-
leave="transition ease-out duration-200"
|
|
39
|
-
leaveStart="opacity-100"
|
|
40
|
-
leaveEnd="opacity-0"
|
|
41
|
-
>
|
|
42
|
-
{children}
|
|
43
|
-
</Transition>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export default Info;
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useState, useEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
const ThemeContext = createContext({
|
|
4
|
-
currentTheme: 'light',
|
|
5
|
-
changeCurrentTheme: () => {},
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
export default function ThemeProvider({children}) {
|
|
9
|
-
const persistedTheme = localStorage.getItem('theme');
|
|
10
|
-
const [theme, setTheme] = useState(persistedTheme || 'light');
|
|
11
|
-
|
|
12
|
-
const changeCurrentTheme = (newTheme) => {
|
|
13
|
-
setTheme(newTheme);
|
|
14
|
-
localStorage.setItem('theme', newTheme);
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
document.documentElement.classList.add('**:transition-none!');
|
|
19
|
-
if (theme === 'light') {
|
|
20
|
-
document.documentElement.classList.remove('dark');
|
|
21
|
-
document.documentElement.style.colorScheme = 'light';
|
|
22
|
-
} else {
|
|
23
|
-
document.documentElement.classList.add('dark');
|
|
24
|
-
document.documentElement.style.colorScheme = 'dark';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const transitionTimeout = setTimeout(() => {
|
|
28
|
-
document.documentElement.classList.remove('**:transition-none!');
|
|
29
|
-
}, 1);
|
|
30
|
-
|
|
31
|
-
return () => clearTimeout(transitionTimeout);
|
|
32
|
-
}, [theme]);
|
|
33
|
-
|
|
34
|
-
return <ThemeContext.Provider value={{ currentTheme: theme, changeCurrentTheme }}>{children}</ThemeContext.Provider>;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const useThemeProvider = () => useContext(ThemeContext);
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useEffect, useContext } from 'react';
|
|
2
|
-
import { CSSTransition as ReactCSSTransition } from 'react-transition-group';
|
|
3
|
-
|
|
4
|
-
const TransitionContext = React.createContext({
|
|
5
|
-
parent: {},
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
function useIsInitialRender() {
|
|
9
|
-
const isInitialRender = useRef(true);
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
isInitialRender.current = false;
|
|
12
|
-
}, [])
|
|
13
|
-
return isInitialRender.current;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function CSSTransition({
|
|
17
|
-
show,
|
|
18
|
-
enter = '',
|
|
19
|
-
enterStart = '',
|
|
20
|
-
enterEnd = '',
|
|
21
|
-
leave = '',
|
|
22
|
-
leaveStart = '',
|
|
23
|
-
leaveEnd = '',
|
|
24
|
-
appear,
|
|
25
|
-
unmountOnExit,
|
|
26
|
-
tag = 'div',
|
|
27
|
-
children,
|
|
28
|
-
...rest
|
|
29
|
-
}) {
|
|
30
|
-
const enterClasses = enter.split(' ').filter((s) => s.length);
|
|
31
|
-
const enterStartClasses = enterStart.split(' ').filter((s) => s.length);
|
|
32
|
-
const enterEndClasses = enterEnd.split(' ').filter((s) => s.length);
|
|
33
|
-
const leaveClasses = leave.split(' ').filter((s) => s.length);
|
|
34
|
-
const leaveStartClasses = leaveStart.split(' ').filter((s) => s.length);
|
|
35
|
-
const leaveEndClasses = leaveEnd.split(' ').filter((s) => s.length);
|
|
36
|
-
const removeFromDom = unmountOnExit;
|
|
37
|
-
|
|
38
|
-
function addClasses(node, classes) {
|
|
39
|
-
classes.length && node.classList.add(...classes);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function removeClasses(node, classes) {
|
|
43
|
-
classes.length && node.classList.remove(...classes);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const nodeRef = React.useRef(null);
|
|
47
|
-
const Component = tag;
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<ReactCSSTransition
|
|
51
|
-
appear={appear}
|
|
52
|
-
nodeRef={nodeRef}
|
|
53
|
-
unmountOnExit={removeFromDom}
|
|
54
|
-
in={show}
|
|
55
|
-
addEndListener={(done) => {
|
|
56
|
-
nodeRef.current.addEventListener('transitionend', done, false)
|
|
57
|
-
}}
|
|
58
|
-
onEnter={() => {
|
|
59
|
-
if (!removeFromDom) nodeRef.current.style.display = null;
|
|
60
|
-
addClasses(nodeRef.current, [...enterClasses, ...enterStartClasses])
|
|
61
|
-
}}
|
|
62
|
-
onEntering={() => {
|
|
63
|
-
removeClasses(nodeRef.current, enterStartClasses)
|
|
64
|
-
addClasses(nodeRef.current, enterEndClasses)
|
|
65
|
-
}}
|
|
66
|
-
onEntered={() => {
|
|
67
|
-
removeClasses(nodeRef.current, [...enterEndClasses, ...enterClasses])
|
|
68
|
-
}}
|
|
69
|
-
onExit={() => {
|
|
70
|
-
addClasses(nodeRef.current, [...leaveClasses, ...leaveStartClasses])
|
|
71
|
-
}}
|
|
72
|
-
onExiting={() => {
|
|
73
|
-
removeClasses(nodeRef.current, leaveStartClasses)
|
|
74
|
-
addClasses(nodeRef.current, leaveEndClasses)
|
|
75
|
-
}}
|
|
76
|
-
onExited={() => {
|
|
77
|
-
removeClasses(nodeRef.current, [...leaveEndClasses, ...leaveClasses])
|
|
78
|
-
if (!removeFromDom) nodeRef.current.style.display = 'none';
|
|
79
|
-
}}
|
|
80
|
-
>
|
|
81
|
-
<Component ref={nodeRef} {...rest} style={{ display: !removeFromDom ? 'none': null }}>{children}</Component>
|
|
82
|
-
</ReactCSSTransition>
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function Transition({ show, appear, ...rest }) {
|
|
87
|
-
const { parent } = useContext(TransitionContext);
|
|
88
|
-
const isInitialRender = useIsInitialRender();
|
|
89
|
-
const isChild = show === undefined;
|
|
90
|
-
|
|
91
|
-
if (isChild) {
|
|
92
|
-
return (
|
|
93
|
-
<CSSTransition
|
|
94
|
-
appear={parent.appear || !parent.isInitialRender}
|
|
95
|
-
show={parent.show}
|
|
96
|
-
{...rest}
|
|
97
|
-
/>
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<TransitionContext.Provider
|
|
103
|
-
value={{
|
|
104
|
-
parent: {
|
|
105
|
-
show,
|
|
106
|
-
isInitialRender,
|
|
107
|
-
appear,
|
|
108
|
-
},
|
|
109
|
-
}}
|
|
110
|
-
>
|
|
111
|
-
<CSSTransition appear={appear} show={show} {...rest} />
|
|
112
|
-
</TransitionContext.Provider>
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export default Transition;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
export const formatValue = (value) => Intl.NumberFormat('en-US', {
|
|
2
|
-
style: 'currency',
|
|
3
|
-
currency: 'USD',
|
|
4
|
-
maximumSignificantDigits: 3,
|
|
5
|
-
notation: 'compact',
|
|
6
|
-
}).format(value);
|
|
7
|
-
|
|
8
|
-
export const formatThousands = (value) => Intl.NumberFormat('en-US', {
|
|
9
|
-
maximumSignificantDigits: 3,
|
|
10
|
-
notation: 'compact',
|
|
11
|
-
}).format(value);
|
|
12
|
-
|
|
13
|
-
export const getCssVariable = (variable) => {
|
|
14
|
-
return getComputedStyle(document.documentElement).getPropertyValue(variable).trim();
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const adjustHexOpacity = (hexColor, opacity) => {
|
|
18
|
-
// Remove the '#' if it exists
|
|
19
|
-
hexColor = hexColor.replace('#', '');
|
|
20
|
-
|
|
21
|
-
// Convert hex to RGB
|
|
22
|
-
const r = parseInt(hexColor.substring(0, 2), 16);
|
|
23
|
-
const g = parseInt(hexColor.substring(2, 4), 16);
|
|
24
|
-
const b = parseInt(hexColor.substring(4, 6), 16);
|
|
25
|
-
|
|
26
|
-
// Return RGBA string
|
|
27
|
-
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const adjustHSLOpacity = (hslColor, opacity) => {
|
|
31
|
-
// Convert HSL to HSLA
|
|
32
|
-
return hslColor.replace('hsl(', 'hsla(').replace(')', `, ${opacity})`);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const adjustOKLCHOpacity = (oklchColor, opacity) => {
|
|
36
|
-
// Add alpha value to OKLCH color
|
|
37
|
-
return oklchColor.replace(/oklch\((.*?)\)/, (match, p1) => `oklch(${p1} / ${opacity})`);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const adjustColorOpacity = (color, opacity) => {
|
|
41
|
-
if (color.startsWith('#')) {
|
|
42
|
-
return adjustHexOpacity(color, opacity);
|
|
43
|
-
} else if (color.startsWith('hsl')) {
|
|
44
|
-
return adjustHSLOpacity(color, opacity);
|
|
45
|
-
} else if (color.startsWith('oklch')) {
|
|
46
|
-
return adjustOKLCHOpacity(color, opacity);
|
|
47
|
-
} else {
|
|
48
|
-
throw new Error('Unsupported color format');
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const oklchToRGBA = (oklchColor) => {
|
|
53
|
-
// Create a temporary div to use for color conversion
|
|
54
|
-
const tempDiv = document.createElement('div');
|
|
55
|
-
tempDiv.style.color = oklchColor;
|
|
56
|
-
document.body.appendChild(tempDiv);
|
|
57
|
-
|
|
58
|
-
// Get the computed style and convert to RGB
|
|
59
|
-
const computedColor = window.getComputedStyle(tempDiv).color;
|
|
60
|
-
document.body.removeChild(tempDiv);
|
|
61
|
-
|
|
62
|
-
return computedColor;
|
|
63
|
-
};
|