@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.
@@ -29522,13 +29522,13 @@ var init_MapView = __esm({
29522
29522
  shadowSize: [41, 41]
29523
29523
  });
29524
29524
  L.Marker.prototype.options.icon = defaultIcon;
29525
- const { useEffect: useEffect79, useRef: useRef75, useCallback: useCallback118, useState: useState109 } = React82__namespace.default;
29525
+ const { useEffect: useEffect80, useRef: useRef75, useCallback: useCallback118, useState: useState109 } = React82__namespace.default;
29526
29526
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29527
29527
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29528
29528
  function MapUpdater({ centerLat, centerLng, zoom }) {
29529
29529
  const map = useMap();
29530
29530
  const prevRef = useRef75({ centerLat, centerLng, zoom });
29531
- useEffect79(() => {
29531
+ useEffect80(() => {
29532
29532
  const prev = prevRef.current;
29533
29533
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29534
29534
  map.setView([centerLat, centerLng], zoom);
@@ -29539,7 +29539,7 @@ var init_MapView = __esm({
29539
29539
  }
29540
29540
  function MapClickHandler({ onMapClick }) {
29541
29541
  const map = useMap();
29542
- useEffect79(() => {
29542
+ useEffect80(() => {
29543
29543
  if (!onMapClick) return;
29544
29544
  const handler = (e) => {
29545
29545
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -38000,6 +38000,9 @@ var init_GraphCanvas = __esm({
38000
38000
  actions,
38001
38001
  onNodeClick,
38002
38002
  nodeClickEvent,
38003
+ selectedNodeId,
38004
+ repulsion = 800,
38005
+ linkDistance = 100,
38003
38006
  layout = "force",
38004
38007
  entity,
38005
38008
  isLoading = false,
@@ -38015,6 +38018,35 @@ var init_GraphCanvas = __esm({
38015
38018
  const [hoveredNode, setHoveredNode] = React82.useState(null);
38016
38019
  const nodesRef = React82.useRef([]);
38017
38020
  const [, forceUpdate] = React82.useState(0);
38021
+ const interactionRef = React82.useRef({
38022
+ mode: "none",
38023
+ dragNodeId: null,
38024
+ startMouse: { x: 0, y: 0 },
38025
+ startOffset: { x: 0, y: 0 },
38026
+ downPos: { x: 0, y: 0 }
38027
+ });
38028
+ const toCoords = React82.useCallback(
38029
+ (e) => {
38030
+ const canvas = canvasRef.current;
38031
+ if (!canvas) return null;
38032
+ const rect = canvas.getBoundingClientRect();
38033
+ const screenX = e.clientX - rect.left;
38034
+ const screenY = e.clientY - rect.top;
38035
+ return {
38036
+ screenX,
38037
+ screenY,
38038
+ graphX: (screenX - offset.x) / zoom,
38039
+ graphY: (screenY - offset.y) / zoom
38040
+ };
38041
+ },
38042
+ [offset, zoom]
38043
+ );
38044
+ const nodeAt = React82.useCallback((graphX, graphY) => {
38045
+ return nodesRef.current.find((n) => {
38046
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
38047
+ return dist < (n.size || 8) + 4;
38048
+ });
38049
+ }, []);
38018
38050
  const handleAction = React82.useCallback(
38019
38051
  (action) => {
38020
38052
  if (action.event) {
@@ -38080,7 +38112,7 @@ var init_GraphCanvas = __esm({
38080
38112
  const dx = nodes[j].x - nodes[i].x;
38081
38113
  const dy = nodes[j].y - nodes[i].y;
38082
38114
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38083
- const force = 800 / (dist * dist);
38115
+ const force = repulsion / (dist * dist);
38084
38116
  const fx = dx / dist * force;
38085
38117
  const fy = dy / dist * force;
38086
38118
  nodes[i].fx -= fx;
@@ -38096,7 +38128,7 @@ var init_GraphCanvas = __esm({
38096
38128
  const dx = target.x - source.x;
38097
38129
  const dy = target.y - source.y;
38098
38130
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
38099
- const force = (dist - 100) * 0.05;
38131
+ const force = (dist - linkDistance) * 0.05;
38100
38132
  const fx = dx / dist * force;
38101
38133
  const fy = dy / dist * force;
38102
38134
  source.fx += fx;
@@ -38130,7 +38162,7 @@ var init_GraphCanvas = __esm({
38130
38162
  return () => {
38131
38163
  cancelAnimationFrame(animRef.current);
38132
38164
  };
38133
- }, [propNodes, propEdges, layout]);
38165
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
38134
38166
  React82.useEffect(() => {
38135
38167
  const canvas = canvasRef.current;
38136
38168
  if (!canvas) return;
@@ -38166,18 +38198,25 @@ var init_GraphCanvas = __esm({
38166
38198
  const size = node.size || 8;
38167
38199
  const color = node.color || getGroupColor(node.group, groups);
38168
38200
  const isHovered = hoveredNode === node.id;
38201
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
38202
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
38169
38203
  ctx.beginPath();
38170
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
38204
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
38171
38205
  ctx.fillStyle = color;
38172
38206
  ctx.fill();
38173
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38174
- ctx.lineWidth = isHovered ? 2 : 1;
38207
+ if (isSelected) {
38208
+ ctx.strokeStyle = "var(--color-accent)";
38209
+ ctx.lineWidth = 3;
38210
+ } else {
38211
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
38212
+ ctx.lineWidth = isHovered ? 2 : 1;
38213
+ }
38175
38214
  ctx.stroke();
38176
38215
  if (showLabels && node.label) {
38177
38216
  ctx.fillStyle = "#666666";
38178
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
38217
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
38179
38218
  ctx.textAlign = "center";
38180
- ctx.fillText(node.label, node.x, node.y + size + 12);
38219
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
38181
38220
  }
38182
38221
  }
38183
38222
  ctx.restore();
@@ -38188,6 +38227,97 @@ var init_GraphCanvas = __esm({
38188
38227
  setZoom(1);
38189
38228
  setOffset({ x: 0, y: 0 });
38190
38229
  }, []);
38230
+ const handleWheel = React82.useCallback(
38231
+ (e) => {
38232
+ if (!interactive) return;
38233
+ e.preventDefault();
38234
+ const coords = toCoords(e);
38235
+ if (!coords) return;
38236
+ const oldZoom = zoom;
38237
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
38238
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
38239
+ if (newZoom === oldZoom) return;
38240
+ setOffset((o) => ({
38241
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
38242
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
38243
+ }));
38244
+ setZoom(newZoom);
38245
+ },
38246
+ [interactive, toCoords, zoom]
38247
+ );
38248
+ const handleMouseDown = React82.useCallback(
38249
+ (e) => {
38250
+ const coords = toCoords(e);
38251
+ if (!coords) return;
38252
+ const node = nodeAt(coords.graphX, coords.graphY);
38253
+ const state = interactionRef.current;
38254
+ state.downPos = { x: e.clientX, y: e.clientY };
38255
+ state.startMouse = { x: e.clientX, y: e.clientY };
38256
+ state.startOffset = { ...offset };
38257
+ if (draggable && node) {
38258
+ state.mode = "dragging";
38259
+ state.dragNodeId = node.id;
38260
+ } else if (interactive) {
38261
+ state.mode = "panning";
38262
+ state.dragNodeId = null;
38263
+ } else {
38264
+ state.mode = "none";
38265
+ state.dragNodeId = null;
38266
+ }
38267
+ },
38268
+ [toCoords, nodeAt, draggable, interactive, offset]
38269
+ );
38270
+ const handleMouseMove = React82.useCallback(
38271
+ (e) => {
38272
+ const state = interactionRef.current;
38273
+ if (state.mode === "panning") {
38274
+ const dx = e.clientX - state.startMouse.x;
38275
+ const dy = e.clientY - state.startMouse.y;
38276
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
38277
+ return;
38278
+ }
38279
+ if (state.mode === "dragging" && state.dragNodeId) {
38280
+ const coords2 = toCoords(e);
38281
+ if (!coords2) return;
38282
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
38283
+ if (node2) {
38284
+ node2.x = coords2.graphX;
38285
+ node2.y = coords2.graphY;
38286
+ node2.vx = 0;
38287
+ node2.vy = 0;
38288
+ forceUpdate((n) => n + 1);
38289
+ }
38290
+ return;
38291
+ }
38292
+ const coords = toCoords(e);
38293
+ if (!coords) return;
38294
+ const node = nodeAt(coords.graphX, coords.graphY);
38295
+ setHoveredNode(node?.id ?? null);
38296
+ },
38297
+ [toCoords, nodeAt]
38298
+ );
38299
+ const handleMouseUp = React82.useCallback(
38300
+ (e) => {
38301
+ const state = interactionRef.current;
38302
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
38303
+ state.mode = "none";
38304
+ state.dragNodeId = null;
38305
+ if (moved < 4) {
38306
+ const coords = toCoords(e);
38307
+ if (!coords) return;
38308
+ const node = nodeAt(coords.graphX, coords.graphY);
38309
+ if (node) {
38310
+ handleNodeClick(node);
38311
+ }
38312
+ }
38313
+ },
38314
+ [toCoords, nodeAt, handleNodeClick]
38315
+ );
38316
+ const handleMouseLeave = React82.useCallback(() => {
38317
+ interactionRef.current.mode = "none";
38318
+ interactionRef.current.dragNodeId = null;
38319
+ setHoveredNode(null);
38320
+ }, []);
38191
38321
  if (isLoading) {
38192
38322
  return /* @__PURE__ */ jsxRuntime.jsx(LoadingState, { message: t("common.loading"), className });
38193
38323
  }
@@ -38249,20 +38379,11 @@ var init_GraphCanvas = __esm({
38249
38379
  height,
38250
38380
  className: "w-full cursor-grab active:cursor-grabbing",
38251
38381
  style: { height },
38252
- onClick: (e) => {
38253
- const canvas = canvasRef.current;
38254
- if (!canvas) return;
38255
- const rect = canvas.getBoundingClientRect();
38256
- const x = (e.clientX - rect.left - offset.x) / zoom;
38257
- const y = (e.clientY - rect.top - offset.y) / zoom;
38258
- const clickedNode = nodesRef.current.find((n) => {
38259
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
38260
- return dist < (n.size || 8) + 4;
38261
- });
38262
- if (clickedNode) {
38263
- handleNodeClick(clickedNode);
38264
- }
38265
- }
38382
+ onWheel: handleWheel,
38383
+ onMouseDown: handleMouseDown,
38384
+ onMouseMove: handleMouseMove,
38385
+ onMouseUp: handleMouseUp,
38386
+ onMouseLeave: handleMouseLeave
38266
38387
  }
38267
38388
  ) }),
38268
38389
  groups.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "md", className: "px-4 py-2 border-t border-border", wrap: true, children: groups.map((group, idx) => /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", align: "center", children: [
@@ -49339,12 +49460,25 @@ function pathToFeatures(path) {
49339
49460
  sprite: `${CDN6}world-map/road_straight.png`
49340
49461
  }));
49341
49462
  }
49463
+ function heroToUnit(hero) {
49464
+ return {
49465
+ id: hero.id,
49466
+ position: { x: hero.x, y: hero.y },
49467
+ name: "Hero",
49468
+ team: "player",
49469
+ sprite: HERO_SPRITE,
49470
+ unitType: "amir",
49471
+ health: 1,
49472
+ maxHealth: 1
49473
+ };
49474
+ }
49342
49475
  function TowerDefenseBoard({
49343
49476
  entity,
49344
49477
  tiles: propTiles,
49345
49478
  path: propPath,
49346
49479
  towers: propTowers,
49347
49480
  creeps: propCreeps,
49481
+ hero: propHero,
49348
49482
  gold: propGold,
49349
49483
  lives: propLives,
49350
49484
  wave: propWave,
@@ -49360,6 +49494,7 @@ function TowerDefenseBoard({
49360
49494
  startWaveEvent,
49361
49495
  playAgainEvent,
49362
49496
  gameEndEvent,
49497
+ moveHeroEvent,
49363
49498
  className
49364
49499
  }) {
49365
49500
  const board = boardEntity(entity) ?? {};
@@ -49371,6 +49506,7 @@ function TowerDefenseBoard({
49371
49506
  const path = rawPath.length > 0 ? rawPath : DEFAULT_TD_PATH;
49372
49507
  const towers = propTowers ?? rows(board.towers);
49373
49508
  const creeps = propCreeps ?? rows(board.creeps);
49509
+ const hero = propHero ?? { id: "hero", x: 8, y: 8 };
49374
49510
  const gold = propGold ?? num(board.gold, 100);
49375
49511
  const lives = propLives ?? num(board.lives, 20);
49376
49512
  const wave = propWave ?? num(board.wave, 1);
@@ -49388,6 +49524,31 @@ function TowerDefenseBoard({
49388
49524
  if (result === "none") {
49389
49525
  emittedGameEnd.current = false;
49390
49526
  }
49527
+ const moveHeroEventRef = React82.useRef(moveHeroEvent);
49528
+ moveHeroEventRef.current = moveHeroEvent;
49529
+ const resultRef = React82.useRef(result);
49530
+ resultRef.current = result;
49531
+ React82.useEffect(() => {
49532
+ function onKeyDown(e) {
49533
+ const evt = moveHeroEventRef.current;
49534
+ if (!evt || resultRef.current !== "none") return;
49535
+ let dx = 0;
49536
+ let dy = 0;
49537
+ if (e.key === "ArrowUp") {
49538
+ dy = -1;
49539
+ } else if (e.key === "ArrowDown") {
49540
+ dy = 1;
49541
+ } else if (e.key === "ArrowLeft") {
49542
+ dx = -1;
49543
+ } else if (e.key === "ArrowRight") {
49544
+ dx = 1;
49545
+ } else return;
49546
+ e.preventDefault();
49547
+ eventBus.emit(`UI:${evt}`, { dx, dy });
49548
+ }
49549
+ window.addEventListener("keydown", onKeyDown);
49550
+ return () => window.removeEventListener("keydown", onKeyDown);
49551
+ }, [eventBus]);
49391
49552
  const towerPositions = React82.useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
49392
49553
  const pathPositions = React82.useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
49393
49554
  const validMoves = React82.useMemo(() => {
@@ -49399,7 +49560,8 @@ function TowerDefenseBoard({
49399
49560
  const isoTiles = React82.useMemo(() => tilesToIso(tiles), [tiles]);
49400
49561
  const towerUnits = React82.useMemo(() => towersToUnits(towers), [towers]);
49401
49562
  const creepUnits = React82.useMemo(() => creepsToUnits(creeps), [creeps]);
49402
- const isoUnits = React82.useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
49563
+ const heroUnit = React82.useMemo(() => heroToUnit(hero), [hero]);
49564
+ const isoUnits = React82.useMemo(() => [...towerUnits, ...creepUnits, heroUnit], [towerUnits, creepUnits, heroUnit]);
49403
49565
  const pathFeatures = React82.useMemo(() => pathToFeatures(path), [path]);
49404
49566
  const handleTileClick = React82.useCallback((x, y) => {
49405
49567
  if (result !== "none") return;
@@ -49498,7 +49660,7 @@ function TowerDefenseBoard({
49498
49660
  ] }) })
49499
49661
  ] });
49500
49662
  }
49501
- var CDN6, TD_GRID_W, TD_GRID_H, TERRAIN_SPRITES, DEFAULT_TD_PATH, DEFAULT_TD_TILES, TOWER_SPRITE, CREEP_SPRITE;
49663
+ var CDN6, TD_GRID_W, TD_GRID_H, TERRAIN_SPRITES, DEFAULT_TD_PATH, DEFAULT_TD_TILES, TOWER_SPRITE, CREEP_SPRITE, HERO_SPRITE;
49502
49664
  var init_TowerDefenseBoard = __esm({
49503
49665
  "components/game/organisms/TowerDefenseBoard.tsx"() {
49504
49666
  "use client";
@@ -49572,8 +49734,9 @@ var init_TowerDefenseBoard = __esm({
49572
49734
  { x: 13, y: 15 }
49573
49735
  ];
49574
49736
  DEFAULT_TD_TILES = buildDefaultTDTiles();
49575
- TOWER_SPRITE = `${CDN6}units/guardian.png`;
49576
- CREEP_SPRITE = `${CDN6}units/scrapper.png`;
49737
+ TOWER_SPRITE = `${CDN6}sprite-sheets/guardian-sprite-sheet-se.png`;
49738
+ CREEP_SPRITE = `${CDN6}sprite-sheets/scrapper-sprite-sheet-se.png`;
49739
+ HERO_SPRITE = `${CDN6}sprite-sheets/amir-sprite-sheet-se.png`;
49577
49740
  TowerDefenseBoard.displayName = "TowerDefenseBoard";
49578
49741
  }
49579
49742
  });