@canmingir/link 1.2.4 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canmingir/link",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -1,13 +1,10 @@
1
- import config from "../config/config";
2
1
  import { jwtDecode } from "jwt-decode";
3
2
  import { storage } from "@nucleoidjs/webstorage";
4
3
 
5
- const { name } = config();
6
-
7
4
  let login = true;
8
5
  const itemId = storage.get("itemId");
9
6
  try {
10
- const token = storage.get(name, "accessToken");
7
+ const token = storage.get("link", "accessToken");
11
8
  const decodedToken = jwtDecode(token);
12
9
 
13
10
  if (decodedToken.exp * 1000 < Date.now()) {
package/src/http/index.js CHANGED
@@ -15,8 +15,8 @@ const instance = axios.create({
15
15
  });
16
16
 
17
17
  instance.interceptors.request.use((request) => {
18
- const { name, base } = config();
19
- const accessToken = storage.get(name, "accessToken");
18
+ const { base } = config();
19
+ const accessToken = storage.get("link", "accessToken");
20
20
 
21
21
  if (!accessToken) {
22
22
  window.location.href = base === "/" ? "/login" : `${base}/login`;
@@ -85,14 +85,17 @@ export const fetcher = (url) => instance.get(url).then((res) => res.data);
85
85
 
86
86
  const refreshAuthLogic = async (failedRequest) => {
87
87
  try {
88
- const { name, appId } = config();
88
+ const { appId } = config();
89
89
 
90
90
  const projectId = storage.get("projectId");
91
91
 
92
+ const identityProvider = storage.get("link", "identityProvider");
93
+
92
94
  const { data } = await oauth.post("/oauth", {
93
- refreshToken: storage.get(name, "refreshToken"),
95
+ refreshToken: storage.get("link", "refreshToken"),
94
96
  appId,
95
97
  projectId,
98
+ identityProvider,
96
99
  });
97
100
 
98
101
  const accessToken = data.accessToken;
@@ -100,13 +103,13 @@ const refreshAuthLogic = async (failedRequest) => {
100
103
  failedRequest.response.config.headers["Authorization"] =
101
104
  "Bearer " + accessToken;
102
105
 
103
- storage.set(name, "accessToken", accessToken);
106
+ storage.set("link", "accessToken", accessToken);
104
107
  return Promise.resolve();
105
108
  } catch (error) {
106
109
  const { name, base } = config();
107
110
 
108
- storage.remove(name, "accessToken");
109
- storage.remove(name, "refreshToken");
111
+ storage.remove("link", "accessToken");
112
+ storage.remove("link", "refreshToken");
110
113
 
111
114
  window.location.href = `${window.location.origin}${base}/login`;
112
115
  return Promise.reject(error);
@@ -122,7 +125,7 @@ refreshInterceptor(instance, refreshAuthLogic, {
122
125
  statusCodes: [401, 403],
123
126
  shouldRefresh: () => {
124
127
  const { name, base } = config();
125
- const token = storage.get(name, "accessToken");
128
+ const token = storage.get("link", "accessToken");
126
129
 
127
130
  if (!token) {
128
131
  window.location.href = `${window.location.origin}${base}/login`;
package/src/http/user.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import axios from "axios";
2
2
  import config from "../config/config.js";
3
3
  import http from "./index";
4
+ import { jwtDecode } from "jwt-decode";
5
+ import oauth from "./oauth";
4
6
  import { storage } from "@nucleoidjs/webstorage";
5
7
 
6
8
  const instance = axios.create({
@@ -11,15 +13,8 @@ const instance = axios.create({
11
13
  },
12
14
  });
13
15
 
14
- function getProjectName() {
15
- const { name } = config();
16
- if (name) {
17
- return name;
18
- }
19
- }
20
-
21
16
  instance.interceptors.request.use(async (request) => {
22
- const refreshToken = await storage.get(getProjectName(), "refreshToken");
17
+ const refreshToken = await storage.get("link", "refreshToken");
23
18
  if (refreshToken) {
24
19
  request.headers["Authorization"] = `Bearer ${refreshToken}`;
25
20
  }
@@ -28,25 +23,62 @@ instance.interceptors.request.use(async (request) => {
28
23
 
29
24
  instance.getUserDetails = async () => {
30
25
  try {
31
- const refreshToken = await storage.get(getProjectName(), "refreshToken");
26
+ const refreshToken = await storage.get("link", "refreshToken");
27
+
32
28
  if (!refreshToken) {
33
29
  console.log("No refresh token found");
34
30
  return null;
35
31
  }
36
32
 
33
+ let accessToken = await storage.get("link", "accessToken");
34
+
35
+ if (!accessToken) {
36
+ console.log("No access token found");
37
+ return null;
38
+ }
39
+
40
+ try {
41
+ const decodedToken = jwtDecode(accessToken);
42
+ const isExpired = decodedToken.exp * 1000 < Date.now();
43
+
44
+ if (isExpired) {
45
+ console.log(
46
+ "Access token expired, refreshing before fetching user details..."
47
+ );
48
+ const { appId } = config();
49
+ const projectId = storage.get("projectId");
50
+ const identityProvider = storage.get("link", "identityProvider");
51
+
52
+ const { data } = await oauth.post("/oauth", {
53
+ refreshToken,
54
+ appId,
55
+ projectId,
56
+ identityProvider,
57
+ });
58
+
59
+ accessToken = data.accessToken;
60
+ storage.set("link", "accessToken", accessToken);
61
+ console.log(
62
+ "Access token refreshed successfully, now fetching user details"
63
+ );
64
+ }
65
+ } catch (error) {
66
+ console.error("Error checking or refreshing token:", error);
67
+ return null;
68
+ }
69
+
37
70
  const response = await http.get("/oauth/user", {
38
71
  headers: {
39
- 'X-Refresh-Token': refreshToken
40
- }
72
+ "X-Refresh-Token": refreshToken,
73
+ },
41
74
  });
42
-
75
+
43
76
  if (response.data && response.data.user) {
44
77
  return response.data.user;
45
78
  }
46
-
79
+
47
80
  console.log("No user data received from server");
48
81
  return null;
49
-
50
82
  } catch (error) {
51
83
  console.error("Error fetching user details from server:", error);
52
84
  return null;
@@ -55,7 +87,7 @@ instance.getUserDetails = async () => {
55
87
 
56
88
  instance.getPermittedUsers = async () => {
57
89
  const userIds = [];
58
- const refreshToken = await storage.get(getProjectName(), "refreshToken");
90
+ const refreshToken = await storage.get("link", "refreshToken");
59
91
  const response = await http.get("/permissions");
60
92
 
61
93
  response.data.forEach((permission) => {
@@ -75,4 +107,4 @@ instance.getPermittedUsers = async () => {
75
107
  return users;
76
108
  };
77
109
 
78
- export default instance;
110
+ export default instance;
@@ -9,15 +9,16 @@ import SettingsDialog from "../../widgets/SettingsDialog";
9
9
  import Stack from "@mui/material/Stack";
10
10
  import config from "../../config/config";
11
11
  import { hideScroll } from "../../theme/css";
12
+ import { useResponsive } from "../../hooks/use-responsive";
12
13
  import { useUser } from "../../hooks/use-user";
13
14
 
14
15
  import React, { useState } from "react";
15
16
 
16
17
  export default function NavMini({ only }) {
17
18
  const { user } = useUser();
18
- const { sideMenu } = config().menu;
19
+ const { sideMenu, actionButtons } = config().menu;
19
20
  const [openSettings, setOpenSettings] = useState(false);
20
-
21
+ const lgUp = useResponsive("up", "lg");
21
22
  const handleCloseSettings = () => {
22
23
  setOpenSettings(false);
23
24
  };
@@ -57,21 +58,40 @@ export default function NavMini({ only }) {
57
58
  }}
58
59
  />
59
60
  <Box sx={{ flexGrow: 1 }} />
60
- <Button
61
- data-cy="end-item"
62
- fullWidth={true}
63
- onClick={() => setOpenSettings(true)}
61
+ <Stack
62
+ direction={"column"}
63
+ alignItems={"center"}
64
+ justifyItems={"center"}
65
+ sx={{
66
+ marginBottom: lgUp ? 3 : 0,
67
+ position: lgUp ? "static" : "fixed",
68
+ bottom: lgUp ? "auto" : 66,
69
+ width: "100%",
70
+ }}
71
+ gap={2}
64
72
  >
65
- <Iconify
66
- icon={"ic:baseline-settings"}
73
+ {actionButtons &&
74
+ actionButtons.map((Action, index) => (
75
+ <Box key={index} component={Action}></Box>
76
+ ))}
77
+ <Button
78
+ onClick={() => setOpenSettings(true)}
67
79
  sx={{
68
- width: 32,
69
- height: 32,
70
- color: "text.secondary",
71
- mx: "auto",
80
+ position: lgUp ? "static" : "fixed",
81
+ bottom: lgUp ? "auto" : 16,
82
+ width: "100%",
72
83
  }}
73
- />
74
- </Button>
84
+ >
85
+ <Iconify
86
+ icon={"ic:baseline-settings"}
87
+ sx={{
88
+ width: 32,
89
+ height: 32,
90
+ color: "text.secondary",
91
+ }}
92
+ />
93
+ </Button>
94
+ </Stack>
75
95
  <SettingsDialog open={openSettings} handleClose={handleCloseSettings} />
76
96
  </Stack>
77
97
  </Box>
@@ -16,7 +16,7 @@ function NavHorizontal() {
16
16
 
17
17
  const { user } = useUser();
18
18
 
19
- const { sideMenu } = config().menu;
19
+ const { sideMenu, actionButtons } = config().menu;
20
20
  return (
21
21
  <AppBar component="div" data-cy="nav-horizontal">
22
22
  <Toolbar
@@ -35,6 +35,11 @@ function NavHorizontal() {
35
35
  ...theme.mixins.toolbar,
36
36
  }}
37
37
  />
38
+
39
+ {actionButtons &&
40
+ actionButtons.map((Action, index) => (
41
+ <Box key={index} component={Action}></Box>
42
+ ))}
38
43
  </Toolbar>
39
44
 
40
45
  <HeaderShadow />
@@ -1,10 +1,3 @@
1
- import { Button, DialogActions } from "@mui/material";
2
- import Dialog, { dialogClasses } from "@mui/material/Dialog";
3
- import React, { useCallback, useState } from "react";
4
- import { publish, useEvent } from "@nucleoidai/react-event";
5
- import { storage, useStorage } from "@nucleoidjs/webstorage";
6
- import { useMediaQuery, useTheme } from "@mui/material";
7
-
8
1
  import Box from "@mui/material/Box";
9
2
  import IconButton from "@mui/material/IconButton";
10
3
  import Iconify from "../../../components/Iconify";
@@ -29,9 +22,16 @@ import { useEventListener } from "../../../hooks/use-event-listener";
29
22
  import { useNavigate } from "react-router-dom";
30
23
  import useProjects from "../../../hooks/useProjects";
31
24
 
25
+ import { Button, DialogActions } from "@mui/material";
26
+ import Dialog, { dialogClasses } from "@mui/material/Dialog";
27
+ import React, { useCallback, useState } from "react";
28
+ import { publish, useEvent } from "@nucleoidai/react-event";
29
+ import { storage, useStorage } from "@nucleoidjs/webstorage";
30
+ import { useMediaQuery, useTheme } from "@mui/material";
31
+
32
32
  function ProjectBar() {
33
33
  const label = config().template?.projectBar?.label;
34
- const { appId, name } = config();
34
+ const { appId } = config();
35
35
  const theme = useTheme();
36
36
  const isSmallScreen = useMediaQuery(theme.breakpoints.down(435));
37
37
  const { loading, projects, getProjects } = useProjects();
@@ -91,15 +91,15 @@ function ProjectBar() {
91
91
  const handleSelect = (project) => {
92
92
  const { id: projectId } = project;
93
93
 
94
- const refreshToken = storage.get(name, "refreshToken");
95
- const provider = storage.get(name, "provider");
94
+ const refreshToken = storage.get("link", "refreshToken");
95
+ const identityProvider = storage.get("link", "identityProvider");
96
96
 
97
97
  oauth
98
- .post("/oauth", { appId, refreshToken, projectId, provider })
98
+ .post("/oauth", { appId, refreshToken, projectId, identityProvider })
99
99
  .then(({ data }) => {
100
100
  const { refreshToken, accessToken } = data;
101
- storage.set(name, "accessToken", accessToken);
102
- storage.set(name, "refreshToken", refreshToken);
101
+ storage.set("link", "accessToken", accessToken);
102
+ storage.set("link", "refreshToken", refreshToken);
103
103
  storage.set("projectId", projectId);
104
104
  })
105
105
  .finally(() => {
@@ -19,7 +19,6 @@ import CustomPopover, { usePopover } from "../../components/custom-popover";
19
19
  // ----------------------------------------------------------------------
20
20
 
21
21
  export default function AccountPopover() {
22
- const { name } = config();
23
22
  const { options } = config().menu;
24
23
  const router = useRouter();
25
24
  const { user } = useUser();
@@ -28,8 +27,8 @@ export default function AccountPopover() {
28
27
 
29
28
  const handleLogout = async () => {
30
29
  try {
31
- storage.remove(name, "accessToken");
32
- storage.remove(name, "refreshToken");
30
+ storage.remove("link", "accessToken");
31
+ storage.remove("link", "refreshToken");
33
32
  popover.onClose();
34
33
  router.replace("/login");
35
34
  } catch (error) {
@@ -0,0 +1,62 @@
1
+ import { Box } from "@mui/material";
2
+
3
+ import React, { useRef, useState } from "react";
4
+
5
+ const DraggableNode = ({ children, registerRef, onDrag }) => {
6
+ const [offset, setOffset] = useState({ x: 0, y: 0 });
7
+ const localRef = useRef(null);
8
+
9
+ const setRef = (el) => {
10
+ localRef.current = el;
11
+ if (registerRef) registerRef(el);
12
+ };
13
+
14
+ const handleMouseDown = (e) => {
15
+ if (e.button !== 0) return;
16
+ e.stopPropagation();
17
+
18
+ const startX = e.clientX;
19
+ const startY = e.clientY;
20
+ const startOffset = { ...offset };
21
+
22
+ const onMove = (ev) => {
23
+ const dx = ev.clientX - startX;
24
+ const dy = ev.clientY - startY;
25
+ setOffset({
26
+ x: startOffset.x + dx,
27
+ y: startOffset.y + dy,
28
+ });
29
+ if (onDrag) onDrag();
30
+ };
31
+
32
+ const onUp = () => {
33
+ window.removeEventListener("mousemove", onMove);
34
+ window.removeEventListener("mouseup", onUp);
35
+ };
36
+
37
+ window.addEventListener("mousemove", onMove);
38
+ window.addEventListener("mouseup", onUp);
39
+ };
40
+
41
+ return (
42
+ <Box
43
+ ref={setRef}
44
+ onMouseDown={handleMouseDown}
45
+ sx={{
46
+ display: "inline-flex",
47
+ flexDirection: "column",
48
+ alignItems: "center",
49
+ position: "relative",
50
+ transform: `translate(${offset.x}px, ${offset.y}px)`,
51
+ cursor: "grab",
52
+ "&:active": {
53
+ cursor: "grabbing",
54
+ },
55
+ }}
56
+ >
57
+ {children}
58
+ </Box>
59
+ );
60
+ };
61
+
62
+ export default DraggableNode;
@@ -0,0 +1,172 @@
1
+ import React, { useLayoutEffect, useState } from "react";
2
+
3
+ const DynamicConnector = ({
4
+ containerEl,
5
+ parentEl,
6
+ childEls,
7
+ stroke,
8
+ strokeWidth,
9
+ lineStyle,
10
+ connectorType = "default",
11
+ tick = 0,
12
+ }) => {
13
+ const [dims, setDims] = useState(null);
14
+ const [points, setPoints] = useState({
15
+ parent: null,
16
+ children: [],
17
+ yMid: null,
18
+ });
19
+
20
+ useLayoutEffect(() => {
21
+ if (!containerEl || !parentEl || !childEls?.length) return;
22
+
23
+ const update = () => {
24
+ const cRect = containerEl.getBoundingClientRect();
25
+ const pRect = parentEl.getBoundingClientRect();
26
+
27
+ const parent = {
28
+ x: pRect.left + pRect.width / 2 - cRect.left,
29
+ y: pRect.bottom - cRect.top,
30
+ };
31
+
32
+ const children = childEls.map((el) => {
33
+ const r = el.getBoundingClientRect();
34
+ return {
35
+ x: r.left + r.width / 2 - cRect.left,
36
+ y: r.top - cRect.top,
37
+ };
38
+ });
39
+
40
+ const firstTop = Math.min(...children.map((c) => c.y));
41
+ const yMid =
42
+ parent.y + Math.max(12, Math.min(24, (firstTop - parent.y) * 0.4));
43
+
44
+ setPoints({ parent, children, yMid });
45
+ setDims({ w: cRect.width, h: cRect.height });
46
+ };
47
+
48
+ const ro = new ResizeObserver(update);
49
+ ro.observe(containerEl);
50
+ ro.observe(parentEl);
51
+ childEls.forEach((el) => el && ro.observe(el));
52
+
53
+ update();
54
+ return () => ro.disconnect();
55
+ }, [containerEl, parentEl, childEls, tick]);
56
+
57
+ if (!dims || !points.parent || !points.children.length) return null;
58
+
59
+ const dash =
60
+ lineStyle === "dashed"
61
+ ? `${strokeWidth * 3},${strokeWidth * 2}`
62
+ : lineStyle === "dotted"
63
+ ? `${strokeWidth},${strokeWidth * 1.5}`
64
+ : undefined;
65
+
66
+ const onlyOne = points.children.length === 1;
67
+
68
+ if (connectorType === "curved" || connectorType === "n8n") {
69
+ const createN8nPath = (from, to) => {
70
+ const v = Math.max(32, Math.abs(to.y - from.y) * 0.35);
71
+ const h = Math.max(24, Math.abs(to.x - from.x) * 0.35);
72
+
73
+ const c1 = {
74
+ x: to.x > from.x ? from.x + h : from.x - h,
75
+ y: from.y + v,
76
+ };
77
+ const c2 = {
78
+ x: to.x > from.x ? to.x - h : to.x + h,
79
+ y: to.y - v,
80
+ };
81
+
82
+ return `M ${from.x} ${from.y} C ${c1.x} ${c1.y} ${c2.x} ${c2.y} ${to.x} ${to.y}`;
83
+ };
84
+
85
+ return (
86
+ <svg
87
+ style={{ position: "absolute", inset: 0, pointerEvents: "none" }}
88
+ width="100%"
89
+ height="100%"
90
+ viewBox={`0 0 ${dims.w} ${dims.h}`}
91
+ >
92
+ {points.children.map((child, i) => (
93
+ <path
94
+ key={i}
95
+ d={createN8nPath(points.parent, child)}
96
+ fill="none"
97
+ stroke={stroke}
98
+ strokeWidth={strokeWidth}
99
+ strokeDasharray={dash}
100
+ strokeLinecap="round"
101
+ strokeLinejoin="round"
102
+ />
103
+ ))}
104
+ </svg>
105
+ );
106
+ }
107
+
108
+ return (
109
+ <svg
110
+ style={{ position: "absolute", inset: 0, pointerEvents: "none" }}
111
+ width="100%"
112
+ height="100%"
113
+ viewBox={`0 0 ${dims.w} ${dims.h}`}
114
+ >
115
+ {onlyOne ? (
116
+ <line
117
+ x1={points.parent.x}
118
+ y1={points.parent.y}
119
+ x2={points.children[0].x}
120
+ y2={points.children[0].y}
121
+ stroke={stroke}
122
+ strokeWidth={strokeWidth}
123
+ strokeDasharray={dash}
124
+ />
125
+ ) : (
126
+ <>
127
+ <line
128
+ x1={points.parent.x}
129
+ y1={points.parent.y}
130
+ x2={points.parent.x}
131
+ y2={points.yMid}
132
+ stroke={stroke}
133
+ strokeWidth={strokeWidth}
134
+ strokeDasharray={dash}
135
+ />
136
+
137
+ {(() => {
138
+ const xs = points.children.map((c) => c.x);
139
+ const xMin = Math.min(...xs);
140
+ const xMax = Math.max(...xs);
141
+ return (
142
+ <line
143
+ x1={xMin}
144
+ y1={points.yMid}
145
+ x2={xMax}
146
+ y2={points.yMid}
147
+ stroke={stroke}
148
+ strokeWidth={strokeWidth}
149
+ strokeDasharray={dash}
150
+ />
151
+ );
152
+ })()}
153
+
154
+ {points.children.map((c, i) => (
155
+ <line
156
+ key={i}
157
+ x1={c.x}
158
+ y1={points.yMid}
159
+ x2={c.x}
160
+ y2={c.y}
161
+ stroke={stroke}
162
+ strokeWidth={strokeWidth}
163
+ strokeDasharray={dash}
164
+ />
165
+ ))}
166
+ </>
167
+ )}
168
+ </svg>
169
+ );
170
+ };
171
+
172
+ export default DynamicConnector;
@@ -0,0 +1,34 @@
1
+ import FlowNode from "./FlowNode";
2
+
3
+ import React, { useMemo } from "react";
4
+ import { assertLinkedGraph, buildTreeFromLinked } from "./flowUtils";
5
+
6
+ export const Flow = ({ data, variant = "simple", style, plugin }) => {
7
+ const { nodesById, roots } = useMemo(() => assertLinkedGraph(data), [data]);
8
+
9
+ const treeData = useMemo(() => {
10
+ if (!roots?.length)
11
+ return { id: "__empty__", label: "(empty)", children: [] };
12
+
13
+ if (roots.length === 1) {
14
+ return (
15
+ buildTreeFromLinked(roots[0], nodesById) || {
16
+ id: roots[0],
17
+ children: [],
18
+ }
19
+ );
20
+ }
21
+
22
+ const children = roots
23
+ .map((r) => buildTreeFromLinked(r, nodesById))
24
+ .filter(Boolean);
25
+
26
+ return { id: "__root__", label: "Root", children };
27
+ }, [nodesById, roots]);
28
+
29
+ return (
30
+ <FlowNode node={treeData} variant={variant} style={style} plugin={plugin} />
31
+ );
32
+ };
33
+
34
+ export default Flow;