mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 10:12:45 +00:00
Merge pull request #6679 from VOREStation/upstream-merge-6719
[MIRROR] Adds an In-game Feedback System
This commit is contained in:
52
code/datums/managed_browsers/_managed_browser.dm
Normal file
52
code/datums/managed_browsers/_managed_browser.dm
Normal file
@@ -0,0 +1,52 @@
|
||||
GLOBAL_VAR(managed_browser_id_ticker)
|
||||
|
||||
// This holds information on managing a /datum/browser object.
|
||||
// Managing can include things like persisting the state of specific information inside of this object, receiving Topic() calls, or deleting itself when the window is closed.
|
||||
// This is useful for browser windows to be able to stand 'on their own' instead of being tied to something in the game world, like an object or mob.
|
||||
/datum/managed_browser
|
||||
var/client/my_client = null
|
||||
var/browser_id = null
|
||||
var/base_browser_id = null
|
||||
|
||||
var/title = null
|
||||
var/size_x = 200
|
||||
var/size_y = 400
|
||||
|
||||
var/display_when_created = TRUE
|
||||
|
||||
/datum/managed_browser/New(client/new_client)
|
||||
if(!new_client)
|
||||
crash_with("Managed browser object was not given a client.")
|
||||
return
|
||||
if(!base_browser_id)
|
||||
crash_with("Managed browser object does not have a base browser id defined in its type.")
|
||||
return
|
||||
|
||||
my_client = new_client
|
||||
browser_id = "[base_browser_id]-[GLOB.managed_browser_id_ticker++]"
|
||||
|
||||
if(display_when_created)
|
||||
display()
|
||||
|
||||
/datum/managed_browser/Destroy()
|
||||
my_client = null
|
||||
return ..()
|
||||
|
||||
// Override if you want to have the browser title change conditionally.
|
||||
// Otherwise it's easier to just change the title variable directly.
|
||||
/datum/managed_browser/proc/get_title()
|
||||
return title
|
||||
|
||||
// Override to display the html information.
|
||||
// It is suggested to build it with a list, and use list.Join() at the end.
|
||||
// This helps prevent excessive concatination, which helps preserves BYOND's string tree from becoming a laggy mess.
|
||||
/datum/managed_browser/proc/get_html()
|
||||
return
|
||||
|
||||
/datum/managed_browser/proc/display()
|
||||
interact(get_html(), get_title(), my_client)
|
||||
|
||||
/datum/managed_browser/proc/interact(html, title, client/C)
|
||||
var/datum/browser/popup = new(C.mob, browser_id, title, size_x, size_y, src)
|
||||
popup.set_content(html)
|
||||
popup.open()
|
||||
147
code/datums/managed_browsers/feedback_form.dm
Normal file
147
code/datums/managed_browsers/feedback_form.dm
Normal file
@@ -0,0 +1,147 @@
|
||||
/client
|
||||
var/datum/managed_browser/feedback_form/feedback_form = null
|
||||
|
||||
/client/can_vv_get(var_name)
|
||||
return var_name != NAMEOF(src, feedback_form) // No snooping.
|
||||
|
||||
GENERAL_PROTECT_DATUM(datum/managed_browser/feedback_form)
|
||||
|
||||
// A fairly simple object to hold information about a player's feedback as it's being written.
|
||||
// Having this be it's own object instead of being baked into /mob/new_player allows for it to be used
|
||||
// from other places than just the lobby, and makes it a lot harder for people with dev powers to be naughty with it using VV/proccall.
|
||||
/datum/managed_browser/feedback_form
|
||||
base_browser_id = "feedback_form"
|
||||
title = "Server Feedback"
|
||||
size_x = 480
|
||||
size_y = 520
|
||||
var/feedback_topic = null
|
||||
var/feedback_body = null
|
||||
var/feedback_hide_author = FALSE
|
||||
|
||||
/datum/managed_browser/feedback_form/New(client/new_client)
|
||||
feedback_topic = config.sqlite_feedback_topics[1]
|
||||
..(new_client)
|
||||
|
||||
/datum/managed_browser/feedback_form/Destroy()
|
||||
if(my_client)
|
||||
my_client.feedback_form = null
|
||||
return ..()
|
||||
|
||||
// Privacy option is allowed if both the config allows it, and the pepper file exists and isn't blank.
|
||||
/datum/managed_browser/feedback_form/proc/can_be_private()
|
||||
return config.sqlite_feedback_privacy && SSsqlite.get_feedback_pepper()
|
||||
|
||||
/datum/managed_browser/feedback_form/display()
|
||||
if(!my_client)
|
||||
return
|
||||
if(!SSsqlite.can_submit_feedback(my_client))
|
||||
return
|
||||
..()
|
||||
|
||||
// Builds the window for players to review their feedback.
|
||||
/datum/managed_browser/feedback_form/get_html()
|
||||
var/list/dat = list("<html><body>")
|
||||
dat += "<center>"
|
||||
dat += "<font size='2'>"
|
||||
dat += "Here, you can write some feedback for the server.<br>"
|
||||
dat += "Note that HTML is NOT supported!<br>"
|
||||
dat += "Click the edit button to begin writing.<br>"
|
||||
|
||||
dat += "Your feedback is currently [length(feedback_body)]/[MAX_FEEDBACK_LENGTH] letters long."
|
||||
dat += "</font>"
|
||||
dat += "<hr>"
|
||||
|
||||
dat += "<h2>Preview</h2></center>"
|
||||
|
||||
dat += "Author: "
|
||||
|
||||
if(can_be_private())
|
||||
if(!feedback_hide_author)
|
||||
dat += "[my_client.ckey] "
|
||||
dat += span("linkOn", "<b>Visible</b>")
|
||||
dat += " | "
|
||||
dat += href(src, list("feedback_hide_author" = 1), "Hashed")
|
||||
else
|
||||
dat += "[md5(ckey(lowertext(my_client.ckey + SSsqlite.get_feedback_pepper())))] "
|
||||
dat += href(src, list("feedback_hide_author" = 0), "Visible")
|
||||
dat += " | "
|
||||
dat += span("linkOn", "<b>Hashed</b>")
|
||||
else
|
||||
dat += my_client.ckey
|
||||
dat += "<br>"
|
||||
|
||||
if(config.sqlite_feedback_topics.len > 1)
|
||||
dat += "Topic: [href(src, list("feedback_choose_topic" = 1), feedback_topic)]<br>"
|
||||
else
|
||||
dat += "Topic: [config.sqlite_feedback_topics[1]]<br>"
|
||||
|
||||
dat += "<br>"
|
||||
if(feedback_body)
|
||||
dat += replacetext(feedback_body, "\n", "<br>") // So newlines will look like they work in the preview.
|
||||
else
|
||||
dat += "<i>\[Feedback goes here...\]</i>"
|
||||
dat += "<br>"
|
||||
dat += href(src, list("feedback_edit_body" = 1), "Edit")
|
||||
dat += "<hr>"
|
||||
|
||||
if(config.sqlite_feedback_cooldown)
|
||||
dat += "<i>Please note that you will have to wait [config.sqlite_feedback_cooldown] day\s before \
|
||||
being able to write more feedback after submitting.</i><br>"
|
||||
|
||||
dat += href(src, list("feedback_submit" = 1), "Submit")
|
||||
dat += "</body></html>"
|
||||
return dat.Join()
|
||||
|
||||
/datum/managed_browser/feedback_form/Topic(href, href_list[])
|
||||
if(!my_client)
|
||||
return FALSE
|
||||
|
||||
if(href_list["feedback_edit_body"])
|
||||
// This is deliberately not sanitized here, and is instead checked when hitting the submission button,
|
||||
// as we want to give the user a chance to fix it without needing to rewrite the whole thing.
|
||||
feedback_body = input(my_client, "Please write your feedback here.", "Feedback Body", feedback_body) as null|message
|
||||
display() // Refresh the window with new information.
|
||||
return
|
||||
|
||||
if(href_list["feedback_hide_author"])
|
||||
if(!can_be_private())
|
||||
feedback_hide_author = FALSE
|
||||
else
|
||||
feedback_hide_author = text2num(href_list["feedback_hide_author"])
|
||||
display()
|
||||
return
|
||||
|
||||
if(href_list["feedback_choose_topic"])
|
||||
feedback_topic = input(my_client, "Choose the topic you want to submit your feedback under.", "Feedback Topic", feedback_topic) in config.sqlite_feedback_topics
|
||||
display()
|
||||
return
|
||||
|
||||
if(href_list["feedback_submit"])
|
||||
// Do some last minute validation, and tell the user if something goes wrong,
|
||||
// so we don't wipe out their ten thousand page essay due to having a few too many characters.
|
||||
if(length(feedback_body) > MAX_FEEDBACK_LENGTH)
|
||||
to_chat(my_client, span("warning", "Your feedback is too long, at [length(feedback_body)] characters, where as the \
|
||||
limit is [MAX_FEEDBACK_LENGTH]. Please shorten it and try again."))
|
||||
return
|
||||
|
||||
var/text = sanitize(feedback_body, max_length = 0, encode = TRUE, trim = FALSE, extra = FALSE)
|
||||
if(!text) // No text, or it was super invalid.
|
||||
to_chat(my_client, span("warning", "It appears you didn't write anything, or it was invalid."))
|
||||
return
|
||||
|
||||
if(alert(my_client, "Are you sure you want to submit your feedback?", "Confirm Submission", "No", "Yes") == "Yes")
|
||||
var/author_text = my_client.ckey
|
||||
if(can_be_private() && feedback_hide_author)
|
||||
author_text = md5(my_client.ckey + SSsqlite.get_feedback_pepper())
|
||||
|
||||
var/success = SSsqlite.insert_feedback(author = author_text, topic = feedback_topic, content = feedback_body, sqlite_object = SSsqlite.sqlite_db)
|
||||
if(!success)
|
||||
to_chat(my_client, span("warning", "Something went wrong while inserting your feedback into the database. Please try again. \
|
||||
If this happens again, you should contact a developer."))
|
||||
return
|
||||
|
||||
my_client.mob << browse(null, "window=[browser_id]") // Closes the window.
|
||||
if(istype(my_client.mob, /mob/new_player))
|
||||
var/mob/new_player/NP = my_client.mob
|
||||
NP.new_player_panel_proc() // So the feedback button goes away, if the user gets put on cooldown.
|
||||
qdel(src)
|
||||
162
code/datums/managed_browsers/feedback_viewer.dm
Normal file
162
code/datums/managed_browsers/feedback_viewer.dm
Normal file
@@ -0,0 +1,162 @@
|
||||
/client
|
||||
var/datum/managed_browser/feedback_viewer/feedback_viewer = null
|
||||
|
||||
/datum/admins/proc/view_feedback()
|
||||
set category = "Admin"
|
||||
set name = "View Feedback"
|
||||
set desc = "Open the Feedback Viewer"
|
||||
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
if(usr.client.feedback_viewer)
|
||||
usr.client.feedback_viewer.display()
|
||||
else
|
||||
usr.client.feedback_viewer = new(usr.client)
|
||||
|
||||
// This object holds the code to run the admin feedback viewer.
|
||||
/datum/managed_browser/feedback_viewer
|
||||
base_browser_id = "feedback_viewer"
|
||||
title = "Submitted Feedback"
|
||||
size_x = 900
|
||||
size_y = 500
|
||||
var/database/query/last_query = null
|
||||
|
||||
/datum/managed_browser/feedback_viewer/New(client/new_client)
|
||||
if(!check_rights(R_ADMIN, new_client)) // Just in case someone figures out a way to spawn this as non-staff.
|
||||
message_admins("[new_client] tried to view feedback with insufficent permissions.")
|
||||
qdel(src)
|
||||
|
||||
..()
|
||||
|
||||
/datum/managed_browser/feedback_viewer/Destroy()
|
||||
if(my_client)
|
||||
my_client.feedback_viewer = null
|
||||
return ..()
|
||||
|
||||
/datum/managed_browser/feedback_viewer/proc/feedback_filter(row_name, thing_to_find, exact = FALSE)
|
||||
var/database/query/query = null
|
||||
if(exact) // Useful for ID searches, so searching for 'id 10' doesn't also get 'id 101'.
|
||||
query = new({"
|
||||
SELECT *
|
||||
FROM [SQLITE_TABLE_FEEDBACK]
|
||||
WHERE [row_name] == ?
|
||||
ORDER BY [SQLITE_FEEDBACK_COLUMN_ID]
|
||||
DESC LIMIT 50;
|
||||
"},
|
||||
thing_to_find
|
||||
)
|
||||
|
||||
else
|
||||
// Wrap the thing in %s so LIKE will work.
|
||||
thing_to_find = "%[thing_to_find]%"
|
||||
query = new({"
|
||||
SELECT *
|
||||
FROM [SQLITE_TABLE_FEEDBACK]
|
||||
WHERE [row_name] LIKE ?
|
||||
ORDER BY [SQLITE_FEEDBACK_COLUMN_ID]
|
||||
DESC LIMIT 50;
|
||||
"},
|
||||
thing_to_find
|
||||
)
|
||||
query.Execute(SSsqlite.sqlite_db)
|
||||
SSsqlite.sqlite_check_for_errors(query, "Admin Feedback Viewer - Filter by [row_name] to find [thing_to_find]")
|
||||
return query
|
||||
|
||||
// Builds the window for players to review their feedback.
|
||||
/datum/managed_browser/feedback_viewer/get_html()
|
||||
var/list/dat = list("<html><body>")
|
||||
if(!last_query) // If no query was done before, just show the most recent feedbacks.
|
||||
var/database/query/query = new({"
|
||||
SELECT *
|
||||
FROM [SQLITE_TABLE_FEEDBACK]
|
||||
ORDER BY [SQLITE_FEEDBACK_COLUMN_ID]
|
||||
DESC LIMIT 50;
|
||||
"}
|
||||
)
|
||||
query.Execute(SSsqlite.sqlite_db)
|
||||
SSsqlite.sqlite_check_for_errors(query, "Admin Feedback Viewer")
|
||||
last_query = query
|
||||
|
||||
dat += "<table border='1' style='width:100%'>"
|
||||
dat += "<tr>"
|
||||
dat += "<th>[href(src, list("filter_id" = 1), "ID")]</th>"
|
||||
dat += "<th>[href(src, list("filter_topic" = 1), "Topic")]</th>"
|
||||
dat += "<th>[href(src, list("filter_author" = 1), "Author")]</th>"
|
||||
dat += "<th>[href(src, list("filter_content" = 1), "Content")]</th>"
|
||||
dat += "<th>[href(src, list("filter_datetime" = 1), "Datetime")]</th>"
|
||||
dat += "</tr>"
|
||||
|
||||
while(last_query.NextRow())
|
||||
var/list/row_data = last_query.GetRowData()
|
||||
dat += "<tr>"
|
||||
dat += "<td>[row_data[SQLITE_FEEDBACK_COLUMN_ID]]</td>"
|
||||
dat += "<td>[row_data[SQLITE_FEEDBACK_COLUMN_TOPIC]]</td>"
|
||||
dat += "<td>[row_data[SQLITE_FEEDBACK_COLUMN_AUTHOR]]</td>" // TODO: Color this to make hashed keys more distinguishable.
|
||||
var/text = row_data[SQLITE_FEEDBACK_COLUMN_CONTENT]
|
||||
if(length(text) > 512)
|
||||
text = href(src, list(
|
||||
"show_full_feedback" = 1,
|
||||
"feedback_author" = row_data[SQLITE_FEEDBACK_COLUMN_AUTHOR],
|
||||
"feedback_content" = row_data[SQLITE_FEEDBACK_COLUMN_CONTENT]
|
||||
), "[copytext(text, 1, 64)]... ([length(text)])")
|
||||
else
|
||||
text = replacetext(text, "\n", "<br>")
|
||||
dat += "<td>[text]</td>"
|
||||
dat += "<td>[row_data[SQLITE_FEEDBACK_COLUMN_DATETIME]]</td>"
|
||||
dat += "</tr>"
|
||||
dat += "</table>"
|
||||
|
||||
dat += "</body></html>"
|
||||
return dat.Join()
|
||||
|
||||
// Used to show the full version of feedback in a seperate window.
|
||||
/datum/managed_browser/feedback_viewer/proc/display_big_feedback(author, text)
|
||||
var/list/dat = list("<html><body>")
|
||||
dat += replacetext(text, "\n", "<br>")
|
||||
|
||||
var/datum/browser/popup = new(my_client.mob, "feedback_big", "[author]'s Feedback", 480, 520, src)
|
||||
popup.set_content(dat.Join())
|
||||
popup.open()
|
||||
|
||||
|
||||
/datum/managed_browser/feedback_viewer/Topic(href, href_list[])
|
||||
if(!my_client)
|
||||
return FALSE
|
||||
|
||||
if(href_list["close"]) // To avoid refreshing.
|
||||
return
|
||||
|
||||
if(href_list["show_full_feedback"])
|
||||
display_big_feedback(href_list["feedback_author"], href_list["feedback_content"])
|
||||
return
|
||||
|
||||
if(href_list["filter_id"])
|
||||
var/id_to_search = input(my_client, "Write feedback ID here.", "Filter by ID", null) as null|num
|
||||
if(id_to_search)
|
||||
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_ID, id_to_search, TRUE)
|
||||
|
||||
if(href_list["filter_author"])
|
||||
var/author_to_search = input(my_client, "Write desired key or hash here. Partial keys/hashes are allowed.", "Filter by Author", null) as null|text
|
||||
if(author_to_search)
|
||||
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_AUTHOR, author_to_search)
|
||||
|
||||
if(href_list["filter_topic"])
|
||||
var/topic_to_search = input(my_client, "Write desired topic here. Partial topics are allowed. \
|
||||
\nThe current topics in the config are [english_list(config.sqlite_feedback_topics)].", "Filter by Topic", null) as null|text
|
||||
if(topic_to_search)
|
||||
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_TOPIC, topic_to_search)
|
||||
|
||||
if(href_list["filter_content"])
|
||||
var/content_to_search = input(my_client, "Write desired content to find here. Partial matches are allowed.", "Filter by Content", null) as null|message
|
||||
if(content_to_search)
|
||||
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_CONTENT, content_to_search)
|
||||
|
||||
if(href_list["filter_datetime"])
|
||||
var/datetime_to_search = input(my_client, "Write desired datetime. Partial matches are allowed.\n\
|
||||
Format is 'YYYY-MM-DD HH:MM:SS'.", "Filter by Datetime", null) as null|text
|
||||
if(datetime_to_search)
|
||||
last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_DATETIME, datetime_to_search)
|
||||
|
||||
// Refresh.
|
||||
display()
|
||||
Reference in New Issue
Block a user