${t.description||""}
\n \ndiff --git a/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf b/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf index 8a10c41..bfe05a9 100644 --- a/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf +++ b/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf @@ -32,9 +32,23 @@ diag_log format ["[FORGE:Client:CAD] Handling UI event: %1", _event]; if (_isConfirmDialog) exitWith { true }; switch (_event) do { + case "cad::topbar::ready": { + GVAR(CADUIBridge) call ["handleTopBarReady", []]; + }; case "cad::ready": { GVAR(CADUIBridge) call ["handleReady", [_control, _data]]; }; + case "cad::dispatcher::ready": { + GVAR(CADUIBridge) call ["handleDispatcherReady", []]; + }; + case "cad::mode::set": { + private _mode = ""; + if (_data isEqualType createHashMap) then { + _mode = _data getOrDefault ["mode", ""]; + }; + + GVAR(CADUIBridge) call ["setMode", [_mode]]; + }; case "cad::refresh": { GVAR(CADUIBridge) call ["requestHydrate", []]; }; @@ -76,6 +90,16 @@ switch (_event) do { GVAR(CADUIBridge) call ["requestGroupStatus", [_groupID, _status]]; }; + case "cad::groups::role": { + private _groupID = ""; + private _role = ""; + if (_data isEqualType createHashMap) then { + _groupID = _data getOrDefault ["groupID", ""]; + _role = _data getOrDefault ["role", ""]; + }; + + GVAR(CADUIBridge) call ["requestGroupRole", [_groupID, _role]]; + }; case "map::zoomIn": { private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull]; if (isNull _mapCtrl) exitWith {}; diff --git a/arma/client/addons/cad/functions/fnc_initRepository.sqf b/arma/client/addons/cad/functions/fnc_initRepository.sqf index ba109da..749e691 100644 --- a/arma/client/addons/cad/functions/fnc_initRepository.sqf +++ b/arma/client/addons/cad/functions/fnc_initRepository.sqf @@ -30,19 +30,35 @@ GVAR(CADRepository) = createHashMapObject [[ _self set ["assignments", []]; _self set ["activity", []]; _self set ["session", createHashMap]; + _self set ["mode", "operations"]; + }], + ["getHydratePayload", compileFinal { + createHashMapFromArray [ + ["groups", +(_self getOrDefault ["groups", []])], + ["contracts", +(_self getOrDefault ["contracts", []])], + ["assignments", +(_self getOrDefault ["assignments", []])], + ["activity", +(_self getOrDefault ["activity", []])], + ["session", +(_self getOrDefault ["session", createHashMap])], + ["mode", _self getOrDefault ["mode", "operations"]] + ] + }], + ["getCurrentGroup", compileFinal { + private _session = _self getOrDefault ["session", createHashMap]; + private _groupID = _session getOrDefault ["groupId", ""]; + if (_groupID isEqualTo "") exitWith { createHashMap }; + + private _groups = _self getOrDefault ["groups", []]; + private _group = _groups findIf { (_x getOrDefault ["groupId", ""]) isEqualTo _groupID }; + if (_group < 0) exitWith { createHashMap }; + + +(_groups # _group) }], ["pushHydratePayload", compileFinal { params [["_bridge", createHashMap, [createHashMap]]]; if (_bridge isEqualTo createHashMap) exitWith { false }; - _bridge call ["sendEvent", ["cad::hydrate", createHashMapFromArray [ - ["groups", +(_self getOrDefault ["groups", []])], - ["contracts", +(_self getOrDefault ["contracts", []])], - ["assignments", +(_self getOrDefault ["assignments", []])], - ["activity", +(_self getOrDefault ["activity", []])], - ["session", +(_self getOrDefault ["session", createHashMap])] - ]]] + _bridge call ["sendEvent", ["cad::hydrate", _self call ["getHydratePayload", []]]] }], ["setHydratePayload", compileFinal { params [["_payload", createHashMap, [createHashMap]]]; @@ -54,6 +70,16 @@ GVAR(CADRepository) = createHashMapObject [[ _self set ["session", +(_payload getOrDefault ["session", createHashMap])]; true }], + ["setMode", compileFinal { + params [["_mode", "operations", [""]]]; + + if !(_mode in ["operations", "dispatch"]) then { + _mode = "operations"; + }; + + _self set ["mode", _mode]; + _mode + }], ["setOpen", compileFinal { params [["_isOpen", false, [false]]]; _self set ["isOpen", _isOpen]; diff --git a/arma/client/addons/cad/functions/fnc_initUI.sqf b/arma/client/addons/cad/functions/fnc_initUI.sqf index bb84979..981d317 100644 --- a/arma/client/addons/cad/functions/fnc_initUI.sqf +++ b/arma/client/addons/cad/functions/fnc_initUI.sqf @@ -27,12 +27,16 @@ private _mapCtrl = _display displayCtrl 1001; private _topBarCtrl = _display displayCtrl 1002; private _bottomBarCtrl = _display displayCtrl 1003; private _sidePanelCtrl = _display displayCtrl 1005; +private _dispatcherCtrl = _display displayCtrl 1006; uiNamespace setVariable [QGVAR(Display), _display]; uiNamespace setVariable [QGVAR(MapCtrl), _mapCtrl]; uiNamespace setVariable [QGVAR(TopBarCtrl), _topBarCtrl]; uiNamespace setVariable [QGVAR(BottomBarCtrl), _bottomBarCtrl]; uiNamespace setVariable [QGVAR(SidePanelCtrl), _sidePanelCtrl]; +uiNamespace setVariable [QGVAR(DispatcherCtrl), _dispatcherCtrl]; + +_dispatcherCtrl ctrlShow false; private _center = if (isNull player) then { [worldSize / 2, worldSize / 2, 0] @@ -43,48 +47,5 @@ private _center = if (isNull player) then { _mapCtrl ctrlMapAnimAdd [0, 0.2, _center]; ctrlMapAnimCommit _mapCtrl; -_mapCtrl ctrlAddEventHandler ["MouseButtonClick", { - params ["_ctrl", "_button", "_xPos", "_yPos"]; - - private _worldPos = _ctrl ctrlMapScreenToWorld [_xPos, _yPos]; - private _bottomBar = uiNamespace getVariable [QGVAR(BottomBarCtrl), controlNull]; - if (isNull _bottomBar) exitWith {}; - - private _jsCode = format [ - "updateStatus('Clicked at: %1, %2');", - round (_worldPos # 0), - round (_worldPos # 1) - ]; - _bottomBar ctrlWebBrowserAction ["ExecJS", _jsCode]; -}]; - -_mapCtrl ctrlAddEventHandler ["MouseMoving", { - params ["_ctrl", "_xPos", "_yPos"]; - - private _worldPos = _ctrl ctrlMapScreenToWorld [_xPos, _yPos]; - private _topBar = uiNamespace getVariable [QGVAR(TopBarCtrl), controlNull]; - if (isNull _topBar) exitWith {}; - - private _jsCode = format [ - "updateCoordinates(%1, %2);", - _worldPos # 0, - _worldPos # 1 - ]; - _topBar ctrlWebBrowserAction ["ExecJS", _jsCode]; -}]; - -[] spawn { - while { !isNull (uiNamespace getVariable [QGVAR(Display), displayNull]) } do { - private _mapCtrl = uiNamespace getVariable [QGVAR(MapCtrl), controlNull]; - private _topBar = uiNamespace getVariable [QGVAR(TopBarCtrl), controlNull]; - - if (!isNull _mapCtrl && { !isNull _topBar }) then { - _topBar ctrlWebBrowserAction ["ExecJS", format ["updateScale(%1);", round (ctrlMapScale _mapCtrl)]]; - }; - - sleep 0.5; - }; -}; - diag_log "[FORGE:Client:CAD] CAD UI initialized."; true diff --git a/arma/client/addons/cad/functions/fnc_initUIBridge.sqf b/arma/client/addons/cad/functions/fnc_initUIBridge.sqf index ab4cb86..0fb004b 100644 --- a/arma/client/addons/cad/functions/fnc_initUIBridge.sqf +++ b/arma/client/addons/cad/functions/fnc_initUIBridge.sqf @@ -26,6 +26,10 @@ private _webUIBridgeDeclaration = _webUIDeclarations get "bridgeDeclaration"; GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ ["#base", _webUIBridgeDeclaration], ["#type", "CADUIBridgeBaseClass"], + ["#create", compileFinal { + _self set ["dispatcherReady", false]; + _self set ["topBarReady", false]; + }], ["getActiveBrowserControl", compileFinal { private _display = uiNamespace getVariable [QGVAR(Display), displayNull]; if (isNull _display) exitWith { @@ -37,11 +41,115 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ _self call ["setActiveBrowserControl", [_control]]; _control }], + ["getTopBarControl", compileFinal { + private _display = uiNamespace getVariable [QGVAR(Display), displayNull]; + if (isNull _display) exitWith { controlNull }; + + _display displayCtrl 1002 + }], + ["getBottomBarControl", compileFinal { + private _display = uiNamespace getVariable [QGVAR(Display), displayNull]; + if (isNull _display) exitWith { controlNull }; + + _display displayCtrl 1003 + }], + ["getMapControl", compileFinal { + private _display = uiNamespace getVariable [QGVAR(Display), displayNull]; + if (isNull _display) exitWith { controlNull }; + + _display displayCtrl 1001 + }], + ["getDispatcherControl", compileFinal { + private _display = uiNamespace getVariable [QGVAR(Display), displayNull]; + if (isNull _display) exitWith { controlNull }; + + _display displayCtrl 1006 + }], ["hasOpenScreen", compileFinal { private _screen = _self call ["getScreen", []]; private _control = _self call ["getActiveBrowserControl", []]; !(isNull _control) && { _screen call ["isReady", []] } }], + ["isDispatcher", compileFinal { + if (isNil QGVAR(CADRepository)) exitWith { false }; + + private _session = GVAR(CADRepository) getOrDefault ["session", createHashMap]; + _session getOrDefault ["isDispatcher", false] + }], + ["applyLayout", compileFinal { + private _mode = if (isNil QGVAR(CADRepository)) then { + "operations" + } else { + GVAR(CADRepository) getOrDefault ["mode", "operations"] + }; + + private _mapCtrl = _self call ["getMapControl", []]; + private _bottomBarCtrl = _self call ["getBottomBarControl", []]; + private _sidePanelCtrl = _self call ["getActiveBrowserControl", []]; + private _dispatcherCtrl = _self call ["getDispatcherControl", []]; + + if !(isNull _mapCtrl) then { _mapCtrl ctrlShow (_mode isEqualTo "operations"); }; + if !(isNull _bottomBarCtrl) then { _bottomBarCtrl ctrlShow true; }; + if !(isNull _sidePanelCtrl) then { _sidePanelCtrl ctrlShow (_mode isEqualTo "operations"); }; + if !(isNull _dispatcherCtrl) then { _dispatcherCtrl ctrlShow (_mode isEqualTo "dispatch"); }; + + _self call ["refreshTopBarState", []]; + _self call ["refreshDispatcher", []]; + true + }], + ["setMode", compileFinal { + params [["_mode", "operations", [""]]]; + + if (isNil QGVAR(CADRepository)) exitWith { false }; + + private _targetMode = _mode; + if !(_targetMode in ["operations", "dispatch"]) then { + _targetMode = "operations"; + }; + + if (_targetMode isEqualTo "dispatch" && !(_self call ["isDispatcher", []])) then { + _targetMode = "operations"; + }; + + GVAR(CADRepository) call ["setMode", [_targetMode]]; + _self call ["applyLayout", []] + }], + ["refreshTopBarState", compileFinal { + if !(_self getOrDefault ["topBarReady", false]) exitWith { false }; + + if (isNil QGVAR(CADRepository)) exitWith { false }; + + private _topBarCtrl = _self call ["getTopBarControl", []]; + if (isNull _topBarCtrl) exitWith { false }; + + private _session = +(GVAR(CADRepository) getOrDefault ["session", createHashMap]); + private _currentGroup = GVAR(CADRepository) call ["getCurrentGroup", []]; + private _payload = createHashMapFromArray [ + ["mode", GVAR(CADRepository) getOrDefault ["mode", "operations"]], + ["session", _session], + ["currentGroup", _currentGroup] + ]; + + _topBarCtrl ctrlWebBrowserAction ["ExecJS", format [ + "window.cadTopbar && window.cadTopbar.receiveState(%1);", + toJSON _payload + ]]; + true + }], + ["refreshDispatcher", compileFinal { + if !(_self getOrDefault ["dispatcherReady", false]) exitWith { false }; + if (isNil QGVAR(CADRepository)) exitWith { false }; + + private _dispatcherCtrl = _self call ["getDispatcherControl", []]; + if (isNull _dispatcherCtrl) exitWith { false }; + + private _payload = GVAR(CADRepository) call ["getHydratePayload", []]; + _dispatcherCtrl ctrlWebBrowserAction ["ExecJS", format [ + "window.cadDispatcher && window.cadDispatcher.receiveHydrate(%1);", + toJSON _payload + ]]; + true + }], ["handleReady", compileFinal { params [["_control", controlNull, [controlNull]], ["_data", createHashMap, [createHashMap]]]; @@ -52,8 +160,25 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ _self call ["requestHydrate", []]; _self call ["refreshHydrate", []]; + _self call ["refreshTopBarState", []]; true }], + ["handleClose", compileFinal { + _self set ["dispatcherReady", false]; + _self set ["topBarReady", false]; + + private _screen = _self call ["getScreen", []]; + _screen call ["dispose", []]; + true + }], + ["handleTopBarReady", compileFinal { + _self set ["topBarReady", true]; + _self call ["refreshTopBarState", []] + }], + ["handleDispatcherReady", compileFinal { + _self set ["dispatcherReady", true]; + _self call ["refreshDispatcher", []] + }], ["requestHydrate", compileFinal { [SRPC(cad,requestHydrateCad), [getPlayerUID player]] call CFUNC(serverEvent); true @@ -90,6 +215,14 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ [SRPC(cad,requestUpdateCadGroupStatus), [getPlayerUID player, _groupID, _status]] call CFUNC(serverEvent); true }], + ["requestGroupRole", compileFinal { + params [["_groupID", "", [""]], ["_role", "", [""]]]; + + if (_groupID isEqualTo "" || { _role isEqualTo "" }) exitWith { false }; + + [SRPC(cad,requestUpdateCadGroupRole), [getPlayerUID player, _groupID, _role]] call CFUNC(serverEvent); + true + }], ["refreshHydrate", compileFinal { if (isNil QGVAR(CADRepository)) exitWith { false }; GVAR(CADRepository) call ["pushHydratePayload", [_self]] @@ -100,11 +233,29 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ if (isNil QGVAR(CADRepository)) exitWith { false }; GVAR(CADRepository) call ["setHydratePayload", [_payload]]; - _self call ["refreshHydrate", []] + if !(_self call ["isDispatcher", []]) then { + GVAR(CADRepository) call ["setMode", ["operations"]]; + }; + + _self call ["refreshHydrate", []]; + _self call ["refreshTopBarState", []]; + _self call ["refreshDispatcher", []]; + _self call ["applyLayout", []] }], ["handleAssignmentResponse", compileFinal { params [["_result", createHashMap, [createHashMap]]]; + if (_self getOrDefault ["dispatcherReady", false]) then { + private _dispatcherCtrl = _self call ["getDispatcherControl", []]; + if !(isNull _dispatcherCtrl) then { + _dispatcherCtrl ctrlWebBrowserAction ["ExecJS", format [ + "window.cadDispatcher && window.cadDispatcher.setStatus(%1, %2);", + str (_result getOrDefault ["message", "Task request processed."]), + str ([ "error", "success" ] select (_result getOrDefault ["success", false])) + ]]; + }; + }; + _self call ["sendEvent", ["cad::assignment::response", createHashMapFromArray [ ["message", _result getOrDefault ["message", "Task request processed."]], ["success", _result getOrDefault ["success", false]] @@ -113,6 +264,17 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ ["handleGroupUpdateResponse", compileFinal { params [["_result", createHashMap, [createHashMap]]]; + if (_self getOrDefault ["dispatcherReady", false]) then { + private _dispatcherCtrl = _self call ["getDispatcherControl", []]; + if !(isNull _dispatcherCtrl) then { + _dispatcherCtrl ctrlWebBrowserAction ["ExecJS", format [ + "window.cadDispatcher && window.cadDispatcher.setStatus(%1, %2);", + str (_result getOrDefault ["message", "Group update processed."]), + str ([ "error", "success" ] select (_result getOrDefault ["success", false])) + ]]; + }; + }; + _self call ["sendEvent", ["cad::group::response", createHashMapFromArray [ ["message", _result getOrDefault ["message", "Group update processed."]], ["success", _result getOrDefault ["success", false]] diff --git a/arma/client/addons/cad/functions/fnc_openUI.sqf b/arma/client/addons/cad/functions/fnc_openUI.sqf index 0d0804b..d648613 100644 --- a/arma/client/addons/cad/functions/fnc_openUI.sqf +++ b/arma/client/addons/cad/functions/fnc_openUI.sqf @@ -28,17 +28,19 @@ if (isNull _display) exitWith { private _topBarCtrl = _display displayCtrl 1002; private _bottomBarCtrl = _display displayCtrl 1003; private _sidePanelCtrl = _display displayCtrl 1005; +private _dispatcherCtrl = _display displayCtrl 1006; { _x ctrlAddEventHandler ["JSDialog", { params ["_control", "_isConfirmDialog", "_message"]; [_control, _isConfirmDialog, _message] call FUNC(handleUIEvents); }]; -} forEach [_topBarCtrl, _bottomBarCtrl, _sidePanelCtrl]; +} forEach [_topBarCtrl, _bottomBarCtrl, _sidePanelCtrl, _dispatcherCtrl]; _topBarCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\topbar.html)]; _bottomBarCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\bottombar.html)]; _sidePanelCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\sidepanel.html)]; +_dispatcherCtrl ctrlWebBrowserAction ["LoadFile", QPATHTOF2(ui\_site\dispatcher.html)]; if !(isNil QGVAR(CADRepository)) then { GVAR(CADRepository) call ["setOpen", [true]]; diff --git a/arma/client/addons/cad/ui/RscMapUI.hpp b/arma/client/addons/cad/ui/RscMapUI.hpp index 37f599d..b323f68 100644 --- a/arma/client/addons/cad/ui/RscMapUI.hpp +++ b/arma/client/addons/cad/ui/RscMapUI.hpp @@ -6,15 +6,24 @@ class RscMapUI { fadeout = 0; duration = 1e+011; onLoad = "uiNamespace setVariable ['forge_client_cad_Display', _this select 0]; [_this select 0] call forge_client_cad_fnc_initUI;"; - onUnLoad = "uiNamespace setVariable ['forge_client_cad_Display', nil]; uiNamespace setVariable ['forge_client_cad_MapCtrl', nil]; uiNamespace setVariable ['forge_client_cad_TopBarCtrl', nil]; uiNamespace setVariable ['forge_client_cad_BottomBarCtrl', nil]; uiNamespace setVariable ['forge_client_cad_SidePanelCtrl', nil]; if !(isNil 'forge_client_cad_CADRepository') then { forge_client_cad_CADRepository set ['isOpen', false]; };"; + onUnLoad = "uiNamespace setVariable ['forge_client_cad_Display', nil]; uiNamespace setVariable ['forge_client_cad_MapCtrl', nil]; uiNamespace setVariable ['forge_client_cad_TopBarCtrl', nil]; uiNamespace setVariable ['forge_client_cad_BottomBarCtrl', nil]; uiNamespace setVariable ['forge_client_cad_SidePanelCtrl', nil]; uiNamespace setVariable ['forge_client_cad_DispatcherCtrl', nil]; if !(isNil 'forge_client_cad_CADRepository') then { forge_client_cad_CADRepository set ['isOpen', false]; };"; class controlsBackground { + class SurfaceBackground: RscText { + idc = -1; + x = "safeZoneX + (safeZoneW * 0.1)"; + y = "safeZoneY + (safeZoneH * 0.1)"; + w = "safeZoneW * 0.8"; + h = "safeZoneH * 0.8"; + colorBackground[] = {0.04, 0.06, 0.09, 0.96}; + }; + class MapControl: RscMapControl { idc = 1001; x = "safeZoneX + (safeZoneW * 0.1)"; // 10% margin (80% width centered) - y = "safeZoneY + (safeZoneH * 0.1) + 0.0926"; // 10% margin + 50px top bar + y = "safeZoneY + (safeZoneH * 0.1) + 0.10372"; // 10% margin + 56px visible top bar w = "safeZoneW * 0.8"; // 80% width - h = "(safeZoneH * 0.8) - 0.0926 - 0.0556"; // 80% height minus top and bottom bars + h = "(safeZoneH * 0.8) - 0.10372 - 0.0556"; // 80% height minus visible top and bottom bars // Map specific settings maxSatelliteAlpha = 0.85; @@ -61,7 +70,7 @@ class RscMapUI { x = "safeZoneX + (safeZoneW * 0.1)"; y = "safeZoneY + (safeZoneH * 0.1)"; w = "safeZoneW * 0.8"; - h = "0.0926"; // 50px + h = "0.24076"; // 130px, allows dropdowns to open over the map colorBackground[] = {0, 0, 0, 0}; }; @@ -81,9 +90,19 @@ class RscMapUI { type = 106; idc = 1005; x = "safeZoneX + (safeZoneW * 0.1) + (safeZoneW * 0.8) - 0.4630"; // Right edge of 80% box minus panel width - y = "safeZoneY + (safeZoneH * 0.1) + 0.0926"; // Below top bar + y = "safeZoneY + (safeZoneH * 0.1) + 0.10372"; // Below visible top bar w = "0.4630"; // ~250px width - h = "(safeZoneH * 0.8) - 0.0926 - 0.0556"; // Full height minus bars + h = "(safeZoneH * 0.8) - 0.10372 - 0.0556"; // Full height minus visible bars + colorBackground[] = {0, 0, 0, 0}; + }; + + class DispatcherBrowser: RscText { + type = 106; + idc = 1006; + x = "safeZoneX + (safeZoneW * 0.1)"; + y = "safeZoneY + (safeZoneH * 0.1) + 0.10372"; + w = "safeZoneW * 0.8"; + h = "(safeZoneH * 0.8) - 0.10372 - 0.0556"; colorBackground[] = {0, 0, 0, 0}; }; }; diff --git a/arma/client/addons/cad/ui/_site/bottombar.html b/arma/client/addons/cad/ui/_site/bottombar.html index 33fb1ec..57d4b5e 100644 --- a/arma/client/addons/cad/ui/_site/bottombar.html +++ b/arma/client/addons/cad/ui/_site/bottombar.html @@ -1 +1 @@ -
Map Ready \ No newline at end of file + \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-bottombar.css b/arma/client/addons/cad/ui/_site/cad-bottombar.css index 7133cd2..d6213e6 100644 --- a/arma/client/addons/cad/ui/_site/cad-bottombar.css +++ b/arma/client/addons/cad/ui/_site/cad-bottombar.css @@ -1 +1 @@ -body{-webkit-backdrop-filter:blur(18px);background:linear-gradient(90deg,#0e131bf5,#121720ed 55%,#0d1219f5);border-top:1px solid #ffffff24;justify-content:space-between;align-items:center;min-height:36px;padding:0 20px;display:flex;position:absolute;bottom:0;left:0;right:0;overflow:hidden;box-shadow:0 -12px 26px #0000003d}span{color:#f5f8ffcc;text-shadow:0 1px 10px #00000047;font-size:12px}#statusText{color:var(--accent);font-weight:600} \ No newline at end of file +body{-webkit-backdrop-filter:blur(18px);background:linear-gradient(90deg,#0e131bf5,#121720ed 55%,#0d1219f5);border-top:1px solid #ffffff24;justify-content:space-between;align-items:center;min-height:36px;padding:0 20px;display:flex;position:absolute;bottom:0;left:0;right:0;overflow:hidden;box-shadow:0 -12px 26px #0000003d}.footer-brand,.footer-version{color:#f5f8ffcc;text-shadow:0 1px 10px #00000047;font-size:12px}.footer-brand{color:var(--accent);letter-spacing:.08em;text-transform:uppercase;font-weight:600}.footer-version{color:#f5f8ff9e} \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-bottombar.js b/arma/client/addons/cad/ui/_site/cad-bottombar.js index d39ab4b..7710154 100644 --- a/arma/client/addons/cad/ui/_site/cad-bottombar.js +++ b/arma/client/addons/cad/ui/_site/cad-bottombar.js @@ -1 +1 @@ -window.CADBottombar=window.CADBottombar||{}; \ No newline at end of file +window.CADBottombar=window.CADBottombar||{init:()=>!0},window.CADBottombar.init(); \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-dispatcher.css b/arma/client/addons/cad/ui/_site/cad-dispatcher.css new file mode 100644 index 0000000..e658608 --- /dev/null +++ b/arma/client/addons/cad/ui/_site/cad-dispatcher.css @@ -0,0 +1 @@ +html,body{background:radial-gradient(circle at 0 0,#29455d2e,#0000 30%),linear-gradient(#090e14f5,#0f161ffa);width:100%;height:100%;margin:0;padding:0;overflow:hidden}body{color:var(--text);font-family:var(--font)}.dispatch-shell{flex-direction:column;gap:14px;height:100%;padding:18px;display:flex}.dispatch-header{justify-content:space-between;align-items:center;gap:16px;display:flex}.dispatch-kicker{color:var(--accent);text-transform:uppercase;letter-spacing:.1em;margin:0 0 4px;font-size:11px;font-weight:700}.dispatch-header h2{margin:0;font-size:24px;font-weight:650}.dispatch-header button,.dispatch-btn,.dispatch-select{color:var(--text);background:#181f28e6;border:1px solid #ffffff1f}.dispatch-header button,.dispatch-btn{cursor:pointer;padding:10px 14px}.dispatch-btn-secondary{background:#352827eb}.dispatch-status{color:#e9f1f8c7;min-height:20px;font-size:13px}.dispatch-status[data-type=success]{color:#79d28a}.dispatch-status[data-type=error]{color:#ff8a80}.dispatch-metrics{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;display:grid}.metric-card{background:#0d131ab8;border:1px solid #ffffff14;padding:14px}.metric-label{color:#e9f1f899;text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;font-size:11px;display:block}.metric-card strong{font-size:28px;font-weight:700}.dispatch-grid{flex:1;grid-template-columns:repeat(12,minmax(0,1fr));grid-auto-rows:minmax(0,1fr);gap:14px;min-height:0;display:grid}.dispatch-panel{background:#0b1118c7;border:1px solid #ffffff14;flex-direction:column;min-width:0;min-height:0;display:flex}.dispatch-panel-open{grid-column:span 5}.dispatch-panel-assigned{grid-column:span 7}.dispatch-panel-groups{grid-column:span 8}.dispatch-panel-activity{grid-column:span 4}.dispatch-panel-header{border-bottom:1px solid #ffffff14;justify-content:space-between;align-items:center;padding:12px 14px;display:flex}.dispatch-panel-header h3{text-transform:uppercase;letter-spacing:.08em;color:var(--accent);margin:0;font-size:13px}.dispatch-list{flex-direction:column;flex:1;gap:10px;padding:12px;display:flex;overflow:auto}.dispatch-card{background:#131a22b8;border:1px solid #ffffff0f;padding:12px}.dispatch-card-header,.dispatch-meta{justify-content:space-between;gap:10px;display:flex}.dispatch-card-header-actions{align-items:center;gap:8px;display:flex}.dispatch-card-header-main{align-items:center;gap:8px;min-width:0;display:flex}.dispatch-card-header{margin-bottom:8px}.dispatch-description{color:#f1f6fbd1;margin:0 0 10px;font-size:13px;line-height:1.45}.dispatch-meta{color:#e5edf4b3;margin-bottom:10px;font-size:12px}.dispatch-badge{color:var(--accent);text-transform:uppercase;background:#102b3db3;border:1px solid #5bbbff2e;padding:3px 7px;font-size:11px}.dispatch-icon-btn{width:32px;height:32px;color:var(--text);cursor:pointer;background:#181f28eb;border:1px solid #ffffff24;padding:0}.dispatch-icon-btn:hover{background:#202a34f5}.dispatch-actions{flex-direction:column;gap:8px;display:flex}.dispatch-actions-split{margin-top:10px}.dispatch-select{width:100%;padding:9px 10px}.placeholder-message{text-align:center;color:#e9f1f899;padding:18px}.dispatch-modal{z-index:30;position:fixed;inset:0}.dispatch-modal.is-hidden{display:none}.dispatch-modal-backdrop{background:#04080cb8;position:absolute;inset:0}.dispatch-modal-dialog{background:#0b1118fa;border:1px solid #ffffff1f;width:min(480px,100% - 48px);margin:72px auto 0;position:relative;box-shadow:0 24px 64px #0000006b}.dispatch-modal-header,.dispatch-modal-actions{justify-content:space-between;align-items:center;gap:12px;padding:14px 16px;display:flex}.dispatch-modal-header{border-bottom:1px solid #ffffff14}.dispatch-modal-header h3{margin:0;font-size:22px;font-weight:650}.dispatch-modal-body{padding:16px}.dispatch-meta-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin-bottom:18px;display:grid}.dispatch-meta-grid strong{margin-top:4px;font-size:14px;font-weight:600;display:block}.dispatch-modal-fields{gap:12px;display:grid}.dispatch-field{gap:6px;display:grid}.dispatch-field span{text-transform:uppercase;letter-spacing:.08em;color:#e9f1f8b3;font-size:12px;font-weight:650}.dispatch-modal-actions{border-top:1px solid #ffffff14;justify-content:flex-end} \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-dispatcher.js b/arma/client/addons/cad/ui/_site/cad-dispatcher.js new file mode 100644 index 0000000..6da2ab3 --- /dev/null +++ b/arma/client/addons/cad/ui/_site/cad-dispatcher.js @@ -0,0 +1 @@ +window.cadDispatcher={contracts:[],groups:[],activity:[],session:{},editingGroupId:"",statuses:["available","en_route","on_task","holding","danger","refit","offline"],roles:["infantry","recon","armor","air","logistics","support"],init(){document.getElementById("dispatcherRefreshBtn").addEventListener("click",()=>{this.setStatus("Refreshing board...","info"),window.mapUI.sendEvent("cad::refresh",{})}),document.getElementById("dispatcherGroupModalCloseBtn").addEventListener("click",()=>{this.closeGroupModal()}),document.getElementById("dispatcherGroupModalSaveBtn").addEventListener("click",()=>{this.applyGroupUpdates()}),document.querySelector("#dispatcherGroupModal .dispatch-modal-backdrop").addEventListener("click",()=>{this.closeGroupModal()}),window.mapUI.sendEvent("cad::dispatcher::ready",{})},receiveHydrate(t){this.contracts=Array.isArray(t.contracts)?t.contracts:[],this.groups=Array.isArray(t.groups)?t.groups:[],this.activity=Array.isArray(t.activity)?t.activity:[],this.session=t.session&&"object"==typeof t.session?t.session:{};const e=document.getElementById("dispatcherStatusMessage");!e||e.dataset.type&&"info"!==e.dataset.type||this.setStatus("",""),this.syncOpenModal(),this.render()},setStatus(t,e){const s=document.getElementById("dispatcherStatusMessage");s&&(s.textContent=t||"",s.dataset.type=e||"")},assignTask(t){const e=document.getElementById(`dispatcher-assign-group-${t}`);e&&e.value?(this.setStatus("Submitting assignment...","info"),window.mapUI.sendEvent("cad::tasks::assign",{taskID:t,groupID:e.value,note:""})):this.setStatus("Select a group before assigning a contract.","error")},openGroupModal(t){const e=this.groups.find(e=>e.groupId===t);e&&(this.editingGroupId=t,document.getElementById("dispatcherModalGroupCallsign").textContent=e.callsign||e.groupId||"Unknown",document.getElementById("dispatcherModalGroupLeader").textContent=e.leaderName||"Unknown",document.getElementById("dispatcherModalGroupTask").textContent=e.currentTaskId||"None",document.getElementById("dispatcherModalGroupOrg").textContent=e.orgId||"default",document.getElementById("dispatcherModalRoleSelect").innerHTML=this.roles.map(t=>``).join(""),document.getElementById("dispatcherModalStatusSelect").innerHTML=this.statuses.map(t=>``).join(""),document.getElementById("dispatcherGroupModal").classList.remove("is-hidden"))},closeGroupModal(){this.editingGroupId="",document.getElementById("dispatcherGroupModal").classList.add("is-hidden")},syncOpenModal(){if(!this.editingGroupId)return;const t=this.groups.find(t=>t.groupId===this.editingGroupId);t?(document.getElementById("dispatcherModalGroupCallsign").textContent=t.callsign||t.groupId||"Unknown",document.getElementById("dispatcherModalGroupLeader").textContent=t.leaderName||"Unknown",document.getElementById("dispatcherModalGroupTask").textContent=t.currentTaskId||"None",document.getElementById("dispatcherModalGroupOrg").textContent=t.orgId||"default"):this.closeGroupModal()},applyGroupUpdates(){if(!this.editingGroupId)return;const t=this.groups.find(t=>t.groupId===this.editingGroupId);if(!t)return void this.closeGroupModal();const e=document.getElementById("dispatcherModalRoleSelect").value,s=document.getElementById("dispatcherModalStatusSelect").value;let n=!1;e&&e!==(t.role||"")&&(n=!0,this.setStatus("Updating group role...","info"),window.mapUI.sendEvent("cad::groups::role",{groupID:this.editingGroupId,role:e})),s&&s!==(t.status||"")&&(n=!0,this.setStatus("Updating group status...","info"),window.mapUI.sendEvent("cad::groups::status",{groupID:this.editingGroupId,status:s})),n||this.setStatus("No group changes to save.","info"),this.closeGroupModal()},buildGroupEditorButton:t=>`\n \n `,renderMetrics(){const t=this.contracts.filter(t=>"unassigned"!==(t.assignmentState||"unassigned")),e=this.contracts.filter(t=>"unassigned"===(t.assignmentState||"unassigned")),s=this.groups.filter(t=>"danger"===(t.status||""));document.getElementById("metricOpenContracts").textContent=e.length,document.getElementById("metricAssignedContracts").textContent=t.length,document.getElementById("metricActiveGroups").textContent=this.groups.length,document.getElementById("metricDangerGroups").textContent=s.length},renderOpenContracts(){const t=document.getElementById("dispatcherOpenContracts"),e=this.contracts.filter(t=>"unassigned"===(t.assignmentState||"unassigned"));if(!e.length)return void(t.innerHTML='');const s=this.groups.map(t=>``).join("");t.innerHTML=e.map(t=>{const e=t.taskId||t.taskID||"",n=Array.isArray(t.position)?t.position:[0,0,0];return`\n${t.description||""}
\n \n${t.description||""}
\n \n${t.message||""}
\n${s.description||""}
\n \n ${this.canDispatch()?`${s.message||""}
\n${s.description||""}
\n \n ${o&&"assigned"===r?`${s.message||""}
\nDispatch Dashboard
Group Editor