05864e1ed4
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.
211 lines
8.2 KiB
Lua
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
|