@mindexec/cli 0.2.59 → 0.2.61

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.
Files changed (25) hide show
  1. package/package.json +2 -2
  2. package/remote-hub.js +52 -0
  3. package/scripts/remote-http-smoke.mjs +13 -0
  4. package/scripts/remote-hub-smoke.mjs +19 -1
  5. package/server.js +4 -0
  6. package/wwwroot/_content/MindExecution.Shared/css/app.css +1 -1
  7. package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +556 -17
  8. package/wwwroot/_content/MindExecution.Shared/js/mind-map-menu-manager.js +64 -18
  9. package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +2 -2
  10. package/wwwroot/_framework/MindExecution.Core.csfu1xtj3l.dll +0 -0
  11. package/wwwroot/_framework/{MindExecution.Kernel.cqcbagjpqb.dll → MindExecution.Kernel.mdo1lzjvvp.dll} +0 -0
  12. package/wwwroot/_framework/{MindExecution.Plugins.Admin.tsn0j478un.dll → MindExecution.Plugins.Admin.5fctwf65dx.dll} +0 -0
  13. package/wwwroot/_framework/{MindExecution.Plugins.Business.xg74dmn0vz.dll → MindExecution.Plugins.Business.nwivpk9djf.dll} +0 -0
  14. package/wwwroot/_framework/{MindExecution.Plugins.Concept.xyf0dtv4lr.dll → MindExecution.Plugins.Concept.aa243ne54e.dll} +0 -0
  15. package/wwwroot/_framework/{MindExecution.Plugins.Directory.9i4bd043ia.dll → MindExecution.Plugins.Directory.jnzcrwl049.dll} +0 -0
  16. package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.6awpgrbi0w.dll → MindExecution.Plugins.PlanMaster.udoktewe31.dll} +0 -0
  17. package/wwwroot/_framework/{MindExecution.Plugins.YouTube.phonjrgb40.dll → MindExecution.Plugins.YouTube.z4bhlt9iy7.dll} +0 -0
  18. package/wwwroot/_framework/{MindExecution.Shared.wv4gm07egs.dll → MindExecution.Shared.32hytvk3ws.dll} +0 -0
  19. package/wwwroot/_framework/MindExecution.Web.730hm0dubp.dll +0 -0
  20. package/wwwroot/_framework/blazor.boot.json +21 -21
  21. package/wwwroot/index.html +3 -3
  22. package/wwwroot/service-worker-assets.js +27 -27
  23. package/wwwroot/service-worker.js +1 -1
  24. package/wwwroot/_framework/MindExecution.Core.fu1rl67yt3.dll +0 -0
  25. package/wwwroot/_framework/MindExecution.Web.2qteyfmk41.dll +0 -0
@@ -9838,6 +9838,7 @@
9838
9838
  const edges = collectBusinessAutomationEdges(module);
9839
9839
  const relationContext = syncBusinessAutomationSelectionContext(module, {
9840
9840
  edges,
9841
+ force: true,
9841
9842
  scheduleRender: false
9842
9843
  });
9843
9844
  if (_businessAutomationSelectedEdgeId
@@ -9939,12 +9940,12 @@
9939
9940
  d: pathData,
9940
9941
  class: `mind-map-business-automation-edge-path${isSelected ? ' is-selected' : ''}${isRelationHighlighted ? ' is-relation-highlight' : ''}`,
9941
9942
  stroke: theme.edge,
9942
- 'stroke-width': isSelected ? '3.6' : (isRelationHighlighted ? (isContextEdge ? '2.8' : '3') : (isContextEdge ? '2.1' : '2.4')),
9943
- opacity: isSelected ? '0.96' : (isRelationHighlighted ? '0.94' : (isContextEdge ? '0.68' : '0.82')),
9943
+ 'stroke-width': isSelected ? '3.6' : (isRelationHighlighted ? (isContextEdge ? '2.45' : '2.65') : (isContextEdge ? '2.1' : '2.4')),
9944
+ opacity: isSelected ? '0.96' : (isRelationHighlighted ? (isContextEdge ? '0.72' : '0.78') : (isContextEdge ? '0.68' : '0.82')),
9944
9945
  'stroke-dasharray': isContextEdge ? '7 7' : '',
9945
9946
  filter: isSelected
9946
9947
  ? `drop-shadow(0 0 7px ${theme.edge})`
9947
- : (isRelationHighlighted ? `drop-shadow(0 0 5px ${theme.edge})` : '')
9948
+ : (isRelationHighlighted ? `drop-shadow(0 0 3px ${theme.edge})` : '')
9948
9949
  });
9949
9950
  path.style.pointerEvents = 'none';
9950
9951
  path.dataset.edgeId = edge.id;
@@ -9953,21 +9954,21 @@
9953
9954
  const sourceDot = createBusinessAutomationSvgElement('circle', {
9954
9955
  cx: source.x.toFixed(1),
9955
9956
  cy: source.y.toFixed(1),
9956
- r: (isSelected || isRelationHighlighted) ? '4.2' : '3.2',
9957
+ r: isSelected ? '4.2' : (isRelationHighlighted ? '3.7' : '3.2'),
9957
9958
  fill: '#ffffff',
9958
9959
  stroke: theme.edge,
9959
9960
  'stroke-width': '1.8',
9960
- opacity: isRelationHighlighted ? '0.96' : (isContextEdge ? '0.82' : '0.96')
9961
+ opacity: isSelected ? '0.96' : (isRelationHighlighted ? '0.84' : (isContextEdge ? '0.82' : '0.96'))
9961
9962
  });
9962
9963
  sourceDot.style.pointerEvents = 'none';
9963
9964
  const targetDot = createBusinessAutomationSvgElement('circle', {
9964
9965
  cx: target.x.toFixed(1),
9965
9966
  cy: target.y.toFixed(1),
9966
- r: (isSelected || isRelationHighlighted) ? '4.2' : '3.2',
9967
+ r: isSelected ? '4.2' : (isRelationHighlighted ? '3.7' : '3.2'),
9967
9968
  fill: theme.edge,
9968
9969
  stroke: '#ffffff',
9969
9970
  'stroke-width': '1.4',
9970
- opacity: isRelationHighlighted ? '0.96' : (isContextEdge ? '0.82' : '0.96')
9971
+ opacity: isSelected ? '0.96' : (isRelationHighlighted ? '0.84' : (isContextEdge ? '0.82' : '0.96'))
9971
9972
  });
9972
9973
  targetDot.style.pointerEvents = 'none';
9973
9974
 
@@ -12863,6 +12864,7 @@
12863
12864
  const REMOTE_FLEET_HOST_LEASE_REFRESH_MS = 10000;
12864
12865
  const remoteFleetHostLeaseTimers = new Map();
12865
12866
  const remoteFleetLocalHostTargets = new Map();
12867
+ let activeRemoteFleetControlPopup = null;
12866
12868
 
12867
12869
  function findRemoteFleetBodyByNodeId(nodeId) {
12868
12870
  const id = String(nodeId || '').trim();
@@ -13233,7 +13235,7 @@
13233
13235
  if (canvas.height !== nextHeight) {
13234
13236
  canvas.height = nextHeight;
13235
13237
  }
13236
- return { width: nextWidth, height: nextHeight };
13238
+ return { width: nextWidth, height: nextHeight, cssWidth, cssHeight, dpr };
13237
13239
  }
13238
13240
 
13239
13241
  function getRemoteFleetBitmapSize(bitmap) {
@@ -13245,7 +13247,7 @@
13245
13247
  };
13246
13248
  }
13247
13249
 
13248
- function drawRemoteFleetFrameToCanvas(canvas, bitmap) {
13250
+ function drawRemoteFleetFrameToCanvas(canvas, bitmap, fitMode = 'cover') {
13249
13251
  if (!canvas || !bitmap) {
13250
13252
  return false;
13251
13253
  }
@@ -13272,18 +13274,40 @@
13272
13274
  let sy = 0;
13273
13275
  let sw = source.width;
13274
13276
  let sh = source.height;
13275
- if (sourceAspect > targetAspect) {
13276
- sw = source.height * targetAspect;
13277
- sx = (source.width - sw) / 2;
13278
- } else if (sourceAspect < targetAspect) {
13279
- sh = source.width / targetAspect;
13280
- sy = (source.height - sh) / 2;
13277
+ let dx = 0;
13278
+ let dy = 0;
13279
+ let dw = size.width;
13280
+ let dh = size.height;
13281
+ if (fitMode === 'contain') {
13282
+ if (sourceAspect > targetAspect) {
13283
+ dh = size.width / sourceAspect;
13284
+ dy = (size.height - dh) / 2;
13285
+ } else if (sourceAspect < targetAspect) {
13286
+ dw = size.height * sourceAspect;
13287
+ dx = (size.width - dw) / 2;
13288
+ }
13289
+ } else {
13290
+ if (sourceAspect > targetAspect) {
13291
+ sw = source.height * targetAspect;
13292
+ sx = (source.width - sw) / 2;
13293
+ } else if (sourceAspect < targetAspect) {
13294
+ sh = source.width / targetAspect;
13295
+ sy = (source.height - sh) / 2;
13296
+ }
13281
13297
  }
13282
13298
 
13283
13299
  context.imageSmoothingEnabled = true;
13284
13300
  context.imageSmoothingQuality = 'medium';
13301
+ context.fillStyle = '#020617';
13285
13302
  context.clearRect(0, 0, size.width, size.height);
13286
- context.drawImage(bitmap, sx, sy, sw, sh, 0, 0, size.width, size.height);
13303
+ context.fillRect(0, 0, size.width, size.height);
13304
+ context.drawImage(bitmap, sx, sy, sw, sh, dx, dy, dw, dh);
13305
+ canvas._remoteFleetContentRect = {
13306
+ left: dx / size.dpr,
13307
+ top: dy / size.dpr,
13308
+ width: dw / size.dpr,
13309
+ height: dh / size.dpr
13310
+ };
13287
13311
  return true;
13288
13312
  }
13289
13313
 
@@ -13696,6 +13720,503 @@
13696
13720
  bodyView._remoteFleetFrameLoopRaf = requestRemoteFleetFrameLoopFrame(loop);
13697
13721
  }
13698
13722
 
13723
+ function getRemoteFleetControlPoint(event, canvas, allowClamp = false) {
13724
+ if (!event || !canvas) {
13725
+ return null;
13726
+ }
13727
+
13728
+ const canvasRect = canvas.getBoundingClientRect?.();
13729
+ if (!canvasRect || canvasRect.width <= 0 || canvasRect.height <= 0) {
13730
+ return null;
13731
+ }
13732
+
13733
+ const content = canvas._remoteFleetContentRect || {
13734
+ left: 0,
13735
+ top: 0,
13736
+ width: canvasRect.width,
13737
+ height: canvasRect.height
13738
+ };
13739
+ if (!content.width || !content.height) {
13740
+ return null;
13741
+ }
13742
+
13743
+ let x = event.clientX - canvasRect.left - content.left;
13744
+ let y = event.clientY - canvasRect.top - content.top;
13745
+ const outside = x < 0 || y < 0 || x > content.width || y > content.height;
13746
+ if (outside && !allowClamp) {
13747
+ return null;
13748
+ }
13749
+
13750
+ x = Math.max(0, Math.min(content.width, x));
13751
+ y = Math.max(0, Math.min(content.height, y));
13752
+ return {
13753
+ normalizedX: x / content.width,
13754
+ normalizedY: y / content.height
13755
+ };
13756
+ }
13757
+
13758
+ function getRemoteFleetPointerButton(event) {
13759
+ switch (Number(event?.button ?? 0)) {
13760
+ case 1:
13761
+ return 'middle';
13762
+ case 2:
13763
+ return 'right';
13764
+ default:
13765
+ return 'left';
13766
+ }
13767
+ }
13768
+
13769
+ function closeRemoteFleetControlPopup(reason = 'close') {
13770
+ const session = activeRemoteFleetControlPopup;
13771
+ if (!session) {
13772
+ return;
13773
+ }
13774
+
13775
+ activeRemoteFleetControlPopup = null;
13776
+ session.active = false;
13777
+ if (session.timer) {
13778
+ clearTimeout(session.timer);
13779
+ session.timer = null;
13780
+ }
13781
+ if (session.moveTimer) {
13782
+ clearTimeout(session.moveTimer);
13783
+ session.moveTimer = null;
13784
+ }
13785
+ session.overlay?.remove?.();
13786
+ window.RuntimeTrace?.emit?.('remote.control.closed', {
13787
+ nodeId: session.nodeId,
13788
+ deviceId: session.deviceId,
13789
+ reason
13790
+ });
13791
+
13792
+ if (session.startedStream && !session.wasLiveActive) {
13793
+ invokeDotNetAsync('StopRemoteFleetLiveStreamFromJs', session.nodeId, session.deviceId, session.streamId || '')
13794
+ .catch(() => undefined);
13795
+ }
13796
+ }
13797
+
13798
+ async function paintRemoteFleetControlFrame(session, frame) {
13799
+ if (!session?.active || !frame || !isRemoteFleetFrameSource(frame.frameUrl)) {
13800
+ return false;
13801
+ }
13802
+
13803
+ try {
13804
+ const bitmap = await loadRemoteFleetFrameBitmap(frame);
13805
+ if (!bitmap || !session.active) {
13806
+ return false;
13807
+ }
13808
+
13809
+ requestRemoteFleetFrameLoopFrame(() => {
13810
+ if (!session.active || !document.body.contains(session.overlay)) {
13811
+ if (typeof bitmap.close === 'function') {
13812
+ bitmap.close();
13813
+ }
13814
+ return;
13815
+ }
13816
+
13817
+ drawRemoteFleetFrameToCanvas(session.canvas, bitmap, 'contain');
13818
+ session.canvas.style.display = 'block';
13819
+ if (typeof bitmap.close === 'function') {
13820
+ bitmap.close();
13821
+ }
13822
+ });
13823
+ return true;
13824
+ } catch {
13825
+ return false;
13826
+ }
13827
+ }
13828
+
13829
+ function sendRemoteFleetControlInput(session, payload, throttleMove = false) {
13830
+ if (!session?.active || !payload?.type) {
13831
+ return;
13832
+ }
13833
+
13834
+ const send = next => {
13835
+ if (!session.active) {
13836
+ return;
13837
+ }
13838
+ invokeDotNetAsync('SendRemoteFleetInputFromJs', session.nodeId, session.deviceId, {
13839
+ ...next,
13840
+ controlLeaseId: session.controlLeaseId,
13841
+ issuedAt: new Date().toISOString()
13842
+ }).catch(error => {
13843
+ session.status.textContent = error?.message || 'Input failed';
13844
+ });
13845
+ };
13846
+
13847
+ if (!throttleMove) {
13848
+ send(payload);
13849
+ return;
13850
+ }
13851
+
13852
+ const now = performance.now();
13853
+ const elapsed = now - (session.lastMoveSentAt || 0);
13854
+ if (elapsed >= 32) {
13855
+ session.lastMoveSentAt = now;
13856
+ send(payload);
13857
+ return;
13858
+ }
13859
+
13860
+ session.pendingMove = payload;
13861
+ if (session.moveTimer) {
13862
+ return;
13863
+ }
13864
+
13865
+ session.moveTimer = setTimeout(() => {
13866
+ session.moveTimer = null;
13867
+ const next = session.pendingMove;
13868
+ session.pendingMove = null;
13869
+ if (next) {
13870
+ session.lastMoveSentAt = performance.now();
13871
+ send(next);
13872
+ }
13873
+ }, Math.max(1, 32 - elapsed));
13874
+ }
13875
+
13876
+ function bindRemoteFleetControlInput(session) {
13877
+ const { overlay, shell, stage, canvas } = session;
13878
+ let pointerActive = false;
13879
+
13880
+ ['mousedown', 'mouseup', 'click', 'dblclick', 'keydown', 'keyup', 'wheel', 'contextmenu'].forEach(eventName => {
13881
+ overlay.addEventListener(eventName, event => event.stopPropagation(), true);
13882
+ });
13883
+
13884
+ stage.addEventListener('pointerdown', event => {
13885
+ const point = getRemoteFleetControlPoint(event, canvas, false);
13886
+ if (!point) {
13887
+ return;
13888
+ }
13889
+
13890
+ event.preventDefault();
13891
+ event.stopPropagation();
13892
+ pointerActive = true;
13893
+ shell.focus({ preventScroll: true });
13894
+ stage.setPointerCapture?.(event.pointerId);
13895
+ sendRemoteFleetControlInput(session, {
13896
+ type: 'pointerDown',
13897
+ ...point,
13898
+ button: getRemoteFleetPointerButton(event)
13899
+ });
13900
+ });
13901
+
13902
+ stage.addEventListener('pointermove', event => {
13903
+ const point = getRemoteFleetControlPoint(event, canvas, pointerActive);
13904
+ if (!point) {
13905
+ return;
13906
+ }
13907
+
13908
+ event.preventDefault();
13909
+ event.stopPropagation();
13910
+ sendRemoteFleetControlInput(session, {
13911
+ type: 'pointerMove',
13912
+ ...point,
13913
+ button: getRemoteFleetPointerButton(event)
13914
+ }, true);
13915
+ });
13916
+
13917
+ stage.addEventListener('pointerup', event => {
13918
+ const point = getRemoteFleetControlPoint(event, canvas, true);
13919
+ event.preventDefault();
13920
+ event.stopPropagation();
13921
+ pointerActive = false;
13922
+ stage.releasePointerCapture?.(event.pointerId);
13923
+ if (!point) {
13924
+ return;
13925
+ }
13926
+
13927
+ sendRemoteFleetControlInput(session, {
13928
+ type: 'pointerUp',
13929
+ ...point,
13930
+ button: getRemoteFleetPointerButton(event)
13931
+ });
13932
+ });
13933
+
13934
+ stage.addEventListener('pointercancel', event => {
13935
+ pointerActive = false;
13936
+ stage.releasePointerCapture?.(event.pointerId);
13937
+ });
13938
+
13939
+ stage.addEventListener('wheel', event => {
13940
+ const point = getRemoteFleetControlPoint(event, canvas, false);
13941
+ if (!point) {
13942
+ return;
13943
+ }
13944
+
13945
+ event.preventDefault();
13946
+ event.stopPropagation();
13947
+ sendRemoteFleetControlInput(session, {
13948
+ type: 'wheel',
13949
+ ...point,
13950
+ deltaX: event.deltaX,
13951
+ deltaY: event.deltaY
13952
+ });
13953
+ }, { passive: false });
13954
+
13955
+ stage.addEventListener('contextmenu', event => {
13956
+ event.preventDefault();
13957
+ event.stopPropagation();
13958
+ });
13959
+
13960
+ shell.addEventListener('keydown', event => {
13961
+ if (event.target?.closest?.('[data-remote-fleet-control-close="true"]')) {
13962
+ return;
13963
+ }
13964
+
13965
+ event.preventDefault();
13966
+ event.stopPropagation();
13967
+ sendRemoteFleetControlInput(session, {
13968
+ type: 'keyDown',
13969
+ key: event.key,
13970
+ code: event.code,
13971
+ repeat: event.repeat === true
13972
+ });
13973
+ });
13974
+
13975
+ shell.addEventListener('keyup', event => {
13976
+ if (event.target?.closest?.('[data-remote-fleet-control-close="true"]')) {
13977
+ return;
13978
+ }
13979
+
13980
+ event.preventDefault();
13981
+ event.stopPropagation();
13982
+ sendRemoteFleetControlInput(session, {
13983
+ type: 'keyUp',
13984
+ key: event.key,
13985
+ code: event.code,
13986
+ repeat: event.repeat === true
13987
+ });
13988
+ });
13989
+ }
13990
+
13991
+ function openRemoteFleetControlPopup(bodyView, nodeModel, deviceId) {
13992
+ const nodeId = String(nodeModel?.id ?? nodeModel?.Id ?? '').trim();
13993
+ const targetId = String(deviceId || '').trim();
13994
+ if (!bodyView || !nodeId || !targetId) {
13995
+ return;
13996
+ }
13997
+
13998
+ closeRemoteFleetControlPopup('replace');
13999
+ const devices = parseRemoteFleetDevices(nodeModel);
14000
+ const device = devices.find(item => getRemoteFleetDeviceId(item) === targetId) || null;
14001
+ const name = device ? getRemoteFleetDeviceName(device) : targetId;
14002
+ const wasLiveActive = device ? isRemoteFleetLiveActive(device) : false;
14003
+
14004
+ const overlay = document.createElement('div');
14005
+ overlay.dataset.remoteFleetControlPopup = 'true';
14006
+ overlay.style.cssText = `
14007
+ position: fixed;
14008
+ inset: 0;
14009
+ z-index: 2147483000;
14010
+ display: grid;
14011
+ place-items: center;
14012
+ padding: 24px;
14013
+ background: rgba(15, 23, 42, 0.24);
14014
+ pointer-events: auto;
14015
+ box-sizing: border-box;
14016
+ `;
14017
+
14018
+ const shell = document.createElement('section');
14019
+ shell.tabIndex = 0;
14020
+ shell.dataset.remoteFleetControlShell = 'true';
14021
+ shell.style.cssText = `
14022
+ width: min(96vw, 1280px);
14023
+ height: min(90vh, 860px);
14024
+ min-width: min(720px, calc(100vw - 28px));
14025
+ min-height: min(420px, calc(100vh - 28px));
14026
+ display: flex;
14027
+ flex-direction: column;
14028
+ overflow: hidden;
14029
+ border-radius: 8px;
14030
+ border: 1px solid rgba(148, 163, 184, 0.34);
14031
+ background: #020617;
14032
+ box-shadow: 0 24px 70px rgba(15, 23, 42, 0.35);
14033
+ outline: none;
14034
+ box-sizing: border-box;
14035
+ `;
14036
+
14037
+ const header = document.createElement('header');
14038
+ header.style.cssText = `
14039
+ flex: 0 0 auto;
14040
+ height: 42px;
14041
+ display: flex;
14042
+ align-items: center;
14043
+ justify-content: space-between;
14044
+ gap: 12px;
14045
+ padding: 0 10px 0 14px;
14046
+ background: rgba(15, 23, 42, 0.96);
14047
+ border-bottom: 1px solid rgba(148, 163, 184, 0.20);
14048
+ box-sizing: border-box;
14049
+ `;
14050
+
14051
+ const title = document.createElement('strong');
14052
+ title.textContent = name;
14053
+ title.title = `${name} (${targetId})`;
14054
+ title.style.cssText = `
14055
+ min-width: 0;
14056
+ color: #f8fafc;
14057
+ font-size: 13px;
14058
+ font-weight: 950;
14059
+ line-height: 1;
14060
+ letter-spacing: 0;
14061
+ overflow: hidden;
14062
+ text-overflow: ellipsis;
14063
+ white-space: nowrap;
14064
+ `;
14065
+
14066
+ const right = document.createElement('div');
14067
+ right.style.cssText = 'flex:0 0 auto;display:flex;align-items:center;gap:8px;min-width:0;';
14068
+ const status = document.createElement('span');
14069
+ status.dataset.remoteFleetControlStatus = 'true';
14070
+ status.textContent = 'Connecting';
14071
+ status.style.cssText = `
14072
+ color: #cbd5e1;
14073
+ font-size: 11px;
14074
+ font-weight: 850;
14075
+ letter-spacing: 0;
14076
+ white-space: nowrap;
14077
+ `;
14078
+ const closeButton = document.createElement('button');
14079
+ closeButton.type = 'button';
14080
+ closeButton.dataset.remoteFleetControlClose = 'true';
14081
+ closeButton.textContent = 'X';
14082
+ closeButton.title = 'Close';
14083
+ closeButton.style.cssText = `
14084
+ width: 30px;
14085
+ height: 30px;
14086
+ display: inline-flex;
14087
+ align-items: center;
14088
+ justify-content: center;
14089
+ border-radius: 7px;
14090
+ border: 1px solid rgba(148, 163, 184, 0.34);
14091
+ background: rgba(30, 41, 59, 0.96);
14092
+ color: #f8fafc;
14093
+ font-size: 13px;
14094
+ font-weight: 950;
14095
+ line-height: 1;
14096
+ cursor: pointer;
14097
+ pointer-events: auto;
14098
+ `;
14099
+
14100
+ const stage = document.createElement('div');
14101
+ stage.dataset.remoteFleetControlStage = 'true';
14102
+ stage.style.cssText = `
14103
+ position: relative;
14104
+ flex: 1 1 auto;
14105
+ min-height: 0;
14106
+ overflow: hidden;
14107
+ background: #020617;
14108
+ cursor: crosshair;
14109
+ touch-action: none;
14110
+ `;
14111
+
14112
+ const canvas = document.createElement('canvas');
14113
+ canvas.dataset.remoteFleetControlCanvas = 'true';
14114
+ canvas.style.cssText = `
14115
+ position: absolute;
14116
+ inset: 0;
14117
+ width: 100%;
14118
+ height: 100%;
14119
+ display: block;
14120
+ background: #020617;
14121
+ `;
14122
+
14123
+ right.appendChild(status);
14124
+ right.appendChild(closeButton);
14125
+ header.appendChild(title);
14126
+ header.appendChild(right);
14127
+ stage.appendChild(canvas);
14128
+ shell.appendChild(header);
14129
+ shell.appendChild(stage);
14130
+ overlay.appendChild(shell);
14131
+ document.body.appendChild(overlay);
14132
+
14133
+ const session = {
14134
+ active: true,
14135
+ nodeId,
14136
+ deviceId: targetId,
14137
+ overlay,
14138
+ shell,
14139
+ stage,
14140
+ canvas,
14141
+ status,
14142
+ wasLiveActive,
14143
+ startedStream: false,
14144
+ streamId: '',
14145
+ controlLeaseId: `control-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
14146
+ timer: null,
14147
+ moveTimer: null,
14148
+ pendingMove: null,
14149
+ lastMoveSentAt: 0
14150
+ };
14151
+ activeRemoteFleetControlPopup = session;
14152
+ bindRemoteFleetControlInput(session);
14153
+
14154
+ const refresh = async () => {
14155
+ if (!session.active) {
14156
+ return;
14157
+ }
14158
+
14159
+ try {
14160
+ const result = await invokeDotNetAsync('GetRemoteFleetFrameFromJs', nodeId, targetId, 'live');
14161
+ const frame = normalizeRemoteFleetFramePayload(result?.frame || result?.Frame, 'live');
14162
+ if (frame) {
14163
+ frame.deviceId = frame.deviceId || targetId;
14164
+ await paintRemoteFleetControlFrame(session, frame);
14165
+ applyRemoteFleetFramePatches(bodyView, [frame]);
14166
+ status.textContent = 'Control';
14167
+ } else if (result?.error || result?.Error) {
14168
+ status.textContent = result.error || result.Error;
14169
+ }
14170
+ } catch (error) {
14171
+ status.textContent = error?.message || 'Frame failed';
14172
+ } finally {
14173
+ if (session.active) {
14174
+ session.timer = setTimeout(refresh, REMOTE_FLEET_LIVE_FRAME_REFRESH_MS);
14175
+ }
14176
+ }
14177
+ };
14178
+
14179
+ closeButton.addEventListener('click', event => {
14180
+ event.preventDefault();
14181
+ event.stopPropagation();
14182
+ closeRemoteFleetControlPopup('button');
14183
+ });
14184
+ overlay.addEventListener('pointerdown', event => {
14185
+ if (event.target === overlay) {
14186
+ event.preventDefault();
14187
+ event.stopPropagation();
14188
+ shell.focus({ preventScroll: true });
14189
+ }
14190
+ });
14191
+
14192
+ shell.focus({ preventScroll: true });
14193
+ window.RuntimeTrace?.emit?.('remote.control.opened', {
14194
+ nodeId,
14195
+ deviceId: targetId
14196
+ });
14197
+
14198
+ invokeDotNetAsync('StartRemoteFleetLiveStreamFromJs', nodeId, targetId)
14199
+ .then(async result => {
14200
+ if (!session.active) {
14201
+ return;
14202
+ }
14203
+
14204
+ await syncRemoteFleetNodeStateFromResult(result);
14205
+ session.startedStream = result?.success === true || result?.Success === true;
14206
+ session.streamId = String(result?.streamId || result?.StreamId || '');
14207
+ status.textContent = session.startedStream ? 'Control' : (result?.error || result?.Error || 'View');
14208
+ refresh();
14209
+ })
14210
+ .catch(error => {
14211
+ if (!session.active) {
14212
+ return;
14213
+ }
14214
+
14215
+ status.textContent = error?.message || 'Start failed';
14216
+ refresh();
14217
+ });
14218
+ }
14219
+
13699
14220
  function isRemoteFleetLiveActive(device) {
13700
14221
  return getRemoteFleetDeviceField(device, 'liveStreamActive', 'LiveStreamActive', false) === true;
13701
14222
  }
@@ -15155,7 +15676,15 @@
15155
15676
  : 'Offline';
15156
15677
 
15157
15678
  panel.dataset.deviceId = deviceId;
15158
- panel.appendChild(createDevicePreview(device, 'detail'));
15679
+ const detailPreview = createDevicePreview(device, 'detail');
15680
+ detailPreview.style.cursor = 'pointer';
15681
+ detailPreview.addEventListener('dblclick', event => {
15682
+ event.preventDefault();
15683
+ event.stopPropagation();
15684
+ bodyView.dataset.remoteFleetSelectedDeviceId = deviceId;
15685
+ openRemoteFleetControlPopup(bodyView, nodeModel, deviceId);
15686
+ });
15687
+ panel.appendChild(detailPreview);
15159
15688
 
15160
15689
  const header = document.createElement('div');
15161
15690
  header.style.cssText = 'display:flex;align-items:flex-start;justify-content:space-between;gap:8px;min-width:0;';
@@ -15712,6 +16241,14 @@
15712
16241
  ['mousedown', 'mouseup', 'dblclick'].forEach(eventName => {
15713
16242
  card.addEventListener(eventName, event => event.stopPropagation());
15714
16243
  });
16244
+ card.addEventListener('dblclick', event => {
16245
+ event.preventDefault();
16246
+ event.stopPropagation();
16247
+ const deviceId = String(card.dataset.deviceId || '').trim();
16248
+ if (!deviceId) return;
16249
+ bodyView.dataset.remoteFleetSelectedDeviceId = deviceId;
16250
+ openRemoteFleetControlPopup(bodyView, nodeModel, deviceId);
16251
+ });
15715
16252
  const selectCard = event => {
15716
16253
  event.preventDefault();
15717
16254
  event.stopPropagation();
@@ -20057,6 +20594,8 @@
20057
20594
  renderRemoteFleetMonitorForTest: renderRemoteFleetMonitor,
20058
20595
  renderRemoteFleetDeviceForTest: renderRemoteFleetDevice,
20059
20596
  applyRemoteFleetFramePatchesForTest: applyRemoteFleetFramePatches,
20597
+ openRemoteFleetControlPopupForTest: openRemoteFleetControlPopup,
20598
+ closeRemoteFleetControlPopupForTest: closeRemoteFleetControlPopup,
20060
20599
  renderBusinessAutomationEdges: renderBusinessAutomationEdges,
20061
20600
  scheduleBusinessAutomationEdgeRender: scheduleBusinessAutomationEdgeRender,
20062
20601
  syncBusinessAutomationSelectionContext: syncBusinessAutomationSelectionContext,