Files
bladwin-RP/lua/bladw_announcement/client/admin_menu/cl_menu.lua
T
nocode 05864e1ed4 feat(admin_menu): add server feedback handling for announcement submissions
Implement bidirectional communication for announcement submissions:
- Display server feedback messages (e.g., announcement already in progress) in the admin menu
- Keep the menu open after sending to allow users to retry if submission fails
- Add net.Receive handler to process server responses
- Show countdown timer when announcement is already in progress
- Close menu only on successful publication, not on rejection

This improves UX by giving admins visibility into why their submission failed and allowing them to correct and resubmit without losing their input.
2026-07-20 14:22:50 +02:00

211 lines
8.2 KiB
Lua

local rndx = include("lib/rndx.lua")
local logo = Material("nocode/announcement/logo.png")
BladW = BladW or {}
BladW.Announcement = BladW.Announcement or {}
-- mêmes couleurs que les annonces
local COL_BG = Color(0, 0, 0, 200)
local COL_PANEL = Color(0, 0, 0, 160)
local COL_ACCENT = Color(76, 119, 200)
local COL_ACCENT_DIM = Color(60, 90, 150)
local COL_TEXT = Color(255, 255, 255)
local COL_RED = Color(255, 80, 80)
local COL_GRAY = Color(54, 54, 54, 255)
local COL_MUTED = Color(160, 160, 160)
local COL_WARN = Color(255, 200, 50)
-- nb de caractères d'un champ (UTF-8, un accent = 1)
local function EntryLen(entry)
local v = entry:GetValue()
return utf8.len(v) or #v
end
-- petit triangle warning dessiné à la main (comme ça pas besoin de font emoji)
local function DrawWarning(x, y, size)
draw.NoTexture()
surface.SetDrawColor(COL_WARN)
surface.DrawPoly({
{ x = x + size * 0.5, y = y },
{ x = x + size, y = y + size },
{ x = x, y = y + size },
})
draw.SimpleText("!", "DermaDefaultBold", x + size * 0.5, y + size * 0.62, Color(30, 30, 30), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
-- mon style pour les champs de texte
local function StyleEntry(entry, placeholder, default)
entry:SetFont("bladw_text_Medium")
entry:SetText(default or "")
entry:SetPlaceholderText(placeholder or "")
entry:SetTextColor(COL_TEXT)
entry:SetPaintBackground(false)
entry.Paint = function(self, w, h)
rndx.Draw(10, 0, 0, w, h, COL_PANEL, rndx.SHAPE_FIGMA)
local outline = self:IsEditing() and COL_ACCENT or Color(255, 255, 255, 60)
rndx.DrawOutlined(10, 0, 0, w, h, outline, 2, rndx.SHAPE_FIGMA)
self:DrawTextEntryText(COL_TEXT, COL_ACCENT, COL_TEXT)
end
end
local function OpenAAdminMenu()
-- si le menu est déjà ouvert je le vire
if IsValid(BladW_AdminMenu) then BladW_AdminMenu:Remove() end
local selectedPreset = ""
local maxLen = (BladW.Announcement.Conf or {}).announcement_max_length or 120
local frame = vgui.Create("DFrame")
BladW_AdminMenu = frame
frame:SetSize(500, 460)
frame:Center()
frame:SetTitle("")
frame:ShowCloseButton(false)
frame:MakePopup()
frame.Paint = function(self, w, h)
-- fond flou + sombre + contour blanc (comme la box d'annonce)
rndx.Draw(20, 0, 0, w, h, nil, rndx.SHAPE_FIGMA + rndx.BLUR)
rndx.Draw(20, 0, 0, w, h, COL_BG, rndx.SHAPE_FIGMA)
rndx.DrawOutlined(20, 0, 0, w, h, COL_TEXT, 3, rndx.SHAPE_FIGMA)
-- logo + titre
rndx.DrawMaterial(0, w * 0.5 - 30, 20, 60, 60, COL_TEXT, logo)
draw.SimpleText("Envoyer une annonce", "bladw_text", w * 0.5, 94, COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText("Presets", "bladw_text_Medium", 40, 118, COL_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
-- ESC ferme mon menu sans laisser le menu pause s'afficher.
-- le moteur ouvre le menu pause sur ESC avant que je puisse l'intercepter, donc je le recache direct et je ferme.
frame.Think = function(self)
if gui.IsGameUIVisible() then
gui.HideGameUI()
self:Remove()
end
end
-- bouton pour fermer
local close = vgui.Create("DButton", frame)
close:SetSize(30, 30)
close:SetPos(frame:GetWide() - 42, 14)
close:SetText("")
close.Paint = function(self, w, h)
rndx.Draw(8, 0, 0, w, h, self:IsHovered() and COL_RED or COL_GRAY, rndx.SHAPE_FIGMA)
draw.SimpleText("X", "bladw_text_Medium", w * 0.5, h * 0.5, COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
close.DoClick = function() frame:Remove() end
-- les champs (créés avant les boutons preset pour être captés dans leurs closures)
local titleEntry = vgui.Create("DTextEntry", frame)
titleEntry:SetPos(40, 240)
titleEntry:SetSize(420, 40)
StyleEntry(titleEntry, "Titre de l'annonce", "Annonce")
local msgEntry = vgui.Create("DTextEntry", frame)
msgEntry:SetPos(40, 288)
msgEntry:SetSize(420, 40)
StyleEntry(msgEntry, "Message de l'annonce")
-- bord rouge quand ça dépasse la limite
msgEntry.Paint = function(self, w, h)
local over = EntryLen(self) > maxLen
rndx.Draw(10, 0, 0, w, h, COL_PANEL, rndx.SHAPE_FIGMA)
local outline = over and COL_RED or (self:IsEditing() and COL_ACCENT or Color(255, 255, 255, 60))
rndx.DrawOutlined(10, 0, 0, w, h, outline, over and 3 or 2, rndx.SHAPE_FIGMA)
self:DrawTextEntryText(COL_TEXT, COL_ACCENT, COL_TEXT)
end
-- ligne du bas : warning à gauche, compteur à droite
local status = vgui.Create("DPanel", frame)
status:SetPos(40, 332)
status:SetSize(420, 26)
status:SetPaintBackground(false)
status.Paint = function(self, w, h)
local len = EntryLen(msgEntry)
local over = len > maxLen
-- retour du serveur (ex : annonce déjà en cours) sinon le warning de dépassement
if frame.sendFeedback and (frame.sendFeedbackUntil or 0) > SysTime() then
DrawWarning(0, h * 0.5 - 8, 16)
draw.SimpleText(frame.sendFeedback, "bladw_text_Medium", 24, h * 0.5, COL_WARN, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
elseif over then
DrawWarning(0, h * 0.5 - 8, 16)
draw.SimpleText("Trop de caractères", "bladw_text_Medium", 24, h * 0.5, COL_RED, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
draw.SimpleText(len .. " / " .. maxLen, "bladw_text_Medium", w, h * 0.5,
over and COL_RED or COL_MUTED, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
end
-- boutons preset : remplissent les champs et retiennent le preset choisi
local presets = vgui.Create("DIconLayout", frame)
presets:SetPos(40, 134)
presets:SetSize(420, 90)
presets:SetSpaceX(8)
presets:SetSpaceY(8)
for key, preset in SortedPairs(BladW.Announcement.Presets or {}) do
local b = presets:Add("DButton")
b:SetSize(134, 34)
b:SetText("")
b.Paint = function(self, w, h)
local on = (selectedPreset == key) or self:IsHovered()
rndx.Draw(8, 0, 0, w, h, on and COL_ACCENT or COL_GRAY, rndx.SHAPE_FIGMA)
draw.SimpleText(preset.title or key, "bladw_text_Medium", w * 0.5, h * 0.5, COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
b.DoClick = function()
selectedPreset = key
titleEntry:SetText(preset.title or "")
msgEntry:SetText(preset.text or "")
end
end
-- bouton envoyer (grisé + bloqué si ça dépasse)
local send = vgui.Create("DButton", frame)
send:SetPos(40, 372)
send:SetSize(420, 50)
send:SetText("")
send.Paint = function(self, w, h)
local over = EntryLen(msgEntry) > maxLen
local col = over and COL_GRAY or (self:IsHovered() and COL_ACCENT or COL_ACCENT_DIM)
rndx.Draw(12, 0, 0, w, h, col, rndx.SHAPE_FIGMA)
draw.SimpleText("Envoyer", "bladw_text", w * 0.5, h * 0.5, over and COL_MUTED or COL_TEXT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
send.DoClick = function()
if EntryLen(msgEntry) > maxLen then
surface.PlaySound("buttons/button10.wav")
return
end
if msgEntry:GetValue() == "" and selectedPreset == "" then
surface.PlaySound("buttons/button10.wav")
return
end
-- on envoie mais on NE ferme PAS : c'est la réponse serveur qui fermera si c'est publié,
-- comme ça si c'est refusé l'admin garde son texte
net.Start("bladWSendAnnouncement")
net.WriteString(selectedPreset)
net.WriteString(titleEntry:GetValue())
net.WriteString(msgEntry:GetValue())
net.SendToServer()
end
end
-- réponse du serveur après un envoi : publié -> on ferme, refusé -> on garde le menu + on dit pourquoi
net.Receive("bladWAnnouncementResult", function()
local ok = net.ReadBool()
local left = net.ReadUInt(8)
if not IsValid(BladW_AdminMenu) then return end
if ok then
BladW_AdminMenu:Remove()
else
BladW_AdminMenu.sendFeedback = left > 0 and ("Annonce déjà en cours (" .. left .. "s)") or "Envoi impossible"
BladW_AdminMenu.sendFeedbackUntil = SysTime() + 5
end
end)
return OpenAAdminMenu