@almadar/ui 5.57.0 → 5.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/avl/index.js CHANGED
@@ -32083,13 +32083,13 @@ var init_MapView = __esm({
32083
32083
  shadowSize: [41, 41]
32084
32084
  });
32085
32085
  L.Marker.prototype.options.icon = defaultIcon;
32086
- const { useEffect: useEffect85, useRef: useRef77, useCallback: useCallback125, useState: useState121 } = React90__default;
32086
+ const { useEffect: useEffect86, useRef: useRef77, useCallback: useCallback125, useState: useState121 } = React90__default;
32087
32087
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
32088
32088
  const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
32089
32089
  function MapUpdater({ centerLat, centerLng, zoom }) {
32090
32090
  const map = useMap();
32091
32091
  const prevRef = useRef77({ centerLat, centerLng, zoom });
32092
- useEffect85(() => {
32092
+ useEffect86(() => {
32093
32093
  const prev = prevRef.current;
32094
32094
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
32095
32095
  map.setView([centerLat, centerLng], zoom);
@@ -32100,7 +32100,7 @@ var init_MapView = __esm({
32100
32100
  }
32101
32101
  function MapClickHandler({ onMapClick }) {
32102
32102
  const map = useMap();
32103
- useEffect85(() => {
32103
+ useEffect86(() => {
32104
32104
  if (!onMapClick) return;
32105
32105
  const handler = (e) => {
32106
32106
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -40152,6 +40152,9 @@ var init_GraphCanvas = __esm({
40152
40152
  actions,
40153
40153
  onNodeClick,
40154
40154
  nodeClickEvent,
40155
+ selectedNodeId,
40156
+ repulsion = 800,
40157
+ linkDistance = 100,
40155
40158
  layout = "force",
40156
40159
  entity,
40157
40160
  isLoading = false,
@@ -40167,6 +40170,35 @@ var init_GraphCanvas = __esm({
40167
40170
  const [hoveredNode, setHoveredNode] = useState(null);
40168
40171
  const nodesRef = useRef([]);
40169
40172
  const [, forceUpdate] = useState(0);
40173
+ const interactionRef = useRef({
40174
+ mode: "none",
40175
+ dragNodeId: null,
40176
+ startMouse: { x: 0, y: 0 },
40177
+ startOffset: { x: 0, y: 0 },
40178
+ downPos: { x: 0, y: 0 }
40179
+ });
40180
+ const toCoords = useCallback(
40181
+ (e) => {
40182
+ const canvas = canvasRef.current;
40183
+ if (!canvas) return null;
40184
+ const rect = canvas.getBoundingClientRect();
40185
+ const screenX = e.clientX - rect.left;
40186
+ const screenY = e.clientY - rect.top;
40187
+ return {
40188
+ screenX,
40189
+ screenY,
40190
+ graphX: (screenX - offset.x) / zoom,
40191
+ graphY: (screenY - offset.y) / zoom
40192
+ };
40193
+ },
40194
+ [offset, zoom]
40195
+ );
40196
+ const nodeAt = useCallback((graphX, graphY) => {
40197
+ return nodesRef.current.find((n) => {
40198
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
40199
+ return dist < (n.size || 8) + 4;
40200
+ });
40201
+ }, []);
40170
40202
  const handleAction = useCallback(
40171
40203
  (action) => {
40172
40204
  if (action.event) {
@@ -40232,7 +40264,7 @@ var init_GraphCanvas = __esm({
40232
40264
  const dx = nodes[j].x - nodes[i].x;
40233
40265
  const dy = nodes[j].y - nodes[i].y;
40234
40266
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
40235
- const force = 800 / (dist * dist);
40267
+ const force = repulsion / (dist * dist);
40236
40268
  const fx = dx / dist * force;
40237
40269
  const fy = dy / dist * force;
40238
40270
  nodes[i].fx -= fx;
@@ -40248,7 +40280,7 @@ var init_GraphCanvas = __esm({
40248
40280
  const dx = target.x - source.x;
40249
40281
  const dy = target.y - source.y;
40250
40282
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
40251
- const force = (dist - 100) * 0.05;
40283
+ const force = (dist - linkDistance) * 0.05;
40252
40284
  const fx = dx / dist * force;
40253
40285
  const fy = dy / dist * force;
40254
40286
  source.fx += fx;
@@ -40282,7 +40314,7 @@ var init_GraphCanvas = __esm({
40282
40314
  return () => {
40283
40315
  cancelAnimationFrame(animRef.current);
40284
40316
  };
40285
- }, [propNodes, propEdges, layout]);
40317
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
40286
40318
  useEffect(() => {
40287
40319
  const canvas = canvasRef.current;
40288
40320
  if (!canvas) return;
@@ -40318,18 +40350,25 @@ var init_GraphCanvas = __esm({
40318
40350
  const size = node.size || 8;
40319
40351
  const color = node.color || getGroupColor(node.group, groups);
40320
40352
  const isHovered = hoveredNode === node.id;
40353
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
40354
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
40321
40355
  ctx.beginPath();
40322
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
40356
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
40323
40357
  ctx.fillStyle = color;
40324
40358
  ctx.fill();
40325
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
40326
- ctx.lineWidth = isHovered ? 2 : 1;
40359
+ if (isSelected) {
40360
+ ctx.strokeStyle = "var(--color-accent)";
40361
+ ctx.lineWidth = 3;
40362
+ } else {
40363
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
40364
+ ctx.lineWidth = isHovered ? 2 : 1;
40365
+ }
40327
40366
  ctx.stroke();
40328
40367
  if (showLabels && node.label) {
40329
40368
  ctx.fillStyle = "#666666";
40330
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
40369
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
40331
40370
  ctx.textAlign = "center";
40332
- ctx.fillText(node.label, node.x, node.y + size + 12);
40371
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
40333
40372
  }
40334
40373
  }
40335
40374
  ctx.restore();
@@ -40340,6 +40379,97 @@ var init_GraphCanvas = __esm({
40340
40379
  setZoom(1);
40341
40380
  setOffset({ x: 0, y: 0 });
40342
40381
  }, []);
40382
+ const handleWheel = useCallback(
40383
+ (e) => {
40384
+ if (!interactive) return;
40385
+ e.preventDefault();
40386
+ const coords = toCoords(e);
40387
+ if (!coords) return;
40388
+ const oldZoom = zoom;
40389
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
40390
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
40391
+ if (newZoom === oldZoom) return;
40392
+ setOffset((o) => ({
40393
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
40394
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
40395
+ }));
40396
+ setZoom(newZoom);
40397
+ },
40398
+ [interactive, toCoords, zoom]
40399
+ );
40400
+ const handleMouseDown = useCallback(
40401
+ (e) => {
40402
+ const coords = toCoords(e);
40403
+ if (!coords) return;
40404
+ const node = nodeAt(coords.graphX, coords.graphY);
40405
+ const state = interactionRef.current;
40406
+ state.downPos = { x: e.clientX, y: e.clientY };
40407
+ state.startMouse = { x: e.clientX, y: e.clientY };
40408
+ state.startOffset = { ...offset };
40409
+ if (draggable && node) {
40410
+ state.mode = "dragging";
40411
+ state.dragNodeId = node.id;
40412
+ } else if (interactive) {
40413
+ state.mode = "panning";
40414
+ state.dragNodeId = null;
40415
+ } else {
40416
+ state.mode = "none";
40417
+ state.dragNodeId = null;
40418
+ }
40419
+ },
40420
+ [toCoords, nodeAt, draggable, interactive, offset]
40421
+ );
40422
+ const handleMouseMove = useCallback(
40423
+ (e) => {
40424
+ const state = interactionRef.current;
40425
+ if (state.mode === "panning") {
40426
+ const dx = e.clientX - state.startMouse.x;
40427
+ const dy = e.clientY - state.startMouse.y;
40428
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
40429
+ return;
40430
+ }
40431
+ if (state.mode === "dragging" && state.dragNodeId) {
40432
+ const coords2 = toCoords(e);
40433
+ if (!coords2) return;
40434
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
40435
+ if (node2) {
40436
+ node2.x = coords2.graphX;
40437
+ node2.y = coords2.graphY;
40438
+ node2.vx = 0;
40439
+ node2.vy = 0;
40440
+ forceUpdate((n) => n + 1);
40441
+ }
40442
+ return;
40443
+ }
40444
+ const coords = toCoords(e);
40445
+ if (!coords) return;
40446
+ const node = nodeAt(coords.graphX, coords.graphY);
40447
+ setHoveredNode(node?.id ?? null);
40448
+ },
40449
+ [toCoords, nodeAt]
40450
+ );
40451
+ const handleMouseUp = useCallback(
40452
+ (e) => {
40453
+ const state = interactionRef.current;
40454
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
40455
+ state.mode = "none";
40456
+ state.dragNodeId = null;
40457
+ if (moved < 4) {
40458
+ const coords = toCoords(e);
40459
+ if (!coords) return;
40460
+ const node = nodeAt(coords.graphX, coords.graphY);
40461
+ if (node) {
40462
+ handleNodeClick(node);
40463
+ }
40464
+ }
40465
+ },
40466
+ [toCoords, nodeAt, handleNodeClick]
40467
+ );
40468
+ const handleMouseLeave = useCallback(() => {
40469
+ interactionRef.current.mode = "none";
40470
+ interactionRef.current.dragNodeId = null;
40471
+ setHoveredNode(null);
40472
+ }, []);
40343
40473
  if (isLoading) {
40344
40474
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
40345
40475
  }
@@ -40401,20 +40531,11 @@ var init_GraphCanvas = __esm({
40401
40531
  height,
40402
40532
  className: "w-full cursor-grab active:cursor-grabbing",
40403
40533
  style: { height },
40404
- onClick: (e) => {
40405
- const canvas = canvasRef.current;
40406
- if (!canvas) return;
40407
- const rect = canvas.getBoundingClientRect();
40408
- const x = (e.clientX - rect.left - offset.x) / zoom;
40409
- const y = (e.clientY - rect.top - offset.y) / zoom;
40410
- const clickedNode = nodesRef.current.find((n) => {
40411
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
40412
- return dist < (n.size || 8) + 4;
40413
- });
40414
- if (clickedNode) {
40415
- handleNodeClick(clickedNode);
40416
- }
40417
- }
40534
+ onWheel: handleWheel,
40535
+ onMouseDown: handleMouseDown,
40536
+ onMouseMove: handleMouseMove,
40537
+ onMouseUp: handleMouseUp,
40538
+ onMouseLeave: handleMouseLeave
40418
40539
  }
40419
40540
  ) }),
40420
40541
  groups.length > 1 && /* @__PURE__ */ jsx(HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxs(HStack, { gap: "xs", align: "center", children: [
@@ -51510,12 +51631,25 @@ function pathToFeatures(path) {
51510
51631
  sprite: `${CDN6}world-map/road_straight.png`
51511
51632
  }));
51512
51633
  }
51634
+ function heroToUnit(hero) {
51635
+ return {
51636
+ id: hero.id,
51637
+ position: { x: hero.x, y: hero.y },
51638
+ name: "Hero",
51639
+ team: "player",
51640
+ sprite: HERO_SPRITE,
51641
+ unitType: "amir",
51642
+ health: 1,
51643
+ maxHealth: 1
51644
+ };
51645
+ }
51513
51646
  function TowerDefenseBoard({
51514
51647
  entity,
51515
51648
  tiles: propTiles,
51516
51649
  path: propPath,
51517
51650
  towers: propTowers,
51518
51651
  creeps: propCreeps,
51652
+ hero: propHero,
51519
51653
  gold: propGold,
51520
51654
  lives: propLives,
51521
51655
  wave: propWave,
@@ -51531,6 +51665,7 @@ function TowerDefenseBoard({
51531
51665
  startWaveEvent,
51532
51666
  playAgainEvent,
51533
51667
  gameEndEvent,
51668
+ moveHeroEvent,
51534
51669
  className
51535
51670
  }) {
51536
51671
  const board = boardEntity(entity) ?? {};
@@ -51542,6 +51677,7 @@ function TowerDefenseBoard({
51542
51677
  const path = rawPath.length > 0 ? rawPath : DEFAULT_TD_PATH;
51543
51678
  const towers = propTowers ?? rows(board.towers);
51544
51679
  const creeps = propCreeps ?? rows(board.creeps);
51680
+ const hero = propHero ?? { id: "hero", x: 8, y: 8 };
51545
51681
  const gold = propGold ?? num(board.gold, 100);
51546
51682
  const lives = propLives ?? num(board.lives, 20);
51547
51683
  const wave = propWave ?? num(board.wave, 1);
@@ -51559,6 +51695,31 @@ function TowerDefenseBoard({
51559
51695
  if (result === "none") {
51560
51696
  emittedGameEnd.current = false;
51561
51697
  }
51698
+ const moveHeroEventRef = useRef(moveHeroEvent);
51699
+ moveHeroEventRef.current = moveHeroEvent;
51700
+ const resultRef = useRef(result);
51701
+ resultRef.current = result;
51702
+ useEffect(() => {
51703
+ function onKeyDown(e) {
51704
+ const evt = moveHeroEventRef.current;
51705
+ if (!evt || resultRef.current !== "none") return;
51706
+ let dx = 0;
51707
+ let dy = 0;
51708
+ if (e.key === "ArrowUp") {
51709
+ dy = -1;
51710
+ } else if (e.key === "ArrowDown") {
51711
+ dy = 1;
51712
+ } else if (e.key === "ArrowLeft") {
51713
+ dx = -1;
51714
+ } else if (e.key === "ArrowRight") {
51715
+ dx = 1;
51716
+ } else return;
51717
+ e.preventDefault();
51718
+ eventBus.emit(`UI:${evt}`, { dx, dy });
51719
+ }
51720
+ window.addEventListener("keydown", onKeyDown);
51721
+ return () => window.removeEventListener("keydown", onKeyDown);
51722
+ }, [eventBus]);
51562
51723
  const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
51563
51724
  const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
51564
51725
  const validMoves = useMemo(() => {
@@ -51570,7 +51731,8 @@ function TowerDefenseBoard({
51570
51731
  const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
51571
51732
  const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
51572
51733
  const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
51573
- const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
51734
+ const heroUnit = useMemo(() => heroToUnit(hero), [hero]);
51735
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits, heroUnit], [towerUnits, creepUnits, heroUnit]);
51574
51736
  const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
51575
51737
  const handleTileClick = useCallback((x, y) => {
51576
51738
  if (result !== "none") return;
@@ -51669,7 +51831,7 @@ function TowerDefenseBoard({
51669
51831
  ] }) })
51670
51832
  ] });
51671
51833
  }
51672
- var CDN6, TD_GRID_W, TD_GRID_H, TERRAIN_SPRITES, DEFAULT_TD_PATH, DEFAULT_TD_TILES, TOWER_SPRITE, CREEP_SPRITE;
51834
+ var CDN6, TD_GRID_W, TD_GRID_H, TERRAIN_SPRITES, DEFAULT_TD_PATH, DEFAULT_TD_TILES, TOWER_SPRITE, CREEP_SPRITE, HERO_SPRITE;
51673
51835
  var init_TowerDefenseBoard = __esm({
51674
51836
  "components/game/organisms/TowerDefenseBoard.tsx"() {
51675
51837
  "use client";
@@ -51743,8 +51905,9 @@ var init_TowerDefenseBoard = __esm({
51743
51905
  { x: 13, y: 15 }
51744
51906
  ];
51745
51907
  DEFAULT_TD_TILES = buildDefaultTDTiles();
51746
- TOWER_SPRITE = `${CDN6}units/guardian.png`;
51747
- CREEP_SPRITE = `${CDN6}units/scrapper.png`;
51908
+ TOWER_SPRITE = `${CDN6}sprite-sheets/guardian-sprite-sheet-se.png`;
51909
+ CREEP_SPRITE = `${CDN6}sprite-sheets/scrapper-sprite-sheet-se.png`;
51910
+ HERO_SPRITE = `${CDN6}sprite-sheets/amir-sprite-sheet-se.png`;
51748
51911
  TowerDefenseBoard.displayName = "TowerDefenseBoard";
51749
51912
  }
51750
51913
  });
@@ -59,6 +59,12 @@ export interface GraphCanvasProps {
59
59
  nodeClickEvent?: EventEmit<{
60
60
  id: string;
61
61
  }>;
62
+ /** Currently selected node id (rendered emphasized) */
63
+ selectedNodeId?: string;
64
+ /** Force-sim repulsion strength between nodes (larger ⇒ more spread out) */
65
+ repulsion?: number;
66
+ /** Force-sim target edge length (larger ⇒ more spread out) */
67
+ linkDistance?: number;
62
68
  /** Layout algorithm */
63
69
  layout?: "force" | "circular" | "grid";
64
70
  /** Entity name for schema-driven auto-fetch */
@@ -30,6 +30,11 @@ export interface TowerDefenseCreep {
30
30
  pathIndex: number;
31
31
  speed: number;
32
32
  }
33
+ export interface TowerDefenseHero {
34
+ id: string;
35
+ x: number;
36
+ y: number;
37
+ }
33
38
  export interface TowerDefenseBoardProps extends DisplayStateProps {
34
39
  entity?: EntityRow | readonly EntityRow[];
35
40
  tiles?: TowerDefenseTile[];
@@ -42,6 +47,7 @@ export interface TowerDefenseBoardProps extends DisplayStateProps {
42
47
  maxWaves?: number;
43
48
  result?: 'none' | 'won' | 'lost';
44
49
  waveActive?: boolean;
50
+ hero?: TowerDefenseHero;
45
51
  towerCost?: number;
46
52
  scale?: number;
47
53
  unitScale?: number;
@@ -58,9 +64,13 @@ export interface TowerDefenseBoardProps extends DisplayStateProps {
58
64
  gameEndEvent?: EventEmit<{
59
65
  result: 'won' | 'lost';
60
66
  }>;
67
+ moveHeroEvent?: EventEmit<{
68
+ dx: number;
69
+ dy: number;
70
+ }>;
61
71
  className?: string;
62
72
  }
63
- export declare function TowerDefenseBoard({ entity, tiles: propTiles, path: propPath, towers: propTowers, creeps: propCreeps, gold: propGold, lives: propLives, wave: propWave, maxWaves: propMaxWaves, result: propResult, waveActive: propWaveActive, towerCost, scale, unitScale, spriteHeightRatio, spriteMaxWidthRatio, placeTowerEvent, startWaveEvent, playAgainEvent, gameEndEvent, className, }: TowerDefenseBoardProps): React.JSX.Element;
73
+ export declare function TowerDefenseBoard({ entity, tiles: propTiles, path: propPath, towers: propTowers, creeps: propCreeps, hero: propHero, gold: propGold, lives: propLives, wave: propWave, maxWaves: propMaxWaves, result: propResult, waveActive: propWaveActive, towerCost, scale, unitScale, spriteHeightRatio, spriteMaxWidthRatio, placeTowerEvent, startWaveEvent, playAgainEvent, gameEndEvent, moveHeroEvent, className, }: TowerDefenseBoardProps): React.JSX.Element;
64
74
  export declare namespace TowerDefenseBoard {
65
75
  var displayName: string;
66
76
  }