mirror of
https://github.com/adam-benyekkou/my_portfolio.git
synced 2026-01-15 20:20:09 +00:00
Working on tech skill tree using tree data structure, layout still WIP
This commit is contained in:
BIN
public/cyber_agent.mp4
Normal file
BIN
public/cyber_agent.mp4
Normal file
Binary file not shown.
BIN
public/cyber_skull.mp4
Normal file
BIN
public/cyber_skull.mp4
Normal file
Binary file not shown.
@@ -1,3 +1,19 @@
|
||||
<section class="border-b-1 h-screen flex items-top justify-top checkered-bg p-6 relative">
|
||||
<section class="border-b-1 h-screen checkered-bg p-6 flex flex-col">
|
||||
<app-section-title title="NEURAL PROFILE" />
|
||||
|
||||
<div class="flex-1 flex items-start justify-between gap-6 mt-6">
|
||||
<!-- Left side - Neural Profile Tree (always shown) -->
|
||||
<div class="flex-shrink-0">
|
||||
<app-neural-profile-tree />
|
||||
</div>
|
||||
|
||||
<!-- Right side - Video container (hidden on mobile) -->
|
||||
<div class="hidden md:flex flex-1 items-center justify-end">
|
||||
<div class="w-4/5 h-96 lg:h-[28rem] overflow-hidden">
|
||||
<app-holo-video-container
|
||||
containerClasses="w-full h-full"
|
||||
videoSrc="cyber_skull.mp4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {SectionTitleComponent} from "../../shared/ui/section-title/section-title.component";
|
||||
import { SectionTitleComponent } from '../../shared/ui/section-title/section-title.component';
|
||||
import { HoloVideoContainerComponent } from '../../shared/ui/holo-video-container/holo-video-container.component';
|
||||
import { NeuralProfileTreeComponent } from './neural-profile-tree/neural-profile-tree.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-about-display',
|
||||
imports: [SectionTitleComponent],
|
||||
imports: [
|
||||
SectionTitleComponent,
|
||||
HoloVideoContainerComponent,
|
||||
NeuralProfileTreeComponent,
|
||||
],
|
||||
templateUrl: './about-display.component.html',
|
||||
styleUrl: './about-display.component.css',
|
||||
})
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<div class="bg-nier-bg border-2 border-nier-border text-nier-dark w-96 font-terminal text-sm leading-relaxed p-4">
|
||||
@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="flex items-center py-1 px-2 cursor-pointer transition-all duration-150 ease-in-out hover:bg-nier-mid/20 hover:translate-x-0.5 relative"
|
||||
[style.padding-left]="getIndentPadding(node.level)"
|
||||
(click)="selectNode(node)"
|
||||
>
|
||||
<!-- 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 flex items-center justify-center text-nier-accent mr-2 cursor-pointer select-none"
|
||||
[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 -->
|
||||
<span
|
||||
class="flex-1 text-nier-dark select-none"
|
||||
[class.font-bold]="node.isSelected"
|
||||
>
|
||||
{{ node.title }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,304 @@
|
||||
import { Component, input, signal, computed } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
export interface NeuralProfileNode {
|
||||
id: string;
|
||||
title: string;
|
||||
isExpanded: boolean;
|
||||
isSelected: boolean;
|
||||
children?: NeuralProfileNode[];
|
||||
level: number;
|
||||
hasChildren: boolean;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-neural-profile-tree',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './neural-profile-tree.component.html',
|
||||
styles: [],
|
||||
})
|
||||
export class NeuralProfileTreeComponent {
|
||||
// Input for external data (optional)
|
||||
externalData = input<NeuralProfileNode[]>([]);
|
||||
|
||||
// Internal tree state
|
||||
private treeState = signal<NeuralProfileNode[]>(this.getInitialData());
|
||||
|
||||
// Computed flattened tree for rendering
|
||||
treeData = computed(() => this.flattenTree(this.treeState()));
|
||||
|
||||
private getInitialData(): NeuralProfileNode[] {
|
||||
return [
|
||||
{
|
||||
id: 'neuralProfile',
|
||||
title: ':TECH_CORE ',
|
||||
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 flattenTree(nodes: NeuralProfileNode[]): NeuralProfileNode[] {
|
||||
const result: NeuralProfileNode[] = [];
|
||||
|
||||
const traverse = (nodes: NeuralProfileNode[]) => {
|
||||
for (const node of nodes) {
|
||||
result.push(node);
|
||||
if (node.isExpanded && node.children) {
|
||||
traverse(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
traverse(nodes);
|
||||
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 });
|
||||
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
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')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<p>long-button-dark works!</p>
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-long-button-dark',
|
||||
imports: [],
|
||||
templateUrl: './long-button-dark.component.html',
|
||||
styleUrl: './long-button-dark.component.css'
|
||||
})
|
||||
export class LongButtonDarkComponent {
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<h2>
|
||||
<h2 class="relative">
|
||||
<!-- First shadow layer (light) -->
|
||||
<p
|
||||
#shadow1
|
||||
class="absolute text-6xl font-noto-jp text-white opacity-50"
|
||||
style="top: 1.5rem; left: 1.5rem; transform: translate(1px, 1px)"
|
||||
style="transform: translate(3px, 4px)"
|
||||
>
|
||||
{{title()}}
|
||||
</p>
|
||||
@@ -12,7 +12,7 @@
|
||||
<p
|
||||
#shadow2
|
||||
class="absolute text-6xl font-noto-jp text-black opacity-15"
|
||||
style="top: 1.5rem; left: 1.5rem; transform: translate(5px, 10px)"
|
||||
style="transform: translate(6px, 8px)"
|
||||
>
|
||||
{{title()}}
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user