fix workspace sources: icon visibility, state sync, color mapping, sort order
Made-with: Cursor
This commit is contained in:
parent
cba01a2d61
commit
b96d3dad4a
4 changed files with 65 additions and 25 deletions
|
|
@ -89,6 +89,7 @@ interface FeatureTableNode {
|
|||
|
||||
interface SourcesTabProps {
|
||||
context: UdbContext;
|
||||
onSourcesChanged?: () => void;
|
||||
}
|
||||
|
||||
/* ─── Icons ──────────────────────────────────────────────────────────── */
|
||||
|
|
@ -115,27 +116,46 @@ const _SERVICE_ICONS: Record<string, string> = {
|
|||
|
||||
const _SOURCE_COLORS: Record<string, string> = {
|
||||
sharepointFolder: '#0078d4',
|
||||
sharepoint: '#0078d4',
|
||||
onedriveFolder: '#0078d4',
|
||||
onedrive: '#0078d4',
|
||||
outlookFolder: '#0078d4',
|
||||
outlook: '#0078d4',
|
||||
googleDriveFolder: '#34a853',
|
||||
drive: '#34a853',
|
||||
gmailFolder: '#ea4335',
|
||||
gmail: '#ea4335',
|
||||
ftpFolder: '#795548',
|
||||
files: '#795548',
|
||||
'local:ftp': '#795548',
|
||||
'local:jira': '#1976d2',
|
||||
clickup: '#7b68ee',
|
||||
};
|
||||
|
||||
function _getSourceColor(sourceType: string): string {
|
||||
return _SOURCE_COLORS[sourceType] || '#1976d2';
|
||||
}
|
||||
|
||||
function _getSourceIcon(sourceType: string): string {
|
||||
const map: Record<string, string> = {
|
||||
const _SOURCE_ICONS: Record<string, string> = {
|
||||
sharepointFolder: '\uD83D\uDCC1',
|
||||
sharepoint: '\uD83D\uDCC1',
|
||||
onedriveFolder: '\u2601\uFE0F',
|
||||
onedrive: '\u2601\uFE0F',
|
||||
outlookFolder: '\uD83D\uDCE7',
|
||||
outlook: '\uD83D\uDCE7',
|
||||
googleDriveFolder: '\uD83D\uDCC2',
|
||||
drive: '\uD83D\uDCC2',
|
||||
gmailFolder: '\uD83D\uDCE8',
|
||||
gmail: '\uD83D\uDCE8',
|
||||
ftpFolder: '\uD83D\uDD17',
|
||||
files: '\uD83D\uDD17',
|
||||
'local:ftp': '\uD83D\uDD17',
|
||||
'local:jira': '\uD83D\uDD27',
|
||||
clickup: '\uD83D\uDCCB',
|
||||
};
|
||||
return map[sourceType] || '\uD83D\uDCC1';
|
||||
|
||||
function _getSourceIcon(sourceType: string): string {
|
||||
return _SOURCE_ICONS[sourceType] || '\uD83D\uDCC1';
|
||||
}
|
||||
|
||||
/* ─── Scope / Neutralize constants ───────────────────────────────────── */
|
||||
|
|
@ -162,6 +182,15 @@ function _nextScope(current: string): string {
|
|||
return _SCOPE_ORDER[(idx + 1) % _SCOPE_ORDER.length];
|
||||
}
|
||||
|
||||
const _SERVICE_TO_SOURCE_TYPE: Record<string, string> = {
|
||||
sharepoint: 'sharepointFolder',
|
||||
onedrive: 'onedriveFolder',
|
||||
outlook: 'outlookFolder',
|
||||
drive: 'googleDriveFolder',
|
||||
gmail: 'gmailFolder',
|
||||
files: 'ftpFolder',
|
||||
};
|
||||
|
||||
/* ─── Tree helpers ───────────────────────────────────────────────────── */
|
||||
|
||||
function _mapTree(nodes: TreeNode[], key: string, updater: (n: TreeNode) => TreeNode): TreeNode[] {
|
||||
|
|
@ -301,7 +330,7 @@ function _Spinner(): React.ReactElement {
|
|||
|
||||
/* ─── Component ──────────────────────────────────────────────────────── */
|
||||
|
||||
const SourcesTab: React.FC<SourcesTabProps> = ({ context }) => {
|
||||
const SourcesTab: React.FC<SourcesTabProps> = ({ context, onSourcesChanged }) => {
|
||||
const instanceId = context.instanceId;
|
||||
|
||||
/* ── Active sources (fetched internally) ── */
|
||||
|
|
@ -444,22 +473,15 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context }) => {
|
|||
if (!node.service || !node.connectionId) return;
|
||||
setAddingPath(node.key);
|
||||
try {
|
||||
const sourceTypeMap: Record<string, string> = {
|
||||
sharepoint: 'sharepointFolder',
|
||||
onedrive: 'onedriveFolder',
|
||||
outlook: 'outlookFolder',
|
||||
drive: 'googleDriveFolder',
|
||||
gmail: 'gmailFolder',
|
||||
files: 'ftpFolder',
|
||||
};
|
||||
await api.post(`/api/workspace/${instanceId}/datasources`, {
|
||||
connectionId: node.connectionId,
|
||||
sourceType: sourceTypeMap[node.service] || node.service,
|
||||
sourceType: _SERVICE_TO_SOURCE_TYPE[node.service] || node.service,
|
||||
path: node.path || '/',
|
||||
label: node.label,
|
||||
displayPath: node.displayPath || node.label,
|
||||
});
|
||||
_fetchDataSources();
|
||||
onSourcesChanged?.();
|
||||
} catch (err) {
|
||||
console.error('Failed to add data source:', err);
|
||||
} finally {
|
||||
|
|
@ -472,15 +494,19 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context }) => {
|
|||
try {
|
||||
await api.delete(`/api/workspace/${instanceId}/datasources/${dsId}`);
|
||||
_fetchDataSources();
|
||||
onSourcesChanged?.();
|
||||
} catch (err) {
|
||||
console.error('Failed to remove data source:', err);
|
||||
}
|
||||
}, [instanceId, _fetchDataSources]);
|
||||
|
||||
/* ── Check if a path is already added ── */
|
||||
const _isAdded = useCallback((connectionId: string, _service: string | undefined, path: string | undefined): boolean => {
|
||||
const _isAdded = useCallback((connectionId: string, service: string | undefined, path: string | undefined): boolean => {
|
||||
const expectedSourceType = service ? (_SERVICE_TO_SOURCE_TYPE[service] || service) : undefined;
|
||||
return dataSources.some(ds =>
|
||||
ds.connectionId === connectionId && ds.path === (path || '/'),
|
||||
ds.connectionId === connectionId &&
|
||||
ds.path === (path || '/') &&
|
||||
(!expectedSourceType || ds.sourceType === expectedSourceType),
|
||||
);
|
||||
}, [dataSources]);
|
||||
|
||||
|
|
@ -617,6 +643,7 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context }) => {
|
|||
label: table.label?.en || table.label?.de || table.tableName,
|
||||
});
|
||||
_fetchFeatureDataSources();
|
||||
onSourcesChanged?.();
|
||||
} catch (err) {
|
||||
console.error('Failed to add feature data source:', err);
|
||||
} finally {
|
||||
|
|
@ -629,6 +656,7 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context }) => {
|
|||
try {
|
||||
await api.delete(`/api/workspace/${instanceId}/feature-datasources/${fdsId}`);
|
||||
_fetchFeatureDataSources();
|
||||
onSourcesChanged?.();
|
||||
} catch (err) {
|
||||
console.error('Failed to remove feature data source:', err);
|
||||
}
|
||||
|
|
@ -651,7 +679,11 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context }) => {
|
|||
<div style={{ fontSize: 11, fontWeight: 600, color: '#666', textTransform: 'uppercase', marginBottom: 4 }}>
|
||||
Active Personal Sources
|
||||
</div>
|
||||
{dataSources.map(ds => {
|
||||
{[...dataSources].sort((a, b) => {
|
||||
const aKey = `${a.sourceType}|${a.label || a.path || ''}`;
|
||||
const bKey = `${b.sourceType}|${b.label || b.path || ''}`;
|
||||
return aKey.localeCompare(bKey);
|
||||
}).map(ds => {
|
||||
const connColor = _getSourceColor(ds.sourceType);
|
||||
const connNode = tree.find(n => n.connectionId === ds.connectionId);
|
||||
const connLabel = connNode?.label || ds.connectionId;
|
||||
|
|
@ -751,7 +783,7 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ context }) => {
|
|||
<div style={{ fontSize: 11, fontWeight: 600, color: '#666', textTransform: 'uppercase', marginBottom: 4 }}>
|
||||
Active Feature Sources
|
||||
</div>
|
||||
{featureDataSources.map(fds => {
|
||||
{[...featureDataSources].sort((a, b) => (a.label || a.tableName || '').localeCompare(b.label || b.tableName || '')).map(fds => {
|
||||
const meta = _findFeatureInstanceMeta(featureTree, fds.featureInstanceId);
|
||||
const fdsConnLabel = meta?.instanceLabel || fds.tableName;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ interface UnifiedDataBarProps {
|
|||
onDeleteChat?: (chatId: string) => void;
|
||||
onChatDragStart?: (chatId: string, event: React.DragEvent) => void;
|
||||
onFileSelect?: (fileId: string) => void;
|
||||
onSourcesChanged?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
|
|||
onDeleteChat,
|
||||
onChatDragStart,
|
||||
onFileSelect,
|
||||
onSourcesChanged,
|
||||
className,
|
||||
}) => {
|
||||
const visibleTabs = (['chats', 'files', 'sources'] as UdbTab[]).filter(
|
||||
|
|
@ -91,7 +93,7 @@ const UnifiedDataBar: React.FC<UnifiedDataBarProps> = ({
|
|||
/>
|
||||
)}
|
||||
{currentTab === 'sources' && !hideTabs?.includes('sources') && (
|
||||
<SourcesTab context={context} />
|
||||
<SourcesTab context={context} onSourcesChanged={onSourcesChanged} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -543,7 +543,7 @@ export const WorkspaceInput: React.FC<WorkspaceInputProps> = ({
|
|||
{uploading ? '...' : '+'}
|
||||
</button>
|
||||
|
||||
{dataSources.length > 0 && (
|
||||
{(dataSources.length > 0 || featureDataSources.length > 0) && (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<button
|
||||
onClick={() => setShowSourcePicker(prev => !prev)}
|
||||
|
|
|
|||
|
|
@ -254,6 +254,11 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
featureInstanceId: instanceId,
|
||||
};
|
||||
|
||||
const _handleSourcesChanged = useCallback(() => {
|
||||
workspace.refreshDataSources();
|
||||
workspace.refreshFeatureDataSources();
|
||||
}, [workspace]);
|
||||
|
||||
const _leftPanelBody = (
|
||||
<UnifiedDataBar
|
||||
context={_udbContext}
|
||||
|
|
@ -265,6 +270,7 @@ export const WorkspacePage: React.FC<WorkspacePageProps> = ({ persistentInstance
|
|||
onRenameChat={_handleRenameChat}
|
||||
onDeleteChat={_handleDeleteChat}
|
||||
onFileSelect={_handleFileSelect}
|
||||
onSourcesChanged={_handleSourcesChanged}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue