752 lines
62 KiB
HTML
752 lines
62 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ro">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Red Valley — Resource Synapse Map</title>
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{background:#0a0a1a;color:#e0e0e0;font-family:'Segoe UI',system-ui,sans-serif;overflow:hidden}
|
|
#app{display:flex;height:100vh}
|
|
#sidebar{width:360px;background:#12122a;border-right:1px solid #2a2a5a;overflow-y:auto;padding:16px;flex-shrink:0}
|
|
#sidebar h1{font-size:18px;background:linear-gradient(135deg,#8b5cf6,#ec4899);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:2px}
|
|
#sidebar .sub{font-size:11px;color:#666;margin-bottom:12px}
|
|
.stats{display:flex;gap:8px;margin-bottom:12px}
|
|
.stat{flex:1;background:#1a1a3a;border-radius:8px;padding:8px;text-align:center;font-size:11px}
|
|
.stat b{display:block;font-size:18px;color:#8b5cf6}
|
|
#search{width:100%;padding:10px 14px;background:#1a1a3a;border:1px solid #333;border-radius:10px;color:#fff;font-size:13px;margin-bottom:12px;transition:border .2s}
|
|
#search:focus{outline:none;border-color:#8b5cf6;box-shadow:0 0 12px rgba(139,92,246,.2)}
|
|
#teamFilter{display:flex;gap:4px;margin-bottom:12px;flex-wrap:wrap}
|
|
#teamFilter button{padding:4px 10px;border-radius:12px;border:1px solid #333;background:#1a1a3a;color:#aaa;font-size:10px;cursor:pointer;transition:all .2s}
|
|
#teamFilter button:hover,#teamFilter button.active{border-color:#8b5cf6;color:#fff;background:#2a2a5a}
|
|
#info{font-size:12px;line-height:1.7}
|
|
#info h2{font-size:14px;margin:10px 0 4px;display:flex;align-items:center;gap:6px}
|
|
#info .tag{display:inline-block;padding:2px 8px;border-radius:10px;font-size:9px;margin:1px 2px;background:#1e1e3f;border:1px solid #333}
|
|
#info .dep-item{padding:4px 0;border-bottom:1px solid #1a1a3a;cursor:pointer;transition:all .15s;padding-left:4px;border-radius:4px}
|
|
#info .dep-item:hover{color:#fff;background:#1e1e3f;padding-left:8px}
|
|
.legend{margin-top:14px;font-size:10px;border-top:1px solid #1e1e3f;padding-top:10px}
|
|
.legend-title{font-size:11px;color:#666;margin-bottom:6px}
|
|
.legend div{display:flex;align-items:center;gap:6px;margin:3px 0}
|
|
.legend span{width:10px;height:10px;border-radius:50%;display:inline-block;flex-shrink:0}
|
|
.link-legend{margin-top:8px}
|
|
.link-legend div span{width:16px;height:3px;border-radius:2px}
|
|
svg{flex:1}
|
|
.link{stroke-opacity:.25;fill:none;cursor:pointer}
|
|
.link:hover{stroke-opacity:.8;stroke-width:3!important}
|
|
.link.highlighted{stroke-opacity:1;stroke-width:3!important}
|
|
.link.dimmed{stroke-opacity:.03}
|
|
.node-circle{stroke-width:1.5;cursor:pointer;filter:drop-shadow(0 0 4px rgba(0,0,0,.5))}
|
|
.node-circle.dimmed{opacity:.07}
|
|
.node-circle.highlighted{stroke:#fff;stroke-width:3;filter:drop-shadow(0 0 8px rgba(139,92,246,.6))}
|
|
.node-label{fill:#bbb;pointer-events:none;text-anchor:middle;font-weight:500}
|
|
.node-label.dimmed{opacity:.05}
|
|
.tooltip{position:fixed;background:#1e1e3fee;border:1px solid #8b5cf6;border-radius:10px;padding:10px 14px;font-size:12px;pointer-events:none;display:none;z-index:100;max-width:420px;backdrop-filter:blur(8px);box-shadow:0 8px 32px rgba(0,0,0,.4)}
|
|
.tooltip.pinned{pointer-events:auto;max-height:80vh;overflow-y:auto}
|
|
.tooltip b{color:#c084fc}
|
|
.tooltip .tt-row{padding:3px 0;border-bottom:1px solid #1e1e3f;font-size:11px}
|
|
.tooltip .tt-type{display:inline-block;padding:1px 6px;border-radius:8px;font-size:9px;margin-left:4px}
|
|
.tooltip .tt-close{position:absolute;top:6px;right:10px;cursor:pointer;color:#666;font-size:14px;line-height:1}
|
|
.tooltip .tt-close:hover{color:#fff}
|
|
.tooltip .tt-code{background:#0d0d1a;border:1px solid #2a2a4a;border-radius:6px;padding:8px 10px;margin:6px 0;font-family:'Cascadia Code','Fira Code',monospace;font-size:10px;line-height:1.6;color:#a5b4fc;white-space:pre-wrap;word-break:break-all}
|
|
.tooltip .tt-code .cm{color:#666;font-style:italic}
|
|
.tooltip .tt-code .fn{color:#c084fc}
|
|
.tooltip .tt-code .str{color:#86efac}
|
|
.tooltip .tt-code .kw{color:#f59e0b}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<div id="sidebar">
|
|
<h1>🧠 Resource Synapse Map</h1>
|
|
<div class="sub">Red Valley Roleplay — Click nod/linie | Scroll = zoom</div>
|
|
<div class="stats">
|
|
<div class="stat"><b id="nodeCount">0</b>Resurse</div>
|
|
<div class="stat"><b id="linkCount">0</b>Conexiuni</div>
|
|
<div class="stat"><b id="teamCount">6</b>Echipe Dev</div>
|
|
</div>
|
|
<input id="search" placeholder="🔍 Caută resursă sau keyword..." autocomplete="off">
|
|
<div id="teamFilter">
|
|
<button class="active" data-team="all">Toate</button>
|
|
<button data-team="17mov">17 Movement</button>
|
|
<button data-team="wasabi">Wasabi</button>
|
|
<button data-team="quasar">Quasar</button>
|
|
<button data-team="kq">KuzQuality</button>
|
|
<button data-team="rcore">rCore</button>
|
|
<button data-team="t1ger">T1GER</button>
|
|
<button data-team="qb">QBCore</button>
|
|
<button data-team="other">Altele</button>
|
|
</div>
|
|
<div id="info"><p style="color:#555">Click pe un nod sau pe o linie pentru detalii.</p></div>
|
|
<div class="legend">
|
|
<div class="legend-title">CULORI PER ECHIPĂ</div>
|
|
<div><span style="background:#ef4444"></span> QBCore</div>
|
|
<div><span style="background:#f97316"></span> 17 Movement</div>
|
|
<div><span style="background:#3b82f6"></span> Wasabi</div>
|
|
<div><span style="background:#eab308"></span> Quasar</div>
|
|
<div><span style="background:#a3e635"></span> KuzQuality</div>
|
|
<div><span style="background:#f43f5e"></span> rCore</div>
|
|
<div><span style="background:#14b8a6"></span> T1GER</div>
|
|
<div><span style="background:#8b5cf6"></span> Admin/UI</div>
|
|
<div><span style="background:#64748b"></span> Dependencies/Other</div>
|
|
<div class="link-legend">
|
|
<div class="legend-title" style="margin-top:8px">TIPURI CONEXIUNI (săgeți)</div>
|
|
<div><span style="background:#818cf8"></span> Export call</div>
|
|
<div><span style="background:#f59e0b"></span> Event trigger</div>
|
|
<div><span style="background:#10b981"></span> Config read</div>
|
|
<div><span style="background:#475569"></span> Hard dependency</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<svg id="graph"></svg>
|
|
</div>
|
|
<div class="tooltip" id="tooltip"></div>
|
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
<script>
|
|
// === TEAM COLORS (culori diferite per echipă) ===
|
|
const teamColors={
|
|
qb:"#ef4444","17mov":"#f97316",wasabi:"#3b82f6",quasar:"#eab308",
|
|
kq:"#a3e635",rcore:"#f43f5e",t1ger:"#14b8a6",ui:"#8b5cf6",other:"#64748b",
|
|
cfx:"#94a3b8",stream:"#475569"
|
|
};
|
|
const linkColors={export:"#818cf8",event:"#f59e0b",config:"#10b981",dep:"#475569"};
|
|
const teamMap={"17mov":"17 Movement",wasabi:"Wasabi",quasar:"Quasar",kq:"KuzQuality",rcore:"rCore",t1ger:"T1GER",qb:"QBCore",other:"Altele",cfx:"CFX",stream:"Stream",ui:"Admin/UI"};
|
|
|
|
const nodes=[
|
|
// === QBCORE ===
|
|
{id:"qb-core",team:"qb",r:32,desc:"Nucleul QBCore — jobs, items, player, events",keywords:"framework core player job item shared"},
|
|
{id:"qb-target",team:"qb",r:16,desc:"Target system — interact cu entități",keywords:"target interact eye"},
|
|
{id:"qb-menu",team:"qb",r:10,desc:"Menu system QBCore",keywords:"menu context"},
|
|
{id:"qb-smallresources",team:"qb",r:14,desc:"Consumables, AFK, seatbelt etc",keywords:"consumable food drink stress seatbelt"},
|
|
{id:"qb-input",team:"qb",r:8,desc:"Input dialog QBCore",keywords:"input dialog form"},
|
|
{id:"qb-management",team:"qb",r:14,desc:"Boss menu, society, gang management",keywords:"management boss society money hire fire"},
|
|
{id:"qb-weathersync",team:"qb",r:10,desc:"Weather & time sync",keywords:"weather time sync rain"},
|
|
{id:"qb-interior",team:"qb",r:8,desc:"Interior IPL loader",keywords:"interior ipl room"},
|
|
// === 17 MOVEMENT ===
|
|
{id:"17mov_CharacterSystem",team:"17mov",r:24,desc:"Character select/create, clothing, outfits, skin",keywords:"character clothing outfit skin wardrobe dress clothes save ped"},
|
|
{id:"17mov-plugin-char-creator",team:"17mov",r:10,desc:"Plugin outfit în character creator",keywords:"creator spawn plugin char"},
|
|
{id:"17mov_Hud",team:"17mov",r:22,desc:"HUD — notify, stress, hunger/thirst, progress, radar",keywords:"hud notify notification stress hunger thirst progress bar radar"},
|
|
{id:"17mov_JobCenter",team:"17mov",r:12,desc:"Job center multiplayer",keywords:"jobcenter employment hire"},
|
|
{id:"17mov_Electrician",team:"17mov",r:10,desc:"Job electrician",keywords:"electrician wire job citizen"},
|
|
{id:"17mov_BuilderJob",team:"17mov",r:9,desc:"Job constructor",keywords:"builder construction citizen job"},
|
|
{id:"17mov_Deliverer",team:"17mov",r:9,desc:"Job livrări",keywords:"delivery courier citizen job"},
|
|
{id:"17mov_GarbageCollector",team:"17mov",r:9,desc:"Job gunoi",keywords:"garbage trash collector citizen job"},
|
|
{id:"17mov_Lumberjack",team:"17mov",r:9,desc:"Job lemne",keywords:"lumberjack wood tree citizen job"},
|
|
{id:"17mov_Miner",team:"17mov",r:9,desc:"Job miner",keywords:"miner mining ore rock citizen job"},
|
|
{id:"17mov_OilRig",team:"17mov",r:9,desc:"Job platformă petrol",keywords:"oil rig petrol citizen job"},
|
|
{id:"17mov_Postman",team:"17mov",r:9,desc:"Job poștas",keywords:"postman mail letter citizen job"},
|
|
{id:"17mov_TreasureHunter",team:"17mov",r:9,desc:"Job vânător de comori",keywords:"treasure hunter dig citizen job"},
|
|
{id:"17mov_WindowCleaning",team:"17mov",r:9,desc:"Job spălat geamuri",keywords:"window cleaning building citizen job"},
|
|
// === QUASAR ===
|
|
{id:"qs-inventory",team:"quasar",r:24,desc:"Inventar — items, stash, crafting, trunk",keywords:"inventory item stash trunk glovebox slot weight craft"},
|
|
{id:"qs-vehiclekeys",team:"quasar",r:20,desc:"Chei vehicule — lock/unlock, hotwire",keywords:"key keys lock unlock hotwire vehicle car plate givekey remote"},
|
|
{id:"qs-advancedgarages",team:"quasar",r:16,desc:"Garaje avansate — stash, wardrobe",keywords:"garage park vehicle store wardrobe plate impound"},
|
|
{id:"qs-shops",team:"quasar",r:14,desc:"Magazine configurabile",keywords:"shop store buy sell market"},
|
|
{id:"qs-smartphone-pro",team:"quasar",r:18,desc:"Telefon PRO — apps, calls, camera",keywords:"phone smartphone call sms camera gallery radio remote lock"},
|
|
{id:"qs-housing",team:"quasar",r:14,desc:"Sistem case — stash, wardrobe, furniture",keywords:"house housing property home furniture stash wardrobe"},
|
|
{id:"qs-weed",team:"quasar",r:10,desc:"Sistem weed growing",keywords:"weed drugs grow plant"},
|
|
{id:"qs-notify",team:"quasar",r:8,desc:"Notificări Quasar",keywords:"notify notification alert"},
|
|
{id:"qs-weapondraw",team:"quasar",r:8,desc:"Animații scoatere armă",keywords:"weapon draw holster animation"},
|
|
// === WASABI ===
|
|
{id:"wasabi_bridge",team:"wasabi",r:16,desc:"Bridge framework Wasabi",keywords:"bridge framework wasabi dependency"},
|
|
{id:"wasabi_police",team:"wasabi",r:18,desc:"Job poliție v1.10.8 ✅ — duty, cuff, jail(rcore), CCTV, radar, F6 menu",keywords:"police cop law duty arrest handcuff spike k9 armory jail rcore cctv radar bobby_pin tracking"},
|
|
{id:"wasabi_ambulance",team:"wasabi",r:18,desc:"Job ambulanță v1.14.2 ✅ — revive, heal, stretcher, death system",keywords:"ambulance ems medic heal revive death hospital stretcher defib sedative diagnose"},
|
|
// === T1GER ===
|
|
{id:"t1ger_mechanic",team:"t1ger",r:16,desc:"Job mecanic — duty, garage, repair",keywords:"mechanic repair fix duty garage tuning"},
|
|
{id:"t1ger_tuningsystem",team:"t1ger",r:14,desc:"Tuning system vehicule",keywords:"tuning performance turbo engine suspension"},
|
|
{id:"t1ger_lib",team:"t1ger",r:12,desc:"Librărie T1GER — bridge stash/target",keywords:"library bridge t1ger"},
|
|
{id:"t1ger_carlift",team:"t1ger",r:8,desc:"Elevator mașini mecanic",keywords:"carlift elevator mechanic"},
|
|
{id:"t1ger_mechanicprops",team:"t1ger",r:6,desc:"Props mecanici (stream)",keywords:"props mechanic stream"},
|
|
// === RCORE ===
|
|
{id:"rcore_fuel",team:"rcore",r:14,desc:"Fuel system — benzinării, jerry can, consum",keywords:"fuel gas petrol pump jerry station diesel electric"},
|
|
{id:"rcore_casino",team:"rcore",r:14,desc:"Casino — blackjack, poker, slots, roulette, VIP",keywords:"casino gamble blackjack poker slots roulette lucky wheel vip chips"},
|
|
{id:"rcore_casino_assets",team:"rcore",r:6,desc:"Casino assets (stream)",keywords:"casino props assets stream"},
|
|
{id:"rcore_casino_interior",team:"rcore",r:6,desc:"Casino interior MLO",keywords:"casino interior mlo stream"},
|
|
{id:"rcore_fuel_assets",team:"rcore",r:6,desc:"Fuel assets (stream)",keywords:"fuel props assets stream"},
|
|
{id:"rcore_prison",team:"rcore",r:12,desc:"Închisoare V2 — jail, prison break, solitary",keywords:"prison jail sentence inmate break solitary community service"},
|
|
{id:"rcore_doorlock",team:"rcore",r:10,desc:"Door lock — uși, business, keychain",keywords:"door lock unlock access business keychain crafting permissions"},
|
|
// === KUZQUALITY ===
|
|
{id:"kq_carheist",team:"kq",r:12,desc:"Car heist minigame",keywords:"car heist steal minigame"},
|
|
{id:"kq_dyno",team:"kq",r:10,desc:"Dyno bench — HP, torque testing",keywords:"dyno bench test performance hp torque"},
|
|
{id:"kq_wheeldamage",team:"kq",r:8,desc:"Realistic wheel damage",keywords:"wheel damage tire burst deformation"},
|
|
{id:"kq_link",team:"kq",r:8,desc:"KQ Link — update & licensing",keywords:"kq link update license"},
|
|
{id:"kq_animsuggest",team:"kq",r:7,desc:"Sugestii animații contextuale",keywords:"animation suggest idle context"},
|
|
{id:"kq_bikejump",team:"kq",r:7,desc:"Bike jump realistic",keywords:"bike jump bmx stunt"},
|
|
{id:"kq_brakeoverheat",team:"kq",r:7,desc:"Brake overheat realistic",keywords:"brake heat smoke overheat"},
|
|
{id:"kq_driftsmoke",team:"kq",r:7,desc:"Drift smoke realistic",keywords:"drift smoke tire burnout"},
|
|
// === MDT / DISPATCH ===
|
|
{id:"codem-mdt",team:"other",r:10,desc:"MDT codem — records, warrants",keywords:"mdt codem records warrant"},
|
|
{id:"codem-dispatch",team:"other",r:12,desc:"Dispatch — 911, alerts, GPS",keywords:"dispatch 911 alert gps blip police"},
|
|
{id:"codem-mdtProp",team:"other",r:6,desc:"MDT prop model (laptop)",keywords:"mdt prop laptop model"},
|
|
// === ADMIN / UI ===
|
|
{id:"luxu_admin",team:"ui",r:18,desc:"Admin panel — manage players, vehicles",keywords:"admin panel manage player vehicle item teleport spectate ban"},
|
|
{id:"mBossmenu",team:"other",r:10,desc:"Boss menu CodeM — society",keywords:"boss society money hire fire"},
|
|
// === ADDONS ===
|
|
{id:"0r_idcard",team:"other",r:12,desc:"ID card + driving license",keywords:"id card license driving permit identity photo"},
|
|
{id:"bit-driverschool",team:"other",r:12,desc:"Școală auto — test, license",keywords:"driving school test license learn drive"},
|
|
{id:"jg-dealerships",team:"other",r:12,desc:"Dealer vehicule — showroom, finance",keywords:"dealer dealership buy car vehicle showroom finance"},
|
|
{id:"svdden_banking",team:"other",r:10,desc:"Banking — ATM, transfer",keywords:"bank atm money transfer deposit withdraw"},
|
|
{id:"ac-carcontrol",team:"other",r:10,desc:"Car control — lock/engine toggle",keywords:"car control lock engine toggle vehicle"},
|
|
{id:"aty_busjob",team:"other",r:9,desc:"Job șofer autobuz",keywords:"bus driver transport job citizen"},
|
|
{id:"jo_towtruck",team:"other",r:10,desc:"Job tractări — tow truck",keywords:"tow truck tractor transport mechanic"},
|
|
{id:"ak4y-dice",team:"other",r:7,desc:"Minigame zaruri",keywords:"dice gamble minigame"},
|
|
{id:"bennylift",team:"other",r:7,desc:"Benny's car lift animation",keywords:"benny lift car mechanic"},
|
|
{id:"squidgame",team:"other",r:7,desc:"Squid game minigame",keywords:"squid game minigame event"},
|
|
{id:"VehicleDeformation",team:"other",r:8,desc:"Vehicle deformation realistic",keywords:"vehicle deformation crash damage"},
|
|
// === DEPENDENCIES ===
|
|
{id:"ox_lib",team:"other",r:14,desc:"Overextended Lib — notify, locale, UI",keywords:"oxlib notify locale context menu ui"},
|
|
{id:"oxmysql",team:"other",r:14,desc:"MySQL driver — DB queries",keywords:"mysql database query db sql"},
|
|
{id:"pma-voice",team:"other",r:14,desc:"Voice chat — proximity, calls, radio",keywords:"voice chat proximity call radio microphone"},
|
|
{id:"progressbar",team:"other",r:8,desc:"Progress bar UI",keywords:"progress bar loading"},
|
|
{id:"PolyZone",team:"other",r:8,desc:"Zone management — poly, circles",keywords:"zone polygon circle box area"},
|
|
{id:"screenshot-basic",team:"other",r:8,desc:"Screenshot capture",keywords:"screenshot capture photo headshot"},
|
|
{id:"interact-sound",team:"other",r:6,desc:"Sunete interacțiune",keywords:"sound effect interact"},
|
|
{id:"xsound",team:"other",r:6,desc:"Sound system 3D audio",keywords:"sound music audio 3d xsound"},
|
|
{id:"phone-radio",team:"other",r:8,desc:"Radio pe telefon",keywords:"radio frequency channel"},
|
|
{id:"phone-props",team:"other",r:6,desc:"Prop-uri telefon (stream)",keywords:"phone prop model stream"},
|
|
{id:"phone-recorder",team:"other",r:6,desc:"Recorder telefon",keywords:"phone recorder video"},
|
|
{id:"phone-render",team:"other",r:6,desc:"Render telefon",keywords:"phone render camera"},
|
|
{id:"connectqueue",team:"other",r:8,desc:"Queue la conectare",keywords:"queue connect join loading"},
|
|
{id:"wasabi_bridge_dep",team:"other",r:0},
|
|
{id:"bob74_ipl",team:"other",r:8,desc:"IPL loader v2.6.0 — interiors, DLC maps (Casino, Tuner, Drug Wars, Mansions)",keywords:"ipl interior loader dlc casino tuner drugwars mansions simeon criminal enterprise"},
|
|
{id:"Howdy-Minigame",team:"other",r:8,desc:"Minigame hacking",keywords:"minigame hack hacking"},
|
|
{id:"mhacking",team:"other",r:7,desc:"Hacking minigame alternativ",keywords:"hacking minigame laptop"},
|
|
{id:"no-npc",team:"other",r:6,desc:"Remove NPC ambient",keywords:"npc remove ambient ped"},
|
|
{id:"minimap",team:"stream",r:6,desc:"Custom minimap",keywords:"minimap radar custom"},
|
|
// === HOUSING SUB-RESOURCES ===
|
|
{id:"housing-addons",team:"quasar",r:6,desc:"Housing addons props",keywords:"housing addon prop furniture"},
|
|
{id:"housing-island",team:"quasar",r:6,desc:"Housing island map",keywords:"housing island cayo"},
|
|
{id:"housing-sign",team:"quasar",r:6,desc:"Housing for sale signs",keywords:"housing sign sale"},
|
|
];
|
|
|
|
// Remove hidden placeholder node
|
|
const filteredNodes = nodes.filter(n => n.r > 0);
|
|
|
|
const links=[
|
|
// === qb-core dependencies ===
|
|
{s:"qs-inventory",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qs-vehiclekeys",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qs-advancedgarages",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qs-shops",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qs-smartphone-pro",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qs-housing",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"17mov_CharacterSystem",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"17mov-plugin-char-creator",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"17mov_Hud",t:"qb-core",type:"event",label:"OnPlayerLoaded, OnJobUpdate"},
|
|
{s:"17mov_JobCenter",t:"qb-core",type:"event",label:"OnPlayerLoaded, OnJobUpdate"},
|
|
{s:"17mov_Electrician",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"17mov_BuilderJob",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"17mov_Deliverer",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"17mov_GarbageCollector",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"17mov_Lumberjack",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"17mov_Miner",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"17mov_OilRig",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"17mov_Postman",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"17mov_TreasureHunter",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"17mov_WindowCleaning",t:"qb-core",type:"event",label:"PlayerLoaded"},
|
|
{s:"t1ger_mechanic",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"wasabi_bridge",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"luxu_admin",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qb-target",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qb-menu",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qb-smallresources",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qb-management",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qb-weathersync",t:"qb-core",type:"event",label:"OnPlayerLoaded"},
|
|
{s:"0r_idcard",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"bit-driverschool",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"jg-dealerships",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"rcore_fuel",t:"qb-core",type:"event",label:"OnPlayerLoaded, OnJobUpdate"},
|
|
{s:"rcore_casino",t:"qb-core",type:"event",label:"OnPlayerLoaded, OnJobUpdate"},
|
|
{s:"rcore_prison",t:"qb-core",type:"event",label:"OnPlayerLoaded"},
|
|
{s:"rcore_doorlock",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"svdden_banking",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"ac-carcontrol",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"kq_carheist",t:"qb-core",type:"event",label:"OnPlayerLoaded"},
|
|
{s:"kq_dyno",t:"qb-core",type:"event",label:"OnPlayerLoaded"},
|
|
{s:"progressbar",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"codem-mdt",t:"qb-core",type:"event",label:"OnPlayerLoaded, OnJobUpdate"},
|
|
{s:"codem-dispatch",t:"qb-core",type:"event",label:"OnPlayerLoaded"},
|
|
{s:"mBossmenu",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qb-input",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"qs-weed",t:"qb-core",type:"event",label:"OnJobUpdate"},
|
|
{s:"jo_towtruck",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
{s:"aty_busjob",t:"qb-core",type:"export",label:"GetCoreObject"},
|
|
// === 17mov_Hud (notificări) ===
|
|
{s:"qb-core",t:"17mov_Hud",type:"export",label:"ShowNotification routing"},
|
|
{s:"ox_lib",t:"17mov_Hud",type:"export",label:"ShowNotification bridge"},
|
|
{s:"0r_idcard",t:"17mov_Hud",type:"export",label:"ShowNotification"},
|
|
{s:"bit-driverschool",t:"17mov_Hud",type:"export",label:"ShowNotification"},
|
|
{s:"qb-smallresources",t:"17mov_Hud",type:"event",label:"stress relief notify"},
|
|
{s:"qs-inventory",t:"17mov_Hud",type:"event",label:"hunger/thirst/stress"},
|
|
{s:"wasabi_ambulance",t:"17mov_Hud",type:"event",label:"RelieveStress, UpdateNeeds"},
|
|
{s:"luxu_admin",t:"17mov_Hud",type:"event",label:"UpdateNeeds admin"},
|
|
{s:"jo_towtruck",t:"17mov_Hud",type:"export",label:"ShowNotification"},
|
|
// === qs-inventory connections ===
|
|
{s:"qb-core",t:"qs-inventory",type:"export",label:"AddItem/RemoveItem bridge"},
|
|
{s:"qb-smallresources",t:"qs-inventory",type:"export",label:"consumables, usable items"},
|
|
{s:"17mov_CharacterSystem",t:"qs-inventory",type:"export",label:"setInClothing(true/false)"},
|
|
{s:"luxu_admin",t:"qs-inventory",type:"export",label:"item management"},
|
|
{s:"mBossmenu",t:"qs-inventory",type:"export",label:"society items"},
|
|
{s:"qs-advancedgarages",t:"qs-inventory",type:"export",label:"trunk/glovebox stash"},
|
|
{s:"qs-shops",t:"qs-inventory",type:"export",label:"shop items"},
|
|
{s:"qs-smartphone-pro",t:"qs-inventory",type:"export",label:"phone item check"},
|
|
{s:"qs-vehiclekeys",t:"qs-inventory",type:"export",label:"key item metadata"},
|
|
{s:"rcore_casino",t:"qs-inventory",type:"export",label:"chips / bar items"},
|
|
{s:"rcore_fuel",t:"qs-inventory",type:"export",label:"jerry can items"},
|
|
{s:"rcore_prison",t:"qs-inventory",type:"export",label:"prison items"},
|
|
{s:"rcore_doorlock",t:"qs-inventory",type:"export",label:"keychain / lockpick"},
|
|
{s:"t1ger_lib",t:"qs-inventory",type:"export",label:"stash AddItem mechanic"},
|
|
{s:"codem-mdt",t:"qs-inventory",type:"export",label:"inventory check"},
|
|
// === qs-vehiclekeys connections ===
|
|
{s:"0r_idcard",t:"qs-vehiclekeys",type:"export",label:"GiveKeys driving license"},
|
|
{s:"bit-driverschool",t:"qs-vehiclekeys",type:"export",label:"GiveKeys test auto"},
|
|
{s:"ac-carcontrol",t:"qs-vehiclekeys",type:"export",label:"GetVehicleKeys, SetLocked"},
|
|
{s:"qs-advancedgarages",t:"qs-vehiclekeys",type:"export",label:"GiveKeys/RemoveKeys"},
|
|
{s:"qs-smartphone-pro",t:"qs-vehiclekeys",type:"export",label:"remote lock/unlock"},
|
|
{s:"rcore_fuel",t:"qs-vehiclekeys",type:"export",label:"owner key check pump"},
|
|
{s:"rcore_casino",t:"qs-vehiclekeys",type:"export",label:"GiveKeys casino vehicle"},
|
|
{s:"luxu_admin",t:"qs-vehiclekeys",type:"export",label:"giveVehicleKeys admin"},
|
|
{s:"wasabi_bridge",t:"qs-vehiclekeys",type:"export",label:"GiveKeys police/ambulance"},
|
|
{s:"qb-target",t:"qs-vehiclekeys",type:"export",label:"GetVehicleKeys target"},
|
|
{s:"qb-smallresources",t:"qs-vehiclekeys",type:"export",label:"key check"},
|
|
{s:"kq_carheist",t:"qs-vehiclekeys",type:"export",label:"hotwire/keys check"},
|
|
// === 17mov_CharacterSystem connections ===
|
|
{s:"17mov-plugin-char-creator",t:"17mov_CharacterSystem",type:"event",label:"SaveCurrentSkin"},
|
|
{s:"t1ger_mechanic",t:"17mov_CharacterSystem",type:"config",label:"Config.Outfits['mechanic']"},
|
|
{s:"qs-housing",t:"17mov_CharacterSystem",type:"event",label:"openOutfitMenu"},
|
|
{s:"qs-advancedgarages",t:"17mov_CharacterSystem",type:"event",label:"openOutfitMenu"},
|
|
{s:"qb-management",t:"17mov_CharacterSystem",type:"event",label:"openOutfitMenu"},
|
|
{s:"wasabi_ambulance",t:"17mov_CharacterSystem",type:"event",label:"openMenu clothing"},
|
|
{s:"qb-smallresources",t:"17mov_CharacterSystem",type:"event",label:"loadOutfit (parașută)"},
|
|
{s:"rcore_casino",t:"17mov_CharacterSystem",type:"config",label:"casino outfit bridge"},
|
|
{s:"rcore_prison",t:"17mov_CharacterSystem",type:"config",label:"prison clothing bridge"},
|
|
// === wasabi connections ===
|
|
{s:"wasabi_police",t:"wasabi_bridge",type:"dep",label:"framework bridge"},
|
|
{s:"wasabi_ambulance",t:"wasabi_bridge",type:"dep",label:"framework bridge"},
|
|
{s:"wasabi_police",t:"oxmysql",type:"dep",label:"CCTV, radar, jail DB"},
|
|
{s:"wasabi_police",t:"qs-inventory",type:"export",label:"items: handcuffs, bobby_pin, tracking_bracelet"},
|
|
{s:"wasabi_police",t:"rcore_prison",type:"config",label:"Config.Jail.jail='rcore'"},
|
|
{s:"wasabi_police",t:"codem-mdt",type:"event",label:"MDT records integration"},
|
|
{s:"wasabi_ambulance",t:"oxmysql",type:"dep",label:"death/injury DB"},
|
|
{s:"wasabi_ambulance",t:"qs-inventory",type:"export",label:"medical items: defib, medikit"},
|
|
// === t1ger connections ===
|
|
{s:"t1ger_mechanic",t:"t1ger_lib",type:"dep",label:"GetLib core"},
|
|
{s:"t1ger_tuningsystem",t:"t1ger_lib",type:"dep",label:"GetLib core"},
|
|
{s:"t1ger_tuningsystem",t:"t1ger_mechanic",type:"export",label:"IsPlayerMechanic"},
|
|
{s:"t1ger_carlift",t:"t1ger_mechanic",type:"config",label:"car lift integration"},
|
|
{s:"t1ger_lib",t:"qb-target",type:"export",label:"target system"},
|
|
{s:"t1ger_lib",t:"qb-management",type:"export",label:"society billing"},
|
|
// === 17mov citizen jobs → JobCenter ===
|
|
{s:"17mov_BuilderJob",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
{s:"17mov_Deliverer",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
{s:"17mov_Electrician",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
{s:"17mov_GarbageCollector",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
{s:"17mov_Lumberjack",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
{s:"17mov_Miner",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
{s:"17mov_OilRig",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
{s:"17mov_Postman",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
{s:"17mov_TreasureHunter",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
{s:"17mov_WindowCleaning",t:"17mov_JobCenter",type:"event",label:"job registration"},
|
|
// === admin connections ===
|
|
{s:"luxu_admin",t:"pma-voice",type:"export",label:"voice control mute"},
|
|
{s:"luxu_admin",t:"qs-smartphone-pro",type:"export",label:"phone control"},
|
|
{s:"luxu_admin",t:"oxmysql",type:"dep",label:"database queries"},
|
|
// === phone connections ===
|
|
{s:"qs-smartphone-pro",t:"pma-voice",type:"export",label:"call system voice"},
|
|
{s:"phone-radio",t:"qs-smartphone-pro",type:"dep",label:"radio app"},
|
|
{s:"phone-radio",t:"xsound",type:"dep",label:"audio playback"},
|
|
{s:"phone-radio",t:"pma-voice",type:"export",label:"radio voice"},
|
|
// === qb-management ===
|
|
{s:"qb-management",t:"qb-target",type:"export",label:"boss menu target"},
|
|
{s:"qb-management",t:"qb-input",type:"export",label:"input dialogs"},
|
|
{s:"qb-management",t:"qb-menu",type:"export",label:"context menus"},
|
|
{s:"qb-menu",t:"qb-input",type:"dep",label:"input integration"},
|
|
// === jg-dealerships ===
|
|
{s:"jg-dealerships",t:"oxmysql",type:"dep",label:"vehicle DB"},
|
|
{s:"jg-dealerships",t:"ox_lib",type:"dep",label:"UI/locale"},
|
|
// === driving school / idcard ===
|
|
{s:"bit-driverschool",t:"0r_idcard",type:"export",label:"setPlayerLicense"},
|
|
{s:"0r_idcard",t:"screenshot-basic",type:"export",label:"headshot capture"},
|
|
// === MDT / dispatch ===
|
|
{s:"codem-dispatch",t:"wasabi_police",type:"event",label:"police alerts"},
|
|
{s:"codem-mdt",t:"oxmysql",type:"dep",label:"records DB"},
|
|
{s:"codem-mdtProp",t:"codem-mdt",type:"dep",label:"prop model"},
|
|
// === housing ===
|
|
{s:"qs-housing",t:"oxmysql",type:"dep",label:"housing DB"},
|
|
{s:"housing-addons",t:"qs-housing",type:"dep",label:"addons props"},
|
|
{s:"housing-island",t:"qs-housing",type:"dep",label:"island map"},
|
|
{s:"housing-sign",t:"qs-housing",type:"dep",label:"sale signs"},
|
|
// === rcore ===
|
|
{s:"rcore_fuel",t:"oxmysql",type:"dep",label:"fuel DB"},
|
|
{s:"rcore_casino",t:"oxmysql",type:"dep",label:"casino DB"},
|
|
{s:"rcore_prison",t:"oxmysql",type:"dep",label:"prison DB (auto)"},
|
|
{s:"rcore_doorlock",t:"oxmysql",type:"dep",label:"doors DB"},
|
|
{s:"rcore_prison",t:"qb-target",type:"export",label:"target interactions"},
|
|
{s:"rcore_prison",t:"wasabi_police",type:"event",label:"jail integration"},
|
|
{s:"rcore_prison",t:"codem-dispatch",type:"event",label:"prison break dispatch"},
|
|
{s:"rcore_doorlock",t:"qb-target",type:"export",label:"door interactions"},
|
|
{s:"rcore_casino_assets",t:"rcore_casino",type:"dep",label:"casino props"},
|
|
{s:"rcore_casino_interior",t:"rcore_casino",type:"dep",label:"casino MLO"},
|
|
{s:"rcore_fuel_assets",t:"rcore_fuel",type:"dep",label:"fuel props"},
|
|
// === KQ connections ===
|
|
{s:"kq_carheist",t:"kq_link",type:"dep",label:"license & auto-update"},
|
|
{s:"kq_dyno",t:"kq_link",type:"dep",label:"license & auto-update"},
|
|
{s:"kq_wheeldamage",t:"kq_link",type:"dep",label:"license & auto-update"},
|
|
{s:"kq_animsuggest",t:"kq_link",type:"dep",label:"license & auto-update"},
|
|
{s:"kq_bikejump",t:"kq_link",type:"dep",label:"license & auto-update"},
|
|
{s:"kq_brakeoverheat",t:"kq_link",type:"dep",label:"license & auto-update"},
|
|
{s:"kq_driftsmoke",t:"kq_link",type:"dep",label:"license & auto-update"},
|
|
{s:"kq_carheist",t:"codem-dispatch",type:"event",label:"policeAlert la furt"},
|
|
{s:"kq_dyno",t:"t1ger_mechanic",type:"config",label:"job mechanic access"},
|
|
// === other ===
|
|
{s:"jo_towtruck",t:"t1ger_mechanic",type:"config",label:"job mechanic tow"},
|
|
{s:"aty_busjob",t:"17mov_Hud",type:"export",label:"ShowNotification"},
|
|
{s:"svdden_banking",t:"oxmysql",type:"dep",label:"banking DB"},
|
|
{s:"qs-weed",t:"qs-inventory",type:"export",label:"weed items"},
|
|
{s:"qs-weed",t:"oxmysql",type:"dep",label:"weed DB"},
|
|
{s:"bob74_ipl",t:"qb-interior",type:"dep",label:"IPL loading"},
|
|
];
|
|
|
|
document.getElementById("nodeCount").textContent=filteredNodes.length;
|
|
document.getElementById("linkCount").textContent=links.length;
|
|
|
|
const W=Math.max(window.innerWidth-360,600),H=window.innerHeight;
|
|
const svg=d3.select("#graph").attr("width",W).attr("height",H);
|
|
const defs=svg.append("defs");
|
|
|
|
// Arrow markers per link type
|
|
Object.entries(linkColors).forEach(([type,color])=>{
|
|
defs.append("marker").attr("id","arrow-"+type).attr("viewBox","0 0 10 10")
|
|
.attr("refX",10).attr("refY",5).attr("markerWidth",4).attr("markerHeight",4)
|
|
.attr("orient","auto-start-reverse").attr("fill",color).attr("fill-opacity",.6)
|
|
.append("path").attr("d","M 0 0 L 10 5 L 0 10 z");
|
|
});
|
|
|
|
const g=svg.append("g");
|
|
svg.call(d3.zoom().scaleExtent([.1,5]).on("zoom",e=>g.attr("transform",e.transform)));
|
|
|
|
links.forEach(l=>{l.source=l.s;l.target=l.t});
|
|
|
|
function nodeColor(d){return teamColors[d.team]||"#64748b"}
|
|
|
|
const simulation=d3.forceSimulation(filteredNodes)
|
|
.force("link",d3.forceLink(links).id(d=>d.id).distance(d=>{
|
|
const core=d.source.id==="qb-core"||d.target.id==="qb-core";
|
|
return core?220:140;
|
|
}))
|
|
.force("charge",d3.forceManyBody().strength(d=>d.id==="qb-core"?-1200:-300))
|
|
.force("center",d3.forceCenter(W/2,H/2))
|
|
.force("collision",d3.forceCollide().radius(d=>d.r+6))
|
|
.force("x",d3.forceX(W/2).strength(.02))
|
|
.force("y",d3.forceY(H/2).strength(.02));
|
|
|
|
// Links as lines with arrow markers
|
|
const link=g.append("g").selectAll("line").data(links).join("line")
|
|
.attr("class","link")
|
|
.attr("stroke",d=>linkColors[d.type]||"#444")
|
|
.attr("stroke-width",d=>d.type==="export"?1:d.type==="event"?.8:.6)
|
|
.attr("marker-end",d=>"url(#arrow-"+(d.type||"dep")+")");
|
|
|
|
// Invisible wider hit area for clicking links
|
|
const linkHit=g.append("g").selectAll("line").data(links).join("line")
|
|
.attr("stroke","transparent").attr("stroke-width",12).attr("fill","none")
|
|
.style("cursor","pointer");
|
|
|
|
const node=g.append("g").selectAll("g").data(filteredNodes).join("g")
|
|
.call(d3.drag()
|
|
.on("start",(e,d)=>{if(!e.active)simulation.alphaTarget(.3).restart();d.fx=d.x;d.fy=d.y})
|
|
.on("drag",(e,d)=>{d.fx=e.x;d.fy=e.y})
|
|
.on("end",(e,d)=>{if(!e.active)simulation.alphaTarget(0);d.fx=null;d.fy=null}));
|
|
|
|
node.append("circle").attr("class","node-circle").attr("r",d=>d.r)
|
|
.attr("fill",d=>nodeColor(d))
|
|
.attr("stroke",d=>d3.color(nodeColor(d)).darker(.5));
|
|
|
|
const labels=g.append("g").selectAll("text").data(filteredNodes).join("text")
|
|
.attr("class","node-label")
|
|
.attr("dy",d=>d.r+13)
|
|
.attr("font-size",d=>Math.max(8,Math.min(11,d.r*.5)))
|
|
.text(d=>d.id);
|
|
|
|
simulation.on("tick",()=>{
|
|
// Shorten lines so arrows don't overlap circles
|
|
link.each(function(d){
|
|
const dx=d.target.x-d.source.x, dy=d.target.y-d.source.y;
|
|
const dist=Math.sqrt(dx*dx+dy*dy)||1;
|
|
const sr=d.source.r||8, tr=d.target.r||8;
|
|
const sx=d.source.x+dx/dist*(sr+2), sy=d.source.y+dy/dist*(sr+2);
|
|
const tx=d.target.x-dx/dist*(tr+6), ty=d.target.y-dy/dist*(tr+6);
|
|
d3.select(this).attr("x1",sx).attr("y1",sy).attr("x2",tx).attr("y2",ty);
|
|
});
|
|
linkHit.attr("x1",d=>d.source.x).attr("y1",d=>d.source.y)
|
|
.attr("x2",d=>d.target.x).attr("y2",d=>d.target.y);
|
|
node.attr("transform",d=>`translate(${d.x},${d.y})`);
|
|
labels.attr("x",d=>d.x).attr("y",d=>d.y);
|
|
});
|
|
|
|
// === INTERACTION: NODE CLICK ===
|
|
let selected=null;
|
|
function selectNode(d){
|
|
if(selected===d.id){resetHighlight();selected=null;showDefault();return}
|
|
selected=d.id;
|
|
const connected=new Set([d.id]);
|
|
const outgoing=[],incoming=[];
|
|
links.forEach(l=>{
|
|
const sid=typeof l.source==="object"?l.source.id:l.source;
|
|
const tid=typeof l.target==="object"?l.target.id:l.target;
|
|
if(sid===d.id){connected.add(tid);outgoing.push({to:tid,type:l.type,label:l.label||l.type})}
|
|
if(tid===d.id){connected.add(sid);incoming.push({from:sid,type:l.type,label:l.label||l.type})}
|
|
});
|
|
d3.selectAll(".node-circle").classed("dimmed",n=>!connected.has(n.id)).classed("highlighted",n=>n.id===d.id);
|
|
d3.selectAll(".node-label").classed("dimmed",n=>!connected.has(n.id));
|
|
link.classed("dimmed",l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const t=typeof l.target==="object"?l.target.id:l.target;
|
|
return s!==d.id&&t!==d.id;
|
|
}).classed("highlighted",l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const t=typeof l.target==="object"?l.target.id:l.target;
|
|
return s===d.id||t===d.id;
|
|
});
|
|
const total=outgoing.length+incoming.length;
|
|
let h=`<h2 style="color:${nodeColor(d)}">🧠 ${d.id}</h2>`;
|
|
h+=`<p style="color:#888;margin:4px 0 8px;font-size:11px">${d.desc||""}</p>`;
|
|
h+=`<span class="tag" style="border-color:${nodeColor(d)};color:${nodeColor(d)}">${teamMap[d.team]||d.team}</span>`;
|
|
h+=`<span class="tag">${total} conexiuni</span>`;
|
|
if(outgoing.length){
|
|
h+=`<h2 style="color:#f97316">→ Depinde de (${outgoing.length})</h2>`;
|
|
outgoing.forEach(o=>h+=`<div class="dep-item" style="color:#f97316" data-id="${o.to}">• ${o.to} <span class="tag">${o.label}</span></div>`);
|
|
}
|
|
if(incoming.length){
|
|
h+=`<h2 style="color:#22d3ee">← Folosit de (${incoming.length})</h2>`;
|
|
incoming.forEach(i=>h+=`<div class="dep-item" style="color:#22d3ee" data-id="${i.from}">• ${i.from} <span class="tag">${i.label}</span></div>`);
|
|
}
|
|
document.getElementById("info").innerHTML=h;
|
|
document.querySelectorAll(".dep-item").forEach(el=>el.addEventListener("click",()=>{
|
|
const n=filteredNodes.find(n=>n.id===el.dataset.id);if(n)selectNode(n);
|
|
}));
|
|
}
|
|
|
|
node.on("click",(e,d)=>{e.stopPropagation();selectNode(d)});
|
|
svg.on("click",()=>{resetHighlight();selected=null;showDefault();unpinTooltip()});
|
|
|
|
function resetHighlight(){
|
|
d3.selectAll(".node-circle").classed("dimmed",false).classed("highlighted",false);
|
|
d3.selectAll(".node-label").classed("dimmed",false);
|
|
link.classed("dimmed",false).classed("highlighted",false);
|
|
}
|
|
function showDefault(){document.getElementById("info").innerHTML='<p style="color:#555">Click pe un nod sau pe o linie pentru detalii.</p>'}
|
|
|
|
// === CODE SNIPPET GENERATOR ===
|
|
function getCodeSnippet(l){
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const t=typeof l.target==="object"?l.target.id:l.target;
|
|
if(l.type==="export"){
|
|
if(l.label==="GetCoreObject") return `<span class='cm'>-- ${s} obține framework-ul QBCore</span>\n<span class='kw'>local</span> QBCore = <span class='fn'>exports</span>[<span class='str'>'qb-core'</span>]:<span class='fn'>GetCoreObject</span>() <span class='cm'>-- returnează obiectul principal cu toate funcțiile QBCore</span>`;
|
|
if(l.label&&l.label.includes("ShowNotification")) return `<span class='cm'>-- ${s} trimite notificare prin ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>ShowNotification</span>(msg, type) <span class='cm'>-- type: 'success'|'error'|'info'</span>`;
|
|
if(l.label&&l.label.includes("GiveKeys")) return `<span class='cm'>-- ${s} dă cheile vehiculului prin ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>GiveKeys</span>(plate) <span class='cm'>-- plate = nr înmatriculare, adaugă keys in inventar</span>`;
|
|
if(l.label&&l.label.includes("RemoveKeys")) return `<span class='cm'>-- ${s} gestionează cheile vehiculului prin ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>GiveKeys</span>(plate) <span class='cm'>-- dă cheia la player</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>RemoveKeys</span>(plate) <span class='cm'>-- scoate cheia de la player</span>`;
|
|
if(l.label&&l.label.includes("GetVehicleKeys")) return `<span class='cm'>-- ${s} verifică dacă player-ul are cheile</span>\n<span class='kw'>local</span> hasKeys = <span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>GetVehicleKeys</span>(plate) <span class='cm'>-- returnează true/false</span>`;
|
|
if(l.label&&l.label.includes("setInClothing")) return `<span class='cm'>-- ${s} blochează inventarul când deschide clothing</span>\n<span class='fn'>exports</span>[<span class='str'>'qs-inventory'</span>]:<span class='fn'>setInClothing</span>(<span class='kw'>true</span>) <span class='cm'>-- blochează deschiderea inventarului</span>\n<span class='cm'>-- și la închiderea meniului de clothing:</span>\n<span class='fn'>exports</span>[<span class='str'>'qs-inventory'</span>]:<span class='fn'>setInClothing</span>(<span class='kw'>false</span>) <span class='cm'>-- permite iar inventarul</span>`;
|
|
if(l.label&&l.label.includes("AddItem")) return `<span class='cm'>-- ${s} adaugă/scoate items prin inventar</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>AddItem</span>(src, item, amount) <span class='cm'>-- src=server id, item=string, amount=nr</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>RemoveItem</span>(src, item, amount) <span class='cm'>-- scoate item din inventar</span>`;
|
|
if(l.label&&l.label.includes("stash")) return `<span class='cm'>-- ${s} deschide/creează stash prin ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>OpenStash</span>(id, label, slots, weight) <span class='cm'>-- id=unic, slots=nr sloturi, weight=greutate max</span>`;
|
|
if(l.label&&l.label.includes("target")) return `<span class='cm'>-- ${s} adaugă target zone prin ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>AddBoxZone</span>( <span class='cm'>-- creează zonă de interacțiune 3D</span>\n name, <span class='cm'>-- identificator unic zone</span>\n center, <span class='cm'>-- vector3 coordonate centru</span>\n length, width, <span class='cm'>-- dimensiuni zonă</span>\n options, <span class='cm'>-- {heading, minZ, maxZ}</span>\n targetoptions <span class='cm'>-- {icon, label, action}</span>\n)`;
|
|
if(l.label&&l.label.includes("IsPlayerMechanic")) return `<span class='cm'>-- ${s} verifică dacă player-ul e mecanic activ</span>\n<span class='kw'>local</span> isMech = <span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>IsPlayerMechanic</span>() <span class='cm'>-- true dacă e on-duty ca mecanic</span>`;
|
|
if(l.label&&l.label.includes("IsPlayerTuner")) return `<span class='cm'>-- ${s} verifică dacă player-ul e tuner activ</span>\n<span class='kw'>local</span> isTuner, shopId = <span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>IsPlayerTuner</span>() <span class='cm'>-- returnează bool + id-ul shop-ului</span>`;
|
|
if(l.label&&l.label.includes("society")) return `<span class='cm'>-- ${s} accesează contul society prin ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>GetAccount</span>(job) <span class='cm'>-- returnează balanța contului societății</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>AddMoney</span>(job, amount) <span class='cm'>-- adaugă bani în contul societății</span>`;
|
|
if(l.label&&l.label.includes("remote lock")) return `<span class='cm'>-- ${s} controlează vehiculul remote prin ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>SetVehicleLocked</span>(plate, <span class='kw'>true</span>) <span class='cm'>-- lock/unlock de pe telefon</span>`;
|
|
if(l.label&&l.label.includes("phone item")) return `<span class='cm'>-- ${s} verifică dacă player-ul are telefon</span>\n<span class='kw'>local</span> hasPhone = <span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>HasItem</span>(<span class='str'>'phone'</span>) <span class='cm'>-- bool, necesar pt a deschide telefonul</span>`;
|
|
if(l.label&&l.label.includes("key item")) return `<span class='cm'>-- ${s} gestionează metadata cheilor</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>GetItemByName</span>(<span class='str'>'vehiclekey'</span>) <span class='cm'>-- returnează item cu metadata.plate</span>`;
|
|
if(l.label&&l.label.includes("call system")) return `<span class='cm'>-- ${s} folosește voice chat pentru apeluri</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>setVoiceProperty</span>(<span class='str'>'callChannel'</span>, ch) <span class='cm'>-- setează canalul de voce pt call</span>`;
|
|
if(l.label&&l.label.includes("voice control")) return `<span class='cm'>-- ${s} controlează voice-ul playerilor</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>setPlayerMuted</span>(src, <span class='kw'>true</span>) <span class='cm'>-- mute admin</span>`;
|
|
if(l.label&&l.label.includes("phone control")) return `<span class='cm'>-- ${s} controlează telefonul playerilor</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>GetPlayerPhone</span>(src) <span class='cm'>-- acces la date telefon din admin</span>`;
|
|
if(l.label&&l.label.includes("item management")) return `<span class='cm'>-- ${s} gestionează items prin inventar</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>AddItem</span>(src, item, qty) <span class='cm'>-- admin give item</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>RemoveItem</span>(src, item, qty) <span class='cm'>-- admin remove item</span>`;
|
|
if(l.label&&l.label.includes("setPlayerLicense")) return `<span class='cm'>-- ${s} dă licență de condus prin ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>setPlayerLicense</span>(src, <span class='str'>'driver'</span>) <span class='cm'>-- acordă licența după test</span>`;
|
|
if(l.label&&l.label.includes("headshot")) return `<span class='cm'>-- ${s} face captură foto pentru ID card</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>requestScreenshotUpload</span>(url, <span class='str'>'file'</span>, cb) <span class='cm'>-- captură headshot ped</span>`;
|
|
if(l.label&&l.label.includes("consumable")) return `<span class='cm'>-- ${s} registrează consumabile (mâncare, băutură)</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>AddItem</span>(src, <span class='str'>'sandwich'</span>, 1) <span class='cm'>-- adaugă food item</span>\n<span class='cm'>-- hunger/thirst se actualizează automat prin metadata</span>`;
|
|
if(l.label&&l.label.includes("boss menu")) return `<span class='cm'>-- ${s} accesează boss menu prin target</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>AddBoxZone</span>(<span class='str'>'boss'</span>, coords, ...) <span class='cm'>-- zonă interact boss menu</span>`;
|
|
if(l.label&&l.label.includes("input")) return `<span class='cm'>-- ${s} deschide dialog de input</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>ShowInput</span>(header, inputs) <span class='cm'>-- afișează input dialog cu text fields</span>`;
|
|
if(l.label&&l.label.includes("context")) return `<span class='cm'>-- ${s} deschide context menu</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>openMenu</span>(menuData) <span class='cm'>-- afișează meniu cu opțiuni</span>`;
|
|
if(l.label&&l.label.includes("hotwire")) return `<span class='cm'>-- ${s} verifică cheile pt hotwire</span>\n<span class='kw'>local</span> keys = <span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>GetVehicleKeys</span>(plate) <span class='cm'>-- dacă nu are chei → hotwire minigame</span>`;
|
|
if(l.label&&l.label.includes("trunk")) return `<span class='cm'>-- ${s} accesează portbagajul prin ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>OpenStash</span>(trunkId, label, slots, weight) <span class='cm'>-- stash trunk vehicul</span>`;
|
|
if(l.label&&l.label.includes("chips")) return `<span class='cm'>-- ${s} gestionează chips casino prin inventar</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>AddItem</span>(src, <span class='str'>'casino_chip'</span>, amount) <span class='cm'>-- adaugă chips câștigate</span>`;
|
|
if(l.label&&l.label.includes("jerry")) return `<span class='cm'>-- ${s} verifică/consumă jerry can din inventar</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>GetItemByName</span>(<span class='str'>'jerrycan'</span>) <span class='cm'>-- item cu metadata.fuel (nivel)</span>`;
|
|
if(l.label&&l.label.includes("door")) return `<span class='cm'>-- ${s} adaugă interact pe uși</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>AddTargetEntity</span>(door, options) <span class='cm'>-- target pe entity door</span>`;
|
|
if(l.label&&l.label.includes("prison items")) return `<span class='cm'>-- ${s} gestionează items deținuți</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>ClearInventory</span>(src) <span class='cm'>-- golește inventar la intrare în închisoare</span>`;
|
|
if(l.label&&l.label.includes("keychain")) return `<span class='cm'>-- ${s} verifică lockpick/keychain din inventar</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>HasItem</span>(<span class='str'>'lockpick'</span>) <span class='cm'>-- bool, necesar pt a sparge uși</span>`;
|
|
if(l.label&&l.label.includes("radio voice")) return `<span class='cm'>-- ${s} folosește voice channel pt radio</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>setVoiceProperty</span>(<span class='str'>'radioChannel'</span>, freq) <span class='cm'>-- intră pe frecvență radio</span>`;
|
|
if(l.label&&l.label.includes("weed items")) return `<span class='cm'>-- ${s} adaugă items weed în inventar</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>AddItem</span>(src, <span class='str'>'weed_brick'</span>, qty) <span class='cm'>-- adaugă produsul procesat</span>`;
|
|
if(l.label&&l.label.includes("inventory check")) return `<span class='cm'>-- ${s} verifică inventarul playerului</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>GetItemsByName</span>(src, item) <span class='cm'>-- caută items specifice în inventar</span>`;
|
|
return `<span class='cm'>-- ${s} apelează export din ${t}</span>\n<span class='fn'>exports</span>[<span class='str'>'${t}'</span>]:<span class='fn'>${l.label||'FunctionName'}</span>(...) <span class='cm'>-- apel cross-resource</span>`;
|
|
}
|
|
if(l.type==="event"){
|
|
if(l.label&&l.label.includes("OnPlayerLoaded")) return `<span class='cm'>-- ${s} ascultă când player-ul se încarcă</span>\n<span class='fn'>RegisterNetEvent</span>(<span class='str'>'QBCore:Client:OnPlayerLoaded'</span>) <span class='cm'>-- înregistrează event-ul</span>\n<span class='fn'>AddEventHandler</span>(<span class='str'>'QBCore:Client:OnPlayerLoaded'</span>, <span class='kw'>function</span>() <span class='cm'>-- callback la load</span>\n <span class='cm'>-- inițializare ${s}: UI, variabile, zone, blips</span>\n<span class='kw'>end</span>)`;
|
|
if(l.label&&l.label.includes("OnJobUpdate")) return `<span class='cm'>-- ${s} reacționează la schimbarea job-ului</span>\n<span class='fn'>RegisterNetEvent</span>(<span class='str'>'QBCore:Client:OnJobUpdate'</span>) <span class='cm'>-- se trigger automat de QBCore</span>\n<span class='fn'>AddEventHandler</span>(<span class='str'>'QBCore:Client:OnJobUpdate'</span>, <span class='kw'>function</span>(JobInfo) <span class='cm'>-- JobInfo={name,label,grade,...}</span>\n <span class='cm'>-- actualizare duty/UI pentru ${s}: verifică dacă e on-duty</span>\n<span class='kw'>end</span>)`;
|
|
if(l.label&&l.label.includes("openOutfitMenu")) return `<span class='cm'>-- ${s} deschide meniul de outfits din ${t}</span>\n<span class='fn'>TriggerEvent</span>(<span class='str'>'qb-clothing:client:openOutfitMenu'</span>) <span class='cm'>-- bridge event → 17mov outfit UI</span>`;
|
|
if(l.label&&l.label.includes("openMenu")) return `<span class='cm'>-- ${s} deschide clothing menu complet</span>\n<span class='fn'>TriggerEvent</span>(<span class='str'>'qb-clothing:client:openMenu'</span>) <span class='cm'>-- bridge → 17mov full clothing editor</span>`;
|
|
if(l.label&&l.label.includes("loadOutfit")) return `<span class='cm'>-- ${s} aplică un outfit predefinit</span>\n<span class='fn'>TriggerEvent</span>(<span class='str'>'qb-clothing:client:loadOutfit'</span>, outfitData) <span class='cm'>-- outfitData = tabel cu componentele ped</span>`;
|
|
if(l.label&&l.label.includes("SaveCurrentSkin")) return `<span class='cm'>-- ${s} salvează skin-ul curent în DB</span>\n<span class='fn'>TriggerEvent</span>(<span class='str'>'17mov_CharacterSystem:SaveCurrentSkin'</span>) <span class='cm'>-- salvare permanentă skin + clothing</span>`;
|
|
if(l.label&&l.label.includes("hunger")) return `<span class='cm'>-- ${s} trimite statusuri metabolice la HUD</span>\n<span class='fn'>TriggerClientEvent</span>(<span class='str'>'hud:client:UpdateNeeds'</span>, src, hunger, thirst) <span class='cm'>-- src=player, hunger/thirst=0-100</span>`;
|
|
if(l.label&&l.label.includes("stress")) return `<span class='cm'>-- ${s} eliberează stress la player</span>\n<span class='fn'>TriggerEvent</span>(<span class='str'>'hud:client:RelieveStress'</span>, amount) <span class='cm'>-- amount=cât stress se scade (int)</span>`;
|
|
if(l.label&&l.label.includes("jail")) return `<span class='cm'>-- ${s} trimite player la închisoare via ${t}</span>\n<span class='fn'>TriggerEvent</span>(<span class='str'>'police:client:JailPlayer'</span>, time) <span class='cm'>-- time=minute de pedeapsă</span>`;
|
|
if(l.label&&l.label.includes("dispatch")) return `<span class='cm'>-- ${s} trimite alertă prin dispatch</span>\n<span class='fn'>TriggerServerEvent</span>(<span class='str'>'dispatch:server:notify'</span>, {\n message = <span class='str'>'Alert text'</span>, <span class='cm'>-- textul alertei vizibil de poliție</span>\n code = <span class='str'>'10-XX'</span>, <span class='cm'>-- codul radio (10-71, 10-50, etc)</span>\n icon = <span class='str'>'fas fa-exclamation'</span>, <span class='cm'>-- iconița din notification</span>\n coords = playerCoords <span class='cm'>-- locația pe GPS/blip</span>\n})`;
|
|
if(l.label&&l.label.includes("policeAlert")) return `<span class='cm'>-- ${s} trimite alertă de furt vehicul</span>\n<span class='fn'>TriggerServerEvent</span>(<span class='str'>'dispatch:server:notify'</span>, {\n message = <span class='str'>'Vehicul furat!'</span>, <span class='cm'>-- alertă vizibil pe MDT</span>\n code = <span class='str'>'10-50'</span> <span class='cm'>-- cod furt vehicul</span>\n})`;
|
|
if(l.label&&l.label.includes("job registration")) return `<span class='cm'>-- ${s} se înregistrează ca job în ${t}</span>\n<span class='fn'>TriggerEvent</span>(<span class='str'>'17mov_JobCenter:RegisterJob'</span>, jobName, jobLabel) <span class='cm'>-- apare în lista de joburi</span>`;
|
|
if(l.label&&l.label.includes("police alerts")) return `<span class='cm'>-- ${s} trimite alerte de poliție la ${t}</span>\n<span class='fn'>TriggerServerEvent</span>(<span class='str'>'dispatch:server:notify'</span>, alertData) <span class='cm'>-- alertă dispatch cu coords + blip</span>`;
|
|
if(l.label&&l.label.includes("UpdateNeeds")) return `<span class='cm'>-- ${s} actualizează HUD-ul cu nevoi vitale</span>\n<span class='fn'>TriggerClientEvent</span>(<span class='str'>'hud:client:UpdateNeeds'</span>, src, hunger, thirst) <span class='cm'>-- după heal/revive</span>\n<span class='fn'>TriggerEvent</span>(<span class='str'>'hud:client:RelieveStress'</span>, amount) <span class='cm'>-- după tratament medical</span>`;
|
|
return `<span class='cm'>-- ${s} trigger event spre ${t}</span>\n<span class='fn'>TriggerEvent</span>(<span class='str'>'${l.label||'eventName'}'</span>) <span class='cm'>-- event cross-resource</span>`;
|
|
}
|
|
if(l.type==="config"){
|
|
if(l.label&&l.label.includes("Config.Outfits")) return `<span class='cm'>-- ${s} citește outfitul din config-ul ${t}</span>\n<span class='kw'>Config</span>.Outfits = {\n [<span class='str'>'mechanic'</span>] = { <span class='cm'>-- numele job-ului</span>\n <span class='cm'>-- componente ped (hat, torso, pants, shoes, etc)</span>\n male = {...}, <span class='cm'>-- outfit masculin</span>\n female = {...} <span class='cm'>-- outfit feminin</span>\n }\n}`;
|
|
if(l.label&&l.label.includes("outfit bridge")) return `<span class='cm'>-- ${s} folosește bridge-ul de clothing din ${t}</span>\n<span class='kw'>Config</span>.ClothingScript = <span class='str'>'${t}'</span> <span class='cm'>-- scriptul de clothing folosit pe server</span>`;
|
|
if(l.label&&l.label.includes("job mechanic")) return `<span class='cm'>-- ${s} verifică job-ul de mecanic din config</span>\n<span class='kw'>Config</span>.RequiredJob = <span class='str'>'mechanic'</span> <span class='cm'>-- job necesar pentru acces la funcționalitate</span>`;
|
|
if(l.label&&l.label.includes("car lift")) return `<span class='cm'>-- ${s} se integrează cu shop-urile din ${t}</span>\n<span class='kw'>Config</span>.CarLiftShops = {...} <span class='cm'>-- lista shop-urilor unde e montat car lift-ul</span>`;
|
|
return `<span class='cm'>-- ${s} citește configurare din ${t}</span>\n<span class='kw'>Config</span>.${l.label||'Setting'} = ... <span class='cm'>-- valoare din config.lua</span>`;
|
|
}
|
|
if(l.type==="dep"){
|
|
return `<span class='cm'>-- ${s} depinde de ${t} (trebuie pornit înainte)</span>\n<span class='cm'>-- în fxmanifest.lua al resursei ${s}:</span>\n<span class='fn'>dependency</span> <span class='str'>'${t}'</span> <span class='cm'>-- serverul pornește ${t} automat înainte de ${s}</span>`;
|
|
}
|
|
return `<span class='cm'>-- ${s} → ${t}: ${l.label||'connection'}</span>`;
|
|
}
|
|
|
|
// === PINNED TOOLTIP STATE ===
|
|
let tooltipPinned=false;
|
|
function unpinTooltip(){
|
|
const tt=document.getElementById("tooltip");
|
|
tt.classList.remove("pinned");
|
|
tt.style.display="none";
|
|
tooltipPinned=false;
|
|
}
|
|
|
|
// === LINK CLICK → PIN TOOLTIP WITH CODE ===
|
|
linkHit.on("click",(e,d)=>{
|
|
e.stopPropagation();
|
|
const tt=document.getElementById("tooltip");
|
|
const sid=typeof d.source==="object"?d.source.id:d.source;
|
|
const tid=typeof d.target==="object"?d.target.id:d.target;
|
|
const between=links.filter(l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const t=typeof l.target==="object"?l.target.id:l.target;
|
|
return (s===sid&&t===tid)||(s===tid&&t===sid);
|
|
});
|
|
let html=`<span class="tt-close" onclick="unpinTooltip()">✕</span>`;
|
|
html+=`<b>${sid} ↔ ${tid}</b><br>`;
|
|
html+=`<span style="color:#666;font-size:10px">${between.length} conexiune(i)</span><br>`;
|
|
between.forEach(l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const color=linkColors[l.type]||"#666";
|
|
html+=`<div class="tt-row">${s} → ${s===sid?tid:sid} <span class="tt-type" style="background:${color}30;color:${color};border:1px solid ${color}">${l.type}</span> ${l.label||""}</div>`;
|
|
html+=`<div class="tt-code">${getCodeSnippet(l)}</div>`;
|
|
});
|
|
tt.innerHTML=html;
|
|
tt.style.display="block";
|
|
tt.classList.add("pinned");
|
|
tooltipPinned=true;
|
|
// Position near click
|
|
const x=Math.min(e.clientX+14,window.innerWidth-440);
|
|
const y=Math.min(e.clientY-10,window.innerHeight-300);
|
|
tt.style.left=x+"px";tt.style.top=y+"px";
|
|
// Also highlight in sidebar
|
|
let h=`<h2 style="color:#c084fc">🔗 ${sid} ↔ ${tid}</h2>`;
|
|
h+=`<p style="color:#888;font-size:11px;margin-bottom:8px">${between.length} conexiune(i) între aceste resurse</p>`;
|
|
between.forEach(l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const color=linkColors[l.type]||"#666";
|
|
h+=`<div class="dep-item" style="color:${color}">${s} → ${s===sid?tid:sid} <span class="tag" style="border-color:${color};color:${color}">${l.type}</span> <span class="tag">${l.label||""}</span></div>`;
|
|
});
|
|
document.getElementById("info").innerHTML=h;
|
|
// Highlight nodes
|
|
d3.selectAll(".node-circle").classed("dimmed",n=>n.id!==sid&&n.id!==tid).classed("highlighted",n=>n.id===sid||n.id===tid);
|
|
d3.selectAll(".node-label").classed("dimmed",n=>n.id!==sid&&n.id!==tid);
|
|
link.classed("dimmed",l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const t=typeof l.target==="object"?l.target.id:l.target;
|
|
return !((s===sid&&t===tid)||(s===tid&&t===sid));
|
|
}).classed("highlighted",l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const t=typeof l.target==="object"?l.target.id:l.target;
|
|
return (s===sid&&t===tid)||(s===tid&&t===sid);
|
|
});
|
|
});
|
|
|
|
// === LINK HOVER TOOLTIP (only when not pinned) ===
|
|
linkHit.on("mouseenter",(e,d)=>{
|
|
if(tooltipPinned) return;
|
|
const tt=document.getElementById("tooltip");
|
|
const sid=typeof d.source==="object"?d.source.id:d.source;
|
|
const tid=typeof d.target==="object"?d.target.id:d.target;
|
|
const between=links.filter(l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const t=typeof l.target==="object"?l.target.id:l.target;
|
|
return (s===sid&&t===tid)||(s===tid&&t===sid);
|
|
});
|
|
let html=`<b>${sid} ↔ ${tid}</b><br>`;
|
|
between.forEach(l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const color=linkColors[l.type]||"#666";
|
|
html+=`<div class="tt-row">${s} → ${s===sid?tid:sid} <span class="tt-type" style="background:${color}30;color:${color};border:1px solid ${color}">${l.type}</span> ${l.label||""}</div>`;
|
|
});
|
|
html+=`<div style="color:#555;font-size:9px;margin-top:4px">click → cod & detalii</div>`;
|
|
tt.innerHTML=html;tt.style.display="block";
|
|
tt.style.left=(e.clientX+14)+"px";tt.style.top=(e.clientY-10)+"px";
|
|
}).on("mousemove",(e)=>{
|
|
if(tooltipPinned) return;
|
|
const tt=document.getElementById("tooltip");
|
|
tt.style.left=(e.clientX+14)+"px";tt.style.top=(e.clientY-10)+"px";
|
|
}).on("mouseleave",()=>{
|
|
if(tooltipPinned) return;
|
|
document.getElementById("tooltip").style.display="none";
|
|
});
|
|
|
|
// === SEARCH ===
|
|
document.getElementById("search").addEventListener("input",function(){
|
|
const q=this.value.toLowerCase().trim();
|
|
if(!q){resetHighlight();selected=null;return}
|
|
const matched=filteredNodes.filter(n=>n.id.toLowerCase().includes(q)||(n.keywords||"").includes(q)||(n.desc||"").toLowerCase().includes(q));
|
|
if(matched.length===1){selectNode(matched[0])}
|
|
else if(matched.length>0){
|
|
const ids=new Set(matched.map(n=>n.id));
|
|
d3.selectAll(".node-circle").classed("dimmed",n=>!ids.has(n.id));
|
|
d3.selectAll(".node-label").classed("dimmed",n=>!ids.has(n.id));
|
|
link.classed("dimmed",true);
|
|
let h=`<h2 style="color:#8b5cf6">🔍 ${matched.length} rezultate pt "${q}"</h2>`;
|
|
matched.forEach(m=>h+=`<div class="dep-item" style="color:#8b5cf6" data-id="${m.id}">• ${m.id} <span class="tag">${teamMap[m.team]||m.team}</span></div>`);
|
|
document.getElementById("info").innerHTML=h;
|
|
document.querySelectorAll(".dep-item").forEach(el=>el.addEventListener("click",()=>{
|
|
const n=filteredNodes.find(n=>n.id===el.dataset.id);if(n)selectNode(n);
|
|
}));
|
|
}
|
|
});
|
|
|
|
// === TEAM FILTER ===
|
|
document.querySelectorAll("#teamFilter button").forEach(btn=>{
|
|
btn.addEventListener("click",()=>{
|
|
document.querySelectorAll("#teamFilter button").forEach(b=>b.classList.remove("active"));
|
|
btn.classList.add("active");
|
|
const team=btn.dataset.team;
|
|
if(team==="all"){resetHighlight();selected=null;return}
|
|
const matched=new Set(filteredNodes.filter(n=>n.team===team).map(n=>n.id));
|
|
d3.selectAll(".node-circle").classed("dimmed",n=>!matched.has(n.id));
|
|
d3.selectAll(".node-label").classed("dimmed",n=>!matched.has(n.id));
|
|
link.classed("dimmed",l=>{
|
|
const s=typeof l.source==="object"?l.source.id:l.source;
|
|
const t=typeof l.target==="object"?l.target.id:l.target;
|
|
return !matched.has(s)&&!matched.has(t);
|
|
}).classed("highlighted",false);
|
|
});
|
|
});
|
|
|
|
// === NODE TOOLTIP ===
|
|
node.on("mouseenter",(e,d)=>{
|
|
const tt=document.getElementById("tooltip");
|
|
tt.style.display="block";
|
|
const out=links.filter(l=>(typeof l.source==="object"?l.source.id:l.source)===d.id).length;
|
|
const inc=links.filter(l=>(typeof l.target==="object"?l.target.id:l.target)===d.id).length;
|
|
tt.innerHTML=`<b style="color:${nodeColor(d)}">${d.id}</b><br><span style="color:#888">${d.desc||""}</span><br><span style="color:#f97316">→${out}</span> <span style="color:#22d3ee">←${inc}</span> <span class="tag">${teamMap[d.team]||d.team}</span>`;
|
|
tt.style.left=(e.clientX+14)+"px";tt.style.top=(e.clientY-10)+"px";
|
|
}).on("mousemove",(e)=>{
|
|
const tt=document.getElementById("tooltip");
|
|
tt.style.left=(e.clientX+14)+"px";tt.style.top=(e.clientY-10)+"px";
|
|
}).on("mouseleave",()=>{document.getElementById("tooltip").style.display="none"});
|
|
</script>
|
|
</body>
|
|
</html>
|