@teambit/workspace 1.0.983 → 1.0.985
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/dist/{preview-1778621260574.js → preview-1778686821215.js} +2 -2
- package/dist/ui/workspace/use-workspace.d.ts +7 -6
- package/dist/ui/workspace/workspace-overview/card-overlays.d.ts +4 -0
- package/dist/ui/workspace/workspace-overview/card-overlays.js +133 -0
- package/dist/ui/workspace/workspace-overview/card-overlays.js.map +1 -0
- package/dist/ui/workspace/workspace-overview/card-overlays.module.scss +72 -0
- package/dist/ui/workspace/workspace-overview/filter-utils.d.ts +4 -1
- package/dist/ui/workspace/workspace-overview/filter-utils.js +17 -1
- package/dist/ui/workspace/workspace-overview/filter-utils.js.map +1 -1
- package/dist/ui/workspace/workspace-overview/hope-component-card.d.ts +14 -0
- package/dist/ui/workspace/workspace-overview/hope-component-card.js +186 -0
- package/dist/ui/workspace/workspace-overview/hope-component-card.js.map +1 -0
- package/dist/ui/workspace/workspace-overview/hope-component-card.module.scss +162 -0
- package/dist/ui/workspace/workspace-overview/namespace-header.d.ts +8 -0
- package/dist/ui/workspace/workspace-overview/namespace-header.js +107 -0
- package/dist/ui/workspace/workspace-overview/namespace-header.js.map +1 -0
- package/dist/ui/workspace/workspace-overview/namespace-header.module.scss +88 -0
- package/dist/ui/workspace/workspace-overview/use-workspace-aggregation.js +11 -5
- package/dist/ui/workspace/workspace-overview/use-workspace-aggregation.js.map +1 -1
- package/dist/ui/workspace/workspace-overview/workspace-filter-panel.js +8 -6
- package/dist/ui/workspace/workspace-overview/workspace-filter-panel.js.map +1 -1
- package/dist/ui/workspace/workspace-overview/workspace-overview.d.ts +0 -6
- package/dist/ui/workspace/workspace-overview/workspace-overview.js +38 -105
- package/dist/ui/workspace/workspace-overview/workspace-overview.js.map +1 -1
- package/dist/ui/workspace/workspace-overview/workspace-overview.module.scss +38 -158
- package/dist/ui/workspace/workspace-overview/workspace-overview.types.d.ts +6 -0
- package/dist/ui/workspace/workspace-overview/workspace-overview.types.js.map +1 -1
- package/dist/ui/workspace/workspace.js +22 -5
- package/dist/ui/workspace/workspace.js.map +1 -1
- package/dist/ui/workspace/workspace.module.scss +38 -30
- package/package.json +43 -44
- package/ui/workspace/workspace-overview/card-overlays.module.scss +72 -0
- package/ui/workspace/workspace-overview/card-overlays.tsx +66 -0
- package/ui/workspace/workspace-overview/filter-utils.ts +18 -1
- package/ui/workspace/workspace-overview/hope-component-card.module.scss +162 -0
- package/ui/workspace/workspace-overview/hope-component-card.tsx +152 -0
- package/ui/workspace/workspace-overview/namespace-header.module.scss +88 -0
- package/ui/workspace/workspace-overview/namespace-header.tsx +72 -0
- package/ui/workspace/workspace-overview/use-workspace-aggregation.ts +11 -5
- package/ui/workspace/workspace-overview/workspace-filter-panel.tsx +10 -9
- package/ui/workspace/workspace-overview/workspace-overview.module.scss +38 -158
- package/ui/workspace/workspace-overview/workspace-overview.tsx +40 -88
- package/ui/workspace/workspace-overview/workspace-overview.types.ts +7 -1
- package/ui/workspace/workspace.module.scss +38 -30
- package/ui/workspace/workspace.tsx +24 -7
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
.card {
|
|
2
|
+
position: relative;
|
|
3
|
+
border-radius: 10px;
|
|
4
|
+
overflow: hidden;
|
|
5
|
+
background: var(--surface-color);
|
|
6
|
+
border: 1px solid var(--border-medium-color);
|
|
7
|
+
cursor: pointer;
|
|
8
|
+
transition:
|
|
9
|
+
box-shadow 0.15s ease,
|
|
10
|
+
border-color 0.15s ease;
|
|
11
|
+
|
|
12
|
+
&:hover {
|
|
13
|
+
box-shadow: 0 4px 14px rgba(20, 0, 104, 0.06);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.cardBuilding {
|
|
18
|
+
composes: card;
|
|
19
|
+
|
|
20
|
+
&:hover {
|
|
21
|
+
box-shadow: none;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.linkWrapper {
|
|
26
|
+
text-decoration: none;
|
|
27
|
+
color: inherit;
|
|
28
|
+
display: block;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ---- Preview ---- */
|
|
32
|
+
|
|
33
|
+
.preview {
|
|
34
|
+
height: 180px;
|
|
35
|
+
position: relative;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
background: white;
|
|
38
|
+
border-bottom: 1px solid var(--border-medium-color);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.previewQueued {
|
|
42
|
+
composes: preview;
|
|
43
|
+
background: var(--surface01-color);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.previewInner {
|
|
47
|
+
position: absolute;
|
|
48
|
+
inset: 0;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ---- Env badge ---- */
|
|
53
|
+
|
|
54
|
+
.envBadge {
|
|
55
|
+
position: absolute;
|
|
56
|
+
bottom: 8px;
|
|
57
|
+
right: 8px;
|
|
58
|
+
background: var(--surface-color);
|
|
59
|
+
padding: 4px;
|
|
60
|
+
border-radius: 8px;
|
|
61
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
width: 24px;
|
|
66
|
+
height: 24px;
|
|
67
|
+
z-index: 3;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.envIcon {
|
|
71
|
+
height: 14px;
|
|
72
|
+
display: block;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ---- Status corner ---- */
|
|
76
|
+
|
|
77
|
+
.statusCorner {
|
|
78
|
+
position: absolute;
|
|
79
|
+
top: 10px;
|
|
80
|
+
right: 10px;
|
|
81
|
+
z-index: 2;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ---- Footer ---- */
|
|
85
|
+
|
|
86
|
+
.footer {
|
|
87
|
+
padding: 8px 12px;
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: 8px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.name {
|
|
94
|
+
flex: 1;
|
|
95
|
+
min-width: 0;
|
|
96
|
+
font-size: 12.5px;
|
|
97
|
+
font-weight: 500;
|
|
98
|
+
font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
|
|
99
|
+
color: var(--on-background-color);
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
text-overflow: ellipsis;
|
|
102
|
+
white-space: nowrap;
|
|
103
|
+
letter-spacing: -0.01em;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.nameQueued {
|
|
107
|
+
composes: name;
|
|
108
|
+
color: var(--on-background-medium-color);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.hash {
|
|
112
|
+
font-size: 10.5px;
|
|
113
|
+
padding: 1px 6px;
|
|
114
|
+
border-radius: 4px;
|
|
115
|
+
background: var(--surface01-color);
|
|
116
|
+
font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
|
|
117
|
+
color: var(--on-background-low-color);
|
|
118
|
+
flex-shrink: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.scopeBadge {
|
|
122
|
+
width: 20px;
|
|
123
|
+
height: 20px;
|
|
124
|
+
border-radius: 5px;
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
justify-content: center;
|
|
128
|
+
flex-shrink: 0;
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.scopeBadgeIcon {
|
|
133
|
+
width: 12px;
|
|
134
|
+
height: 12px;
|
|
135
|
+
object-fit: contain;
|
|
136
|
+
filter: brightness(0) invert(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.scopeBadgeInitial {
|
|
140
|
+
font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
|
|
141
|
+
font-size: 10px;
|
|
142
|
+
font-weight: 700;
|
|
143
|
+
color: white;
|
|
144
|
+
line-height: 1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.buildingLabel {
|
|
148
|
+
font-size: 10px;
|
|
149
|
+
font-weight: 600;
|
|
150
|
+
letter-spacing: 0.06em;
|
|
151
|
+
text-transform: uppercase;
|
|
152
|
+
flex-shrink: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ---- Load preview button ---- */
|
|
156
|
+
|
|
157
|
+
.loadPreview {
|
|
158
|
+
position: absolute;
|
|
159
|
+
right: 4px !important;
|
|
160
|
+
left: unset !important;
|
|
161
|
+
z-index: 4;
|
|
162
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Link } from '@teambit/base-react.navigation.link';
|
|
3
|
+
import { PreviewPlaceholder } from '@teambit/preview.ui.preview-placeholder';
|
|
4
|
+
import { Tooltip } from '@teambit/design.ui.tooltip';
|
|
5
|
+
import { LoadPreview } from '@teambit/workspace.ui.load-preview';
|
|
6
|
+
import { ComponentID } from '@teambit/component-id';
|
|
7
|
+
import type { ComponentModel } from '@teambit/component';
|
|
8
|
+
import type { ComponentDescriptor } from '@teambit/component-descriptor';
|
|
9
|
+
import type { ScopeID } from '@teambit/scopes.scope-id';
|
|
10
|
+
import { getComponentStatus } from './filter-utils';
|
|
11
|
+
import { ChangedPill, BuildSpinner, BuildingPreview } from './card-overlays';
|
|
12
|
+
import styles from './hope-component-card.module.scss';
|
|
13
|
+
|
|
14
|
+
export type HopeComponentCardProps = {
|
|
15
|
+
component: ComponentModel;
|
|
16
|
+
componentDescriptor: ComponentDescriptor;
|
|
17
|
+
scope?: { id: ScopeID; icon?: string; backgroundIconColor?: string };
|
|
18
|
+
showPreview?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function HopeComponentCard({
|
|
22
|
+
component,
|
|
23
|
+
componentDescriptor,
|
|
24
|
+
scope,
|
|
25
|
+
showPreview: showPreviewProp,
|
|
26
|
+
}: HopeComponentCardProps) {
|
|
27
|
+
const [shouldShowPreview, setShouldShowPreview] = useState(Boolean(showPreviewProp));
|
|
28
|
+
const prevServerUrlRef = useRef(component.server?.url);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (prevServerUrlRef.current !== component.server?.url && shouldShowPreview) {
|
|
32
|
+
setShouldShowPreview(false);
|
|
33
|
+
setTimeout(() => setShouldShowPreview(true), 50);
|
|
34
|
+
}
|
|
35
|
+
prevServerUrlRef.current = component.server?.url;
|
|
36
|
+
}, [component.server?.url]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
setShouldShowPreview(Boolean(showPreviewProp));
|
|
40
|
+
}, [showPreviewProp]);
|
|
41
|
+
|
|
42
|
+
const item = { component } as any;
|
|
43
|
+
const status = getComponentStatus(item);
|
|
44
|
+
const accent = 'var(--bit-accent-color, #6c5ce7)';
|
|
45
|
+
|
|
46
|
+
const isBuilding = status === 'building';
|
|
47
|
+
const isQueued = status === 'queued';
|
|
48
|
+
const isChanged = status === 'changed';
|
|
49
|
+
|
|
50
|
+
const href = `${component.id.fullName}?scope=${component.id.scope}`;
|
|
51
|
+
|
|
52
|
+
const loadPreviewVisible = component.compositions.length > 0 && !isBuilding && !shouldShowPreview;
|
|
53
|
+
|
|
54
|
+
const showPreviewClick = (e: React.MouseEvent) => {
|
|
55
|
+
e.stopPropagation();
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
setShouldShowPreview(true);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const envAspect = componentDescriptor.get<any>('teambit.envs/envs');
|
|
61
|
+
const env = envAspect?.data || envAspect;
|
|
62
|
+
const envComponentId = env?.id ? ComponentID.fromString(env.id) : undefined;
|
|
63
|
+
|
|
64
|
+
const cardClass = isBuilding ? styles.cardBuilding : styles.card;
|
|
65
|
+
const buildingBorderStyle = isBuilding
|
|
66
|
+
? {
|
|
67
|
+
borderColor: accent,
|
|
68
|
+
boxShadow: `0 0 0 3px color-mix(in srgb, var(--bit-accent-color, #6c5ce7) 10%, transparent)`,
|
|
69
|
+
}
|
|
70
|
+
: undefined;
|
|
71
|
+
|
|
72
|
+
const nameLabel = component.id.namespace ? `${component.id.namespace}/${component.id.name}` : component.id.name;
|
|
73
|
+
|
|
74
|
+
const shortHash = component.id.version?.slice(0, 7);
|
|
75
|
+
|
|
76
|
+
const scopeInitial = component.id.scope?.split('.').pop()?.charAt(0).toUpperCase();
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<div className={cardClass} style={buildingBorderStyle}>
|
|
80
|
+
{loadPreviewVisible && <LoadPreview className={styles.loadPreview} onClick={showPreviewClick} />}
|
|
81
|
+
|
|
82
|
+
<Link href={href} className={styles.linkWrapper}>
|
|
83
|
+
<div className={styles.preview}>
|
|
84
|
+
<div className={styles.previewInner}>
|
|
85
|
+
<CardPreview
|
|
86
|
+
component={component}
|
|
87
|
+
componentDescriptor={componentDescriptor}
|
|
88
|
+
status={status}
|
|
89
|
+
shouldShowPreview={shouldShowPreview}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{!isQueued && env?.icon && (
|
|
94
|
+
<div className={styles.envBadge}>
|
|
95
|
+
<Tooltip delay={300} content={envComponentId?.name}>
|
|
96
|
+
<img src={env.icon} className={styles.envIcon} alt="" />
|
|
97
|
+
</Tooltip>
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
{(isChanged || isBuilding) && (
|
|
102
|
+
<div className={styles.statusCorner}>
|
|
103
|
+
{isChanged && <ChangedPill />}
|
|
104
|
+
{isBuilding && <BuildSpinner />}
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div className={styles.footer}>
|
|
110
|
+
<Tooltip delay={300} content={component.id.scope}>
|
|
111
|
+
<div className={styles.scopeBadge} style={{ background: scope?.backgroundIconColor || accent }}>
|
|
112
|
+
{scope?.icon ? (
|
|
113
|
+
<img src={scope.icon} className={styles.scopeBadgeIcon} alt="" />
|
|
114
|
+
) : (
|
|
115
|
+
<span className={styles.scopeBadgeInitial}>{scopeInitial}</span>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
</Tooltip>
|
|
119
|
+
<span className={styles.name}>{nameLabel}</span>
|
|
120
|
+
{!isBuilding && !isQueued && shortHash && <span className={styles.hash}>{shortHash}</span>}
|
|
121
|
+
{isBuilding && (
|
|
122
|
+
<span className={styles.buildingLabel} style={{ color: accent }}>
|
|
123
|
+
BUILDING
|
|
124
|
+
</span>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</Link>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function CardPreview({
|
|
133
|
+
component,
|
|
134
|
+
componentDescriptor,
|
|
135
|
+
status,
|
|
136
|
+
shouldShowPreview,
|
|
137
|
+
}: {
|
|
138
|
+
component: ComponentModel;
|
|
139
|
+
componentDescriptor: ComponentDescriptor;
|
|
140
|
+
status: string;
|
|
141
|
+
shouldShowPreview: boolean;
|
|
142
|
+
}) {
|
|
143
|
+
if (status === 'building') return <BuildingPreview />;
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<PreviewPlaceholder
|
|
147
|
+
component={component}
|
|
148
|
+
componentDescriptor={componentDescriptor}
|
|
149
|
+
shouldShowPreview={shouldShowPreview}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.header {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: 10px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.dot {
|
|
8
|
+
width: 10px;
|
|
9
|
+
height: 10px;
|
|
10
|
+
border-radius: 3px;
|
|
11
|
+
flex-shrink: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.name {
|
|
15
|
+
font-size: 20px;
|
|
16
|
+
font-weight: 600;
|
|
17
|
+
letter-spacing: 0.02em;
|
|
18
|
+
color: var(--on-background-color);
|
|
19
|
+
flex-shrink: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.count {
|
|
23
|
+
font-size: 12px;
|
|
24
|
+
color: var(--on-background-low-color);
|
|
25
|
+
flex-shrink: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.buildingPill {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 5px;
|
|
32
|
+
font-size: 10.5px;
|
|
33
|
+
font-weight: 500;
|
|
34
|
+
padding: 1px 8px;
|
|
35
|
+
border-radius: 999px;
|
|
36
|
+
margin-left: 4px;
|
|
37
|
+
flex-shrink: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.buildingDot {
|
|
41
|
+
width: 5px;
|
|
42
|
+
height: 5px;
|
|
43
|
+
border-radius: 50%;
|
|
44
|
+
animation: pulse 1.4s ease-in-out infinite;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.scopeIconBadge {
|
|
48
|
+
width: 24px;
|
|
49
|
+
height: 24px;
|
|
50
|
+
border-radius: 6px;
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
justify-content: center;
|
|
54
|
+
flex-shrink: 0;
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.scopeIconImg {
|
|
59
|
+
width: 14px;
|
|
60
|
+
height: 14px;
|
|
61
|
+
object-fit: contain;
|
|
62
|
+
filter: brightness(0) invert(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.scopeInitial {
|
|
66
|
+
font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
|
|
67
|
+
font-size: 11px;
|
|
68
|
+
font-weight: 700;
|
|
69
|
+
color: white;
|
|
70
|
+
line-height: 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.divider {
|
|
74
|
+
flex: 1;
|
|
75
|
+
height: 1px;
|
|
76
|
+
background: var(--border-medium-color);
|
|
77
|
+
margin-left: 4px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@keyframes pulse {
|
|
81
|
+
0%,
|
|
82
|
+
100% {
|
|
83
|
+
opacity: 1;
|
|
84
|
+
}
|
|
85
|
+
50% {
|
|
86
|
+
opacity: 0.55;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { getComponentStatus } from './filter-utils';
|
|
3
|
+
import type { WorkspaceItem } from './workspace-overview.types';
|
|
4
|
+
import styles from './namespace-header.module.scss';
|
|
5
|
+
|
|
6
|
+
export interface NamespaceHeaderProps {
|
|
7
|
+
namespace: string;
|
|
8
|
+
items: WorkspaceItem[];
|
|
9
|
+
scopeIcon?: string;
|
|
10
|
+
scopeIconColor?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function NamespaceHeader({ namespace, items, scopeIcon, scopeIconColor }: NamespaceHeaderProps) {
|
|
14
|
+
const accent = 'var(--bit-accent-color, #6c5ce7)';
|
|
15
|
+
const tint = 'color-mix(in srgb, var(--bit-accent-color, #6c5ce7) 12%, transparent)';
|
|
16
|
+
|
|
17
|
+
let buildingCount = 0;
|
|
18
|
+
let readyCount = 0;
|
|
19
|
+
for (const item of items) {
|
|
20
|
+
const s = getComponentStatus(item);
|
|
21
|
+
if (s === 'building') buildingCount++;
|
|
22
|
+
if (s === 'built' || s === 'changed') readyCount++;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<header className={styles.header}>
|
|
27
|
+
<HeaderIcon scopeIcon={scopeIcon} scopeIconColor={scopeIconColor} namespace={namespace} accent={accent} />
|
|
28
|
+
<span className={styles.name}>{namespace}</span>
|
|
29
|
+
<span className={styles.count}>
|
|
30
|
+
{readyCount}/{items.length}
|
|
31
|
+
</span>
|
|
32
|
+
{buildingCount > 0 && (
|
|
33
|
+
<span className={styles.buildingPill} style={{ color: accent, background: tint }}>
|
|
34
|
+
<span className={styles.buildingDot} style={{ background: accent }} />
|
|
35
|
+
building
|
|
36
|
+
</span>
|
|
37
|
+
)}
|
|
38
|
+
<div className={styles.divider} />
|
|
39
|
+
</header>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function HeaderIcon({
|
|
44
|
+
scopeIcon,
|
|
45
|
+
scopeIconColor,
|
|
46
|
+
namespace,
|
|
47
|
+
accent,
|
|
48
|
+
}: {
|
|
49
|
+
scopeIcon?: string;
|
|
50
|
+
scopeIconColor?: string;
|
|
51
|
+
namespace: string;
|
|
52
|
+
accent: string;
|
|
53
|
+
}) {
|
|
54
|
+
if (scopeIcon) {
|
|
55
|
+
return (
|
|
56
|
+
<div className={styles.scopeIconBadge} style={{ background: scopeIconColor || accent }}>
|
|
57
|
+
<img src={scopeIcon} className={styles.scopeIconImg} alt="" />
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (scopeIconColor) {
|
|
63
|
+
const initial = namespace.split(/[./]/).pop()?.charAt(0).toUpperCase() || '?';
|
|
64
|
+
return (
|
|
65
|
+
<div className={styles.scopeIconBadge} style={{ background: scopeIconColor }}>
|
|
66
|
+
<span className={styles.scopeInitial}>{initial}</span>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return <span className={styles.dot} style={{ background: accent }} />;
|
|
72
|
+
}
|
|
@@ -58,11 +58,17 @@ export function useWorkspaceAggregation(
|
|
|
58
58
|
|
|
59
59
|
const sortedScopes = [...map.keys()].sort();
|
|
60
60
|
|
|
61
|
-
const groups: AggregationGroup[] = sortedScopes.map((sc) =>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
const groups: AggregationGroup[] = sortedScopes.map((sc) => {
|
|
62
|
+
const groupItems = map.get(sc)!;
|
|
63
|
+
const sampleScope = groupItems.find((i) => i.scope?.icon)?.scope;
|
|
64
|
+
return {
|
|
65
|
+
name: sc,
|
|
66
|
+
displayName: sc,
|
|
67
|
+
items: sortItemsByNamespace(groupItems),
|
|
68
|
+
scopeIcon: sampleScope?.icon,
|
|
69
|
+
scopeIconColor: sampleScope?.backgroundIconColor,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
66
72
|
|
|
67
73
|
return {
|
|
68
74
|
groups,
|
|
@@ -50,13 +50,15 @@ export function WorkspaceFilterPanel({
|
|
|
50
50
|
const activeNsOptions = activeNamespaces.map((v) => ({ value: v }));
|
|
51
51
|
const activeScopeOptions = activeScopes.map((v) => ({ value: v }));
|
|
52
52
|
|
|
53
|
-
const applyNs = (opts) => {
|
|
54
|
-
const
|
|
53
|
+
const applyNs = (opts: readonly { value?: string }[] | { value?: string } | null) => {
|
|
54
|
+
const arr = Array.isArray(opts) ? opts : opts ? [opts] : [];
|
|
55
|
+
const list = arr.map((o) => o.value).filter((v): v is string => typeof v === 'string');
|
|
55
56
|
onNamespacesChange(list);
|
|
56
57
|
};
|
|
57
58
|
|
|
58
|
-
const applyScopes = (opts) => {
|
|
59
|
-
const
|
|
59
|
+
const applyScopes = (opts: readonly { value?: string }[] | { value?: string } | null) => {
|
|
60
|
+
const arr = Array.isArray(opts) ? opts : opts ? [opts] : [];
|
|
61
|
+
const list = arr.map((o) => o.value).filter((v): v is string => typeof v === 'string');
|
|
60
62
|
onScopesChange(list);
|
|
61
63
|
};
|
|
62
64
|
|
|
@@ -71,8 +73,8 @@ export function WorkspaceFilterPanel({
|
|
|
71
73
|
};
|
|
72
74
|
|
|
73
75
|
return (
|
|
74
|
-
<div className={styles.
|
|
75
|
-
<div className={styles.
|
|
76
|
+
<div className={styles.commandBar}>
|
|
77
|
+
<div className={styles.leftCluster}>
|
|
76
78
|
<BaseFilter
|
|
77
79
|
id="namespaces"
|
|
78
80
|
placeholder="Namespaces"
|
|
@@ -81,7 +83,6 @@ export function WorkspaceFilterPanel({
|
|
|
81
83
|
onChange={applyNs}
|
|
82
84
|
isSearchable
|
|
83
85
|
/>
|
|
84
|
-
|
|
85
86
|
<BaseFilter
|
|
86
87
|
id="scopes"
|
|
87
88
|
placeholder="Scopes"
|
|
@@ -92,9 +93,9 @@ export function WorkspaceFilterPanel({
|
|
|
92
93
|
/>
|
|
93
94
|
</div>
|
|
94
95
|
|
|
95
|
-
<div className={styles.
|
|
96
|
+
<div className={styles.rightCluster}>
|
|
96
97
|
<ToggleButton
|
|
97
|
-
className={styles.
|
|
98
|
+
className={styles.aggToggle}
|
|
98
99
|
defaultIndex={currentIndex}
|
|
99
100
|
onOptionSelect={(idx) => applyAgg(idx)}
|
|
100
101
|
options={availableAggregations.map((agg) => ({
|