@teambit/ui-foundation.ui.side-bar 0.0.0-a9e2edbdd7f2d36a9ebd1ee8d02d04427ed71d54
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/component-tree/component-tree.module.scss +3 -0
- package/component-tree/component-tree.tsx +71 -0
- package/component-tree/component-view/component-view.module.scss +104 -0
- package/component-tree/component-view/component-view.tsx +249 -0
- package/component-tree/component-view/index.ts +1 -0
- package/component-tree/default-tree-node-renderer/default-tree-node-renderer.tsx +17 -0
- package/component-tree/default-tree-node-renderer/index.ts +1 -0
- package/component-tree/index.ts +6 -0
- package/component-tree/namespace-tree-node/index.ts +1 -0
- package/component-tree/namespace-tree-node/namespace-tree-node.module.scss +53 -0
- package/component-tree/namespace-tree-node/namespace-tree-node.tsx +63 -0
- package/component-tree/payload-type.tsx +10 -0
- package/component-tree/scope-tree-node/index.ts +1 -0
- package/component-tree/scope-tree-node/scope-tree-node.module.scss +53 -0
- package/component-tree/scope-tree-node/scope-tree-node.tsx +68 -0
- package/component-tree/utils/get-name.tsx +3 -0
- package/dist/component-tree/component-tree.d.ts +14 -0
- package/dist/component-tree/component-tree.js +42 -0
- package/dist/component-tree/component-tree.js.map +1 -0
- package/dist/component-tree/component-tree.module.scss +3 -0
- package/dist/component-tree/component-view/component-view.d.ts +7 -0
- package/dist/component-tree/component-view/component-view.js +163 -0
- package/dist/component-tree/component-view/component-view.js.map +1 -0
- package/dist/component-tree/component-view/component-view.module.scss +104 -0
- package/dist/component-tree/component-view/index.d.ts +1 -0
- package/dist/component-tree/component-view/index.js +2 -0
- package/dist/component-tree/component-view/index.js.map +1 -0
- package/dist/component-tree/default-tree-node-renderer/default-tree-node-renderer.d.ts +3 -0
- package/dist/component-tree/default-tree-node-renderer/default-tree-node-renderer.js +15 -0
- package/dist/component-tree/default-tree-node-renderer/default-tree-node-renderer.js.map +1 -0
- package/dist/component-tree/default-tree-node-renderer/index.d.ts +1 -0
- package/dist/component-tree/default-tree-node-renderer/index.js +2 -0
- package/dist/component-tree/default-tree-node-renderer/index.js.map +1 -0
- package/dist/component-tree/index.d.ts +6 -0
- package/dist/component-tree/index.js +6 -0
- package/dist/component-tree/index.js.map +1 -0
- package/dist/component-tree/namespace-tree-node/index.d.ts +1 -0
- package/dist/component-tree/namespace-tree-node/index.js +2 -0
- package/dist/component-tree/namespace-tree-node/index.js.map +1 -0
- package/dist/component-tree/namespace-tree-node/namespace-tree-node.d.ts +4 -0
- package/dist/component-tree/namespace-tree-node/namespace-tree-node.js +38 -0
- package/dist/component-tree/namespace-tree-node/namespace-tree-node.js.map +1 -0
- package/dist/component-tree/namespace-tree-node/namespace-tree-node.module.scss +53 -0
- package/dist/component-tree/payload-type.d.ts +6 -0
- package/dist/component-tree/payload-type.js +6 -0
- package/dist/component-tree/payload-type.js.map +1 -0
- package/dist/component-tree/scope-tree-node/index.d.ts +1 -0
- package/dist/component-tree/scope-tree-node/index.js +2 -0
- package/dist/component-tree/scope-tree-node/index.js.map +1 -0
- package/dist/component-tree/scope-tree-node/scope-tree-node.d.ts +4 -0
- package/dist/component-tree/scope-tree-node/scope-tree-node.js +38 -0
- package/dist/component-tree/scope-tree-node/scope-tree-node.js.map +1 -0
- package/dist/component-tree/scope-tree-node/scope-tree-node.module.scss +53 -0
- package/dist/component-tree/utils/get-name.d.ts +1 -0
- package/dist/component-tree/utils/get-name.js +4 -0
- package/dist/component-tree/utils/get-name.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/preview-1752267760470.js +7 -0
- package/index.ts +3 -0
- package/package.json +62 -0
- package/types/asset.d.ts +41 -0
- package/types/style.d.ts +42 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ComponentModel, useIdFromLocation } from '@teambit/component';
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
import { useLocation } from '@teambit/base-react.navigation.link';
|
|
4
|
+
import { indentStyle } from '@teambit/base-ui.graph.tree.indent';
|
|
5
|
+
import { inflateToTree, attachPayload } from '@teambit/base-ui.graph.tree.inflate-paths';
|
|
6
|
+
import { Tree, TreeNodeRenderer, TreeNode as TreeNodeType } from '@teambit/design.ui.tree';
|
|
7
|
+
import { LanesModel } from '@teambit/lanes.ui.models.lanes-model';
|
|
8
|
+
import { TreeContextProvider } from '@teambit/base-ui.graph.tree.tree-context';
|
|
9
|
+
import { LanesContext } from '@teambit/lanes.hooks.use-lanes';
|
|
10
|
+
import { PayloadType, ScopePayload } from './payload-type';
|
|
11
|
+
import { DefaultTreeNodeRenderer } from './default-tree-node-renderer';
|
|
12
|
+
|
|
13
|
+
type ComponentTreeProps = {
|
|
14
|
+
components: ComponentModel[];
|
|
15
|
+
transformTree?: (rootNode: TreeNodeType) => TreeNodeType;
|
|
16
|
+
TreeNode?: TreeNodeRenderer<PayloadType>;
|
|
17
|
+
isCollapsed?: boolean;
|
|
18
|
+
lanesModel?: LanesModel;
|
|
19
|
+
// assumeScopeInUrl?: boolean;
|
|
20
|
+
} & React.HTMLAttributes<HTMLDivElement>;
|
|
21
|
+
|
|
22
|
+
function calcPayload(components: ComponentModel[]) {
|
|
23
|
+
const payloadMap = new Map<string, PayloadType>(components.map((c) => [c.id.toString({ ignoreVersion: true }), c]));
|
|
24
|
+
|
|
25
|
+
const scopeIds = new Set(components.map((x) => x.id.scope).filter((x) => !!x));
|
|
26
|
+
scopeIds.forEach((x) => x && payloadMap.set(`${x}/`, new ScopePayload()));
|
|
27
|
+
|
|
28
|
+
return payloadMap;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function ComponentTree({
|
|
32
|
+
components,
|
|
33
|
+
isCollapsed,
|
|
34
|
+
className,
|
|
35
|
+
transformTree,
|
|
36
|
+
lanesModel,
|
|
37
|
+
// assumeScopeInUrl = false,
|
|
38
|
+
TreeNode = DefaultTreeNodeRenderer,
|
|
39
|
+
}: ComponentTreeProps) {
|
|
40
|
+
const { pathname = '/' } = useLocation() || {};
|
|
41
|
+
// override default splat from location when viewing a lane component
|
|
42
|
+
const laneCompUrl = pathname.split(LanesModel.baseLaneComponentRoute.concat('/'))[1];
|
|
43
|
+
const idFromLocation = useIdFromLocation(laneCompUrl, true);
|
|
44
|
+
const activeComponent = useMemo(() => {
|
|
45
|
+
const active = components.find((x) => {
|
|
46
|
+
return idFromLocation && (idFromLocation === x.id.fullName || idFromLocation === x.id.toStringWithoutVersion());
|
|
47
|
+
});
|
|
48
|
+
return active?.id.toString({ ignoreVersion: true });
|
|
49
|
+
}, [components.length, pathname]);
|
|
50
|
+
|
|
51
|
+
const rootNode = useMemo(() => {
|
|
52
|
+
const tree = inflateToTree<ComponentModel, PayloadType>(components, (c) => c.id.toString({ ignoreVersion: true }));
|
|
53
|
+
|
|
54
|
+
const payloadMap = calcPayload(components);
|
|
55
|
+
|
|
56
|
+
attachPayload(tree, payloadMap);
|
|
57
|
+
|
|
58
|
+
if (transformTree) return transformTree(tree);
|
|
59
|
+
return tree;
|
|
60
|
+
}, [components.length]);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<LanesContext.Provider value={{ lanesModel }}>
|
|
64
|
+
<TreeContextProvider>
|
|
65
|
+
<div style={indentStyle(1)} className={className}>
|
|
66
|
+
<Tree TreeNode={TreeNode} activePath={activeComponent} tree={rootNode} isCollapsed={isCollapsed} />
|
|
67
|
+
</div>
|
|
68
|
+
</TreeContextProvider>
|
|
69
|
+
</LanesContext.Provider>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
@import '@teambit/base-ui.theme.colors/colors.module.scss';
|
|
2
|
+
|
|
3
|
+
.component {
|
|
4
|
+
height: 32px;
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: space-between;
|
|
8
|
+
padding-right: 8px;
|
|
9
|
+
font-size: inherit;
|
|
10
|
+
font-size: var(--bit-p-xs);
|
|
11
|
+
|
|
12
|
+
//TODO - move to a link component
|
|
13
|
+
text-decoration: none;
|
|
14
|
+
color: inherit;
|
|
15
|
+
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
border-left: 0px solid transparent;
|
|
18
|
+
|
|
19
|
+
&:hover {
|
|
20
|
+
background: var(--bit-bg-heavy, #f6f6f6);
|
|
21
|
+
.envIcon {
|
|
22
|
+
// filter: grayscale(0);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&.active {
|
|
27
|
+
background: var(--bit-accent-bg, #eceaff);
|
|
28
|
+
.envIcon {
|
|
29
|
+
// filter: grayscale(0);
|
|
30
|
+
}
|
|
31
|
+
// .right {
|
|
32
|
+
// .componentIcon {
|
|
33
|
+
// color: $w10;
|
|
34
|
+
// }
|
|
35
|
+
// }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.icon {
|
|
39
|
+
width: 16px;
|
|
40
|
+
height: 16px;
|
|
41
|
+
opacity: 0.8;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.left {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
flex: 1;
|
|
48
|
+
min-width: 0;
|
|
49
|
+
line-height: 24px;
|
|
50
|
+
|
|
51
|
+
> span {
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
text-overflow: ellipsis;
|
|
54
|
+
white-space: nowrap;
|
|
55
|
+
padding: 0 8px;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
.envIcon {
|
|
59
|
+
transition: filter 300ms ease-in-out;
|
|
60
|
+
// filter: grayscale(1);
|
|
61
|
+
width: 16px;
|
|
62
|
+
height: 16px;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.right {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
font-size: 12px;
|
|
70
|
+
|
|
71
|
+
> * {
|
|
72
|
+
margin-right: 4px;
|
|
73
|
+
}
|
|
74
|
+
> :last-child() {
|
|
75
|
+
margin-right: 0px;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.componentEnvTooltip {
|
|
81
|
+
font-size: var(--bit-p-xs);
|
|
82
|
+
max-width: unset !important; // override tippy's default max-width: 350px
|
|
83
|
+
.componentEnvTitle {
|
|
84
|
+
font-size: var(--bit-p-xxs, 12px);
|
|
85
|
+
color: #c9c3f6;
|
|
86
|
+
font-weight: bold;
|
|
87
|
+
margin-bottom: 4px;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.tooltip {
|
|
92
|
+
max-width: unset !important; // override tippy's default max-width: 350px
|
|
93
|
+
font-size: var(--bit-p-xxs);
|
|
94
|
+
opacity: 100%;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.envLink {
|
|
98
|
+
color: white;
|
|
99
|
+
text-decoration: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.opacity {
|
|
103
|
+
opacity: 50%;
|
|
104
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/* eslint-disable complexity */
|
|
2
|
+
import { ComponentTreeSlot } from '@teambit/component-tree';
|
|
3
|
+
import { Link, useLocation } from '@teambit/base-react.navigation.link';
|
|
4
|
+
import { EnvIcon } from '@teambit/envs.ui.env-icon';
|
|
5
|
+
import { DeprecationIcon } from '@teambit/component.ui.deprecation-icon';
|
|
6
|
+
import classNames from 'classnames';
|
|
7
|
+
import { ComponentID } from '@teambit/component-id';
|
|
8
|
+
import { ComponentModel } from '@teambit/component';
|
|
9
|
+
import { ComponentUrl } from '@teambit/component.modules.component-url';
|
|
10
|
+
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
|
11
|
+
import { Tooltip } from '@teambit/design.ui.tooltip';
|
|
12
|
+
import { TreeContext } from '@teambit/base-ui.graph.tree.tree-context';
|
|
13
|
+
import { indentClass } from '@teambit/base-ui.graph.tree.indent';
|
|
14
|
+
import { TreeNodeProps } from '@teambit/base-ui.graph.tree.recursive-tree';
|
|
15
|
+
import { LanesContext } from '@teambit/lanes.hooks.use-lanes';
|
|
16
|
+
import { LanesModel } from '@teambit/lanes.ui.models.lanes-model';
|
|
17
|
+
import { getName } from '../utils/get-name';
|
|
18
|
+
|
|
19
|
+
import styles from './component-view.module.scss';
|
|
20
|
+
|
|
21
|
+
export type ComponentViewProps<Payload = any> = {
|
|
22
|
+
treeNodeSlot?: ComponentTreeSlot;
|
|
23
|
+
scopeName?: string;
|
|
24
|
+
} & TreeNodeProps<Payload>;
|
|
25
|
+
|
|
26
|
+
export function ComponentView(props: ComponentViewProps) {
|
|
27
|
+
const { node, scopeName, treeNodeSlot } = props;
|
|
28
|
+
|
|
29
|
+
let component = node.payload;
|
|
30
|
+
|
|
31
|
+
const { onSelect } = useContext(TreeContext);
|
|
32
|
+
const lanesContextModel = useContext(LanesContext);
|
|
33
|
+
const lanesModel = lanesContextModel?.lanesModel;
|
|
34
|
+
const [mounted, setMounted] = useState(false);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
setMounted(true);
|
|
38
|
+
return () => {
|
|
39
|
+
setMounted(false);
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const handleClick = useCallback(
|
|
44
|
+
(event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
|
45
|
+
onSelect?.(node.id, event);
|
|
46
|
+
},
|
|
47
|
+
[onSelect, node.id]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const location = useLocation();
|
|
51
|
+
const scope = { name: scopeName };
|
|
52
|
+
|
|
53
|
+
const isMissingCompOrEnvId = !component?.id || !component?.environment?.id;
|
|
54
|
+
|
|
55
|
+
component = component as ComponentModel;
|
|
56
|
+
|
|
57
|
+
const envId = !isMissingCompOrEnvId ? ComponentID.fromString(component.environment?.id as string) : undefined;
|
|
58
|
+
const isEnvOnCurrentLane = envId
|
|
59
|
+
? lanesModel?.isComponentOnLaneButNotOnMain(envId, false, lanesModel.viewedLane?.id)
|
|
60
|
+
: false;
|
|
61
|
+
|
|
62
|
+
const envUrl = !envId
|
|
63
|
+
? undefined
|
|
64
|
+
: (isEnvOnCurrentLane &&
|
|
65
|
+
lanesModel?.viewedLane?.id &&
|
|
66
|
+
mounted &&
|
|
67
|
+
`${window.location.origin}${LanesModel.getLaneComponentUrl(envId, lanesModel.viewedLane?.id, undefined, lanesModel.viewedLane)}`) ||
|
|
68
|
+
ComponentUrl.toUrl(envId, {
|
|
69
|
+
includeVersion: true,
|
|
70
|
+
useLocationOrigin: mounted ? window.location.host.startsWith('localhost') : false,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const envTooltip = envId ? (
|
|
74
|
+
<Link
|
|
75
|
+
external
|
|
76
|
+
className={styles.envLink}
|
|
77
|
+
href={envUrl}
|
|
78
|
+
onClick={(event) => {
|
|
79
|
+
// do not trigger component selection
|
|
80
|
+
event.stopPropagation();
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<div className={styles.componentEnvTitle}>Environment</div>
|
|
84
|
+
<div>{component.environment?.id}</div>
|
|
85
|
+
</Link>
|
|
86
|
+
) : null;
|
|
87
|
+
|
|
88
|
+
const href = !isMissingCompOrEnvId
|
|
89
|
+
? lanesModel?.getLaneComponentUrlByVersion(
|
|
90
|
+
component.id as any,
|
|
91
|
+
lanesModel.viewedLane?.id,
|
|
92
|
+
!scope.name,
|
|
93
|
+
lanesModel.viewedLane
|
|
94
|
+
)
|
|
95
|
+
: undefined;
|
|
96
|
+
|
|
97
|
+
const viewingMainCompOnLane = React.useMemo(() => {
|
|
98
|
+
if (isMissingCompOrEnvId) return false;
|
|
99
|
+
return (
|
|
100
|
+
!component.status?.isNew &&
|
|
101
|
+
!lanesModel?.viewedLane?.id.isDefault() &&
|
|
102
|
+
lanesModel?.isComponentOnMainButNotOnLane(component.id as any, undefined, lanesModel?.viewedLane?.id)
|
|
103
|
+
);
|
|
104
|
+
}, [lanesModel?.viewedLane?.id.toString(), component.id.toString()]);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* this covers the following use cases when matching the active component's href with location
|
|
108
|
+
*
|
|
109
|
+
* 1. viewing main component
|
|
110
|
+
*
|
|
111
|
+
* - /<component-id>/
|
|
112
|
+
* - /<component-id>/~sub-route
|
|
113
|
+
* - /<component-id>/~sub-route/another-sub-route
|
|
114
|
+
* - /<component-id>/~sub-route?version=<version>
|
|
115
|
+
* - /<component-id>/~sub-route?scope=<scope>
|
|
116
|
+
* - /<component-id>?scope=<scope>
|
|
117
|
+
*
|
|
118
|
+
* 2. viewing a lane component
|
|
119
|
+
*
|
|
120
|
+
* - /~lane/<lane-id>/<component-id>/
|
|
121
|
+
* - /~lane/<lane-id>/<component-id>?scope=<scope>
|
|
122
|
+
* - /~lane/<lane-id>/<component-id>/~sub-route
|
|
123
|
+
* - /~lane/<lane-id>/<component-id>/~sub-route/another-sub-route
|
|
124
|
+
* - /~lane/<lane-id>/<component-id>/~sub-route?version=<version>
|
|
125
|
+
* - /~lane/<lane-id>/<component-id>/~sub-route?scope=<scope>
|
|
126
|
+
*
|
|
127
|
+
* 3. viewing a main component when on a lane
|
|
128
|
+
*
|
|
129
|
+
* - /?lane=<lane-id>/<component-id>/
|
|
130
|
+
* - /?lane=<lane-id>/<component-id>?scope=<scope>
|
|
131
|
+
* - /?lane=<lane-id>/<component-id>/~sub-route
|
|
132
|
+
* - /?lane=<lane-id>/<component-id>/~sub-route/another-sub-route
|
|
133
|
+
* - /?lane=<lane-id>/<component-id>/~sub-route?version=<version>
|
|
134
|
+
* - /?lane=<lane-id>/<component-id>/~sub-route?scope=<scope>
|
|
135
|
+
*
|
|
136
|
+
* 4. viewing currently checked out lane component on workspace
|
|
137
|
+
*
|
|
138
|
+
* - /<component-id-without-scope>/
|
|
139
|
+
* - /<component-id-without-scope>?scope=<scope>
|
|
140
|
+
*
|
|
141
|
+
* 5. viewing lane component from the same scope as the lane on a workspace
|
|
142
|
+
*
|
|
143
|
+
* - /~lane/<lane-id>/<component-id-without-scope>/
|
|
144
|
+
*
|
|
145
|
+
* @todo - move this logic to a util function
|
|
146
|
+
*/
|
|
147
|
+
const isActive = React.useMemo(() => {
|
|
148
|
+
if (!href || !location || !component?.id) return false;
|
|
149
|
+
|
|
150
|
+
const searchParams = new URLSearchParams(location.search);
|
|
151
|
+
const scopeFromQueryParams = searchParams.get('scope');
|
|
152
|
+
const pathname = location.pathname.substring(1).split('?')[0];
|
|
153
|
+
const compIdStr = component.id.toStringWithoutVersion();
|
|
154
|
+
const compIdName = component.id.fullName;
|
|
155
|
+
const componentScope = component.id.scope;
|
|
156
|
+
const locationIncludesLaneId = location.pathname.includes(LanesModel.lanesPrefix);
|
|
157
|
+
|
|
158
|
+
const sanitizedHref = (href.startsWith('/') ? href.substring(1) : href).split('?')[0];
|
|
159
|
+
|
|
160
|
+
// if you are in a workspace, the componentId might not have a scopeId, if you are viewing
|
|
161
|
+
// a component on the checked out lane
|
|
162
|
+
const viewingCheckedOutLaneComp =
|
|
163
|
+
lanesModel?.currentLane && lanesModel.currentLane.id.toString() === lanesModel?.viewedLane?.id.toString();
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
!locationIncludesLaneId &&
|
|
167
|
+
(lanesModel?.viewedLane?.id.isDefault() || viewingMainCompOnLane || viewingCheckedOutLaneComp)
|
|
168
|
+
) {
|
|
169
|
+
// split out any sub routes if exist
|
|
170
|
+
const compUrl = pathname.split('/~')[0];
|
|
171
|
+
// may or may not contain scope
|
|
172
|
+
const scopeUrl = scope.name ? `${scope.name.replace('.', '/')}/` : '';
|
|
173
|
+
const compUrlWithoutScope = compUrl.replace(scopeUrl, '');
|
|
174
|
+
return !scopeFromQueryParams
|
|
175
|
+
? sanitizedHref === compUrlWithoutScope
|
|
176
|
+
: sanitizedHref === compUrl && componentScope === scopeFromQueryParams;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const laneCompUrlWithSubRoutes = pathname.split(LanesModel.baseLaneComponentRoute)[1] ?? '';
|
|
180
|
+
|
|
181
|
+
const extractedLaneCompUrl = laneCompUrlWithSubRoutes.split('/~')[0] ?? '';
|
|
182
|
+
const laneCompUrl = extractedLaneCompUrl.startsWith('/') ? extractedLaneCompUrl.substring(1) : extractedLaneCompUrl;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* if the laneCompUrl doesn't have the scope as part of it and you are on a bare scope
|
|
186
|
+
* attach the bare scope to the laneCompUrl and parse it as a ComponentID
|
|
187
|
+
*/
|
|
188
|
+
const laneCompIdFromUrl =
|
|
189
|
+
ComponentID.tryFromString(laneCompUrl) ??
|
|
190
|
+
(scope.name ? ComponentID.tryFromString(`${scope.name}/${laneCompUrl}`) : undefined);
|
|
191
|
+
|
|
192
|
+
// viewing lane component from the same scope as the lane on a workspace
|
|
193
|
+
const laneAndCompFromSameScopeOnWs =
|
|
194
|
+
!scope.name && lanesModel?.viewedLane?.id && component.id.scope === lanesModel?.viewedLane?.id.scope;
|
|
195
|
+
|
|
196
|
+
if (laneAndCompFromSameScopeOnWs) {
|
|
197
|
+
return !scopeFromQueryParams
|
|
198
|
+
? compIdName === laneCompUrl
|
|
199
|
+
: compIdName === laneCompUrl && componentScope === scopeFromQueryParams;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!laneCompIdFromUrl) return false;
|
|
203
|
+
|
|
204
|
+
return !scopeFromQueryParams
|
|
205
|
+
? laneCompIdFromUrl?.toString() === compIdStr || laneCompIdFromUrl.fullName === compIdName
|
|
206
|
+
: laneCompIdFromUrl?.toString() === compIdStr ||
|
|
207
|
+
(laneCompIdFromUrl.fullName === compIdName && componentScope === scopeFromQueryParams);
|
|
208
|
+
}, [href, viewingMainCompOnLane, location?.pathname, component?.id.toString(), scope.name]);
|
|
209
|
+
|
|
210
|
+
if (isMissingCompOrEnvId) return null;
|
|
211
|
+
|
|
212
|
+
const isCompModified =
|
|
213
|
+
component.status?.modifyInfo?.hasModifiedFiles || component.status?.modifyInfo?.hasModifiedDependencies;
|
|
214
|
+
|
|
215
|
+
const addOpacity = viewingMainCompOnLane && !isCompModified;
|
|
216
|
+
|
|
217
|
+
const Name = viewingMainCompOnLane ? (
|
|
218
|
+
<Tooltip className={styles.tooltip} placement="top" content="On Main">
|
|
219
|
+
<span className={classNames(addOpacity && styles.opacity)}>{getName(node.id)}</span>
|
|
220
|
+
</Tooltip>
|
|
221
|
+
) : (
|
|
222
|
+
<span>{getName(node.id)}</span>
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<Link
|
|
227
|
+
href={href}
|
|
228
|
+
className={classNames(indentClass, styles.component)}
|
|
229
|
+
activeClassName={styles.active}
|
|
230
|
+
onClick={handleClick}
|
|
231
|
+
// exact={true}
|
|
232
|
+
active={isActive}
|
|
233
|
+
>
|
|
234
|
+
<div className={styles.left}>
|
|
235
|
+
<Tooltip className={styles.componentEnvTooltip} placement="top" content={envTooltip}>
|
|
236
|
+
<EnvIcon component={component} className={styles.envIcon} />
|
|
237
|
+
</Tooltip>
|
|
238
|
+
{Name}
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div className={styles.right}>
|
|
242
|
+
<DeprecationIcon component={component} />
|
|
243
|
+
{/* {isInternal && <Icon of="Internal" className={styles.componentIcon} />} */}
|
|
244
|
+
{treeNodeSlot &&
|
|
245
|
+
treeNodeSlot.toArray().map(([id, treeNode]) => <treeNode.widget key={id} component={component} />)}
|
|
246
|
+
</div>
|
|
247
|
+
</Link>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ComponentView } from './component-view';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { TreeNodeProps } from '@teambit/base-ui.graph.tree.recursive-tree';
|
|
3
|
+
import { PayloadType, ScopePayload } from '../payload-type';
|
|
4
|
+
|
|
5
|
+
import { ComponentView } from '../component-view';
|
|
6
|
+
import { ScopeTreeNode } from '../scope-tree-node';
|
|
7
|
+
import { NamespaceTreeNode } from '../namespace-tree-node';
|
|
8
|
+
|
|
9
|
+
export function DefaultTreeNodeRenderer(props: TreeNodeProps<PayloadType>) {
|
|
10
|
+
const { node } = props;
|
|
11
|
+
const { children, payload } = node;
|
|
12
|
+
if (!children) return <ComponentView {...props} />;
|
|
13
|
+
|
|
14
|
+
if (payload instanceof ScopePayload) return <ScopeTreeNode {...props} />;
|
|
15
|
+
|
|
16
|
+
return <NamespaceTreeNode {...props} />;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DefaultTreeNodeRenderer } from './default-tree-node-renderer';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './component-tree';
|
|
2
|
+
export { ComponentView } from './component-view';
|
|
3
|
+
export { ScopeTreeNode } from './scope-tree-node';
|
|
4
|
+
export { NamespaceTreeNode } from './namespace-tree-node';
|
|
5
|
+
export { ScopePayload } from './payload-type';
|
|
6
|
+
export type { PayloadType } from './payload-type';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NamespaceTreeNode } from './namespace-tree-node';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
@import '@teambit/base-ui.theme.colors/colors.module.scss';
|
|
2
|
+
|
|
3
|
+
.namespace {
|
|
4
|
+
position: relative;
|
|
5
|
+
height: 32px;
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
user-select: none;
|
|
10
|
+
font-weight: bold;
|
|
11
|
+
font-size: inherit;
|
|
12
|
+
padding-right: 8px;
|
|
13
|
+
font-size: var(--bit-p-xs);
|
|
14
|
+
cursor: pointer;
|
|
15
|
+
&:hover {
|
|
16
|
+
background: var(--bit-bg-heavy, #f6f6f6);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.arrow {
|
|
20
|
+
display: inline-block;
|
|
21
|
+
line-height: inherit;
|
|
22
|
+
transition: all 300ms;
|
|
23
|
+
margin-right: 10px;
|
|
24
|
+
color: var(--bit-text-color-light, #6c707c);
|
|
25
|
+
&.collapsed {
|
|
26
|
+
transform: rotate(-0.25turn);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.highlighted {
|
|
32
|
+
color: var(--bit-accent-heavy, #5d4aec);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.left {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
flex: 1;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
.name {
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
text-overflow: ellipsis;
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
margin-right: 8px;
|
|
45
|
+
line-height: 24px;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.componentTree {
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
transition: max-height 400ms ease-in-out;
|
|
52
|
+
font-size: var(--bit-p-xs);
|
|
53
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Icon } from '@teambit/evangelist.elements.icon';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
4
|
+
import AnimateHeight from 'react-animate-height';
|
|
5
|
+
import { indentClass, indentStyle } from '@teambit/base-ui.graph.tree.indent';
|
|
6
|
+
import { useTree, TreeNodeProps, TreeLayer } from '@teambit/design.ui.tree';
|
|
7
|
+
import { PayloadType } from '../payload-type';
|
|
8
|
+
import { getName } from '../utils/get-name';
|
|
9
|
+
import styles from './namespace-tree-node.module.scss';
|
|
10
|
+
|
|
11
|
+
export type NamespaceTreeNodeProps = {} & TreeNodeProps<PayloadType>;
|
|
12
|
+
|
|
13
|
+
export function NamespaceTreeNode({ node, depth }: NamespaceTreeNodeProps) {
|
|
14
|
+
const { isCollapsed, activePath } = useTree();
|
|
15
|
+
const isActive = activePath?.startsWith(node.id);
|
|
16
|
+
|
|
17
|
+
const initialOpen = isActive || !isCollapsed;
|
|
18
|
+
const [open, toggle] = useState<boolean | void>(initialOpen);
|
|
19
|
+
|
|
20
|
+
const firstRun = useRef(true);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const { current } = firstRun;
|
|
23
|
+
if (current) return;
|
|
24
|
+
if (isActive === true) toggle(true);
|
|
25
|
+
}, [isActive]);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (firstRun.current) return;
|
|
29
|
+
toggle(!isCollapsed);
|
|
30
|
+
}, [isCollapsed]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
firstRun.current = false;
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const displayName = getName(node.id.replace(/\/$/, ''));
|
|
37
|
+
const highlighted = !open && isActive;
|
|
38
|
+
return (
|
|
39
|
+
<div>
|
|
40
|
+
{node.id && (
|
|
41
|
+
<div
|
|
42
|
+
className={classNames(indentClass, styles.namespace, highlighted && styles.highlighted)}
|
|
43
|
+
onClick={() => toggle(!open)}
|
|
44
|
+
tabIndex={0}
|
|
45
|
+
role="button"
|
|
46
|
+
onKeyDown={(e) => {
|
|
47
|
+
if (e.key === 'Enter') toggle(!open);
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<div className={styles.left}>
|
|
51
|
+
<Icon className={classNames(styles.arrow, !open && styles.collapsed)} of="fat-arrow-down" />
|
|
52
|
+
<span className={styles.name}>{displayName}</span>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
<AnimateHeight height={open ? 'auto' : 0}>
|
|
57
|
+
<div style={indentStyle(depth + 1)} className={classNames(styles.componentTree)}>
|
|
58
|
+
{node.children && <TreeLayer childNodes={node.children} depth={depth} />}
|
|
59
|
+
</div>
|
|
60
|
+
</AnimateHeight>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ComponentModel } from '@teambit/component';
|
|
2
|
+
import { LaneModel } from '@teambit/lanes.ui.models.lanes-model';
|
|
3
|
+
|
|
4
|
+
export class ScopePayload {
|
|
5
|
+
get isScope() {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type PayloadType = ComponentModel | ScopePayload | LaneModel | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ScopeTreeNode } from './scope-tree-node';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
@import '@teambit/base-ui.theme.colors/colors.module.scss';
|
|
2
|
+
|
|
3
|
+
.scope {
|
|
4
|
+
position: relative;
|
|
5
|
+
height: 32px;
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
user-select: none;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
font-weight: bold;
|
|
12
|
+
font-size: inherit;
|
|
13
|
+
padding-right: 8px;
|
|
14
|
+
font-size: var(--bit-p-xs);
|
|
15
|
+
&:hover {
|
|
16
|
+
background: var(--bit-bg-heavy, #f6f6f6);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.arrow {
|
|
20
|
+
display: inline-block;
|
|
21
|
+
line-height: inherit;
|
|
22
|
+
transition: all 300ms;
|
|
23
|
+
margin-right: 10px;
|
|
24
|
+
color: var(--bit-text-color-light, #6c707c);
|
|
25
|
+
&.collapsed {
|
|
26
|
+
transform: rotate(-0.25turn);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.highlighted {
|
|
32
|
+
color: var(--bit-accent-heavy, #5d4aec);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.left {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
flex: 1;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
.name {
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
text-overflow: ellipsis;
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
margin-right: 8px;
|
|
45
|
+
line-height: 115%;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.componentTree {
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
transition: max-height 400ms ease-in-out;
|
|
52
|
+
font-size: var(--bit-p-xs);
|
|
53
|
+
}
|