mirror of
https://github.com/adam-benyekkou/my_portfolio.git
synced 2026-01-16 04:30:08 +00:00
Total refactoring of file structure for better and simple organization
This commit is contained in:
0
src/app/pages/about/about.component.css
Normal file
0
src/app/pages/about/about.component.css
Normal file
58
src/app/pages/about/about.component.html
Normal file
58
src/app/pages/about/about.component.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<section class="border-b border-nier-border min-h-screen checkered-background p-6 lg:p-8 flex flex-col relative">
|
||||
|
||||
<!-- Header section with NieR styling -->
|
||||
<div class="mb-6 lg:mb-8">
|
||||
<app-section-title title="NEURAL PROFILE"/>
|
||||
<!-- Subtle divider line -->
|
||||
<div class="mt-3 h-px bg-nier-border opacity-50"></div>
|
||||
</div>
|
||||
|
||||
<!-- Main content grid -->
|
||||
<div class="flex-1 grid grid-cols-1 lg:grid-cols-12 gap-6 lg:gap-8">
|
||||
|
||||
<!-- Left panel - Neural Profile Tree -->
|
||||
<div class="lg:col-span-7 xl:col-span-6">
|
||||
<div class="h-full bg-nier-bg border-2 border-nier-border font-terminal text-nier-dark">
|
||||
<!-- Inner content container with proper padding -->
|
||||
<div class="h-full p-4 lg:p-6">
|
||||
<app-neural-profile-tree/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right panel - Video and status (responsive) -->
|
||||
<div class="lg:col-span-5 xl:col-span-6 flex flex-col gap-4 lg:gap-6">
|
||||
|
||||
<!-- Main video display -->
|
||||
<div class="flex-1 bg-nier-dark border-2 border-nier-accent">
|
||||
<div class="h-full min-h-[250px] sm:min-h-[300px] lg:min-h-[400px] xl:min-h-[500px] p-2">
|
||||
<div class="w-full h-full bg-nier-bg/10 border border-nier-border/50">
|
||||
<app-holo-video-container
|
||||
containerClasses="w-full h-full"
|
||||
videoSrc="video/cyber_skull.mp4"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status panel -->
|
||||
<div class="bg-nier-mid border-2 border-nier-border font-terminal">
|
||||
<div class="p-3 sm:p-4 lg:p-6">
|
||||
<!-- Status grid - responsive layout -->
|
||||
<div class="nier-grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 p-3">
|
||||
<div class="text-nier-dark">
|
||||
<div class="text-xs font-terminal-retro tracking-wider opacity-60 mb-1">STATUS</div>
|
||||
<div class="flex items-center gap-2 font-terminal text-sm">
|
||||
<div class="w-2 h-2 bg-nier-accent animate-pulse"></div>
|
||||
<span>NEURAL SCAN ACTIVE</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-nier-dark">
|
||||
<div class="text-xs font-terminal-retro tracking-wider opacity-60 mb-1">MODE</div>
|
||||
<div class="font-terminal text-sm">REAL-TIME ANALYSIS</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
16
src/app/pages/about/about.component.ts
Normal file
16
src/app/pages/about/about.component.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { SectionTitleComponent } from '../../components/section-title/section-title.component';
|
||||
import { HoloVideoContainerComponent } from '../../components/holo-video-container/holo-video-container.component';
|
||||
import { NeuralProfileTreeComponent } from './neural-profile-tree/neural-profile-tree.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about',
|
||||
imports: [
|
||||
SectionTitleComponent,
|
||||
HoloVideoContainerComponent,
|
||||
NeuralProfileTreeComponent,
|
||||
],
|
||||
templateUrl: './about.component.html',
|
||||
styleUrl: './about.component.css',
|
||||
})
|
||||
export class AboutComponent {}
|
||||
@@ -0,0 +1 @@
|
||||
<p>chip-container works!</p>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-chip-container',
|
||||
imports: [],
|
||||
templateUrl: './chip-container.component.html',
|
||||
styleUrl: './chip-container.component.css',
|
||||
})
|
||||
export class ChipContainerComponent {}
|
||||
@@ -0,0 +1,279 @@
|
||||
/* NieR-style hover effects for tree nodes */
|
||||
.tree-node-item {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: color 0.3s ease;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* Underline effect */
|
||||
.tree-node-item::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background-color: var(--color-nier-text-dark);
|
||||
transition: width 0.3s ease;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Text color change on hover */
|
||||
.tree-node-item:hover {
|
||||
color: var(--color-nier-text-dark);
|
||||
border-bottom-color: var(--color-nier-text-dark);
|
||||
}
|
||||
|
||||
/* Underline animation on hover */
|
||||
.tree-node-item:hover::before {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Glitch effect on hover - target the text span directly */
|
||||
.tree-node-item:hover span.text-nier-dark {
|
||||
animation: nier-text-glitch 0.6s ease;
|
||||
}
|
||||
|
||||
/* Alternative - target any span with text */
|
||||
.tree-node-item:hover span:last-of-type {
|
||||
animation: nier-text-glitch 0.6s ease;
|
||||
}
|
||||
|
||||
/* Scan line for tree nodes */
|
||||
.tree-node-item .scan-line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--color-nier-text-dark);
|
||||
opacity: 0;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tree-node-item:hover .scan-line {
|
||||
animation: nier-button-scan 0.3s ease forwards;
|
||||
}
|
||||
|
||||
/* Active/click state for tree nodes */
|
||||
.tree-node-item:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* NieR-style Chip Container System - NO INTERACTIONS */
|
||||
.chip-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chip-parent {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
border-radius: 0;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15); /* Very thin border */
|
||||
box-shadow: none;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease; /* Allow parent scaling */
|
||||
}
|
||||
|
||||
/* Parent Chip Colors - Complete NieR Earth Palette */
|
||||
.tools-parent {
|
||||
background: linear-gradient(135deg, #a67c63 0%, #8b6b52 100%);
|
||||
}
|
||||
|
||||
.frontend-parent {
|
||||
background: linear-gradient(135deg, #b8916f 0%, #a67c63 100%);
|
||||
}
|
||||
|
||||
.backend-parent {
|
||||
background: linear-gradient(135deg, #c9b896 0%, #b8916f 100%);
|
||||
}
|
||||
|
||||
.database-parent {
|
||||
background: linear-gradient(135deg, #e6d7b8 0%, #d4c4a0 100%);
|
||||
}
|
||||
|
||||
/* Locked Parent Category */
|
||||
.locked-parent {
|
||||
background: linear-gradient(135deg, #606060 0%, #4a4a4a 100%);
|
||||
border: none;
|
||||
cursor: not-allowed;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chip-locked-category {
|
||||
opacity: 0.7;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Chip Cells Column Layout */
|
||||
.chip-cells-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Locked Content Styling */
|
||||
.locked-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lock-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
color: #b0b0b0;
|
||||
stroke-width: 1.5;
|
||||
opacity: 0.8;
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3));
|
||||
}
|
||||
|
||||
/* Individual Chip Cells - Keep original grayish overlay */
|
||||
.chip-cell {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
border-radius: 0;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2); /* Keep original border */
|
||||
background: rgba(0, 0, 0, 0.1); /* Keep original grayish overlay */
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); /* Keep original shadow */
|
||||
transition: transform 0.2s ease; /* Allow scale animation */
|
||||
}
|
||||
|
||||
.chip-cell::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
right: 1px;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1); /* Keep original highlight */
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* Scale animation when tree node is hovered OR selected */
|
||||
.chip-cell.cell-hovered,
|
||||
.chip-cell.cell-selected {
|
||||
transform: scale(1.05) !important;
|
||||
background: rgba(0, 0, 0, 0.1) !important; /* Keep original grayish color */
|
||||
border: 1px solid rgba(0, 0, 0, 0.2) !important; /* Keep original border */
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2) !important; /* Keep original shadow */
|
||||
}
|
||||
|
||||
/* Parent categories scale when children are hovered OR selected */
|
||||
.chip-container.chip-hovered .chip-parent,
|
||||
.chip-container.chip-selected .chip-parent {
|
||||
transform: scale(1.02) !important;
|
||||
}
|
||||
|
||||
/* NO direct hover effects on cells */
|
||||
.chip-cell:hover,
|
||||
.chip-cell:focus,
|
||||
.chip-cell:active {
|
||||
background: rgba(0, 0, 0, 0.1) !important; /* Keep original grayish */
|
||||
border: 1px solid rgba(0, 0, 0, 0.2) !important; /* Keep original border */
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2) !important; /* Keep original shadow */
|
||||
transform: none !important;
|
||||
filter: none !important;
|
||||
opacity: 1 !important;
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.chip-cell:hover::before,
|
||||
.chip-cell:focus::before,
|
||||
.chip-cell:active::before,
|
||||
.chip-cell.cell-hovered::before,
|
||||
.chip-cell.cell-selected::before {
|
||||
background: rgba(255, 255, 255, 0.1) !important; /* Keep original highlight */
|
||||
height: 4px !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* NO CONTAINER EFFECTS */
|
||||
.chip-normal,
|
||||
.chip-selected,
|
||||
.chip-hovered {
|
||||
/* No effects */
|
||||
}
|
||||
|
||||
/* Text glitch effect for tree nodes only */
|
||||
@keyframes nier-text-glitch {
|
||||
0%, 100% {
|
||||
transform: translateX(0);
|
||||
clip-path: inset(0 0 0 0);
|
||||
}
|
||||
10% {
|
||||
transform: translateX(-2px);
|
||||
clip-path: inset(0 0 40% 0);
|
||||
}
|
||||
15% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
20% {
|
||||
transform: translateX(1px);
|
||||
clip-path: inset(40% 0 0 0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
30% {
|
||||
transform: translateX(-1px);
|
||||
clip-path: inset(20% 0 20% 0);
|
||||
}
|
||||
35% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Scan line animation for tree nodes only */
|
||||
@keyframes nier-button-scan {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
left: -100%;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.chip-cell {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.chip-cells-column {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.chip-parent {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Touch device optimization for tree nodes only */
|
||||
@media (hover: none) {
|
||||
.tree-node-item:active::before {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tree-node-item:active .scan-line {
|
||||
animation: nier-button-scan 0.3s ease forwards;
|
||||
}
|
||||
|
||||
.tree-node-item:active {
|
||||
color: var(--color-nier-text-dark);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<div class="flex gap-6 h-auto">
|
||||
<!-- Tree Component -->
|
||||
<div
|
||||
class="bg-nier-bg border-2 border-nier-border text-nier-dark w-96 md:w-[28rem] lg:w-[32rem] font-terminal text-sm md:text-base lg:text-lg leading-relaxed p-4 md:p-6 h-auto">
|
||||
@for (node of treeData(); track node.id) {
|
||||
<div
|
||||
class="border-l-2 border-transparent"
|
||||
[class.border-nier-accent]="node.level >= 2"
|
||||
[class.hidden]="!node.visible"
|
||||
>
|
||||
<div
|
||||
class="tree-node-item flex items-center py-1 md:py-2 px-2 md:px-3 cursor-pointer transition-all duration-150 ease-in-out hover:bg-nier-mid/20 hover:translate-x-0.5 relative overflow-hidden"
|
||||
[class.font-bold]="node.isSelected"
|
||||
[style.padding-left]="getIndentPadding(node.level)"
|
||||
[attr.data-label]="node.title"
|
||||
(click)="selectNode(node)"
|
||||
(mouseenter)="hoverNode(node)"
|
||||
(mouseleave)="clearHover()"
|
||||
>
|
||||
<!-- Selection indicator -->
|
||||
@if (node.isSelected) {
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-nier-accent"></div>
|
||||
}
|
||||
|
||||
<!-- Expand/Collapse Icon -->
|
||||
<span
|
||||
class="w-4 h-4 md:w-5 md:h-5 lg:w-6 lg:h-6 flex items-center justify-center text-nier-accent mr-2 md:mr-3 cursor-pointer select-none text-sm md:text-base lg:text-lg"
|
||||
[class.text-nier-mid]="!node.hasChildren"
|
||||
(click)="$event.stopPropagation(); toggleExpand(node)"
|
||||
>
|
||||
@if (node.hasChildren) {
|
||||
{{ node.isExpanded ? '▼' : '▶' }}
|
||||
} @else {
|
||||
@switch (node.level) {
|
||||
@case (2) {
|
||||
├
|
||||
}
|
||||
@case (3) {
|
||||
@if (isLastChild(node)) {
|
||||
└
|
||||
} @else {
|
||||
├
|
||||
}
|
||||
}
|
||||
@default {
|
||||
+
|
||||
}
|
||||
}
|
||||
}
|
||||
</span>
|
||||
|
||||
<!-- Node Title with NieR effects -->
|
||||
<span
|
||||
class="flex-1 text-nier-dark select-none relative z-10"
|
||||
[class.font-bold]="node.isSelected"
|
||||
>
|
||||
{{ node.title }}
|
||||
</span>
|
||||
|
||||
<!-- Scan line effect -->
|
||||
<span class="scan-line"></span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Chip Slots Container -->
|
||||
<article class="hidden md:flex flex-col bg-nier-bg border-2 border-nier-border w-80 h-auto p-6 gap-4 self-stretch">
|
||||
|
||||
<!-- Tools Chip -->
|
||||
<div class="chip-container" [ngClass]="'chip-' + getVisualState('tools')">
|
||||
<div class="chip-parent tools-parent">
|
||||
<div class="chip-cells-column">
|
||||
@for (tool of ['webstorm', 'git', 'docker']; track tool) {
|
||||
<div
|
||||
class="chip-cell"
|
||||
[class.cell-hovered]="hoveredNodeId() === tool"
|
||||
[class.cell-selected]="selectedNodeId() === tool"
|
||||
(click)="selectToolChild(tool)"
|
||||
(mouseenter)="hoverChipChild(tool)"
|
||||
(mouseleave)="clearHover()"
|
||||
>
|
||||
<span class="scan-line"></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Frontend Chip -->
|
||||
<div class="chip-container" [ngClass]="'chip-' + getVisualState('front')">
|
||||
<div class="chip-parent frontend-parent">
|
||||
<div class="chip-cells-column">
|
||||
@for (tech of ['angular', 'tailwind', 'html-css']; track tech) {
|
||||
<div
|
||||
class="chip-cell"
|
||||
[class.cell-hovered]="hoveredNodeId() === tech"
|
||||
[class.cell-selected]="selectedNodeId() === tech"
|
||||
(click)="selectToolChild(tech)"
|
||||
(mouseenter)="hoverChipChild(tech)"
|
||||
(mouseleave)="clearHover()"
|
||||
>
|
||||
<span class="scan-line"></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backend Chip -->
|
||||
<div class="chip-container" [ngClass]="'chip-' + getVisualState('back')">
|
||||
<div class="chip-parent backend-parent">
|
||||
<div class="chip-cells-column">
|
||||
@for (lang of ['nodejs', 'typescript', 'php', 'python']; track lang) {
|
||||
<div
|
||||
class="chip-cell"
|
||||
[class.cell-hovered]="hoveredNodeId() === lang"
|
||||
[class.cell-selected]="selectedNodeId() === lang"
|
||||
(click)="selectToolChild(lang)"
|
||||
(mouseenter)="hoverChipChild(lang)"
|
||||
(mouseleave)="clearHover()"
|
||||
>
|
||||
<span class="scan-line"></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Database Chip -->
|
||||
<div class="chip-container" [ngClass]="'chip-' + getVisualState('data')">
|
||||
<div class="chip-parent database-parent">
|
||||
<div class="chip-cells-column">
|
||||
@for (db of ['postgresql', 'mongodb']; track db) {
|
||||
<div
|
||||
class="chip-cell"
|
||||
[class.cell-hovered]="hoveredNodeId() === db"
|
||||
[class.cell-selected]="selectedNodeId() === db"
|
||||
(click)="selectToolChild(db)"
|
||||
(mouseenter)="hoverChipChild(db)"
|
||||
(mouseleave)="clearHover()"
|
||||
>
|
||||
<span class="scan-line"></span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Locked Category -->
|
||||
<div class="chip-container chip-locked-category">
|
||||
<div class="chip-parent locked-parent">
|
||||
<div class="locked-content">
|
||||
<svg class="lock-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
@@ -0,0 +1,379 @@
|
||||
import { Component, input, signal, computed } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { type NeuralProfileNode } from '../../../shared/models/about.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-neural-profile-tree',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './neural-profile-tree.component.html',
|
||||
styleUrl: './neural-profile-tree.component.css',
|
||||
})
|
||||
export class NeuralProfileTreeComponent {
|
||||
// Input for external data (optional)
|
||||
externalData = input<NeuralProfileNode[]>([]);
|
||||
|
||||
// Internal tree state and interaction signals
|
||||
private treeState = signal<NeuralProfileNode[]>(this.getInitialData());
|
||||
public hoveredNodeId = signal<string | null>(null);
|
||||
public selectedNodeId = signal<string | null>(null);
|
||||
|
||||
// Computed flattened tree for rendering
|
||||
treeData = computed(() => this.flattenTreeWithVisibility(this.treeState()));
|
||||
|
||||
private getInitialData(): NeuralProfileNode[] {
|
||||
return [
|
||||
{
|
||||
id: 'workstation',
|
||||
title: 'Workstation/',
|
||||
isExpanded: true,
|
||||
isSelected: false,
|
||||
level: 0,
|
||||
hasChildren: true,
|
||||
visible: true,
|
||||
children: [
|
||||
{
|
||||
id: 'tools',
|
||||
title: 'Tools/',
|
||||
isExpanded: true,
|
||||
isSelected: false,
|
||||
level: 1,
|
||||
hasChildren: true,
|
||||
visible: true,
|
||||
children: [
|
||||
{
|
||||
id: 'webstorm',
|
||||
title: 'Webstorm/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 2,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'git',
|
||||
title: 'Git/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 2,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'docker',
|
||||
title: 'Docker/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 2,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'dev',
|
||||
title: 'Dev/',
|
||||
isExpanded: true,
|
||||
isSelected: false,
|
||||
level: 1,
|
||||
hasChildren: true,
|
||||
visible: true,
|
||||
children: [
|
||||
{
|
||||
id: 'front',
|
||||
title: 'Front/',
|
||||
isExpanded: true,
|
||||
isSelected: false,
|
||||
level: 2,
|
||||
hasChildren: true,
|
||||
visible: true,
|
||||
children: [
|
||||
{
|
||||
id: 'angular',
|
||||
title: 'Angular/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 3,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'tailwind',
|
||||
title: 'Tailwind/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 3,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'html-css',
|
||||
title: 'HTML_CSS/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 3,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'back',
|
||||
title: 'Back/',
|
||||
isExpanded: true,
|
||||
isSelected: false,
|
||||
level: 2,
|
||||
hasChildren: true,
|
||||
visible: true,
|
||||
children: [
|
||||
{
|
||||
id: 'nodejs',
|
||||
title: 'NodeJS_Express/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 3,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'typescript',
|
||||
title: 'Typescript/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 3,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'php',
|
||||
title: 'PHP/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 3,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'python',
|
||||
title: 'Python/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 3,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'data',
|
||||
title: 'Data/',
|
||||
isExpanded: true,
|
||||
isSelected: false,
|
||||
level: 2,
|
||||
hasChildren: true,
|
||||
visible: true,
|
||||
children: [
|
||||
{
|
||||
id: 'postgresql',
|
||||
title: 'PostgreSQL/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 3,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
id: 'mongodb',
|
||||
title: 'MongoDB/',
|
||||
isExpanded: false,
|
||||
isSelected: false,
|
||||
level: 3,
|
||||
hasChildren: false,
|
||||
visible: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private flattenTreeWithVisibility(
|
||||
nodes: NeuralProfileNode[],
|
||||
): NeuralProfileNode[] {
|
||||
const result: NeuralProfileNode[] = [];
|
||||
|
||||
const traverse = (
|
||||
nodes: NeuralProfileNode[],
|
||||
parentVisible: boolean = true,
|
||||
) => {
|
||||
for (const node of nodes) {
|
||||
// Node is visible if parent is visible
|
||||
node.visible = parentVisible;
|
||||
result.push(node);
|
||||
|
||||
// Only traverse children if this node is expanded AND visible
|
||||
if (node.children) {
|
||||
const childrenVisible = parentVisible && node.isExpanded;
|
||||
traverse(node.children, childrenVisible);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
traverse(nodes, true); // Root is always visible
|
||||
return result;
|
||||
}
|
||||
|
||||
toggleExpand(node: NeuralProfileNode): void {
|
||||
if (node.hasChildren) {
|
||||
this.treeState.update((state) => {
|
||||
// Create a deep copy to avoid mutation issues
|
||||
const newState = JSON.parse(JSON.stringify(state));
|
||||
|
||||
this.updateNodeInTree(newState, node.id, {
|
||||
isExpanded: !node.isExpanded,
|
||||
});
|
||||
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
selectNode(node: NeuralProfileNode): void {
|
||||
// Simple selection - just update the selected state
|
||||
this.treeState.update((state) => {
|
||||
// Create a deep copy to avoid mutation issues
|
||||
const newState = JSON.parse(JSON.stringify(state));
|
||||
|
||||
// Clear all selections
|
||||
this.clearAllSelections(newState);
|
||||
|
||||
// Set current node as selected
|
||||
this.updateNodeInTree(newState, node.id, { isSelected: true });
|
||||
|
||||
// Update selected node ID for visual representation
|
||||
this.selectedNodeId.set(node.id);
|
||||
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
// NEW METHOD: Handle chip cell hover
|
||||
hoverChipChild(childId: string): void {
|
||||
this.hoveredNodeId.set(childId);
|
||||
}
|
||||
|
||||
selectToolChild(childId: string): void {
|
||||
// Find the node in the tree and select it
|
||||
this.treeState.update((state) => {
|
||||
const newState = JSON.parse(JSON.stringify(state));
|
||||
|
||||
// Clear all selections first
|
||||
this.clearAllSelections(newState);
|
||||
|
||||
// Find and select the specific child node
|
||||
this.updateNodeInTree(newState, childId, { isSelected: true });
|
||||
|
||||
// Update the signal for visual state
|
||||
this.selectedNodeId.set(childId);
|
||||
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
hoverNode(node: NeuralProfileNode): void {
|
||||
this.hoveredNodeId.set(node.id);
|
||||
}
|
||||
|
||||
clearHover(): void {
|
||||
this.hoveredNodeId.set(null);
|
||||
}
|
||||
|
||||
getVisualState(sectionId: string): 'normal' | 'selected' | 'hovered' {
|
||||
const hoveredId = this.hoveredNodeId();
|
||||
const selectedId = this.selectedNodeId();
|
||||
|
||||
// Map node IDs to their corresponding visual sections
|
||||
const nodeToSection: { [key: string]: string } = {
|
||||
workstation: 'workstation',
|
||||
tools: 'tools',
|
||||
webstorm: 'tools',
|
||||
git: 'tools',
|
||||
docker: 'tools',
|
||||
dev: 'dev',
|
||||
front: 'front',
|
||||
angular: 'front',
|
||||
tailwind: 'front',
|
||||
'html-css': 'front',
|
||||
back: 'back',
|
||||
nodejs: 'back',
|
||||
typescript: 'back',
|
||||
php: 'back',
|
||||
python: 'back',
|
||||
data: 'data',
|
||||
postgresql: 'data',
|
||||
mongodb: 'data',
|
||||
};
|
||||
|
||||
// Check if any hovered node belongs to this section
|
||||
if (hoveredId && nodeToSection[hoveredId] === sectionId) {
|
||||
return 'hovered';
|
||||
}
|
||||
|
||||
// Check if any selected node belongs to this section
|
||||
if (selectedId && nodeToSection[selectedId] === sectionId) {
|
||||
return 'selected';
|
||||
}
|
||||
|
||||
return 'normal';
|
||||
}
|
||||
|
||||
private updateNodeInTree(
|
||||
nodes: NeuralProfileNode[],
|
||||
id: string,
|
||||
updates: Partial<NeuralProfileNode>,
|
||||
): boolean {
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) {
|
||||
Object.assign(node, updates);
|
||||
return true;
|
||||
}
|
||||
if (node.children && this.updateNodeInTree(node.children, id, updates)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private clearAllSelections(nodes: NeuralProfileNode[]): void {
|
||||
for (const node of nodes) {
|
||||
node.isSelected = false;
|
||||
if (node.children) {
|
||||
this.clearAllSelections(node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getIndentPadding(level: number): string {
|
||||
const paddingMap = {
|
||||
0: '0.5rem',
|
||||
1: '1.5rem',
|
||||
2: '2.5rem',
|
||||
3: '3.5rem',
|
||||
};
|
||||
return paddingMap[level as keyof typeof paddingMap] || '0.5rem';
|
||||
}
|
||||
|
||||
isLastChild(node: NeuralProfileNode): boolean {
|
||||
// This would need parent context to determine if it's the last child
|
||||
// For now, we'll use the node title as a simple heuristic
|
||||
return (
|
||||
node.title.includes('MongoDB') ||
|
||||
node.title.includes('Docker') ||
|
||||
node.title.includes('HTML_CSS') ||
|
||||
node.title.includes('Python')
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user