mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-23 03:57:13 +01:00
Changelog improvements (#18498)
* Changelog improvements * Farie tweaks * Fix * Improvements
This commit is contained in:
@@ -15,9 +15,8 @@ SUBSYSTEM_DEF(changelog)
|
||||
flags = SS_NO_FIRE
|
||||
var/current_cl_timestamp = "0" // Timestamp is seconds since UNIX epoch (1st January 1970). ITs also a string because BYOND doesnt like big numbers.
|
||||
var/ss_ready = FALSE // Is the SS ready? We dont want to run procs if we have not generated yet
|
||||
var/list/startup_clients_button = list() // Clients who connected before initialization who need their button color updating
|
||||
var/list/startup_clients_open = list() // Clients who connected before initialization who need the CL opening
|
||||
var/changelogHTML = "" // HTML that the changelog will use to display
|
||||
var/list/client/startup_clients_open = list() // Clients who connected before initialization who need the CL opening
|
||||
var/list/changelog_data = list() // Parsed changelog data
|
||||
|
||||
/datum/controller/subsystem/changelog/Initialize()
|
||||
// This entire subsystem relies on SQL being here.
|
||||
@@ -32,20 +31,21 @@ SUBSYSTEM_DEF(changelog)
|
||||
|
||||
while(latest_cl_date.NextRow())
|
||||
current_cl_timestamp = latest_cl_date.item[1]
|
||||
|
||||
qdel(latest_cl_date)
|
||||
|
||||
if(!GenerateChangelogHTML()) // if this failed to generate
|
||||
if(!GenerateChangelogData()) // if this failed to generate
|
||||
to_chat(world, "<span class='alert'>WARNING: Changelog failed to generate. Please inform a coder/server dev</span>")
|
||||
return ..()
|
||||
|
||||
ss_ready = TRUE
|
||||
// Now we can alert anyone who wanted to check the changelog
|
||||
for(var/x in startup_clients_button)
|
||||
var/client/C = x
|
||||
|
||||
// Update buttons for those who logged in
|
||||
for(var/client/C as anything in GLOB.clients)
|
||||
UpdatePlayerChangelogButton(C)
|
||||
|
||||
// Now we can alert anyone who wanted to check the changelog
|
||||
for(var/client/C in startup_clients_open)
|
||||
for(var/client/C as anything in startup_clients_open)
|
||||
OpenChangelog(C)
|
||||
|
||||
return ..()
|
||||
@@ -59,49 +59,40 @@ SUBSYSTEM_DEF(changelog)
|
||||
winset(C, "rpane.changelog", "background-color=#40628a;font-color=#ffffff;font-style=none")
|
||||
else
|
||||
winset(C, "rpane.changelog", "background-color=none;font-style=none")
|
||||
|
||||
|
||||
C.prefs.lastchangelog = current_cl_timestamp
|
||||
|
||||
var/datum/db_query/updatePlayerCLTime = SSdbcore.NewQuery(
|
||||
"UPDATE player SET lastchangelog=:lastchangelog WHERE ckey=:ckey",
|
||||
list(
|
||||
"UPDATE player SET lastchangelog=:lastchangelog WHERE ckey=:ckey", list(
|
||||
"lastchangelog" = current_cl_timestamp,
|
||||
"ckey" = C.ckey
|
||||
)
|
||||
)
|
||||
|
||||
// We dont do anything with this query so we dont care about errors too much
|
||||
updatePlayerCLTime.warn_execute()
|
||||
qdel(updatePlayerCLTime)
|
||||
|
||||
|
||||
/datum/controller/subsystem/changelog/proc/UpdatePlayerChangelogButton(client/C)
|
||||
// If SQL aint even enabled, just set the button to default style
|
||||
if(!SSdbcore.IsConnected())
|
||||
// If SQL aint even enabled, or we aint ready just set the button to default style
|
||||
if(!SSdbcore.IsConnected() || !ss_ready)
|
||||
if(C.prefs.toggles & PREFTOGGLE_UI_DARKMODE)
|
||||
winset(C, "rpane.changelog", "background-color=#40628a;text-color=#FFFFFF")
|
||||
else
|
||||
winset(C, "rpane.changelog", "background-color=none;text-color=#000000")
|
||||
return
|
||||
|
||||
// If SQL is enabled but we aint ready, queue them up, and use the default style
|
||||
if(!ss_ready)
|
||||
startup_clients_button |= C
|
||||
// If we are ready, process the button style
|
||||
if(C.prefs.lastchangelog != current_cl_timestamp)
|
||||
winset(C, "rpane.changelog", "background-color=#bb7700;text-color=#FFFFFF;font-style=bold")
|
||||
to_chat(C, "<span class='boldnotice'>Changelog has changed since your last visit.</span>")
|
||||
else
|
||||
if(C.prefs.toggles & PREFTOGGLE_UI_DARKMODE)
|
||||
winset(C, "rpane.changelog", "background-color=#40628a;text-color=#FFFFFF")
|
||||
else
|
||||
winset(C, "rpane.changelog", "background-color=none;text-color=#000000")
|
||||
return
|
||||
|
||||
// Sanity check to ensure clients still exist (If a client DCs mid startup this would runtime)
|
||||
if(C && C.prefs)
|
||||
// If we are ready, process the button style
|
||||
if(C.prefs.lastchangelog != current_cl_timestamp)
|
||||
winset(C, "rpane.changelog", "background-color=#bb7700;text-color=#FFFFFF;font-style=bold")
|
||||
to_chat(C, "<span class='info'>Changelog has changed since your last visit.</span>")
|
||||
else
|
||||
if(C.prefs.toggles & PREFTOGGLE_UI_DARKMODE)
|
||||
winset(C, "rpane.changelog", "background-color=#40628a;text-color=#FFFFFF")
|
||||
else
|
||||
winset(C, "rpane.changelog", "background-color=none;text-color=#000000")
|
||||
|
||||
|
||||
/datum/controller/subsystem/changelog/proc/OpenChangelog(client/C)
|
||||
@@ -119,9 +110,7 @@ SUBSYSTEM_DEF(changelog)
|
||||
UpdatePlayerChangelogDate(C)
|
||||
UpdatePlayerChangelogButton(C)
|
||||
|
||||
var/datum/browser/cl_popup = new(C.mob, "changelog", "Changelog", 700, 800)
|
||||
cl_popup.set_content(changelogHTML)
|
||||
cl_popup.open()
|
||||
ui_interact(C.mob)
|
||||
|
||||
/client/verb/changes()
|
||||
set name = "Changelog"
|
||||
@@ -130,51 +119,13 @@ SUBSYSTEM_DEF(changelog)
|
||||
// Just invoke the actual CL thing
|
||||
SSchangelog.OpenChangelog(src)
|
||||
|
||||
// Helper to turn CL types into a fontawesome icon instead of an image
|
||||
// The colors are #28a745 for green, #fd7e14 for orange, and #dc3545 for red.
|
||||
// These colours are from bootstrap and look good with black and white
|
||||
/datum/controller/subsystem/changelog/proc/Text2Icon(text)
|
||||
switch(text)
|
||||
if("FIX")
|
||||
return "<i title='Fix' class='fas fa-tools'></i></span>" // Fixes are white because while they are good, they have no negative coutnerpart
|
||||
if("WIP")
|
||||
return "<span style='color: #fd7e14;'><i title='Work In Progress' class='fas fa-hard-hat'></i></span>" // WIP stuff is orange because new code is good but its not done yet
|
||||
if("TWEAK")
|
||||
return "<i title='Tweak' class='fas fa-sliders-h'></i>" // Tweaks are white because they could be good or bad, and theres no specific add or remove
|
||||
if("SOUNDADD")
|
||||
return "<span style='color: #28a745;'><i title='Sound Added' class='fas fa-volume-up'></i></span>" // Sound additions are green because its something new
|
||||
if("SOUNDDEL")
|
||||
return "<span style='color: #dc3545;'><i title='Sound Removed' class='fas fa-volume-mute'></i></span>" // Sound removals are red because something has been removed
|
||||
if("CODEADD")
|
||||
return "<span style='color: #28a745;'><i title='Code Addition' class='fas fa-plus'></i></span>" // Code additions are green because its something new
|
||||
if("CODEDEL")
|
||||
return "<span style='color: #dc3545;'><i title='Code Removal' class='fas fa-minus'></i></span>" // Code removals are red becuase someting has been removed
|
||||
if("IMAGEADD")
|
||||
return "<span style='color: #28a745;'><i title='Image/Sprite Addition' class='fas fa-folder-plus'></i></span>" // Image additions are green because something has been added
|
||||
if("IMAGEDEL")
|
||||
return "<span style='color: #dc3545;'><i title='Image/Sprite Removal' class='fas fa-folder-minus'></i></span>" // Image removals are red because something has been removed
|
||||
if("SPELLCHECK")
|
||||
return "<i title='Spelling/Grammar Fix' class='fas fa-font'></i>" // Spellcheck is white because theres no dedicated negative to it, so theres no red for it to collate with
|
||||
if("EXPERIMENT")
|
||||
return "<span style='color: #fd7e14;'><i title='Experimental' class='fas fa-exclamation-triangle'></i></span>" // Experimental stuff is orange because while its a new feature, its unstable
|
||||
else // Just incase the DB somehow breaks
|
||||
return "<span style='color: #28a745;'><i title='Code Addition' class='fas fa-plus'></i></span>" // Same here
|
||||
|
||||
// This proc is the star of the show
|
||||
/datum/controller/subsystem/changelog/proc/GenerateChangelogHTML()
|
||||
/datum/controller/subsystem/changelog/proc/GenerateChangelogData()
|
||||
// This value will be returned if the proc crashes
|
||||
. = FALSE
|
||||
// Modify the code below to modify the header of the changelog
|
||||
var/changelog_header = {"
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" rel="stylesheet">
|
||||
<title>ParadiseSS13 Changelog</title>
|
||||
<link rel='styelsheet' href='fontawesome.min.css'>
|
||||
<center>
|
||||
<p style='font-size: 20px'><b>Paradise Station Changelog</b></p>
|
||||
<p><a href='?src=[UID()];openPage=forum'>Forum</a> - <a href='?src=[UID()];openPage=wiki'>Wiki</a> - <a href='?src=[UID()];openPage=github'>GitHub</a></p>
|
||||
</center>
|
||||
"}
|
||||
|
||||
var/list/prs_to_process = list()
|
||||
|
||||
// Grab all from last 30 days
|
||||
var/datum/db_query/pr_list_query = SSdbcore.NewQuery("SELECT DISTINCT pr_number FROM changelog WHERE date_merged BETWEEN NOW() - INTERVAL 30 DAY AND NOW() ORDER BY date_merged DESC")
|
||||
if(!pr_list_query.warn_execute())
|
||||
@@ -183,10 +134,8 @@ SUBSYSTEM_DEF(changelog)
|
||||
|
||||
while(pr_list_query.NextRow())
|
||||
prs_to_process += text2num(pr_list_query.item[1])
|
||||
qdel(pr_list_query)
|
||||
|
||||
// Load in the header
|
||||
changelogHTML += changelog_header
|
||||
qdel(pr_list_query)
|
||||
|
||||
// We put all these queries into a list so we can batch-execute them to avoid excess delays
|
||||
// We index these based on PR numbers. MAKE SURE YOU USE STRING INDICIES IN THIS IF YOU EVER TWEAK IT -aa
|
||||
@@ -196,7 +145,7 @@ SUBSYSTEM_DEF(changelog)
|
||||
// Create some queries for each PR
|
||||
for(var/pr_number in prs_to_process)
|
||||
var/datum/db_query/pr_meta = SSdbcore.NewQuery(
|
||||
"SELECT author, DATE_FORMAT(date_merged, '%Y-%m-%d at %T') AS date FROM changelog WHERE pr_number = :prnum LIMIT 1",
|
||||
"SELECT author, DATE_FORMAT(date_merged, '%Y-%m-%d at %T') AS date, CAST(UNIX_TIMESTAMP(date_merged) AS CHAR) AS ts FROM changelog WHERE pr_number = :prnum LIMIT 1",
|
||||
list("prnum" = pr_number)
|
||||
)
|
||||
|
||||
@@ -221,26 +170,27 @@ SUBSYSTEM_DEF(changelog)
|
||||
SSdbcore.MassExecute(entry_queries, TRUE, FALSE, TRUE)
|
||||
|
||||
for(var/pr_number in prs_to_process)
|
||||
// Initial declarations
|
||||
var/pr_block = "" // HTML for the changelog section
|
||||
var/author = "" // Author of the PR
|
||||
var/merge_date = "" // Timestamp of when the PR was merged
|
||||
var/list/this_pr = list()
|
||||
|
||||
this_pr["num"] = pr_number
|
||||
// Assemble metadata
|
||||
while(meta_queries["[pr_number]"].NextRow())
|
||||
author = meta_queries["[pr_number]"].item[1]
|
||||
merge_date = meta_queries["[pr_number]"].item[2]
|
||||
this_pr["author"] = meta_queries["[pr_number]"].item[1]
|
||||
this_pr["merge_date"] = meta_queries["[pr_number]"].item[2]
|
||||
this_pr["merge_ts"] = meta_queries["[pr_number]"].item[3]
|
||||
|
||||
// Now for each actual entry
|
||||
pr_block += "<div class='statusDisplay'>"
|
||||
pr_block += "<p class='white'><a href='?src=[UID()];openPR=[pr_number]'>#[pr_number]</a> by <b>[author]</b> (Merged on [merge_date])</span>"
|
||||
var/list/cl_entries = list()
|
||||
|
||||
while(entry_queries["[pr_number]"].NextRow())
|
||||
pr_block += "<p>[Text2Icon(entry_queries["[pr_number]"].item[1])] [entry_queries["[pr_number]"].item[2]]</p>"
|
||||
var/list/this_entry = list()
|
||||
this_entry["etype"] = entry_queries["[pr_number]"].item[1]
|
||||
this_entry["etext"] = entry_queries["[pr_number]"].item[2]
|
||||
cl_entries += list(this_entry) // Double list required or it merges them
|
||||
|
||||
pr_block += "</div><br>"
|
||||
this_pr["entries"] = cl_entries
|
||||
|
||||
changelog_data += list(this_pr)
|
||||
|
||||
changelogHTML += pr_block
|
||||
|
||||
// Cleanup queries
|
||||
QDEL_LIST_ASSOC_VAL(meta_queries)
|
||||
@@ -249,28 +199,40 @@ SUBSYSTEM_DEF(changelog)
|
||||
// Make sure we return TRUE so we know it worked
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/changelog/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["cl_data"] = changelog_data
|
||||
data["last_cl"] = user.client.prefs.lastchangelog_2
|
||||
|
||||
return data
|
||||
|
||||
/datum/controller/subsystem/changelog/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "ChangelogView", name, 750, 800, master_ui, state)
|
||||
ui.set_autoupdate(FALSE)
|
||||
ui.open()
|
||||
|
||||
|
||||
// Topic handler so that PRs and forums and stuff open in another window
|
||||
/datum/controller/subsystem/changelog/Topic(href, href_list)
|
||||
// Handler to open pages in your browser instead of inside the CL window
|
||||
// Yes usr.client is gross here but src is the subsystem
|
||||
// Takes the page to open as an argument
|
||||
if(href_list["openPage"])
|
||||
switch(href_list["openPage"])
|
||||
if("forum")
|
||||
usr.client.forum()
|
||||
if("wiki")
|
||||
usr.client.wiki()
|
||||
if("github")
|
||||
usr.client.github()
|
||||
// Takes a PR number as argument
|
||||
if(href_list["openPR"])
|
||||
if(GLOB.configuration.url.github_url)
|
||||
if(alert("This will open PR #[href_list["openPR"]] in your browser. Are you sure?",,"Yes","No")=="No")
|
||||
/datum/controller/subsystem/changelog/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
if(..())
|
||||
return
|
||||
|
||||
. = TRUE
|
||||
|
||||
switch(action)
|
||||
// Takes a PR number as argument
|
||||
if("open_pr")
|
||||
var/pr_num = params["pr_number"]
|
||||
if(GLOB.configuration.url.github_url)
|
||||
if(alert("This will open PR #[pr_num] in your browser. Are you sure?", "Open PR", "Yes", "No") == "No")
|
||||
return
|
||||
|
||||
// If the github URL in the config has a trailing slash, it doesnt matter here, thankfully github accepts having a double slash: https://github.com/org/repo//pull/1
|
||||
var/url = "[GLOB.configuration.url.github_url]/pull/[pr_num]"
|
||||
usr << link(url)
|
||||
return
|
||||
// If the github URL in the config has a trailing slash, it doesnt matter here, thankfully github accepts having a double slash: https://github.com/org/repo//pull/1
|
||||
var/url = "[GLOB.configuration.url.github_url]/pull/[href_list["openPR"]]"
|
||||
usr << link(url)
|
||||
else
|
||||
|
||||
to_chat(usr, "<span class='danger'>The GitHub URL is not set in the server configuration. PRs cannot be opened from changelog view. Please inform the server host.</span>")
|
||||
|
||||
|
||||
@@ -429,6 +429,7 @@
|
||||
/client/Destroy()
|
||||
announce_leave() // Do not put this below
|
||||
SSdebugview.stop_processing(src)
|
||||
SSchangelog.startup_clients_open -= src
|
||||
if(holder)
|
||||
holder.owner = null
|
||||
GLOB.admins -= src
|
||||
|
||||
@@ -61,6 +61,7 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
|
||||
|
||||
//game-preferences
|
||||
var/lastchangelog = "1" //Saved changlog timestamp (unix epoch) to detect if there was a change. Dont set this to 0 unless you want the last changelog date to be 4x longer than the expected lifespan of the universe.
|
||||
var/lastchangelog_2 = "1" // Clone of the above var for viewing changes since last connection. This is never overriden. Yes it needs to exist.
|
||||
var/exp
|
||||
var/ooccolor = "#b82e00"
|
||||
var/list/be_special = list() //Special role selection
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
colourblind_mode = query.item[21]
|
||||
keybindings = init_keybindings(raw = query.item[22])
|
||||
|
||||
lastchangelog_2 = lastchangelog // Clone please
|
||||
|
||||
//Sanitize
|
||||
ooccolor = sanitize_hexcolor(ooccolor, initial(ooccolor))
|
||||
UI_style = sanitize_inlist(UI_style, list("White", "Midnight", "Plasmafire", "Retro", "Slimecore", "Operative"), initial(UI_style))
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Button, Section, Box, Icon } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const ChangelogView = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const [onlyRecent, showOnlyRecent] = useLocalState(context, "onlyRecent", 0);
|
||||
const {
|
||||
cl_data,
|
||||
last_cl,
|
||||
} = data;
|
||||
|
||||
const iconMap = {
|
||||
"FIX": (<Icon name="tools" title="Fix" />),
|
||||
"WIP": (<Icon name="hard-hat" title="WIP" color="orange" />),
|
||||
"TWEAK": (<Icon name="sliders-h" title="Tweak" />),
|
||||
"SOUNDADD": (<Icon name="volume-up" title="Sound Added" color="green" />),
|
||||
"SOUNDDEL": (<Icon name="volume-mute" title="Sound Removed" color="red" />),
|
||||
"CODEADD": (<Icon name="plus" title="Code Addition" color="green" />),
|
||||
"CODEDEL": (<Icon name="minus" title="Code Removal" color="red" />),
|
||||
"IMAGEADD": (<Icon name="folder-plus" title="Sprite Addition" color="green" />),
|
||||
"IMAGEDEL": (<Icon name="folder-minus" title="Sprite Removal" color="red" />),
|
||||
"SPELLCHECK": (<Icon name="font" title="Spelling/Grammar Fix" />),
|
||||
"EXPERIMENT": (<Icon name="exclamation-triangle" title="Experimental" color="orange" />),
|
||||
}
|
||||
|
||||
const cl2icon = (cl) => {
|
||||
if (cl in iconMap) {
|
||||
return iconMap[cl];
|
||||
}
|
||||
|
||||
// Sane default if not in list
|
||||
return <Icon name="plus" color="green" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Window resizable>
|
||||
<Window.Content scrollable>
|
||||
<Section title="ParadiseSS13 Changelog" mt={2} buttons={
|
||||
<Button
|
||||
content={onlyRecent ? "Showing all changes" : "Showing changes since last connection"}
|
||||
onClick={() => showOnlyRecent(!onlyRecent)}
|
||||
/>
|
||||
}>
|
||||
{cl_data.map(e => (
|
||||
(!onlyRecent && (e.merge_ts <= last_cl) || (
|
||||
<Section mb={2}
|
||||
key={e}
|
||||
title={e.author + " - Merged on " + e.merge_date}
|
||||
buttons={
|
||||
<Button content={"#" + e.num} onClick={() => act("open_pr", { pr_number: e.num })} />
|
||||
}>
|
||||
{e.entries.map(ent => (
|
||||
<Box key={ent} m={1}>
|
||||
{cl2icon(ent.etype)} {ent.etext}
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
))
|
||||
))}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user