@@ -583,317 +583,34 @@ const TreeVisualizer: React.FC<TreeVisualizerProps> = ({ |
| 583 | .attr('height', bbox.height + LABEL_CONFIG.background.padding.y * 2); | 583 | .attr('height', bbox.height + LABEL_CONFIG.background.padding.y * 2); |
| 584 | }); | 584 | }); |
| 585 | | 585 | |
| 586 | - // Add minimal SVG icons for special directories | 586 | + // Add icon images for special directories |
| 587 | node.each(function(d) { | 587 | node.each(function(d) { |
| 588 | const nodeEl = d3.select(this); | 588 | const nodeEl = d3.select(this); |
| 589 | - const iconSize = 24; | 589 | + let iconPath = null; |
| 590 | - const iconOffset = -iconSize / 2; | | |
| 591 | | 590 | |
| 592 | - // Create icon group | 591 | + // Map paths to icon files |
| 593 | - const iconGroup = nodeEl.append('g') | 592 | + if (d.data.path === '/home') iconPath = '/icons/home.svg'; |
| 594 | - .attr('class', 'directory-icon') | 593 | + else if (d.data.path === '/tmp') iconPath = '/icons/trash.svg'; |
| 595 | - .style('pointer-events', 'none'); | 594 | + else if (d.data.path === '/etc') iconPath = '/icons/config.svg'; |
| | 595 | + else if (d.data.path === '/bin' || d.data.path === '/sbin') iconPath = '/icons/terminal.svg'; |
| | 596 | + else if (d.data.path === '/var') iconPath = '/icons/database.svg'; |
| | 597 | + else if (d.data.path === '/usr') iconPath = '/icons/folder.svg'; |
| | 598 | + else if (d.data.path === '/opt') iconPath = '/icons/package.svg'; |
| | 599 | + else if (d.data.path.includes('Documents')) iconPath = '/icons/document.svg'; |
| | 600 | + else if (d.data.path.includes('Pictures')) iconPath = '/icons/picture.svg'; |
| | 601 | + else if (d.data.path.includes('Downloads')) iconPath = '/icons/download.svg'; |
| | 602 | + else if (d.data.path.includes('Desktop')) iconPath = '/icons/desktop.svg'; |
| 596 | | 603 | |
| 597 | - if (d.data.path === '/home') { | 604 | + if (iconPath) { |
| 598 | - // Simple house icon | 605 | + const iconSize = 24; |
| 599 | - iconGroup.append('path') | 606 | + nodeEl.append('image') |
| 600 | - .attr('d', `M ${iconOffset} 5 L 0 ${iconOffset + 5} L ${iconSize/2} 5 L ${iconSize/2} ${iconSize/2 - 2} L ${iconOffset} ${iconSize/2 - 2} Z`) | 607 | + .attr('class', 'directory-icon') |
| 601 | - .attr('fill', '#F59E0B') | 608 | + .attr('xlink:href', iconPath) |
| 602 | - .attr('stroke', '#92400E') | 609 | + .attr('width', iconSize) |
| 603 | - .attr('stroke-width', 1.5); | 610 | + .attr('height', iconSize) |
| 604 | - | 611 | + .attr('x', -iconSize / 2) |
| 605 | - iconGroup.append('rect') | 612 | + .attr('y', -iconSize / 2) |
| 606 | - .attr('x', iconOffset + 8) | 613 | + .style('pointer-events', 'none') |
| 607 | - .attr('y', 5) | | |
| 608 | - .attr('width', 8) | | |
| 609 | - .attr('height', 10) | | |
| 610 | - .attr('fill', '#92400E'); | | |
| 611 | - | | |
| 612 | - } else if (d.data.path === '/tmp') { | | |
| 613 | - // Simple trash can icon | | |
| 614 | - iconGroup.append('rect') | | |
| 615 | - .attr('x', iconOffset + 4) | | |
| 616 | - .attr('y', iconOffset + 8) | | |
| 617 | - .attr('width', 16) | | |
| 618 | - .attr('height', 14) | | |
| 619 | - .attr('rx', 1) | | |
| 620 | - .attr('fill', '#6B7280') | | |
| 621 | - .attr('stroke', '#374151') | | |
| 622 | - .attr('stroke-width', 1.5); | | |
| 623 | - | | |
| 624 | - iconGroup.append('rect') | | |
| 625 | - .attr('x', iconOffset + 2) | | |
| 626 | - .attr('y', iconOffset + 6) | | |
| 627 | - .attr('width', 20) | | |
| 628 | - .attr('height', 3) | | |
| 629 | - .attr('fill', '#374151'); | | |
| 630 | - | | |
| 631 | - iconGroup.append('line') | | |
| 632 | - .attr('x1', iconOffset + 8) | | |
| 633 | - .attr('y1', iconOffset + 12) | | |
| 634 | - .attr('x2', iconOffset + 8) | | |
| 635 | - .attr('y2', iconOffset + 18) | | |
| 636 | - .attr('stroke', '#9CA3AF') | | |
| 637 | - .attr('stroke-width', 1); | | |
| 638 | - | | |
| 639 | - iconGroup.append('line') | | |
| 640 | - .attr('x1', iconOffset + 12) | | |
| 641 | - .attr('y1', iconOffset + 12) | | |
| 642 | - .attr('x2', iconOffset + 12) | | |
| 643 | - .attr('y2', iconOffset + 18) | | |
| 644 | - .attr('stroke', '#9CA3AF') | | |
| 645 | - .attr('stroke-width', 1); | | |
| 646 | - | | |
| 647 | - iconGroup.append('line') | | |
| 648 | - .attr('x1', iconOffset + 16) | | |
| 649 | - .attr('y1', iconOffset + 12) | | |
| 650 | - .attr('x2', iconOffset + 16) | | |
| 651 | - .attr('y2', iconOffset + 18) | | |
| 652 | - .attr('stroke', '#9CA3AF') | | |
| 653 | - .attr('stroke-width', 1); | | |
| 654 | - | | |
| 655 | - } else if (d.data.path === '/etc') { | | |
| 656 | - // Simple gear/config icon | | |
| 657 | - iconGroup.append('circle') | | |
| 658 | - .attr('cx', iconOffset + 12) | | |
| 659 | - .attr('cy', iconOffset + 12) | | |
| 660 | - .attr('r', 8) | | |
| 661 | - .attr('fill', '#8B5CF6') | | |
| 662 | - .attr('stroke', '#6D28D9') | | |
| 663 | - .attr('stroke-width', 1.5); | | |
| 664 | - | | |
| 665 | - iconGroup.append('circle') | | |
| 666 | - .attr('cx', iconOffset + 12) | | |
| 667 | - .attr('cy', iconOffset + 12) | | |
| 668 | - .attr('r', 4) | | |
| 669 | - .attr('fill', '#DDD6FE'); | | |
| 670 | - | | |
| 671 | - // Gear teeth | | |
| 672 | - for (let i = 0; i < 8; i++) { | | |
| 673 | - const angle = (i * Math.PI * 2) / 8; | | |
| 674 | - const x1 = iconOffset + 12 + Math.cos(angle) * 6; | | |
| 675 | - const y1 = iconOffset + 12 + Math.sin(angle) * 6; | | |
| 676 | - const x2 = iconOffset + 12 + Math.cos(angle) * 9; | | |
| 677 | - const y2 = iconOffset + 12 + Math.sin(angle) * 9; | | |
| 678 | - | | |
| 679 | - iconGroup.append('line') | | |
| 680 | - .attr('x1', x1) | | |
| 681 | - .attr('y1', y1) | | |
| 682 | - .attr('x2', x2) | | |
| 683 | - .attr('y2', y2) | | |
| 684 | - .attr('stroke', '#6D28D9') | | |
| 685 | - .attr('stroke-width', 2) | | |
| 686 | - .attr('stroke-linecap', 'round'); | | |
| 687 | - } | | |
| 688 | - | | |
| 689 | - } else if (d.data.path === '/bin' || d.data.path === '/sbin') { | | |
| 690 | - // Simple terminal/binary icon | | |
| 691 | - iconGroup.append('rect') | | |
| 692 | - .attr('x', iconOffset + 4) | | |
| 693 | - .attr('y', iconOffset + 6) | | |
| 694 | - .attr('width', 16) | | |
| 695 | - .attr('height', 12) | | |
| 696 | - .attr('rx', 2) | | |
| 697 | - .attr('fill', '#1F2937') | | |
| 698 | - .attr('stroke', '#374151') | | |
| 699 | - .attr('stroke-width', 1.5); | | |
| 700 | - | | |
| 701 | - iconGroup.append('text') | | |
| 702 | - .attr('x', iconOffset + 12) | | |
| 703 | - .attr('y', iconOffset + 14) | | |
| 704 | - .attr('text-anchor', 'middle') | | |
| 705 | - .attr('fill', '#10B981') | | |
| 706 | - .attr('font-family', 'monospace') | | |
| 707 | - .attr('font-size', '10px') | | |
| 708 | - .attr('font-weight', 'bold') | | |
| 709 | - .text('>_'); | | |
| 710 | - | | |
| 711 | - } else if (d.data.path === '/var') { | | |
| 712 | - // Simple database/variable icon | | |
| 713 | - iconGroup.append('ellipse') | | |
| 714 | - .attr('cx', iconOffset + 12) | | |
| 715 | - .attr('cy', iconOffset + 8) | | |
| 716 | - .attr('rx', 8) | | |
| 717 | - .attr('ry', 3) | | |
| 718 | - .attr('fill', '#F59E0B') | | |
| 719 | - .attr('stroke', '#D97706') | | |
| 720 | - .attr('stroke-width', 1.5); | | |
| 721 | - | | |
| 722 | - iconGroup.append('rect') | | |
| 723 | - .attr('x', iconOffset + 4) | | |
| 724 | - .attr('y', iconOffset + 8) | | |
| 725 | - .attr('width', 16) | | |
| 726 | - .attr('height', 8) | | |
| 727 | - .attr('fill', '#F59E0B'); | | |
| 728 | - | | |
| 729 | - iconGroup.append('ellipse') | | |
| 730 | - .attr('cx', iconOffset + 12) | | |
| 731 | - .attr('cy', iconOffset + 16) | | |
| 732 | - .attr('rx', 8) | | |
| 733 | - .attr('ry', 3) | | |
| 734 | - .attr('fill', '#FBBF24') | | |
| 735 | - .attr('stroke', '#D97706') | | |
| 736 | - .attr('stroke-width', 1.5); | | |
| 737 | - | | |
| 738 | - } else if (d.data.path === '/usr') { | | |
| 739 | - // Simple user/folder icon | | |
| 740 | - iconGroup.append('path') | | |
| 741 | - .attr('d', `M ${iconOffset + 4} ${iconOffset + 8} L ${iconOffset + 4} ${iconOffset + 18} L ${iconOffset + 20} ${iconOffset + 18} L ${iconOffset + 20} ${iconOffset + 10} L ${iconOffset + 18} ${iconOffset + 8} Z`) | | |
| 742 | - .attr('fill', '#3B82F6') | | |
| 743 | - .attr('stroke', '#2563EB') | | |
| 744 | - .attr('stroke-width', 1.5); | | |
| 745 | - | | |
| 746 | - iconGroup.append('path') | | |
| 747 | - .attr('d', `M ${iconOffset + 4} ${iconOffset + 8} L ${iconOffset + 10} ${iconOffset + 8} L ${iconOffset + 12} ${iconOffset + 10} L ${iconOffset + 18} ${iconOffset + 10}`) | | |
| 748 | - .attr('fill', 'none') | | |
| 749 | - .attr('stroke', '#2563EB') | | |
| 750 | - .attr('stroke-width', 1.5); | | |
| 751 | - | | |
| 752 | - } else if (d.data.path === '/opt') { | | |
| 753 | - // Simple package/box icon | | |
| 754 | - iconGroup.append('path') | | |
| 755 | - .attr('d', `M ${iconOffset + 4} ${iconOffset + 10} L ${iconOffset + 12} ${iconOffset + 6} L ${iconOffset + 20} ${iconOffset + 10} L ${iconOffset + 20} ${iconOffset + 18} L ${iconOffset + 12} ${iconOffset + 22} L ${iconOffset + 4} ${iconOffset + 18} Z`) | | |
| 756 | - .attr('fill', '#10B981') | | |
| 757 | - .attr('stroke', '#059669') | | |
| 758 | - .attr('stroke-width', 1.5); | | |
| 759 | - | | |
| 760 | - iconGroup.append('path') | | |
| 761 | - .attr('d', `M ${iconOffset + 4} ${iconOffset + 10} L ${iconOffset + 12} ${iconOffset + 14} L ${iconOffset + 20} ${iconOffset + 10}`) | | |
| 762 | - .attr('fill', 'none') | | |
| 763 | - .attr('stroke', '#059669') | | |
| 764 | - .attr('stroke-width', 1.5); | | |
| 765 | - | | |
| 766 | - iconGroup.append('line') | | |
| 767 | - .attr('x1', iconOffset + 12) | | |
| 768 | - .attr('y1', iconOffset + 14) | | |
| 769 | - .attr('x2', iconOffset + 12) | | |
| 770 | - .attr('y2', iconOffset + 22) | | |
| 771 | - .attr('stroke', '#059669') | | |
| 772 | - .attr('stroke-width', 1.5); | | |
| 773 | - | | |
| 774 | - } else if (d.data.path.includes('Documents')) { | | |
| 775 | - // Simple document icon | | |
| 776 | - iconGroup.append('rect') | | |
| 777 | - .attr('x', iconOffset + 6) | | |
| 778 | - .attr('y', iconOffset + 4) | | |
| 779 | - .attr('width', 12) | | |
| 780 | - .attr('height', 16) | | |
| 781 | - .attr('rx', 1) | | |
| 782 | - .attr('fill', '#E5E7EB') | | |
| 783 | - .attr('stroke', '#6B7280') | | |
| 784 | - .attr('stroke-width', 1.5); | | |
| 785 | - | | |
| 786 | - iconGroup.append('path') | | |
| 787 | - .attr('d', `M ${iconOffset + 12} ${iconOffset + 4} L ${iconOffset + 18} ${iconOffset + 4} L ${iconOffset + 18} ${iconOffset + 10}`) | | |
| 788 | - .attr('fill', '#D1D5DB') | | |
| 789 | - .attr('stroke', '#6B7280') | | |
| 790 | - .attr('stroke-width', 1.5); | | |
| 791 | - | | |
| 792 | - // Text lines | | |
| 793 | - iconGroup.append('line') | | |
| 794 | - .attr('x1', iconOffset + 9) | | |
| 795 | - .attr('y1', iconOffset + 12) | | |
| 796 | - .attr('x2', iconOffset + 15) | | |
| 797 | - .attr('y2', iconOffset + 12) | | |
| 798 | - .attr('stroke', '#6B7280') | | |
| 799 | - .attr('stroke-width', 1); | | |
| 800 | - | | |
| 801 | - iconGroup.append('line') | | |
| 802 | - .attr('x1', iconOffset + 9) | | |
| 803 | - .attr('y1', iconOffset + 15) | | |
| 804 | - .attr('x2', iconOffset + 15) | | |
| 805 | - .attr('y2', iconOffset + 15) | | |
| 806 | - .attr('stroke', '#6B7280') | | |
| 807 | - .attr('stroke-width', 1); | | |
| 808 | - | | |
| 809 | - } else if (d.data.path.includes('Pictures')) { | | |
| 810 | - // Simple picture frame icon | | |
| 811 | - iconGroup.append('rect') | | |
| 812 | - .attr('x', iconOffset + 4) | | |
| 813 | - .attr('y', iconOffset + 6) | | |
| 814 | - .attr('width', 16) | | |
| 815 | - .attr('height', 12) | | |
| 816 | - .attr('rx', 1) | | |
| 817 | - .attr('fill', '#DBEAFE') | | |
| 818 | - .attr('stroke', '#3B82F6') | | |
| 819 | - .attr('stroke-width', 1.5); | | |
| 820 | - | | |
| 821 | - // Mountain | | |
| 822 | - iconGroup.append('path') | | |
| 823 | - .attr('d', `M ${iconOffset + 4} ${iconOffset + 18} L ${iconOffset + 10} ${iconOffset + 10} L ${iconOffset + 14} ${iconOffset + 14} L ${iconOffset + 20} ${iconOffset + 8} L ${iconOffset + 20} ${iconOffset + 18} Z`) | | |
| 824 | - .attr('fill', '#60A5FA'); | | |
| 825 | - | | |
| 826 | - // Sun | | |
| 827 | - iconGroup.append('circle') | | |
| 828 | - .attr('cx', iconOffset + 15) | | |
| 829 | - .attr('cy', iconOffset + 10) | | |
| 830 | - .attr('r', 2) | | |
| 831 | - .attr('fill', '#FDE047'); | | |
| 832 | - | | |
| 833 | - } else if (d.data.path.includes('Downloads')) { | | |
| 834 | - // Simple download arrow icon | | |
| 835 | - iconGroup.append('rect') | | |
| 836 | - .attr('x', iconOffset + 4) | | |
| 837 | - .attr('y', iconOffset + 14) | | |
| 838 | - .attr('width', 16) | | |
| 839 | - .attr('height', 3) | | |
| 840 | - .attr('fill', '#10B981') | | |
| 841 | - .attr('stroke', '#059669') | | |
| 842 | - .attr('stroke-width', 1); | | |
| 843 | - | | |
| 844 | - iconGroup.append('rect') | | |
| 845 | - .attr('x', iconOffset + 11) | | |
| 846 | - .attr('y', iconOffset + 4) | | |
| 847 | - .attr('width', 2) | | |
| 848 | - .attr('height', 10) | | |
| 849 | - .attr('fill', '#10B981'); | | |
| 850 | - | | |
| 851 | - iconGroup.append('path') | | |
| 852 | - .attr('d', `M ${iconOffset + 8} ${iconOffset + 10} L ${iconOffset + 12} ${iconOffset + 14} L ${iconOffset + 16} ${iconOffset + 10}`) | | |
| 853 | - .attr('fill', 'none') | | |
| 854 | - .attr('stroke', '#10B981') | | |
| 855 | - .attr('stroke-width', 2) | | |
| 856 | - .attr('stroke-linecap', 'round') | | |
| 857 | - .attr('stroke-linejoin', 'round'); | | |
| 858 | - | | |
| 859 | - } else if (d.data.path.includes('Desktop')) { | | |
| 860 | - // Simple monitor icon | | |
| 861 | - iconGroup.append('rect') | | |
| 862 | - .attr('x', iconOffset + 3) | | |
| 863 | - .attr('y', iconOffset + 5) | | |
| 864 | - .attr('width', 18) | | |
| 865 | - .attr('height', 12) | | |
| 866 | - .attr('rx', 1) | | |
| 867 | - .attr('fill', '#1F2937') | | |
| 868 | - .attr('stroke', '#111827') | | |
| 869 | - .attr('stroke-width', 1.5); | | |
| 870 | - | | |
| 871 | - iconGroup.append('rect') | | |
| 872 | - .attr('x', iconOffset + 5) | | |
| 873 | - .attr('y', iconOffset + 7) | | |
| 874 | - .attr('width', 14) | | |
| 875 | - .attr('height', 8) | | |
| 876 | - .attr('fill', '#60A5FA'); | | |
| 877 | - | | |
| 878 | - iconGroup.append('rect') | | |
| 879 | - .attr('x', iconOffset + 10) | | |
| 880 | - .attr('y', iconOffset + 17) | | |
| 881 | - .attr('width', 4) | | |
| 882 | - .attr('height', 3) | | |
| 883 | - .attr('fill', '#374151'); | | |
| 884 | - | | |
| 885 | - iconGroup.append('rect') | | |
| 886 | - .attr('x', iconOffset + 7) | | |
| 887 | - .attr('y', iconOffset + 20) | | |
| 888 | - .attr('width', 10) | | |
| 889 | - .attr('height', 2) | | |
| 890 | - .attr('rx', 1) | | |
| 891 | - .attr('fill', '#374151'); | | |
| 892 | - } | | |
| 893 | - | | |
| 894 | - // Add subtle animation to icons | | |
| 895 | - if (iconGroup.node()?.hasChildNodes()) { | | |
| 896 | - iconGroup | | |
| 897 | .style('opacity', 0.8) | 614 | .style('opacity', 0.8) |
| 898 | .on('mouseover', function() { | 615 | .on('mouseover', function() { |
| 899 | d3.select(this) | 616 | d3.select(this) |