From 56b560edf27f1d9762e6faa78dca251937349013 Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Mon, 30 Mar 2026 21:39:39 -0500 Subject: [PATCH] Support dispatch board and map view switching - Add dispatchView state to the CAD repository and hydrate payloads - Toggle the dispatcher between board and map layouts in the UI bridge - Update side panel logic to hide contract tabs in dispatch map mode --- .../cad/functions/fnc_handleUIEvents.sqf | 8 ++ .../cad/functions/fnc_initRepository.sqf | 14 ++- .../addons/cad/functions/fnc_initUIBridge.sqf | 30 ++++- .../addons/cad/ui/_site/cad-dispatcher.js | 2 +- .../addons/cad/ui/_site/cad-sidepanel.css | 2 +- .../addons/cad/ui/_site/cad-sidepanel.js | 2 +- .../client/addons/cad/ui/_site/cad-topbar.css | 2 +- arma/client/addons/cad/ui/_site/cad-topbar.js | 2 +- .../addons/cad/ui/_site/dispatcher.html | 2 +- .../client/addons/cad/ui/_site/sidepanel.html | 2 +- arma/client/addons/cad/ui/_site/topbar.html | 2 +- arma/client/addons/cad/ui/src/dispatcher.html | 3 - arma/client/addons/cad/ui/src/dispatcher.js | 7 -- arma/client/addons/cad/ui/src/sidepanel.html | 3 - arma/client/addons/cad/ui/src/sidepanel.js | 108 ++++++++++++++++-- .../addons/cad/ui/src/styles/sidepanel.css | 8 -- .../addons/cad/ui/src/styles/topbar.css | 83 +++++++++++--- arma/client/addons/cad/ui/src/topbar.html | 27 ++++- arma/client/addons/cad/ui/src/topbar.js | 52 +++++++++ arma/client/addons/phone.7z | Bin 0 -> 691770 bytes arma/server/addons/phone.7z | Bin 0 -> 6025 bytes 21 files changed, 301 insertions(+), 58 deletions(-) create mode 100644 arma/client/addons/phone.7z create mode 100644 arma/server/addons/phone.7z diff --git a/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf b/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf index bfe05a9..fa8a2b0 100644 --- a/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf +++ b/arma/client/addons/cad/functions/fnc_handleUIEvents.sqf @@ -49,6 +49,14 @@ switch (_event) do { GVAR(CADUIBridge) call ["setMode", [_mode]]; }; + case "cad::dispatchView::set": { + private _dispatchView = ""; + if (_data isEqualType createHashMap) then { + _dispatchView = _data getOrDefault ["dispatchView", ""]; + }; + + GVAR(CADUIBridge) call ["setDispatchView", [_dispatchView]]; + }; case "cad::refresh": { GVAR(CADUIBridge) call ["requestHydrate", []]; }; diff --git a/arma/client/addons/cad/functions/fnc_initRepository.sqf b/arma/client/addons/cad/functions/fnc_initRepository.sqf index 749e691..28a44ba 100644 --- a/arma/client/addons/cad/functions/fnc_initRepository.sqf +++ b/arma/client/addons/cad/functions/fnc_initRepository.sqf @@ -31,6 +31,7 @@ GVAR(CADRepository) = createHashMapObject [[ _self set ["activity", []]; _self set ["session", createHashMap]; _self set ["mode", "operations"]; + _self set ["dispatchView", "board"]; }], ["getHydratePayload", compileFinal { createHashMapFromArray [ @@ -39,7 +40,8 @@ GVAR(CADRepository) = createHashMapObject [[ ["assignments", +(_self getOrDefault ["assignments", []])], ["activity", +(_self getOrDefault ["activity", []])], ["session", +(_self getOrDefault ["session", createHashMap])], - ["mode", _self getOrDefault ["mode", "operations"]] + ["mode", _self getOrDefault ["mode", "operations"]], + ["dispatchView", _self getOrDefault ["dispatchView", "board"]] ] }], ["getCurrentGroup", compileFinal { @@ -80,6 +82,16 @@ GVAR(CADRepository) = createHashMapObject [[ _self set ["mode", _mode]; _mode }], + ["setDispatchView", compileFinal { + params [["_dispatchView", "board", [""]]]; + + if !(_dispatchView in ["board", "map"]) then { + _dispatchView = "board"; + }; + + _self set ["dispatchView", _dispatchView]; + _dispatchView + }], ["setOpen", compileFinal { params [["_isOpen", false, [false]]]; _self set ["isOpen", _isOpen]; diff --git a/arma/client/addons/cad/functions/fnc_initUIBridge.sqf b/arma/client/addons/cad/functions/fnc_initUIBridge.sqf index 0fb004b..2ce505b 100644 --- a/arma/client/addons/cad/functions/fnc_initUIBridge.sqf +++ b/arma/client/addons/cad/functions/fnc_initUIBridge.sqf @@ -82,17 +82,25 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ } else { GVAR(CADRepository) getOrDefault ["mode", "operations"] }; + private _dispatchView = if (isNil QGVAR(CADRepository)) then { + "board" + } else { + GVAR(CADRepository) getOrDefault ["dispatchView", "board"] + }; 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"); }; + private _showMapLayout = (_mode isEqualTo "operations") || { _mode isEqualTo "dispatch" && { _dispatchView isEqualTo "map" } }; + if !(isNull _mapCtrl) then { _mapCtrl ctrlShow _showMapLayout; }; + if !(isNull _bottomBarCtrl) then { _bottomBarCtrl ctrlShow true; }; + if !(isNull _sidePanelCtrl) then { _sidePanelCtrl ctrlShow _showMapLayout; }; + if !(isNull _dispatcherCtrl) then { _dispatcherCtrl ctrlShow (_mode isEqualTo "dispatch" && { _dispatchView isEqualTo "board" }); }; + + _self call ["refreshHydrate", []]; _self call ["refreshTopBarState", []]; _self call ["refreshDispatcher", []]; true @@ -112,6 +120,19 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ }; GVAR(CADRepository) call ["setMode", [_targetMode]]; + if (_targetMode isEqualTo "dispatch") then { + GVAR(CADRepository) call ["setDispatchView", ["board"]]; + }; + _self call ["applyLayout", []] + }], + ["setDispatchView", compileFinal { + params [["_dispatchView", "board", [""]]]; + + if (isNil QGVAR(CADRepository)) exitWith { false }; + if ((GVAR(CADRepository) getOrDefault ["mode", "operations"]) isNotEqualTo "dispatch") exitWith { false }; + if !(_self call ["isDispatcher", []]) exitWith { false }; + + GVAR(CADRepository) call ["setDispatchView", [_dispatchView]]; _self call ["applyLayout", []] }], ["refreshTopBarState", compileFinal { @@ -126,6 +147,7 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [ private _currentGroup = GVAR(CADRepository) call ["getCurrentGroup", []]; private _payload = createHashMapFromArray [ ["mode", GVAR(CADRepository) getOrDefault ["mode", "operations"]], + ["dispatchView", GVAR(CADRepository) getOrDefault ["dispatchView", "board"]], ["session", _session], ["currentGroup", _currentGroup] ]; diff --git a/arma/client/addons/cad/ui/_site/cad-dispatcher.js b/arma/client/addons/cad/ui/_site/cad-dispatcher.js index 6da2ab3..f88477a 100644 --- a/arma/client/addons/cad/ui/_site/cad-dispatcher.js +++ b/arma/client/addons/cad/ui/_site/cad-dispatcher.js @@ -1 +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 ⚙\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='

No open contracts.

');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
\n
\n ${t.title||e}\n ${t.type||"task"}\n
\n

${t.description||""}

\n
\n Unassigned\n X: ${Math.round(n[0]||0)} Y: ${Math.round(n[1]||0)}\n
\n
\n \n \n
\n
\n `}).join("")},renderAssignedContracts(){const t=document.getElementById("dispatcherAssignedContracts"),e=this.contracts.filter(t=>"unassigned"!==(t.assignmentState||"unassigned"));e.length?t.innerHTML=e.map(t=>{const e=t.taskId||t.taskID||"",s=this.groups.find(e=>e.groupId===(t.assignedGroupId||""));return`\n
\n
\n ${t.title||e}\n ${t.assignmentState||"assigned"}\n
\n

${t.description||""}

\n
\n Group: ${s?s.callsign:t.assignedGroupId||"Unknown"}\n Type: ${t.type||"task"}\n
\n
\n `}).join(""):t.innerHTML='

No assigned contracts.

'},renderGroups(){const t=document.getElementById("dispatcherGroups");this.groups.length?t.innerHTML=this.groups.map(t=>`\n
\n
\n
\n ${t.callsign||t.groupId}\n ${t.role||"group"}\n
\n
\n ${this.buildGroupEditorButton(t.groupId)}\n
\n
\n
\n Leader: ${t.leaderName||"Unknown"}\n Status: ${t.status||"unknown"}\n
\n
\n Org: ${t.orgId||"default"}\n Task: ${t.currentTaskId||"None"}\n
\n
\n `).join(""):t.innerHTML='

No active groups available.

'},renderActivity(){const t=document.getElementById("dispatcherActivity");this.activity.length?t.innerHTML=this.activity.slice().reverse().slice(0,12).map(t=>`\n
\n
\n ${t.type||"activity"}\n ${Math.round(t.timestamp||0)}s\n
\n

${t.message||""}

\n
\n `).join(""):t.innerHTML='

No recent activity.

'},render(){this.renderMetrics(),this.renderOpenContracts(),this.renderAssignedContracts(),this.renderGroups(),this.renderActivity()}},window.cadDispatcher.init(); \ No newline at end of file +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("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 ⚙\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='

No open contracts.

');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
\n
\n ${t.title||e}\n ${t.type||"task"}\n
\n

${t.description||""}

\n
\n Unassigned\n X: ${Math.round(n[0]||0)} Y: ${Math.round(n[1]||0)}\n
\n
\n \n \n
\n
\n `}).join("")},renderAssignedContracts(){const t=document.getElementById("dispatcherAssignedContracts"),e=this.contracts.filter(t=>"unassigned"!==(t.assignmentState||"unassigned"));e.length?t.innerHTML=e.map(t=>{const e=t.taskId||t.taskID||"",s=this.groups.find(e=>e.groupId===(t.assignedGroupId||""));return`\n
\n
\n ${t.title||e}\n ${t.assignmentState||"assigned"}\n
\n

${t.description||""}

\n
\n Group: ${s?s.callsign:t.assignedGroupId||"Unknown"}\n Type: ${t.type||"task"}\n
\n
\n `}).join(""):t.innerHTML='

No assigned contracts.

'},renderGroups(){const t=document.getElementById("dispatcherGroups");this.groups.length?t.innerHTML=this.groups.map(t=>`\n
\n
\n
\n ${t.callsign||t.groupId}\n ${t.role||"group"}\n
\n
\n ${this.buildGroupEditorButton(t.groupId)}\n
\n
\n
\n Leader: ${t.leaderName||"Unknown"}\n Status: ${t.status||"unknown"}\n
\n
\n Org: ${t.orgId||"default"}\n Task: ${t.currentTaskId||"None"}\n
\n
\n `).join(""):t.innerHTML='

No active groups available.

'},renderActivity(){const t=document.getElementById("dispatcherActivity");this.activity.length?t.innerHTML=this.activity.slice().reverse().slice(0,12).map(t=>`\n
\n
\n ${t.type||"activity"}\n ${Math.round(t.timestamp||0)}s\n
\n

${t.message||""}

\n
\n `).join(""):t.innerHTML='

No recent activity.

'},render(){this.renderMetrics(),this.renderOpenContracts(),this.renderAssignedContracts(),this.renderGroups(),this.renderActivity()}},window.cadDispatcher.init(); \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-sidepanel.css b/arma/client/addons/cad/ui/_site/cad-sidepanel.css index eae1ee5..d58ae49 100644 --- a/arma/client/addons/cad/ui/_site/cad-sidepanel.css +++ b/arma/client/addons/cad/ui/_site/cad-sidepanel.css @@ -1 +1 @@ -html,body{background:var(--panel);border-left:1px solid var(--stroke);width:100%;height:100%;box-shadow:var(--shadow);-webkit-backdrop-filter:blur(12px);margin:0;padding:0;overflow:hidden}body{opacity:1;visibility:visible}.panel-header{border-bottom:1px solid var(--stroke);background:linear-gradient(#ffffff0d,#0000);justify-content:space-between;align-items:center;padding:14px;display:flex}.panel-header h3{color:var(--accent);text-transform:uppercase;letter-spacing:.8px;font-size:14px;font-weight:650}.panel-content{height:calc(100% - 56px);padding:14px;overflow:auto}.placeholder-message{text-align:center;padding:20px}.placeholder-message p{color:var(--muted);font-size:13px;font-style:italic}.task-toolbar{margin-bottom:10px}.cad-tabs{grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:12px;display:grid}.cad-tab{color:#f3f6f9c7;text-transform:uppercase;letter-spacing:.08em;cursor:pointer;background:#141b21e0;border:1px solid #ffffff24;padding:8px 10px;font-size:11px}.cad-tab:hover{color:#f3f6f9;background:#1f282ff0}.cad-tab.is-active{color:var(--accent);background:#0f283af5;border-color:#5bbbff6b}.cad-tab-panels{min-height:0}.cad-section{display:none}.cad-section.is-active{display:block}.cad-section-header{color:var(--accent);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;font-size:12px;font-weight:700}.task-toolbar button,.task-accept-btn,.task-secondary-btn,.cad-select{color:#f3f6f9;background:#1e252be6;border:1px solid #fff3;width:100%;padding:8px 10px}.task-toolbar button,.task-accept-btn,.task-secondary-btn{cursor:pointer}.task-toolbar button:hover,.task-accept-btn:hover,.task-secondary-btn:hover{background:#2e3942f2}.task-toolbar button:disabled,.task-accept-btn:disabled,.task-secondary-btn:disabled{opacity:.55;cursor:default}.task-status-message{color:#cdd6dd;min-height:18px;margin-bottom:10px;font-size:12px}.task-status-message[data-type=success]{color:#79d28a}.task-status-message[data-type=error]{color:#ff8a80}.task-list{flex-direction:column;gap:10px;display:flex}.task-action-stack,.task-action-row{flex-direction:column;gap:8px;display:flex}.task-action-row{flex-direction:row}.task-card{background:#0c10149e;border:1px solid #ffffff14;padding:10px}.task-card-header{justify-content:space-between;gap:8px;margin-bottom:8px;display:flex}.task-type{opacity:.7;text-transform:uppercase;font-size:11px}.task-description{margin:0 0 8px;font-size:12px;line-height:1.4}.task-meta{opacity:.8;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:11px;display:flex}.task-secondary-btn{background:#3c302deb}.roster-summary-card{background:#10171dd1;border:1px solid #ffffff14;padding:10px}.roster-member-card{background:#0c1014bd}.roster-leader-badge{color:var(--accent);letter-spacing:.06em;text-transform:uppercase;background:#0f283ad1;border:1px solid #5bbbff47;align-items:center;padding:2px 8px;font-size:10px;font-weight:700;display:inline-flex} \ No newline at end of file +html,body{background:var(--panel);border-left:1px solid var(--stroke);width:100%;height:100%;box-shadow:var(--shadow);-webkit-backdrop-filter:blur(12px);margin:0;padding:0;overflow:hidden}body{opacity:1;visibility:visible}.panel-header{border-bottom:1px solid var(--stroke);background:linear-gradient(#ffffff0d,#0000);justify-content:space-between;align-items:center;padding:14px;display:flex}.panel-header h3{color:var(--accent);text-transform:uppercase;letter-spacing:.8px;font-size:14px;font-weight:650}.panel-content{height:calc(100% - 56px);padding:14px;overflow:auto}.placeholder-message{text-align:center;padding:20px}.placeholder-message p{color:var(--muted);font-size:13px;font-style:italic}.cad-tabs{grid-template-columns:repeat(3,1fr);gap:6px;margin-bottom:12px;display:grid}.cad-tab{color:#f3f6f9c7;text-transform:uppercase;letter-spacing:.08em;cursor:pointer;background:#141b21e0;border:1px solid #ffffff24;padding:8px 10px;font-size:11px}.cad-tab:hover{color:#f3f6f9;background:#1f282ff0}.cad-tab.is-active{color:var(--accent);background:#0f283af5;border-color:#5bbbff6b}.cad-tab-panels{min-height:0}.cad-section{display:none}.cad-section.is-active{display:block}.cad-section-header{color:var(--accent);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;font-size:12px;font-weight:700}.task-accept-btn,.task-secondary-btn,.cad-select{color:#f3f6f9;background:#1e252be6;border:1px solid #fff3;width:100%;padding:8px 10px}.task-accept-btn,.task-secondary-btn{cursor:pointer}.task-accept-btn:hover,.task-secondary-btn:hover{background:#2e3942f2}.task-accept-btn:disabled,.task-secondary-btn:disabled{opacity:.55;cursor:default}.task-status-message{color:#cdd6dd;min-height:18px;margin-bottom:10px;font-size:12px}.task-status-message[data-type=success]{color:#79d28a}.task-status-message[data-type=error]{color:#ff8a80}.task-list{flex-direction:column;gap:10px;display:flex}.task-action-stack,.task-action-row{flex-direction:column;gap:8px;display:flex}.task-action-row{flex-direction:row}.task-card{background:#0c10149e;border:1px solid #ffffff14;padding:10px}.task-card-header{justify-content:space-between;gap:8px;margin-bottom:8px;display:flex}.task-type{opacity:.7;text-transform:uppercase;font-size:11px}.task-description{margin:0 0 8px;font-size:12px;line-height:1.4}.task-meta{opacity:.8;justify-content:space-between;gap:8px;margin-bottom:8px;font-size:11px;display:flex}.task-secondary-btn{background:#3c302deb}.roster-summary-card{background:#10171dd1;border:1px solid #ffffff14;padding:10px}.roster-member-card{background:#0c1014bd}.roster-leader-badge{color:var(--accent);letter-spacing:.06em;text-transform:uppercase;background:#0f283ad1;border:1px solid #5bbbff47;align-items:center;padding:2px 8px;font-size:10px;font-weight:700;display:inline-flex} \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-sidepanel.js b/arma/client/addons/cad/ui/_site/cad-sidepanel.js index c27970a..db38e65 100644 --- a/arma/client/addons/cad/ui/_site/cad-sidepanel.js +++ b/arma/client/addons/cad/ui/_site/cad-sidepanel.js @@ -1 +1 @@ -window.cadTasks={contracts:[],groups:[],activity:[],session:{},mode:"operations",activeTab:"contracts",statuses:["available","en_route","on_task","holding","danger","refit","offline"],roles:["infantry","recon","armor","air","logistics","support"],init(){const s=document.getElementById("refreshCadBtn");s&&s.addEventListener("click",()=>this.refresh()),document.querySelectorAll(".cad-tab").forEach(s=>{s.addEventListener("click",()=>{this.setActiveTab(s.dataset.tab||"contracts")})}),window.ForgeBridge.on("cad::hydrate",s=>{this.setHydratePayload(s||{})}),window.ForgeBridge.on("cad::assignment::response",s=>{this.handleServerResponse(!!s.success,s.message||"")}),window.ForgeBridge.on("cad::group::response",s=>{this.handleServerResponse(!!s.success,s.message||"")}),window.ForgeBridge.ready({loaded:!0})},setActiveTab(s){this.activeTab=s||"contracts",document.querySelectorAll(".cad-tab").forEach(s=>{s.classList.toggle("is-active",s.dataset.tab===this.activeTab)}),document.querySelectorAll("[data-panel]").forEach(s=>{s.classList.toggle("is-active",s.dataset.panel===this.activeTab)})},setHydratePayload(s){this.contracts=Array.isArray(s.contracts)?s.contracts:[],this.groups=Array.isArray(s.groups)?s.groups:[],this.activity=Array.isArray(s.activity)?s.activity:[],this.session=s.session&&"object"==typeof s.session?s.session:{},this.mode=s&&"string"==typeof s.mode?s.mode:"operations";const t=document.getElementById("cadStatusMessage");!t||t.dataset.type&&"info"!==t.dataset.type||this.setStatus("",""),this.render()},setStatus(s,t){const e=document.getElementById("cadStatusMessage");e&&(e.textContent=s||"",e.dataset.type=t||"")},handleServerResponse(s,t){this.setStatus(t||(s?"CAD update succeeded.":"CAD update failed."),s?"success":"error")},refresh(){this.setStatus("Refreshing board...","info"),window.mapUI.sendEvent("cad::refresh",{})},acknowledgeTask(s){this.setStatus("Acknowledging contract...","info"),window.mapUI.sendEvent("cad::tasks::acknowledge",{taskID:s})},declineTask(s){this.setStatus("Declining contract...","info"),window.mapUI.sendEvent("cad::tasks::decline",{taskID:s})},updateGroupStatus(s,t){this.setStatus("Updating group status...","info"),window.mapUI.sendEvent("cad::groups::status",{groupID:s,status:t})},updateGroupRole(s,t){this.setStatus("Updating group role...","info"),window.mapUI.sendEvent("cad::groups::role",{groupID:s,role:t})},getPlayerGroupId(){return this.session.groupId||""},getCurrentGroup(){const s=this.getPlayerGroupId();return this.groups.find(t=>t.groupId===s)||null},normalizeCollection:s=>Array.isArray(s)?s:s&&"object"==typeof s?Object.values(s):[],canDispatch(){return!!this.session.isDispatcher},isDispatchMode(){return"dispatch"===this.mode},isLeader(){return!!this.session.isLeader},renderContracts(){const s=document.getElementById("taskList");if(!s)return;const t=this.getPlayerGroupId(),e=this.contracts.filter(s=>(s.assignedGroupId||"")===t);e.length?s.innerHTML=e.map(s=>{const e=s.taskId||s.taskID||"",a=Array.isArray(s.position)?s.position:[0,0,0],n=s.assignedGroupId||"",r=s.assignmentState||"unassigned",i=this.groups.find(s=>s.groupId===n),o=this.isLeader()&&n===t;return`\n
\n
\n ${s.title||e}\n ${s.type||"task"}\n
\n

${s.description||""}

\n
\n ${"unassigned"===r?"Available":`${r}: ${i?i.callsign:n}`}\n X: ${Math.round(a[0]||0)} Y: ${Math.round(a[1]||0)}\n
\n ${o&&"assigned"===r?`
\n \n \n
`:""}\n
\n `}).join(""):s.innerHTML='

No contract is currently assigned to your group.

'},renderRoster(){const s=document.getElementById("rosterList");if(!s)return;const t=this.getCurrentGroup();if(!t)return void(s.innerHTML='

Your group is not currently available.

');const e=this.normalizeCollection(t.members);e.length?s.innerHTML=`\n
\n
\n ${t.callsign||t.groupId||"Current Group"}\n ${e.length} member${1===e.length?"":"s"}\n
\n
\n Leader: ${t.leaderName||"Unknown"}\n Status: ${t.status||"unknown"}\n
\n
\n Role: ${t.role||"unassigned"}\n Task: ${t.currentTaskId||"None"}\n
\n
\n ${e.map(s=>{const t=(s.lifeState||"unknown").replaceAll("_"," "),e=s.isLeader?'Leader':"";return`\n
\n
\n ${s.name||"Unknown Operator"}\n ${t}\n
\n
\n ${s.uid||"No UID"}\n ${e}\n
\n
\n `}).join("")}\n `:s.innerHTML='

No roster members are currently available.

'},renderActivity(){const s=document.getElementById("activityList");s&&(this.activity.length?s.innerHTML=this.activity.slice().reverse().slice(0,8).map(s=>`\n
\n
\n ${s.type||"activity"}\n ${Math.round(s.timestamp||0)}s\n
\n

${s.message||""}

\n
\n `).join(""):s.innerHTML='

No recent activity.

')},render(){this.renderContracts(),this.renderRoster(),this.renderActivity(),this.setActiveTab(this.activeTab)}},window.cadTasks.init(); \ No newline at end of file +window.cadTasks={contracts:[],groups:[],activity:[],session:{},mode:"operations",dispatchView:"board",activeTab:"contracts",statuses:["available","en_route","on_task","holding","danger","refit","offline"],roles:["infantry","recon","armor","air","logistics","support"],init(){document.querySelectorAll(".cad-tab").forEach(s=>{s.addEventListener("click",()=>{this.setActiveTab(s.dataset.tab||"contracts")})}),window.ForgeBridge.on("cad::hydrate",s=>{this.setHydratePayload(s||{})}),window.ForgeBridge.on("cad::assignment::response",s=>{this.handleServerResponse(!!s.success,s.message||"")}),window.ForgeBridge.on("cad::group::response",s=>{this.handleServerResponse(!!s.success,s.message||"")}),window.ForgeBridge.ready({loaded:!0})},setActiveTab(s){this.activeTab=s||"contracts",document.querySelectorAll(".cad-tab").forEach(s=>{s.classList.toggle("is-active",s.dataset.tab===this.activeTab)}),document.querySelectorAll("[data-panel]").forEach(s=>{s.classList.toggle("is-active",s.dataset.panel===this.activeTab)})},syncLayoutState(){const s=document.querySelector(".cad-tabs"),t=document.getElementById("contractsPanel"),e=document.getElementById("rosterPanel"),a=document.getElementById("activityPanel"),n=e?.querySelector(".cad-section-header");if(this.isDispatchMapMode())return s&&(s.style.display="none"),t&&(t.classList.remove("is-active"),t.style.display="none"),a&&(a.classList.remove("is-active"),a.style.display="none"),e&&(e.classList.add("is-active"),e.style.display="block"),n&&(n.textContent="Active Groups"),void(this.activeTab="roster");s&&(s.style.display=""),t&&(t.style.display=""),a&&(a.style.display=""),e&&(e.style.display=""),n&&(n.textContent="Roster")},setHydratePayload(s){this.contracts=Array.isArray(s.contracts)?s.contracts:[],this.groups=Array.isArray(s.groups)?s.groups:[],this.activity=Array.isArray(s.activity)?s.activity:[],this.session=s.session&&"object"==typeof s.session?s.session:{},this.mode=s&&"string"==typeof s.mode?s.mode:"operations",this.dispatchView=s&&"string"==typeof s.dispatchView?s.dispatchView:"board";const t=document.getElementById("cadStatusMessage");!t||t.dataset.type&&"info"!==t.dataset.type||this.setStatus("",""),"dispatch"===this.mode&&"map"===this.dispatchView&&(this.activeTab="roster"),this.render()},setStatus(s,t){const e=document.getElementById("cadStatusMessage");e&&(e.textContent=s||"",e.dataset.type=t||"")},handleServerResponse(s,t){this.setStatus(t||(s?"CAD update succeeded.":"CAD update failed."),s?"success":"error")},acknowledgeTask(s){this.setStatus("Acknowledging contract...","info"),window.mapUI.sendEvent("cad::tasks::acknowledge",{taskID:s})},declineTask(s){this.setStatus("Declining contract...","info"),window.mapUI.sendEvent("cad::tasks::decline",{taskID:s})},updateGroupStatus(s,t){this.setStatus("Updating group status...","info"),window.mapUI.sendEvent("cad::groups::status",{groupID:s,status:t})},updateGroupRole(s,t){this.setStatus("Updating group role...","info"),window.mapUI.sendEvent("cad::groups::role",{groupID:s,role:t})},getPlayerGroupId(){return this.session.groupId||""},getCurrentGroup(){const s=this.getPlayerGroupId();return this.groups.find(t=>t.groupId===s)||null},normalizeCollection:s=>Array.isArray(s)?s:s&&"object"==typeof s?Object.values(s):[],canDispatch(){return!!this.session.isDispatcher},isDispatchMode(){return"dispatch"===this.mode},isDispatchMapMode(){return"dispatch"===this.mode&&"map"===this.dispatchView},isLeader(){return!!this.session.isLeader},renderContracts(){const s=document.getElementById("taskList");if(!s)return;if(this.isDispatchMapMode())return void(s.innerHTML='

Use the dispatch board view to assign and review contracts.

');const t=this.getPlayerGroupId(),e=this.contracts.filter(s=>(s.assignedGroupId||"")===t);e.length?s.innerHTML=e.map(s=>{const e=s.taskId||s.taskID||"",a=Array.isArray(s.position)?s.position:[0,0,0],n=s.assignedGroupId||"",i=s.assignmentState||"unassigned",r=this.groups.find(s=>s.groupId===n),o=this.isLeader()&&n===t;return`\n
\n
\n ${s.title||e}\n ${s.type||"task"}\n
\n

${s.description||""}

\n
\n ${"unassigned"===i?"Available":`${i}: ${r?r.callsign:n}`}\n X: ${Math.round(a[0]||0)} Y: ${Math.round(a[1]||0)}\n
\n ${o&&"assigned"===i?`
\n \n \n
`:""}\n
\n `}).join(""):s.innerHTML='

No contract is currently assigned to your group.

'},renderRoster(){const s=document.getElementById("rosterList");if(!s)return;if(this.isDispatchMapMode())return this.groups.length?void(s.innerHTML=this.groups.map(s=>`\n
\n
\n ${s.callsign||s.groupId||"Unknown Group"}\n ${s.role||"group"}\n
\n
\n Leader: ${s.leaderName||"Unknown"}\n Status: ${s.status||"unknown"}\n
\n
\n Members: ${this.normalizeCollection(s.members).length}\n Task: ${s.currentTaskId||"None"}\n
\n
\n `).join("")):void(s.innerHTML='

No active groups are currently available.

');const t=this.getCurrentGroup();if(!t)return void(s.innerHTML='

Your group is not currently available.

');const e=this.normalizeCollection(t.members);e.length?s.innerHTML=`\n
\n
\n ${t.callsign||t.groupId||"Current Group"}\n ${e.length} member${1===e.length?"":"s"}\n
\n
\n Leader: ${t.leaderName||"Unknown"}\n Status: ${t.status||"unknown"}\n
\n
\n Role: ${t.role||"unassigned"}\n Task: ${t.currentTaskId||"None"}\n
\n
\n ${e.map(s=>{const t=(s.lifeState||"unknown").replaceAll("_"," "),e=s.isLeader?'Leader':"";return`\n
\n
\n ${s.name||"Unknown Operator"}\n ${t}\n
\n
\n ${s.uid||"No UID"}\n ${e}\n
\n
\n `}).join("")}\n `:s.innerHTML='

No roster members are currently available.

'},renderActivity(){const s=document.getElementById("activityList");s&&(this.activity.length?s.innerHTML=this.activity.slice().reverse().slice(0,8).map(s=>`\n
\n
\n ${s.type||"activity"}\n ${Math.round(s.timestamp||0)}s\n
\n

${s.message||""}

\n
\n `).join(""):s.innerHTML='

No recent activity.

')},render(){this.syncLayoutState(),this.renderContracts(),this.renderRoster(),this.renderActivity(),this.isDispatchMapMode()||this.setActiveTab(this.activeTab)}},window.cadTasks.init(); \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-topbar.css b/arma/client/addons/cad/ui/_site/cad-topbar.css index 5ae3100..90ccdde 100644 --- a/arma/client/addons/cad/ui/_site/cad-topbar.css +++ b/arma/client/addons/cad/ui/_site/cad-topbar.css @@ -1 +1 @@ -body{background:0 0;grid-template-columns:auto minmax(0,1fr) auto auto;align-items:center;column-gap:16px;height:60px;padding:0 16px;display:grid;position:absolute;top:0;left:0;right:0;overflow:visible}body:before{content:"";height:60px;box-shadow:none;-webkit-backdrop-filter:blur(18px);z-index:0;pointer-events:none;background:linear-gradient(90deg,#10161ff5,#131a24f0 55%,#0f141cf5);border-bottom:none;position:absolute;inset:0 0 auto}body>*{z-index:1;position:relative}.logo{color:var(--accent);text-transform:uppercase;letter-spacing:.08em;text-shadow:0 1px 12px #00000059;font-size:15px;font-weight:650}.header-main{align-items:center;gap:12px;min-width:0;display:flex}.title-block{flex-direction:column;flex:none;gap:1px;min-width:0;display:flex}.title-kicker{color:#dae3ec8f;text-transform:uppercase;letter-spacing:.12em;font-size:10px}.title-main{color:#f5f8ffeb;font-size:15px;font-weight:600}.operator-strip{flex:auto;align-items:center;gap:8px;min-width:0;display:flex}.operator-strip.is-hidden,.operator-controls.is-hidden{display:none}.operator-info{flex-direction:column;gap:0;min-width:88px;display:flex}.operator-label{color:#dae3ec80;text-transform:uppercase;letter-spacing:.12em;font-size:9px}.operator-info strong{color:#f5f8ffe6;font-size:12px;font-weight:550}.operator-controls{align-items:center;gap:6px;min-width:0;display:flex}.operator-select{min-width:92px;max-width:112px;color:var(--text);background:#0e141cf5;border:1px solid #ffffff24;padding:5px 8px;font-size:11px}.btn-operator{text-transform:uppercase;letter-spacing:.08em;min-width:84px;font-size:10px}.mode-controls{justify-self:end;align-items:center;gap:8px;display:flex}.mode-controls.is-hidden{display:none}.controls{justify-self:end;align-items:center;gap:8px;display:flex}.mode-text{color:#e9f1f8b8;text-transform:uppercase;letter-spacing:.1em;font-size:10px}.mode-switch{align-items:center;width:54px;height:28px;display:inline-flex;position:relative}.mode-switch input{opacity:0;pointer-events:none;position:absolute}.mode-slider{background:#161d27eb;border:1px solid #ffffff24;border-radius:999px;width:54px;height:28px;transition:border-color .16s,background .16s;position:relative;box-shadow:inset 0 1px 10px #00000038}.mode-slider:after{content:"";background:linear-gradient(#edf4fbfa,#bdcdddeb);border-radius:50%;width:20px;height:20px;transition:transform .16s,background .16s;position:absolute;top:3px;left:3px;box-shadow:0 4px 12px #00000042}.mode-switch input:checked+.mode-slider{background:#0e2538f2;border-color:#5bbbff6b}.mode-switch input:checked+.mode-slider:after{background:linear-gradient(#83d4fffa,#48aae7f0);transform:translate(26px)}.btn-close{min-width:42px}body[data-mode=operations]{pointer-events:none}body[data-mode=operations] .logo,body[data-mode=operations] .title-block,body[data-mode=operations] .operator-strip,body[data-mode=operations] .operator-controls,body[data-mode=operations] .mode-controls,body[data-mode=operations] .controls,body[data-mode=operations] .mode-switch,body[data-mode=operations] .mode-switch *,body[data-mode=operations] button,body[data-mode=operations] select,body[data-mode=operations] label{pointer-events:auto} \ No newline at end of file +body{background:0 0;grid-template-columns:auto minmax(0,1fr) auto auto auto;align-items:center;column-gap:16px;height:60px;padding:0 16px;display:grid;position:absolute;top:0;left:0;right:0;overflow:visible}body[data-mode=operations]{grid-template-columns:auto minmax(0,1fr) auto auto}body[data-mode=dispatch]{grid-template-columns:auto minmax(0,1fr) auto auto auto}body:before{content:"";height:60px;box-shadow:none;-webkit-backdrop-filter:blur(18px);z-index:0;pointer-events:none;background:linear-gradient(90deg,#10161ff5,#131a24f0 55%,#0f141cf5);border-bottom:none;position:absolute;inset:0 0 auto}body>*{z-index:1;position:relative}.logo{color:var(--accent);text-transform:uppercase;letter-spacing:.08em;text-shadow:0 1px 12px #00000059;font-size:15px;font-weight:650}.header-main{align-items:center;gap:12px;min-width:0;display:flex}.title-block{flex-direction:column;flex:none;gap:1px;min-width:0;display:flex}.title-kicker{color:#dae3ec8f;text-transform:uppercase;letter-spacing:.12em;font-size:10px}.title-main{color:#f5f8ffeb;font-size:15px;font-weight:600}.operator-strip{flex:auto;align-items:center;gap:8px;min-width:0;display:flex}.operator-strip.is-hidden,.operator-controls.is-hidden{display:none}.operator-info{flex-direction:column;gap:0;min-width:88px;display:flex}.operator-label{color:#dae3ec80;text-transform:uppercase;letter-spacing:.12em;font-size:9px}.operator-info strong{color:#f5f8ffe6;font-size:12px;font-weight:550}.operator-controls{align-items:center;gap:6px;min-width:0;display:flex}.operator-select{min-width:92px;max-width:112px;color:var(--text);background:#0e141cf5;border:1px solid #ffffff24;padding:5px 8px;font-size:11px}.btn-operator{text-transform:uppercase;letter-spacing:.08em;min-width:84px;font-size:10px}.mode-controls{justify-self:end;align-items:center;gap:8px;display:flex}.mode-controls.is-hidden{display:none}.dispatch-view-controls{justify-self:end;align-items:center;gap:6px;display:flex}.dispatch-view-controls.is-hidden{display:none}.controls{justify-self:end;align-items:center;gap:8px;display:flex}.mode-text{color:#e9f1f8b8;text-transform:uppercase;letter-spacing:.1em;font-size:10px}.mode-switch{align-items:center;width:54px;height:28px;display:inline-flex;position:relative}.mode-switch input{opacity:0;pointer-events:none;position:absolute}.mode-slider{background:#161d27eb;border:1px solid #ffffff24;border-radius:999px;width:54px;height:28px;transition:border-color .16s,background .16s;position:relative;box-shadow:inset 0 1px 10px #00000038}.mode-slider:after{content:"";background:linear-gradient(#edf4fbfa,#bdcdddeb);border-radius:50%;width:20px;height:20px;transition:transform .16s,background .16s;position:absolute;top:3px;left:3px;box-shadow:0 4px 12px #00000042}.mode-switch input:checked+.mode-slider{background:#0e2538f2;border-color:#5bbbff6b}.mode-switch input:checked+.mode-slider:after{background:linear-gradient(#83d4fffa,#48aae7f0);transform:translate(26px)}.btn-close{min-width:42px}.btn-dispatch-view{text-transform:uppercase;letter-spacing:.08em;min-width:66px;padding:6px 10px;font-size:10px}.btn-icon{justify-content:center;align-items:center;width:34px;min-width:34px;height:30px;padding:0;font-size:16px;line-height:1;display:inline-flex}.btn-refresh{width:40px;min-width:40px;font-size:17px;font-weight:600}.btn-dispatch-view.is-active{color:var(--accent);background:#0f283af5;border-color:#5bbbff6b}.btn-close{font-size:14px}body{pointer-events:none}body .logo,body .title-block,body .operator-strip,body .operator-controls,body .mode-controls,body .dispatch-view-controls,body .controls,body .mode-switch,body .mode-switch *,body button,body select,body label{pointer-events:auto} \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/cad-topbar.js b/arma/client/addons/cad/ui/_site/cad-topbar.js index b347404..919a290 100644 --- a/arma/client/addons/cad/ui/_site/cad-topbar.js +++ b/arma/client/addons/cad/ui/_site/cad-topbar.js @@ -1 +1 @@ -window.cadTopbar={mode:"operations",currentGroup:null,session:{},init(){document.getElementById("btnClose").addEventListener("click",()=>{window.mapUI.sendEvent("map::close",null)}),document.getElementById("modeToggle").addEventListener("change",e=>{window.mapUI.sendEvent("cad::mode::set",{mode:e.target.checked?"dispatch":"operations"})}),document.getElementById("operatorRoleBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::role",{groupID:this.currentGroup.groupId||"",role:document.getElementById("operatorRoleSelect").value})}),document.getElementById("operatorStatusBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::status",{groupID:this.currentGroup.groupId||"",status:document.getElementById("operatorStatusSelect").value})}),window.mapUI.sendEvent("cad::topbar::ready",{})},formatLocation(e){const t=Array.isArray(e?.position)?e.position:[0,0,0];return`X: ${Math.round(t[0]||0).toString().padStart(4,"0")} Y: ${Math.round(t[1]||0).toString().padStart(4,"0")}`},receiveState(e){this.session=e&&e.session&&"object"==typeof e.session?e.session:{},this.mode=e&&"string"==typeof e.mode?e.mode:"operations",this.currentGroup=e&&e.currentGroup&&"object"==typeof e.currentGroup?e.currentGroup:null;const t=document.getElementById("modeControls"),o=!!this.session.isDispatcher,r=!(!this.currentGroup||!this.session.isLeader&&!this.session.isDispatcher),n=document.getElementById("operatorStrip"),s=document.getElementById("operatorControls");t.classList.toggle("is-hidden",!o),n.classList.toggle("is-hidden","operations"!==this.mode||!this.currentGroup),s.classList.toggle("is-hidden",!r),document.body.dataset.mode=this.mode,document.body.dataset.dispatcher=o?"true":"false",document.getElementById("modeToggle").checked="dispatch"===this.mode,document.getElementById("operatorGroupName").textContent=this.currentGroup?this.currentGroup.callsign||this.currentGroup.groupId||"Current Group":"No Group",document.getElementById("operatorLocation").textContent=this.currentGroup?this.formatLocation(this.currentGroup):"Unavailable",this.currentGroup&&(document.getElementById("operatorRoleSelect").value=this.currentGroup.role||"infantry",document.getElementById("operatorStatusSelect").value=this.currentGroup.status||"available")}},window.cadTopbar.init(); \ No newline at end of file +window.cadTopbar={mode:"operations",dispatchView:"board",currentGroup:null,session:{},init(){document.getElementById("btnClose").addEventListener("click",()=>{window.mapUI.sendEvent("map::close",null)}),document.getElementById("modeToggle").addEventListener("change",e=>{window.mapUI.sendEvent("cad::mode::set",{mode:e.target.checked?"dispatch":"operations"})}),document.getElementById("dispatchRefreshBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::refresh",{})}),document.getElementById("dispatchBoardBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::dispatchView::set",{dispatchView:"board"})}),document.getElementById("dispatchMapBtn").addEventListener("click",()=>{window.mapUI.sendEvent("cad::dispatchView::set",{dispatchView:"map"})}),document.getElementById("operatorRoleBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::role",{groupID:this.currentGroup.groupId||"",role:document.getElementById("operatorRoleSelect").value})}),document.getElementById("operatorStatusBtn").addEventListener("click",()=>{this.currentGroup&&window.mapUI.sendEvent("cad::groups::status",{groupID:this.currentGroup.groupId||"",status:document.getElementById("operatorStatusSelect").value})}),window.mapUI.sendEvent("cad::topbar::ready",{})},formatLocation(e){const t=Array.isArray(e?.position)?e.position:[0,0,0];return`X: ${Math.round(t[0]||0).toString().padStart(4,"0")} Y: ${Math.round(t[1]||0).toString().padStart(4,"0")}`},receiveState(e){this.session=e&&e.session&&"object"==typeof e.session?e.session:{},this.mode=e&&"string"==typeof e.mode?e.mode:"operations",this.dispatchView=e&&"string"==typeof e.dispatchView?e.dispatchView:"board",this.currentGroup=e&&e.currentGroup&&"object"==typeof e.currentGroup?e.currentGroup:null;const t=document.getElementById("modeControls"),o=!!this.session.isDispatcher,s=!(!this.currentGroup||!this.session.isLeader&&!this.session.isDispatcher),n=document.getElementById("operatorStrip"),d=document.getElementById("operatorControls"),r=document.getElementById("dispatchViewControls"),i=document.getElementById("dispatchRefreshBtn"),a=document.getElementById("dispatchBoardBtn"),c=document.getElementById("dispatchMapBtn");t.classList.toggle("is-hidden",!o),r.classList.toggle("is-hidden",!o||"dispatch"!==this.mode),n.classList.toggle("is-hidden","operations"!==this.mode||!this.currentGroup),d.classList.toggle("is-hidden",!s),document.body.dataset.mode=this.mode,document.body.dataset.dispatcher=o?"true":"false",document.getElementById("modeToggle").checked="dispatch"===this.mode,a.classList.toggle("is-active","board"===this.dispatchView),c.classList.toggle("is-active","map"===this.dispatchView),i.title="dispatch"===this.mode?"Refresh dispatch board":"Refresh CAD",i.setAttribute("aria-label","dispatch"===this.mode?"Refresh dispatch board":"Refresh CAD"),document.getElementById("operatorGroupName").textContent=this.currentGroup?this.currentGroup.callsign||this.currentGroup.groupId||"Current Group":"No Group",document.getElementById("operatorLocation").textContent=this.currentGroup?this.formatLocation(this.currentGroup):"Unavailable",this.currentGroup&&(document.getElementById("operatorRoleSelect").value=this.currentGroup.role||"infantry",document.getElementById("operatorStatusSelect").value=this.currentGroup.status||"available")}},window.cadTopbar.init(); \ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/dispatcher.html b/arma/client/addons/cad/ui/_site/dispatcher.html index f44f296..b06c997 100644 --- a/arma/client/addons/cad/ui/_site/dispatcher.html +++ b/arma/client/addons/cad/ui/_site/dispatcher.html @@ -1 +1 @@ -

Dispatch Dashboard

Operational Board

Open Contracts 0
Assigned Contracts 0
Active Groups 0
Groups In Danger 0

Available Contracts

Assigned Contracts

Group Board

Activity Feed

\ No newline at end of file +

Dispatch Dashboard

Operational Board

Open Contracts 0
Assigned Contracts 0
Active Groups 0
Groups In Danger 0

Available Contracts

Assigned Contracts

Group Board

Activity Feed

\ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/sidepanel.html b/arma/client/addons/cad/ui/_site/sidepanel.html index 7917e62..aa80c55 100644 --- a/arma/client/addons/cad/ui/_site/sidepanel.html +++ b/arma/client/addons/cad/ui/_site/sidepanel.html @@ -1 +1 @@ -

CAD System

Contracts

Loading contracts...

Roster

Loading roster...

Activity

No recent activity.

\ No newline at end of file +

CAD System

Contracts

Loading contracts...

Roster

Loading roster...

Activity

No recent activity.

\ No newline at end of file diff --git a/arma/client/addons/cad/ui/_site/topbar.html b/arma/client/addons/cad/ui/_site/topbar.html index 4e31fcb..d4f30c5 100644 --- a/arma/client/addons/cad/ui/_site/topbar.html +++ b/arma/client/addons/cad/ui/_site/topbar.html @@ -1 +1 @@ -
Cad Systems FORGE Command & Dispatch
\ No newline at end of file +
Cad Systems FORGE Command & Dispatch
\ No newline at end of file diff --git a/arma/client/addons/cad/ui/src/dispatcher.html b/arma/client/addons/cad/ui/src/dispatcher.html index 43def30..207ed7a 100644 --- a/arma/client/addons/cad/ui/src/dispatcher.html +++ b/arma/client/addons/cad/ui/src/dispatcher.html @@ -10,9 +10,6 @@

Dispatch Dashboard

Operational Board

-
diff --git a/arma/client/addons/cad/ui/src/dispatcher.js b/arma/client/addons/cad/ui/src/dispatcher.js index b29b427..8d298ba 100644 --- a/arma/client/addons/cad/ui/src/dispatcher.js +++ b/arma/client/addons/cad/ui/src/dispatcher.js @@ -15,13 +15,6 @@ window.cadDispatcher = { ], 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", () => { diff --git a/arma/client/addons/cad/ui/src/sidepanel.html b/arma/client/addons/cad/ui/src/sidepanel.html index 9fa2c33..e6f8d4c 100644 --- a/arma/client/addons/cad/ui/src/sidepanel.html +++ b/arma/client/addons/cad/ui/src/sidepanel.html @@ -8,9 +8,6 @@

CAD System

-
- -
+
- + +