@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.
@@ -29173,13 +29173,13 @@ var init_MapView = __esm({
29173
29173
  shadowSize: [41, 41]
29174
29174
  });
29175
29175
  L.Marker.prototype.options.icon = defaultIcon;
29176
- const { useEffect: useEffect81, useRef: useRef75, useCallback: useCallback119, useState: useState113 } = React81__default;
29176
+ const { useEffect: useEffect82, useRef: useRef75, useCallback: useCallback119, useState: useState113 } = React81__default;
29177
29177
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
29178
29178
  const { useEventBus: useEventBus4 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
29179
29179
  function MapUpdater({ centerLat, centerLng, zoom }) {
29180
29180
  const map = useMap();
29181
29181
  const prevRef = useRef75({ centerLat, centerLng, zoom });
29182
- useEffect81(() => {
29182
+ useEffect82(() => {
29183
29183
  const prev = prevRef.current;
29184
29184
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
29185
29185
  map.setView([centerLat, centerLng], zoom);
@@ -29190,7 +29190,7 @@ var init_MapView = __esm({
29190
29190
  }
29191
29191
  function MapClickHandler({ onMapClick }) {
29192
29192
  const map = useMap();
29193
- useEffect81(() => {
29193
+ useEffect82(() => {
29194
29194
  if (!onMapClick) return;
29195
29195
  const handler = (e) => {
29196
29196
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -37651,6 +37651,9 @@ var init_GraphCanvas = __esm({
37651
37651
  actions,
37652
37652
  onNodeClick,
37653
37653
  nodeClickEvent,
37654
+ selectedNodeId,
37655
+ repulsion = 800,
37656
+ linkDistance = 100,
37654
37657
  layout = "force",
37655
37658
  entity,
37656
37659
  isLoading = false,
@@ -37666,6 +37669,35 @@ var init_GraphCanvas = __esm({
37666
37669
  const [hoveredNode, setHoveredNode] = useState(null);
37667
37670
  const nodesRef = useRef([]);
37668
37671
  const [, forceUpdate] = useState(0);
37672
+ const interactionRef = useRef({
37673
+ mode: "none",
37674
+ dragNodeId: null,
37675
+ startMouse: { x: 0, y: 0 },
37676
+ startOffset: { x: 0, y: 0 },
37677
+ downPos: { x: 0, y: 0 }
37678
+ });
37679
+ const toCoords = useCallback(
37680
+ (e) => {
37681
+ const canvas = canvasRef.current;
37682
+ if (!canvas) return null;
37683
+ const rect = canvas.getBoundingClientRect();
37684
+ const screenX = e.clientX - rect.left;
37685
+ const screenY = e.clientY - rect.top;
37686
+ return {
37687
+ screenX,
37688
+ screenY,
37689
+ graphX: (screenX - offset.x) / zoom,
37690
+ graphY: (screenY - offset.y) / zoom
37691
+ };
37692
+ },
37693
+ [offset, zoom]
37694
+ );
37695
+ const nodeAt = useCallback((graphX, graphY) => {
37696
+ return nodesRef.current.find((n) => {
37697
+ const dist = Math.sqrt((n.x - graphX) ** 2 + (n.y - graphY) ** 2);
37698
+ return dist < (n.size || 8) + 4;
37699
+ });
37700
+ }, []);
37669
37701
  const handleAction = useCallback(
37670
37702
  (action) => {
37671
37703
  if (action.event) {
@@ -37731,7 +37763,7 @@ var init_GraphCanvas = __esm({
37731
37763
  const dx = nodes[j].x - nodes[i].x;
37732
37764
  const dy = nodes[j].y - nodes[i].y;
37733
37765
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37734
- const force = 800 / (dist * dist);
37766
+ const force = repulsion / (dist * dist);
37735
37767
  const fx = dx / dist * force;
37736
37768
  const fy = dy / dist * force;
37737
37769
  nodes[i].fx -= fx;
@@ -37747,7 +37779,7 @@ var init_GraphCanvas = __esm({
37747
37779
  const dx = target.x - source.x;
37748
37780
  const dy = target.y - source.y;
37749
37781
  const dist = Math.sqrt(dx * dx + dy * dy) || 1;
37750
- const force = (dist - 100) * 0.05;
37782
+ const force = (dist - linkDistance) * 0.05;
37751
37783
  const fx = dx / dist * force;
37752
37784
  const fy = dy / dist * force;
37753
37785
  source.fx += fx;
@@ -37781,7 +37813,7 @@ var init_GraphCanvas = __esm({
37781
37813
  return () => {
37782
37814
  cancelAnimationFrame(animRef.current);
37783
37815
  };
37784
- }, [propNodes, propEdges, layout]);
37816
+ }, [propNodes, propEdges, layout, repulsion, linkDistance]);
37785
37817
  useEffect(() => {
37786
37818
  const canvas = canvasRef.current;
37787
37819
  if (!canvas) return;
@@ -37817,18 +37849,25 @@ var init_GraphCanvas = __esm({
37817
37849
  const size = node.size || 8;
37818
37850
  const color = node.color || getGroupColor(node.group, groups);
37819
37851
  const isHovered = hoveredNode === node.id;
37852
+ const isSelected = selectedNodeId !== void 0 && node.id === selectedNodeId;
37853
+ const radius = isSelected ? size + 4 : isHovered ? size + 2 : size;
37820
37854
  ctx.beginPath();
37821
- ctx.arc(node.x, node.y, isHovered ? size + 2 : size, 0, Math.PI * 2);
37855
+ ctx.arc(node.x, node.y, radius, 0, Math.PI * 2);
37822
37856
  ctx.fillStyle = color;
37823
37857
  ctx.fill();
37824
- ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
37825
- ctx.lineWidth = isHovered ? 2 : 1;
37858
+ if (isSelected) {
37859
+ ctx.strokeStyle = "var(--color-accent)";
37860
+ ctx.lineWidth = 3;
37861
+ } else {
37862
+ ctx.strokeStyle = isHovered ? "#ffffff" : "#00000020";
37863
+ ctx.lineWidth = isHovered ? 2 : 1;
37864
+ }
37826
37865
  ctx.stroke();
37827
37866
  if (showLabels && node.label) {
37828
37867
  ctx.fillStyle = "#666666";
37829
- ctx.font = `${isHovered ? "bold " : ""}10px system-ui`;
37868
+ ctx.font = `${isSelected || isHovered ? "bold " : ""}10px system-ui`;
37830
37869
  ctx.textAlign = "center";
37831
- ctx.fillText(node.label, node.x, node.y + size + 12);
37870
+ ctx.fillText(node.label, node.x, node.y + radius + 12);
37832
37871
  }
37833
37872
  }
37834
37873
  ctx.restore();
@@ -37839,6 +37878,97 @@ var init_GraphCanvas = __esm({
37839
37878
  setZoom(1);
37840
37879
  setOffset({ x: 0, y: 0 });
37841
37880
  }, []);
37881
+ const handleWheel = useCallback(
37882
+ (e) => {
37883
+ if (!interactive) return;
37884
+ e.preventDefault();
37885
+ const coords = toCoords(e);
37886
+ if (!coords) return;
37887
+ const oldZoom = zoom;
37888
+ const factor = e.deltaY < 0 ? 1.1 : 1 / 1.1;
37889
+ const newZoom = Math.max(0.3, Math.min(3, oldZoom * factor));
37890
+ if (newZoom === oldZoom) return;
37891
+ setOffset((o) => ({
37892
+ x: coords.screenX - (coords.screenX - o.x) * (newZoom / oldZoom),
37893
+ y: coords.screenY - (coords.screenY - o.y) * (newZoom / oldZoom)
37894
+ }));
37895
+ setZoom(newZoom);
37896
+ },
37897
+ [interactive, toCoords, zoom]
37898
+ );
37899
+ const handleMouseDown = useCallback(
37900
+ (e) => {
37901
+ const coords = toCoords(e);
37902
+ if (!coords) return;
37903
+ const node = nodeAt(coords.graphX, coords.graphY);
37904
+ const state = interactionRef.current;
37905
+ state.downPos = { x: e.clientX, y: e.clientY };
37906
+ state.startMouse = { x: e.clientX, y: e.clientY };
37907
+ state.startOffset = { ...offset };
37908
+ if (draggable && node) {
37909
+ state.mode = "dragging";
37910
+ state.dragNodeId = node.id;
37911
+ } else if (interactive) {
37912
+ state.mode = "panning";
37913
+ state.dragNodeId = null;
37914
+ } else {
37915
+ state.mode = "none";
37916
+ state.dragNodeId = null;
37917
+ }
37918
+ },
37919
+ [toCoords, nodeAt, draggable, interactive, offset]
37920
+ );
37921
+ const handleMouseMove = useCallback(
37922
+ (e) => {
37923
+ const state = interactionRef.current;
37924
+ if (state.mode === "panning") {
37925
+ const dx = e.clientX - state.startMouse.x;
37926
+ const dy = e.clientY - state.startMouse.y;
37927
+ setOffset({ x: state.startOffset.x + dx, y: state.startOffset.y + dy });
37928
+ return;
37929
+ }
37930
+ if (state.mode === "dragging" && state.dragNodeId) {
37931
+ const coords2 = toCoords(e);
37932
+ if (!coords2) return;
37933
+ const node2 = nodesRef.current.find((n) => n.id === state.dragNodeId);
37934
+ if (node2) {
37935
+ node2.x = coords2.graphX;
37936
+ node2.y = coords2.graphY;
37937
+ node2.vx = 0;
37938
+ node2.vy = 0;
37939
+ forceUpdate((n) => n + 1);
37940
+ }
37941
+ return;
37942
+ }
37943
+ const coords = toCoords(e);
37944
+ if (!coords) return;
37945
+ const node = nodeAt(coords.graphX, coords.graphY);
37946
+ setHoveredNode(node?.id ?? null);
37947
+ },
37948
+ [toCoords, nodeAt]
37949
+ );
37950
+ const handleMouseUp = useCallback(
37951
+ (e) => {
37952
+ const state = interactionRef.current;
37953
+ const moved = Math.abs(e.clientX - state.downPos.x) + Math.abs(e.clientY - state.downPos.y);
37954
+ state.mode = "none";
37955
+ state.dragNodeId = null;
37956
+ if (moved < 4) {
37957
+ const coords = toCoords(e);
37958
+ if (!coords) return;
37959
+ const node = nodeAt(coords.graphX, coords.graphY);
37960
+ if (node) {
37961
+ handleNodeClick(node);
37962
+ }
37963
+ }
37964
+ },
37965
+ [toCoords, nodeAt, handleNodeClick]
37966
+ );
37967
+ const handleMouseLeave = useCallback(() => {
37968
+ interactionRef.current.mode = "none";
37969
+ interactionRef.current.dragNodeId = null;
37970
+ setHoveredNode(null);
37971
+ }, []);
37842
37972
  if (isLoading) {
37843
37973
  return /* @__PURE__ */ jsx(LoadingState, { message: t("common.loading"), className });
37844
37974
  }
@@ -37900,20 +38030,11 @@ var init_GraphCanvas = __esm({
37900
38030
  height,
37901
38031
  className: "w-full cursor-grab active:cursor-grabbing",
37902
38032
  style: { height },
37903
- onClick: (e) => {
37904
- const canvas = canvasRef.current;
37905
- if (!canvas) return;
37906
- const rect = canvas.getBoundingClientRect();
37907
- const x = (e.clientX - rect.left - offset.x) / zoom;
37908
- const y = (e.clientY - rect.top - offset.y) / zoom;
37909
- const clickedNode = nodesRef.current.find((n) => {
37910
- const dist = Math.sqrt((n.x - x) ** 2 + (n.y - y) ** 2);
37911
- return dist < (n.size || 8) + 4;
37912
- });
37913
- if (clickedNode) {
37914
- handleNodeClick(clickedNode);
37915
- }
37916
- }
38033
+ onWheel: handleWheel,
38034
+ onMouseDown: handleMouseDown,
38035
+ onMouseMove: handleMouseMove,
38036
+ onMouseUp: handleMouseUp,
38037
+ onMouseLeave: handleMouseLeave
37917
38038
  }
37918
38039
  ) }),
37919
38040
  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: [
@@ -49009,12 +49130,25 @@ function pathToFeatures(path) {
49009
49130
  sprite: `${CDN6}world-map/road_straight.png`
49010
49131
  }));
49011
49132
  }
49133
+ function heroToUnit(hero) {
49134
+ return {
49135
+ id: hero.id,
49136
+ position: { x: hero.x, y: hero.y },
49137
+ name: "Hero",
49138
+ team: "player",
49139
+ sprite: HERO_SPRITE,
49140
+ unitType: "amir",
49141
+ health: 1,
49142
+ maxHealth: 1
49143
+ };
49144
+ }
49012
49145
  function TowerDefenseBoard({
49013
49146
  entity,
49014
49147
  tiles: propTiles,
49015
49148
  path: propPath,
49016
49149
  towers: propTowers,
49017
49150
  creeps: propCreeps,
49151
+ hero: propHero,
49018
49152
  gold: propGold,
49019
49153
  lives: propLives,
49020
49154
  wave: propWave,
@@ -49030,6 +49164,7 @@ function TowerDefenseBoard({
49030
49164
  startWaveEvent,
49031
49165
  playAgainEvent,
49032
49166
  gameEndEvent,
49167
+ moveHeroEvent,
49033
49168
  className
49034
49169
  }) {
49035
49170
  const board = boardEntity(entity) ?? {};
@@ -49041,6 +49176,7 @@ function TowerDefenseBoard({
49041
49176
  const path = rawPath.length > 0 ? rawPath : DEFAULT_TD_PATH;
49042
49177
  const towers = propTowers ?? rows(board.towers);
49043
49178
  const creeps = propCreeps ?? rows(board.creeps);
49179
+ const hero = propHero ?? { id: "hero", x: 8, y: 8 };
49044
49180
  const gold = propGold ?? num(board.gold, 100);
49045
49181
  const lives = propLives ?? num(board.lives, 20);
49046
49182
  const wave = propWave ?? num(board.wave, 1);
@@ -49058,6 +49194,31 @@ function TowerDefenseBoard({
49058
49194
  if (result === "none") {
49059
49195
  emittedGameEnd.current = false;
49060
49196
  }
49197
+ const moveHeroEventRef = useRef(moveHeroEvent);
49198
+ moveHeroEventRef.current = moveHeroEvent;
49199
+ const resultRef = useRef(result);
49200
+ resultRef.current = result;
49201
+ useEffect(() => {
49202
+ function onKeyDown(e) {
49203
+ const evt = moveHeroEventRef.current;
49204
+ if (!evt || resultRef.current !== "none") return;
49205
+ let dx = 0;
49206
+ let dy = 0;
49207
+ if (e.key === "ArrowUp") {
49208
+ dy = -1;
49209
+ } else if (e.key === "ArrowDown") {
49210
+ dy = 1;
49211
+ } else if (e.key === "ArrowLeft") {
49212
+ dx = -1;
49213
+ } else if (e.key === "ArrowRight") {
49214
+ dx = 1;
49215
+ } else return;
49216
+ e.preventDefault();
49217
+ eventBus.emit(`UI:${evt}`, { dx, dy });
49218
+ }
49219
+ window.addEventListener("keydown", onKeyDown);
49220
+ return () => window.removeEventListener("keydown", onKeyDown);
49221
+ }, [eventBus]);
49061
49222
  const towerPositions = useMemo(() => new Set(towers.map((t2) => `${t2.x},${t2.y}`)), [towers]);
49062
49223
  const pathPositions = useMemo(() => new Set(path.map((p2) => `${p2.x},${p2.y}`)), [path]);
49063
49224
  const validMoves = useMemo(() => {
@@ -49069,7 +49230,8 @@ function TowerDefenseBoard({
49069
49230
  const isoTiles = useMemo(() => tilesToIso(tiles), [tiles]);
49070
49231
  const towerUnits = useMemo(() => towersToUnits(towers), [towers]);
49071
49232
  const creepUnits = useMemo(() => creepsToUnits(creeps), [creeps]);
49072
- const isoUnits = useMemo(() => [...towerUnits, ...creepUnits], [towerUnits, creepUnits]);
49233
+ const heroUnit = useMemo(() => heroToUnit(hero), [hero]);
49234
+ const isoUnits = useMemo(() => [...towerUnits, ...creepUnits, heroUnit], [towerUnits, creepUnits, heroUnit]);
49073
49235
  const pathFeatures = useMemo(() => pathToFeatures(path), [path]);
49074
49236
  const handleTileClick = useCallback((x, y) => {
49075
49237
  if (result !== "none") return;
@@ -49168,7 +49330,7 @@ function TowerDefenseBoard({
49168
49330
  ] }) })
49169
49331
  ] });
49170
49332
  }
49171
- var CDN6, TD_GRID_W, TD_GRID_H, TERRAIN_SPRITES, DEFAULT_TD_PATH, DEFAULT_TD_TILES, TOWER_SPRITE, CREEP_SPRITE;
49333
+ var CDN6, TD_GRID_W, TD_GRID_H, TERRAIN_SPRITES, DEFAULT_TD_PATH, DEFAULT_TD_TILES, TOWER_SPRITE, CREEP_SPRITE, HERO_SPRITE;
49172
49334
  var init_TowerDefenseBoard = __esm({
49173
49335
  "components/game/organisms/TowerDefenseBoard.tsx"() {
49174
49336
  "use client";
@@ -49242,8 +49404,9 @@ var init_TowerDefenseBoard = __esm({
49242
49404
  { x: 13, y: 15 }
49243
49405
  ];
49244
49406
  DEFAULT_TD_TILES = buildDefaultTDTiles();
49245
- TOWER_SPRITE = `${CDN6}units/guardian.png`;
49246
- CREEP_SPRITE = `${CDN6}units/scrapper.png`;
49407
+ TOWER_SPRITE = `${CDN6}sprite-sheets/guardian-sprite-sheet-se.png`;
49408
+ CREEP_SPRITE = `${CDN6}sprite-sheets/scrapper-sprite-sheet-se.png`;
49409
+ HERO_SPRITE = `${CDN6}sprite-sheets/amir-sprite-sheet-se.png`;
49247
49410
  TowerDefenseBoard.displayName = "TowerDefenseBoard";
49248
49411
  }
49249
49412
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.57.0",
3
+ "version": "5.59.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [