Link Explorer

I think it would be wonderful to have a Link Explorer plugin inspired by Tagfolder. I gave the beta plugin ‘datacore’ a try, but since I don’t have any coding experience, I had to rely entirely on AI.

I’m wondering if anyone else feels there’s a need for a plugin like this.

I don’t know much about coding, so I’m not sure how helpful this will be, but here’s a code snippet generated by AI that I used with the Datacore plugin:

```datacorejsx

const FOLDER_TYPES = {
    TASKS: "_tasks",
    ALL: "_all",
    BOARD: "_board",
    ISOLATED: "_isolated", 
    ATTACHMENTS: "_attachments"
};

const FOLDER_TYPE_LABELS = {
    [FOLDER_TYPES.TASKS]: "Task Files",
    [FOLDER_TYPES.ALL]: "All Files",
    [FOLDER_TYPES.BOARD]: "Whiteboard Files",
    [FOLDER_TYPES.ISOLATED]: "Isolated Files",
    [FOLDER_TYPES.ATTACHMENTS]: "Attachment Files"
};

const TASK_CATEGORIES = {
    URGENT_IMPORTANT: {
        tags: ["#_urgent", "#_important"],
        icon: "🔥",
        priority: 1
    },
    URGENT: {
        tags: ["#_urgent"],
        icon: "⚠️",
        priority: 2
    },
    IMPORTANT: {
        tags: ["#_important"],
        icon: "❗",
        priority: 3
    },
    NORMAL: {
        tags: ["#_chores"],
        icon: "📋",
        priority: 4
    }
};

const FILTER_TYPES = {
    ALL: "all",
    LINKS: "links",
    BACKLINKS: "backlinks",
};

const FILTER_TYPE_ICONS = {
    [FILTER_TYPES.ALL]: "🔛",
    [FILTER_TYPES.LINKS]: "🔜",
    [FILTER_TYPES.BACKLINKS]: "🔙",
};

let connectionsCache = null;
let lastCacheTime = 0;
const CACHE_LIFETIME = 30000;
const MAX_TEXT_LENGTH = 40;

const styleTokens = {
    spacing: {
        gap: "var(--size-4-1)",
        padding: "var(--size-4-2)"
    },
    border: {
        default: "1px solid var(--background-modifier-border)",
        dashed: "1px dashed var(--background-modifier-border)"
    },
    typography: {
        uiSmall: "var(--font-ui-small)",
        uiMedium: "var(--font-ui-medium)",
        uiSmaller: "var(--font-ui-smaller)"
    },
    radius: {
        none: "0",
        small: "2px"
    },
    colors: {
        muted: "var(--text-muted)",
        normal: "var(--text-normal)",
        accent: "var(--text-accent)",
        error: "var(--text-error)",
        onAccent: "var(--text-on-accent)",
        bgHover: "var(--background-modifier-hover)",
        bgBorder: "var(--background-modifier-border)",
        interactiveAccent: "var(--interactive-accent)",
        interactiveAccentHover: "var(--interactive-accent-hover)"
    }
};

const createUtilities = () => {
    const pathUtils = {
        removeMarkdownExt: (path) => {
            return path.endsWith('.md') ? path.slice(0, -3) : path;
        },
        
        truncateText: (text, maxLength = MAX_TEXT_LENGTH) => {
            if (!text) return '';
            if (text.length <= maxLength) return text;
            return text.slice(0, maxLength) + '…';
        },
        
        getFileName: (path) => {
            return path.split('/').pop();
        },
        
        isValidFilePath: (path) => {
            return path && path !== '/' && path.trim() !== '';
        }
    };
    
    const safeOpenInternalLink = (linkPath) => {
        try {
            dc.app.workspace.openLinkText(linkPath, '', false);
        } catch (error) {
            console.error("Internal link error:", { linkPath, error });
            dc.notice?.show?.("Cannot open internal link.");
        }
    };
    
    const withHoverEffect = (baseStyle) => ({
        ...baseStyle,
        transition: "all 0.2s ease",
        "&:hover": {
            backgroundColor: styleTokens.colors.bgHover,
            transform: "scale(1.01)"
        }
    });

const fileTypeUtils = {
    isMarkdownFile: (file) => file.extension === 'md' || file.extension === 'markdown',
    isBoardFile: (file) => file.extension === 'canvas' || file.extension === 'excalidraw',
    isAttachmentFile: (file) => {
        if (!fileTypeUtils.isMarkdownFile(file) && !fileTypeUtils.isBoardFile(file)) {
            if (file.path.startsWith('.obsidian/')) {
                return file.extension === 'css';
            }
            return true;
        }
        return false;
    },
    shouldIncludeFile: (file) => {

        if (file.children) return false;
        
        if (file.path.startsWith('.obsidian/')) {
            return file.extension === 'css';
        }
        return true;
    }
};

    const hasTagsInFile = (file, tags) => {
        if (!fileTypeUtils.isMarkdownFile(file)) return false;
        
        const cache = dc.app.metadataCache.getFileCache(file);
        if (!cache || !cache.tags) return false;
        
        const fileTags = cache.tags.map(tag => tag.tag.toLowerCase());
        return tags.every(tag => fileTags.includes(tag.toLowerCase()));
    };

    const getAllConnectionsData = () => {
        const currentTime = Date.now();
        
        if (connectionsCache && (currentTime - lastCacheTime < CACHE_LIFETIME)) {
            return connectionsCache;
        }
        
        const connections = new Map();
        const fileConnections = new Map();
        const taskStats = {
            urgentImportant: 0,
            urgent: 0,
            important: 0,
            normal: 0,
            total: 0
        };
        
        Object.values(FOLDER_TYPES).forEach(type => {
            fileConnections.set(type, []);
        });

        const allFiles = dc.app.vault.getAllLoadedFiles().filter(file => 
            fileTypeUtils.shouldIncludeFile(file)
        );
        
        const markdownFiles = dc.app.vault.getMarkdownFiles();
        
        allFiles.forEach(file => {
            if (!pathUtils.isValidFilePath(file.path)) return;
            
            fileConnections.get(FOLDER_TYPES.ALL).push(file.path);
            
            if (fileTypeUtils.isBoardFile(file)) {
                fileConnections.get(FOLDER_TYPES.BOARD).push(file.path);
            } else if (fileTypeUtils.isAttachmentFile(file)) {
                fileConnections.get(FOLDER_TYPES.ATTACHMENTS).push(file.path);
            }
        });

        markdownFiles.forEach(file => {
            if (hasTagsInFile(file, TASK_CATEGORIES.URGENT_IMPORTANT.tags)) {
                fileConnections.get(FOLDER_TYPES.TASKS).push(file.path);
                taskStats.urgentImportant++;
                taskStats.total++;
            } 
            else if (hasTagsInFile(file, TASK_CATEGORIES.URGENT.tags)) {
                fileConnections.get(FOLDER_TYPES.TASKS).push(file.path);
                taskStats.urgent++;
                taskStats.total++;
            } 
            else if (hasTagsInFile(file, TASK_CATEGORIES.IMPORTANT.tags)) {
                fileConnections.get(FOLDER_TYPES.TASKS).push(file.path);
                taskStats.important++;
                taskStats.total++;
            } 
            else if (hasTagsInFile(file, TASK_CATEGORIES.NORMAL.tags)) {
                fileConnections.get(FOLDER_TYPES.TASKS).push(file.path);
                taskStats.normal++;
                taskStats.total++;
            }
        });

        markdownFiles.forEach(file => {
            connections.set(file.path, {
                links: new Set(),
                backlinks: new Set(),
            });
        });
        
        markdownFiles.forEach(file => {
            const cache = dc.app.metadataCache.getFileCache(file);
            const fileLinks = cache?.links || [];
            
            fileLinks.forEach(link => {
                const linkTarget = link.link;
                const targetFile = dc.app.metadataCache.getFirstLinkpathDest(linkTarget, '');
                
                if (targetFile) {
                    connections.get(file.path).links.add(targetFile.path);
                    
                    if (connections.has(targetFile.path)) {
                        connections.get(targetFile.path).backlinks.add(file.path);
                    }
                }
            });
        });

        markdownFiles.forEach(file => {
            const fileData = connections.get(file.path);
            const hasLinks = fileData.links.size > 0;
            const hasBacklinks = fileData.backlinks.size > 0;

            if (!hasLinks && !hasBacklinks) {
                fileConnections.get(FOLDER_TYPES.ISOLATED).push(file.path);
                return;
            }

            const allConnections = new Set([...fileData.links, ...fileData.backlinks]);
            
            allConnections.forEach(connectedFile => {
                if (!fileConnections.has(connectedFile)) {
                    fileConnections.set(connectedFile, []);
                }
                
                const connectedFiles = fileConnections.get(connectedFile);
                if (!connectedFiles.includes(file.path)) {
                    connectedFiles.push(file.path);
                }
            });
        });
        
        connectionsCache = { connections, fileConnections, taskStats };
        lastCacheTime = currentTime;
        
        return connectionsCache;
    };

    const hasExistingLink = (linkTarget) => {
        const targetFile = dc.app.metadataCache.getFirstLinkpathDest(linkTarget, '');
        return !!targetFile;
    };
    
    const sortFiles = (files) => {
        return [...files].sort((a, b) => {
            const aFile = dc.app.vault.getAbstractFileByPath(a);
            const bFile = dc.app.vault.getAbstractFileByPath(b);
            
            if (aFile && bFile) {
                const aPriority = getTaskPriority(aFile);
                const bPriority = getTaskPriority(bFile);
                
                if (aPriority !== bPriority) {
                    return aPriority - bPriority;
                }
            }
            
            const fileNameA = pathUtils.getFileName(a);
            const fileNameB = pathUtils.getFileName(b);
            return fileNameA.localeCompare(fileNameB);
        });
    };
    
    const getTaskPriority = (file) => {
        if (!fileTypeUtils.isMarkdownFile(file)) return 999;
        
        if (hasTagsInFile(file, TASK_CATEGORIES.URGENT_IMPORTANT.tags)) {
            return TASK_CATEGORIES.URGENT_IMPORTANT.priority;
        } else if (hasTagsInFile(file, TASK_CATEGORIES.URGENT.tags)) {
            return TASK_CATEGORIES.URGENT.priority;
        } else if (hasTagsInFile(file, TASK_CATEGORIES.IMPORTANT.tags)) {
            return TASK_CATEGORIES.IMPORTANT.priority;
        } else if (hasTagsInFile(file, TASK_CATEGORIES.NORMAL.tags)) {
            return TASK_CATEGORIES.NORMAL.priority;
        }
        
        return 999;
    };
    
    const getTaskIcon = (file) => {
        if (!fileTypeUtils.isMarkdownFile(file)) return "";
        
        if (hasTagsInFile(file, TASK_CATEGORIES.URGENT_IMPORTANT.tags)) {
            return TASK_CATEGORIES.URGENT_IMPORTANT.icon;
        } else if (hasTagsInFile(file, TASK_CATEGORIES.URGENT.tags)) {
            return TASK_CATEGORIES.URGENT.icon;
        } else if (hasTagsInFile(file, TASK_CATEGORIES.IMPORTANT.tags)) {
            return TASK_CATEGORIES.IMPORTANT.icon;
        } else if (hasTagsInFile(file, TASK_CATEGORIES.NORMAL.tags)) {
            return TASK_CATEGORIES.NORMAL.icon;
        }
        
        return "";
    };

    return {
        pathUtils,
        fileTypeUtils,
        safeOpenInternalLink,
        withHoverEffect,
        getAllConnectionsData,
        hasExistingLink,
        sortFiles,
        hasTagsInFile,
        getTaskPriority,
        getTaskIcon
    };
};

const createStyles = (styleTokens) => {
    return {
        container: { 
            width: "100%", 
            display: "flex", 
            height: "100%", 
            overflow: "hidden" 
        },
        leftPanel: {
            width: "45%",
            borderRight: styleTokens.border.default,
            overflowY: "auto",
            display: "flex",
            flexDirection: "column",
            padding: styleTokens.spacing.padding
        },
        rightPanel: {
            width: "55%",
            overflowY: "auto",
            padding: styleTokens.spacing.padding
        },
        header: {
            fontSize: styleTokens.typography.uiMedium,
            fontWeight: "var(--font-medium)",
            marginBottom: styleTokens.spacing.padding,
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center"
        },
        tabContainer: {
            display: "flex",
            borderBottom: styleTokens.border.default,
            marginBottom: styleTokens.spacing.padding,
        },
        tab: {
            padding: "8px 16px",
            fontSize: styleTokens.typography.uiSmall,
            cursor: "pointer",
            backgroundColor: "transparent",
            border: "none",
            borderBottom: "2px solid transparent",
            color: styleTokens.colors.muted,
            transition: "all 0.2s ease",
            borderRadius: styleTokens.radius.none,
            "&:hover": {
                color: styleTokens.colors.normal,
                borderBottom: `2px solid ${styleTokens.colors.bgBorder}`
            }
        },
        activeTab: {
            color: styleTokens.colors.accent,
            borderBottom: `2px solid ${styleTokens.colors.interactiveAccent}`,
            fontWeight: "var(--font-medium)",
            "&:hover": {
                borderBottom: `2px solid ${styleTokens.colors.interactiveAccent}`
            }
        },
        connectionCount: {
            fontSize: styleTokens.typography.uiSmaller,
            color: styleTokens.colors.muted,
            backgroundColor: styleTokens.colors.bgBorder,
            padding: "2px 6px",
            borderRadius: styleTokens.radius.small,
            marginLeft: "8px"
        },
        selectedConnectionCount: {
            color: styleTokens.colors.onAccent,
            backgroundColor: styleTokens.colors.interactiveAccentHover
        },
        nonExistingLink: {
            color: styleTokens.colors.error,
            fontStyle: "italic"
        },
        multiSelectHint: {
            fontSize: styleTokens.typography.uiSmaller,
            color: styleTokens.colors.muted,
            marginBottom: styleTokens.spacing.gap
        },
        textContainer: {
            overflow: "hidden",
            textOverflow: "ellipsis",
            whiteSpace: "nowrap",
            flex: 1
        },
        sectionHeader: {
            fontSize: styleTokens.typography.uiSmall,
            fontWeight: "var(--font-medium)",
            color: styleTokens.colors.muted,
            marginTop: styleTokens.spacing.padding,
            marginBottom: styleTokens.spacing.gap,
            borderBottom: styleTokens.border.dashed,
            paddingBottom: "4px",
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center"
        },
        icon: {
            marginRight: "4px",
            opacity: 0.7,
            fontSize: styleTokens.typography.uiSmaller
        },
        listContainer: {
            marginBottom: styleTokens.spacing.padding,
        },
        emptyListMessage: {
            color: styleTokens.colors.muted,
            fontSize: styleTokens.typography.uiSmall,
            fontStyle: "italic",
            padding: styleTokens.spacing.gap
        },
        taskIcon: {
            marginRight: "6px",
            fontSize: styleTokens.typography.uiSmall
        },
        priorityTaskCount: {
            color: styleTokens.colors.accent,
            fontWeight: "var(--font-medium)"
        }
    };
};

const createItemStyles = (styleTokens, withHoverEffect) => {
    const baseItemStyle = {
        padding: "6px 10px",
        marginBottom: "2px",
        backgroundColor: "transparent",
        color: styleTokens.colors.normal,
        cursor: "pointer",
        fontSize: styleTokens.typography.uiSmall,
        borderRadius: styleTokens.radius.none,
        borderLeft: "2px solid transparent"
    };
    
    return {
        connectionListItem: withHoverEffect({
            ...baseItemStyle,
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center"
        }),
        selectedConnectionListItem: {
            backgroundColor: styleTokens.colors.bgHover,
            borderLeft: `2px solid ${styleTokens.colors.interactiveAccent}`,
            fontWeight: "var(--font-medium)"
        },
        fileListItem: withHoverEffect({
            ...baseItemStyle,
            display: "flex",
            alignItems: "center"
        })
    };
};

const createExplorerComponents = (utilities, styles, itemStyles) => {
    const { pathUtils, hasExistingLink, getTaskIcon } = utilities;
    
    const renderConnection = (connection, files, handleConnectionClick, isSelected = false, taskStats = null) => {
        let connectionText = connection.startsWith("_")
            ? FOLDER_TYPE_LABELS[connection] || connection
            : pathUtils.truncateText(pathUtils.removeMarkdownExt(connection));
        
        if (connection === FOLDER_TYPES.TASKS && taskStats) {
            connectionText = 'Task Files';
        }
        
        const isNonExisting = !connection.startsWith("_") && !hasExistingLink(connection);
        
        const itemStyle = Object.assign({},
            itemStyles.connectionListItem,
            isSelected ? itemStyles.selectedConnectionListItem : {},
            isNonExisting ? styles.nonExistingLink : {}
        );
        
        const countStyle = Object.assign({},
            styles.connectionCount,
            isSelected ? styles.selectedConnectionCount : {}
        );

        const connectionCount = (connection === FOLDER_TYPES.TASKS && taskStats) 
            ? `${taskStats.urgentImportant + taskStats.urgent + taskStats.important} (${taskStats.total})`
            : files.length;

        return (
            <div
                key={connection}
                onClick={e => handleConnectionClick(connection, e)}
                title={pathUtils.removeMarkdownExt(connection)}
                style={itemStyle}
            >
                <span style={styles.textContainer}>
                    {connectionText}
                </span>
                <span style={countStyle}>{connectionCount}</span>
            </div>
        );
    };

    const renderFile = (file, handleFileClick, selectedConnection) => {
        const isTaskView = selectedConnection === FOLDER_TYPES.TASKS;
        const icon = isTaskView ? getTaskIcon(dc.app.vault.getAbstractFileByPath(file)) : "";
        
        return (
            <div
                key={file}
                onClick={() => handleFileClick(file)}
                title={pathUtils.removeMarkdownExt(file)}
                style={itemStyles.fileListItem}
            >
                {icon && <span style={styles.taskIcon}>{icon}</span>}
                <span style={{marginRight: icon ? "0" : "4px"}}>{!icon && "•"}</span>
                <span style={styles.textContainer}>
                    {pathUtils.truncateText(pathUtils.removeMarkdownExt(file))}
                </span>
            </div>
        );
    };
    
    const renderTabs = (connectionType, handleFilterChange) => {
        return (
            <div style={styles.tabContainer}>
                {Object.entries(FILTER_TYPE_ICONS).map(([type, icon]) => (
                    <button
                        key={type}
                        style={Object.assign({},
                            styles.tab,
                            connectionType === type ? styles.activeTab : {}
                        )}
                        onClick={() => handleFilterChange(type)}
                    >
                        {icon}
                    </button>
                ))}
            </div>
        );
    };
    
    const renderSection = (title, count = null, children) => {
        return (
            <>
                <div style={styles.sectionHeader}>
                    {title} {count !== null && `(${count})`}
                </div>
                <div style={styles.listContainer}>
                    {children}
                </div>
            </>
        );
    };
    
    const renderEmptyMessage = (message) => {
        return (
            <div style={styles.emptyListMessage}>{message}</div>
        );
    };
    
    return {
        renderConnection,
        renderFile,
        renderTabs,
        renderSection,
        renderEmptyMessage
    };
};

const { useState, useEffect, useMemo, useCallback } = dc;

return function Explorer() {
    const utilities = createUtilities();
    const { pathUtils, sortFiles, safeOpenInternalLink, withHoverEffect } = utilities;

    const styles = createStyles(styleTokens);
    const itemStyles = createItemStyles(styleTokens, withHoverEffect);
    
    const components = createExplorerComponents(
        utilities,
        styles, 
        itemStyles
    );
    
    const [connectionsData, setConnectionsData] = useState({ 
        connections: new Map(), 
        fileConnections: new Map(),
        taskStats: {
            urgentImportant: 0,
            urgent: 0,
            important: 0,
            normal: 0,
            total: 0
        }
    });
    const [selectedConnections, setSelectedConnections] = useState(new Set()); 
    const [relatedFiles, setRelatedFiles] = useState([]);
    const [connectionType, setConnectionType] = useState(FILTER_TYPES.ALL);
    
    useEffect(() => {
        const data = utilities.getAllConnectionsData();
        setConnectionsData(data);
        if (data.fileConnections.size > 0) {
            setSelectedConnections(new Set([FOLDER_TYPES.TASKS]));
        }
        
        const eventRef = dc.app.metadataCache.on('changed', () => {
            const updatedData = utilities.getAllConnectionsData();
            setConnectionsData(updatedData);
        });
        
        return () => dc.app.metadataCache.offref(eventRef);
    }, []);

    useEffect(() => {
        if (selectedConnections.size === 0) {
            setRelatedFiles([]);
            return;
        }
        
        let finalRelatedFiles = null;
        
        selectedConnections.forEach(connection => {
            let filesForThisConnection = [];
            
            if (connection.startsWith("_")) {
                filesForThisConnection = connectionsData.fileConnections.get(connection) || [];
            } else if (connectionsData.fileConnections.has(connection)) {
                const files = connectionsData.fileConnections.get(connection) || [];
                filesForThisConnection = files.filter(file => {
                    if (connectionType === FILTER_TYPES.ALL) return true;
                    
                    const fileData = connectionsData.connections.get(file);
                    if (!fileData) return false;
                    
                    if (connectionType === FILTER_TYPES.LINKS && fileData.links.has(connection)) return true;
                    if (connectionType === FILTER_TYPES.BACKLINKS && fileData.backlinks.has(connection)) return true;
                    
                    return false;
                });
            }
            
            if (finalRelatedFiles === null) {
                finalRelatedFiles = new Set(filesForThisConnection);
            } else {
                finalRelatedFiles = new Set(
                    [...finalRelatedFiles].filter(file => filesForThisConnection.includes(file))
                );
            }
        });
        
        const sortedFiles = finalRelatedFiles 
            ? selectedConnections.has(FOLDER_TYPES.TASKS)
                ? sortFiles([...finalRelatedFiles])
                : sortFiles(finalRelatedFiles)
            : [];
            
        const selectedConnectionsArray = Array.from(selectedConnections);
        const firstSelectedConnection = selectedConnectionsArray[0];
        
        if (selectedConnectionsArray.length === 1 && !firstSelectedConnection.startsWith("_")) {
            setRelatedFiles([firstSelectedConnection, ...sortedFiles.filter(file => file !== firstSelectedConnection)]);
        } else {
            setRelatedFiles(sortedFiles);
        }
    }, [selectedConnections, connectionType, connectionsData]);

    const handleConnectionClick = useCallback((connection, event) => {
        setSelectedConnections(prev => {
            const next = new Set(prev);
            
            if (event.ctrlKey || event.metaKey) {
                next.has(connection) ? next.delete(connection) : next.add(connection);
            } else {
                next.clear();
                next.add(connection);
            }
            
            if (next.size === 0 && connectionsData.fileConnections.size > 0) {
                next.add(FOLDER_TYPES.ALL);
            }
            
            return next;
        });
    }, [connectionsData]);

    const handleFileClick = useCallback(filePath => {
        safeOpenInternalLink(filePath);
    }, []);

    const handleFilterChange = useCallback(type => {
        setConnectionType(type);
    }, []);

    const sortedConnections = useMemo(() => {
        return Array.from(connectionsData.fileConnections.entries()).sort((a, b) => {
            const specialFolders = {
                [FOLDER_TYPES.TASKS]: 0,
                [FOLDER_TYPES.ALL]: 1,
                [FOLDER_TYPES.BOARD]: 2,
                [FOLDER_TYPES.ISOLATED]: 3,
                [FOLDER_TYPES.ATTACHMENTS]: 4
            };
            
            if (a[0] in specialFolders && b[0] in specialFolders) {
                return specialFolders[a[0]] - specialFolders[b[0]];
            }
            if (a[0] in specialFolders) return -1;
            if (b[0] in specialFolders) return 1;
            
            return a[0].localeCompare(b[0]);
        });
    }, [connectionsData.fileConnections]);

    const groupedConnections = useMemo(() => {
        const groups = {
            pinned: [],
            selected: [],
            others: []
        };

        sortedConnections.forEach(([connection, files]) => {
            if (connection.startsWith("_")) {
                groups.pinned.push([connection, files]);
            } 
            else if (files.length === 0) {
                return;
            }
            else if (selectedConnections.has(connection)) {
                groups.selected.push([connection, files]);
            } 
            else {
                groups.others.push([connection, files]);
            }
        });
        
        return groups;
    }, [sortedConnections, selectedConnections]);

    const firstSelectedConnection = useMemo(() => {
        return selectedConnections.size > 0 ? Array.from(selectedConnections)[0] : null;
    }, [selectedConnections]);

    const isTaskFolderSelected = useMemo(() => {
        return firstSelectedConnection === FOLDER_TYPES.TASKS;
    }, [firstSelectedConnection]);

    return (
        <div style={styles.container}>
            <div style={styles.leftPanel}>
                <div style={styles.header}>
                    <span>Folders</span>
                </div>
                
                <div style={styles.multiSelectHint}>
                    Ctrl/Cmd + Click to select multiple connections
                </div>
                
                {groupedConnections.pinned.length > 0 && components.renderSection("Quick Access", groupedConnections.pinned.length, 
                    groupedConnections.pinned.map(([connection, files]) => 
                        components.renderConnection(
                            connection, 
                            files, 
                            handleConnectionClick, 
                            selectedConnections.has(connection),
                            connection === FOLDER_TYPES.TASKS ? connectionsData.taskStats : null
                        )
                    )
                )}
                
                {groupedConnections.selected.length > 0 && selectedConnections.size > 0 && components.renderSection("Selected", groupedConnections.selected.length, 
                    groupedConnections.selected.map(([connection, files]) => 
                        components.renderConnection(
                            connection, 
                            files, 
                            handleConnectionClick, 
                            true
                        )
                    )
                )}
                
                {groupedConnections.others.length > 0 && components.renderSection("All Connections", groupedConnections.others.length, 
                    groupedConnections.others.map(([connection, files]) => 
                        components.renderConnection(
                            connection, 
                            files, 
                            handleConnectionClick
                        )
                    )
                )}
            </div>
            
            <div style={styles.rightPanel}>
                <div style={styles.header}>
                    <span>
                        {'Files'}
                    </span>
                    <span>
                    {components.renderTabs(connectionType, handleFilterChange)}
                    </span>
                </div>
                
                {relatedFiles.length === 0 ? (
                    components.renderEmptyMessage("No related files found")
                ) : (
                    relatedFiles.map(file => components.renderFile(file, handleFileClick, firstSelectedConnection))
                )}
            </div>
        </div>
    );
}
```