diff --git a/code/controllers/subsystem/persistent_paintings.dm b/code/controllers/subsystem/persistent_paintings.dm
index ccf14a37269..f0a1fe83f98 100644
--- a/code/controllers/subsystem/persistent_paintings.dm
+++ b/code/controllers/subsystem/persistent_paintings.dm
@@ -1,3 +1,92 @@
+#define PAINTINGS_DATA_FORMAT_VERSION 1
+/*
+{
+ "version":1
+ "paintings":[
+ {
+ "md5":"2e117d9d372fb6823bd81d3542a419d6", //unique identifier
+ "creator_ckey" : "example",
+ "creator_name" : "example",
+ "creation_date" : "YYYY-MM-DD hh:mm:ss",
+ "creation_round_id": 222,
+ "title": "example title",
+ "tags": ["library","library_private"],
+ "patron_ckey" : "example",
+ "patron_name" : "example",
+ "credit_value" : 999,
+ "width" : 24,
+ "height" : 24,
+ "medium" : "Oil on canvas"
+ },
+ ]
+}
+*/
+
+/datum/painting
+ /// md5 of the png file, also the filename.
+ var/md5
+ /// Title
+ var/title
+ /// Author's ckey
+ var/creator_ckey
+ /// Author's name
+ var/creator_name
+ /// Timestamp when painting was made (finalized ?)
+ var/creation_date
+ /// Round if when the painting was made
+ var/creation_round_id
+ /// List of this painting string tags if any
+ var/list/tags
+ /// Patron ckey
+ var/patron_ckey
+ /// Patron name
+ var/patron_name
+ /// Amount paid by last patron for this painting
+ var/credit_value = 0
+ /// painting width
+ var/width
+ /// painting height
+ var/height
+ /// short painting medium description
+ var/medium
+ /// Was the painting loaded from json or created this round
+ var/loaded_from_json = FALSE
+
+/datum/painting/proc/load_from_json(list/json_data)
+ md5 = json_data["md5"]
+ title = json_data["title"]
+ creator_ckey = json_data["creator_ckey"]
+ creator_name = json_data["creator_name"]
+ creation_date = json_data["creation_date"]
+ creation_round_id = json_data["creation_round_id"]
+ tags = json_data["tags"]
+ patron_ckey = json_data["patron_ckey"]
+ credit_value = json_data["credit_value"]
+ width = json_data["width"]
+ height = json_data["height"]
+ medium = json_data["medium"]
+ loaded_from_json = TRUE
+
+/datum/painting/proc/to_json()
+ var/list/new_data = list()
+ new_data["md5"] = md5
+ new_data["title"] = title
+ new_data["creator_ckey"] = creator_ckey
+ new_data["creator_name"] = creator_name
+ new_data["creation_date"] = creation_date
+ new_data["creation_round_id"] = creation_round_id
+ new_data["tags"] = tags
+ new_data["patron_ckey"] = patron_ckey
+ new_data["patron_name"] = patron_name
+ new_data["credit_value"] = credit_value
+ new_data["width"] = width
+ new_data["height"] = height
+ new_data["medium"] = medium
+ return new_data
+
+/// Only returns paintings with 23x23 or 24x24 sizes fitting AI display icon.
+#define PAINTINGS_FILTER_AI_PORTRAIT 1
+
SUBSYSTEM_DEF(persistent_paintings)
name = "Persistent Paintings"
init_order = INIT_ORDER_PERSISTENT_PAINTINGS
@@ -6,24 +95,111 @@ SUBSYSTEM_DEF(persistent_paintings)
/// A list of painting frames that this controls
var/list/obj/structure/sign/painting/painting_frames = list()
- /// A map of identifiers (such as library) to paintings from paintings.json
+ /// A list of /datum/paintings saved or ready to be saved this round.
var/list/paintings = list()
/datum/controller/subsystem/persistent_paintings/Initialize(start_timeofday)
var/json_file = file("data/paintings.json")
if(fexists(json_file))
- paintings = json_decode(file2text(json_file))
+ var/list/raw_data = update_format(json_decode(file2text(json_file)))
+ for(var/list/painting_data as anything in raw_data["paintings"])
+ var/datum/painting/loaded_painting = new
+ loaded_painting.load_from_json(painting_data)
+ paintings += loaded_painting
for(var/obj/structure/sign/painting/painting_frame as anything in painting_frames)
painting_frame.load_persistent()
return ..()
+/// Generates painting data ready to be consumed by ui
+/datum/controller/subsystem/persistent_paintings/proc/painting_ui_data(filter=NONE,admin=FALSE)
+ . = list()
+ for(var/datum/painting/painting as anything in paintings)
+ if(filter & PAINTINGS_FILTER_AI_PORTRAIT && ((painting.width != 24 && painting.width != 23) || (painting.height != 24 && painting.height != 23)))
+ continue
+ if(admin)
+ var/list/pdata = painting.to_json()
+ pdata["ref"] = REF(painting)
+ . += list(pdata)
+ else
+ . += list(list("title" = painting.title,"md5" = painting.md5,"ref" = REF(painting)))
+
+/// Returns paintings with given tag.
+/datum/controller/subsystem/persistent_paintings/proc/get_paintings_with_tag(tag_name)
+ . = list()
+ for(var/datum/painting/painting as anything in paintings)
+ if(!painting.tags || !(tag_name in painting.tags))
+ continue
+ . += painting
+
+/// Updates paintings data format to latest if necessary
+/datum/controller/subsystem/persistent_paintings/proc/update_format(current_data)
+ if(current_data["version"] && current_data["version"] == PAINTINGS_DATA_FORMAT_VERSION)
+ return current_data
+
+ var/current_format = current_data["version"] || 0
+ switch(current_format)
+ if(0)
+ fcopy("data/paintings.json","data/paintings_migration_backup_0.json") //Better safe than losing all metadata
+ var/list/result = list()
+ result["version"] = 1
+ var/list/data = list()
+ // Squash categories into tags
+ for(var/category in current_data)
+ for(var/old_data in current_data[category])
+ var/duplicate_found = FALSE
+ for(var/list/entry in data)
+ if(entry["md5"] == old_data["md5"])
+ entry["tags"] |= category
+ duplicate_found = TRUE
+ break
+ if(duplicate_found)
+ continue
+ var/old_png_path = "data/paintings/[category]/[old_data["md5"]].png"
+ var/new_png_path = "data/paintings/images/[old_data["md5"]].png"
+ fcopy(old_png_path,new_png_path)
+ fdel(old_png_path)
+ var/icon/painting_icon = new(new_png_path)
+ var/width = painting_icon.Width()
+ var/height = painting_icon.Height()
+ var/list/new_data = list()
+ new_data["md5"] = old_data["md5"]
+ new_data["title"] = old_data["title"] || "Untitled Artwork"
+ new_data["creator_ckey"] = old_data["ckey"] || ""
+ new_data["creator_name"] = "Anonymous"
+ new_data["creation_date"] = time2text(world.realtime) // Could use creation/modified file helpers in rustg
+ new_data["creation_round_id"] = GLOB.round_id
+ new_data["tags"] = list(category,"Migrated from version 0")
+ new_data["patron_ckey"] = ""
+ new_data["patron_name"] = ""
+ new_data["credit_value"] = 0
+ new_data["width"] = width
+ new_data["height"] = height
+ new_data["medium"] = "Spraypaint on canvas" //Let's go with most common tool.
+ data += list(new_data)
+ result["paintings"] = data
+ //We're going to save this immidiately this is non-recoverable operation
+ var/json_file = file("data/paintings.json")
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(result))
+ return update_format(result)
+
/// Saves all persistent paintings
/datum/controller/subsystem/persistent_paintings/proc/save_paintings()
+ // Collect new painting data
for(var/obj/structure/sign/painting/painting_frame as anything in painting_frames)
painting_frame.save_persistent()
+ save_to_file()
+
+/// Saves all currently tracked painting data to file
+/datum/controller/subsystem/persistent_paintings/proc/save_to_file()
var/json_file = file("data/paintings.json")
fdel(json_file)
- WRITE_FILE(json_file, json_encode(paintings))
+ var/list/all_data = list("version" = PAINTINGS_DATA_FORMAT_VERSION)
+ var/list/painting_data = list()
+ for(var/datum/painting/painting as anything in paintings)
+ painting_data += list(painting.to_json())
+ all_data["paintings"] = painting_data
+ WRITE_FILE(json_file, json_encode(all_data))
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index f9d7d3c9e3f..79c231ec5de 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -76,6 +76,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/datum/admins/proc/view_all_circuits,
/datum/admins/proc/view_all_sdql_spells,
/datum/admins/proc/known_alts_panel,
+ /datum/admins/proc/paintings_manager,
)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel))
GLOBAL_PROTECT(admin_verbs_ban)
diff --git a/code/modules/admin/painting_manager.dm b/code/modules/admin/painting_manager.dm
new file mode 100644
index 00000000000..eba5cb61a52
--- /dev/null
+++ b/code/modules/admin/painting_manager.dm
@@ -0,0 +1,96 @@
+/datum/admins/proc/paintings_manager()
+ set name = "Paintings Manager"
+ set category = "Admin"
+
+ if(!check_rights(R_ADMIN))
+ return
+ var/datum/paintings_manager/ui = new(usr)
+ ui.ui_interact(usr)
+
+/// Painting Admin Management Panel
+/datum/paintings_manager
+
+/datum/paintings_manager/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/paintings_manager/ui_close(mob/user)
+ qdel(src)
+
+/datum/paintings_manager/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PaintingAdminPanel")
+ ui.open()
+
+/datum/paintings_manager/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/simple/portraits)
+ )
+
+/datum/paintings_manager/ui_data(mob/user)
+ . = list()
+ .["paintings"] = SSpersistent_paintings.painting_ui_data(filter = NONE, admin = TRUE)
+
+/datum/paintings_manager/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ if(..())
+ return
+ if (!check_rights(R_ADMIN))
+ return
+ var/mob/user = usr
+ var/datum/painting/chosen_painting = locate(params["ref"]) in SSpersistent_paintings.paintings
+ if(!chosen_painting)
+ return
+ switch(action)
+ if("delete")
+ //Delete the png file
+ var/png = "data/paintings/images/[chosen_painting.md5].png"
+ fdel(png)
+ //Remove entry from paintings list
+ SSpersistent_paintings.paintings -= chosen_painting
+ SSpersistent_paintings.save_to_file() // Save now so we don't have broken variations if this round crashes
+ //Delete any painting instances in the current round
+ for(var/obj/structure/sign/painting/painting as anything in SSpersistent_paintings.painting_frames)
+ if(painting.current_canvas && painting.current_canvas.painting_metadata == chosen_painting)
+ QDEL_NULL(painting.current_canvas)
+ painting.update_appearance()
+ log_admin("[key_name(user)] has deleted a persistent painting made by [chosen_painting.creator_ckey].")
+ message_admins(span_notice("[key_name_admin(user)] has deleted persistent painting made by [chosen_painting.creator_ckey]."))
+ return TRUE
+ if("rename")
+ //Modify the metadata
+ var/old_title = chosen_painting.title
+ var/new_title = stripped_input(user, "New painting title?", "Painting rename", chosen_painting.title)
+ if(!new_title)
+ return
+ chosen_painting.title = new_title
+ log_admin("[key_name(user)] has renamed a persistent painting made by [chosen_painting.creator_ckey] with id [chosen_painting.md5] from [old_title] to [chosen_painting.title].")
+ return TRUE
+ if("rename_author")
+ var/old_name = chosen_painting.creator_name
+ var/new_name = stripped_input(user, "New painting author name?", "Painting rename", chosen_painting.creator_name)
+ if(!new_name)
+ return
+ chosen_painting.creator_name = new_name
+ log_admin("[key_name(user)] has renamed a persistent painting author made by [chosen_painting.creator_name] with id [chosen_painting.md5] from [old_name] to [chosen_painting.creator_name].")
+ return TRUE
+ if("dumpit")
+ //Modify the metadata
+ chosen_painting.patron_name = ""
+ chosen_painting.patron_ckey = ""
+ chosen_painting.credit_value = 0
+ log_admin("[key_name(user)] has reset patronage data on a persistent painting made by [chosen_painting.creator_ckey] with id [chosen_painting.md5].")
+ return TRUE
+ if("remove_tag")
+ if(chosen_painting.tags)
+ chosen_painting.tags -= params["tag"]
+ log_admin("[key_name(user)] has removed tag [params["tag"]] from persistent painting made by [chosen_painting.creator_ckey] with id [chosen_painting.md5].")
+ return TRUE
+ if("add_tag")
+ var/tag_name = stripped_input(user, "New tag name?", "???")
+ if(!tag_name)
+ return
+ if(!chosen_painting.tags)
+ chosen_painting.tags = list()
+ chosen_painting.tags |= tag_name
+ log_admin("[key_name(user)] has added tag [tag_name] to persistent painting made by [chosen_painting.creator_ckey] with id [chosen_painting.md5].")
+ return TRUE
diff --git a/code/modules/art/paintings.dm b/code/modules/art/paintings.dm
index e77e64271c8..123799afaaf 100644
--- a/code/modules/art/paintings.dm
+++ b/code/modules/art/paintings.dm
@@ -45,16 +45,18 @@
var/width = 11
var/height = 11
var/list/grid
- var/canvas_color = "#ffffff" //empty canvas color
+ /// empty canvas color
+ var/canvas_color = "#ffffff"
+ /// Is it clean canvas or was there something painted on it at some point, used to decide when to show wip splotch overlay
var/used = FALSE
- var/painting_name = "Untitled Artwork" //Painting name, this is set after framing.
var/finalized = FALSE //Blocks edits
- var/author_ckey
var/icon_generated = FALSE
var/icon/generated_icon
///boolean that blocks persistence from saving it. enabled from printing copies, because we do not want to save copies.
var/no_save = FALSE
+ var/datum/painting/painting_metadata
+
// Painting overlay offset when framed
var/framed_offset_x = 11
var/framed_offset_y = 10
@@ -66,6 +68,12 @@
. = ..()
reset_grid()
+ painting_metadata = new
+ painting_metadata.title = "Untitled Artwork"
+ painting_metadata.creation_round_id = GLOB.round_id
+ painting_metadata.width = width
+ painting_metadata.height = height
+
/obj/item/canvas/proc/reset_grid()
grid = new/list(width,height)
for(var/x in 1 to width)
@@ -86,7 +94,6 @@
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Canvas", name)
- ui.set_autoupdate(FALSE)
ui.open()
/obj/item/canvas/attackby(obj/item/I, mob/living/user, params)
@@ -98,8 +105,15 @@
/obj/item/canvas/ui_data(mob/user)
. = ..()
.["grid"] = grid
- .["name"] = painting_name
+ .["name"] = painting_metadata.title
+ .["author"] = painting_metadata.creator_name
+ .["patron"] = painting_metadata.patron_name
+ .["medium"] = painting_metadata.medium
+ .["date"] = painting_metadata.creation_date
.["finalized"] = finalized
+ .["editable"] = !finalized //Ideally you should be able to draw moustaches on existing paintings in the gallery but that's not implemented yet
+ .["show_plaque"] = istype(loc,/obj/structure/sign/painting)
+ .["paint_tool_color"] = get_paint_tool_color(user.get_active_held_item())
/obj/item/canvas/examine(mob/user)
. = ..()
@@ -107,32 +121,79 @@
/obj/item/canvas/ui_act(action, params)
. = ..()
- if(. || finalized)
+ if(.)
return
var/mob/user = usr
switch(action)
if("paint")
+ if(finalized)
+ return TRUE
var/obj/item/I = user.get_active_held_item()
- var/color = get_paint_tool_color(I)
- if(!color)
+ var/tool_color = get_paint_tool_color(I)
+ if(!tool_color)
return FALSE
- var/x = text2num(params["x"])
- var/y = text2num(params["y"])
- grid[x][y] = color
+ var/list/data = params["data"]
+ //could maybe validate continuity but eh
+ for(var/point in data)
+ var/x = text2num(point["x"])
+ var/y = text2num(point["y"])
+ grid[x][y] = tool_color
+ var/medium = get_paint_tool_medium(I)
+ if(medium && painting_metadata.medium && painting_metadata.medium != medium)
+ painting_metadata.medium = "Mixed medium"
+ else
+ painting_metadata.medium = medium
used = TRUE
update_appearance()
. = TRUE
if("finalize")
. = TRUE
- if(!finalized)
- finalize(user)
+ finalize(user)
+ if("patronage")
+ . = TRUE
+ patron(user)
/obj/item/canvas/proc/finalize(mob/user)
+ if(painting_metadata.loaded_from_json || finalized)
+ return
finalized = TRUE
- author_ckey = user.ckey
+ painting_metadata.creator_ckey = user.ckey
+ painting_metadata.creator_name = user.real_name
+ painting_metadata.creation_date = time2text(world.realtime)
+ painting_metadata.creation_round_id = GLOB.round_id
generate_proper_overlay()
try_rename(user)
+/obj/item/canvas/proc/patron(mob/user)
+ if(!finalized || !painting_metadata.loaded_from_json || !isliving(user))
+ return
+ var/mob/living/living_user = user
+ var/obj/item/card/id/id_card = living_user.get_idcard(TRUE)
+ if(!id_card)
+ to_chat(user,span_notice("You don't even have a id and you want to be an art patron?"))
+ return
+ if(!id_card.registered_account || !id_card.registered_account.account_job)
+ to_chat(user,span_notice("No valid non-departamental account found."))
+ return
+ var/datum/bank_account/account = id_card.registered_account
+ if(account.account_balance < painting_metadata.credit_value)
+ to_chat(user,span_notice("You can't afford this."))
+ return
+ var/sniped_amount = painting_metadata.credit_value
+ var/offer_amount = input(user,"How much do you want to offer ? Minimum : [painting_metadata.credit_value]","Patronage Amount", painting_metadata.credit_value + 1) as num|null
+ if(account.account_balance < offer_amount)
+ to_chat(user,span_notice("You can't afford this."))
+ return
+ if(!offer_amount || sniped_amount != painting_metadata.credit_value || offer_amount < painting_metadata.credit_value+1 || !user.canUseTopic(src))
+ return
+ if(!account.adjust_money(-offer_amount))
+ to_chat(user,span_warning("Transaction failure. Please try again."))
+ return
+ painting_metadata.patron_ckey = user.ckey
+ painting_metadata.patron_name = user.real_name
+ painting_metadata.credit_value = offer_amount
+ to_chat(user,span_notice("Nanotrasen Trust Foundation thanks you for your contribution. You're now offical patron of this painting."))
+
/obj/item/canvas/update_overlays()
. = ..()
if(icon_generated)
@@ -153,9 +214,11 @@
if(icon_generated)
return
var/png_filename = "data/paintings/temp_painting.png"
- var/result = rustg_dmi_create_png(png_filename,"[width]","[height]",get_data_string())
+ var/image_data = get_data_string()
+ var/result = rustg_dmi_create_png(png_filename, "[width]", "[height]", image_data)
if(result)
CRASH("Error generating painting png : [result]")
+ painting_metadata.md5 = md5(lowertext(image_data))
generated_icon = new(png_filename)
icon_generated = TRUE
update_appearance()
@@ -168,14 +231,17 @@
return data.Join("")
//Todo make this element ?
-/obj/item/canvas/proc/get_paint_tool_color(obj/item/I)
- if(!I)
+/obj/item/canvas/proc/get_paint_tool_color(obj/item/painting_implement)
+ if(!painting_implement)
return
- if(istype(I, /obj/item/toy/crayon))
- var/obj/item/toy/crayon/crayon = I
+ if(istype(painting_implement, /obj/item/paint_palette))
+ var/obj/item/paint_palette/palette = painting_implement
+ return palette.current_color
+ if(istype(painting_implement, /obj/item/toy/crayon))
+ var/obj/item/toy/crayon/crayon = painting_implement
return crayon.paint_color
- else if(istype(I, /obj/item/pen))
- var/obj/item/pen/P = I
+ else if(istype(painting_implement, /obj/item/pen))
+ var/obj/item/pen/P = painting_implement
switch(P.colour)
if("black")
return "#000000"
@@ -184,14 +250,37 @@
if("red")
return "#ff0000"
return P.colour
- else if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/glass/rag))
+ else if(istype(painting_implement, /obj/item/soap) || istype(painting_implement, /obj/item/reagent_containers/glass/rag))
return canvas_color
+/// Generates medium description
+/obj/item/canvas/proc/get_paint_tool_medium(obj/item/painting_implement)
+ if(!painting_implement)
+ return
+ if(istype(painting_implement, /obj/item/paint_palette))
+ return "Oil on canvas"
+ else if(istype(painting_implement, /obj/item/toy/crayon/spraycan))
+ return "Spraycan on canvas"
+ else if(istype(painting_implement, /obj/item/toy/crayon))
+ return "Crayon on canvas"
+ else if(istype(painting_implement, /obj/item/pen))
+ return "Ink on canvas"
+ else if(istype(painting_implement, /obj/item/soap) || istype(painting_implement, /obj/item/reagent_containers/glass/rag))
+ return //These are just for cleaning, ignore them
+ else
+ return "Unknown medium"
+
/obj/item/canvas/proc/try_rename(mob/user)
+ if(painting_metadata.loaded_from_json) // No renaming old paintings
+ return
var/new_name = stripped_input(user,"What do you want to name the painting?")
- if(new_name != painting_name && new_name && user.canUseTopic(src,BE_CLOSE))
- painting_name = new_name
- SStgui.update_uis(src)
+ if(new_name != painting_metadata.title && new_name && user.canUseTopic(src, BE_CLOSE))
+ painting_metadata.title = new_name
+ var/sign_choice = tgui_alert(user, "Do you want to sign it or remain anonymous?", "Sign painting?", list("Yes", "No"))
+ if(sign_choice != "Yes")
+ painting_metadata.creator_name = "Anonymous"
+ SStgui.update_uis(src)
+
/obj/item/canvas/nineteen_nineteen
name = "canvas (19x19)"
@@ -271,7 +360,7 @@
/obj/structure/sign/painting/attackby(obj/item/I, mob/user, params)
if(!current_canvas && istype(I, /obj/item/canvas))
frame_canvas(user,I)
- else if(current_canvas && current_canvas.painting_name == initial(current_canvas.painting_name) && istype(I,/obj/item/pen))
+ else if(current_canvas && current_canvas.painting_metadata.title == initial(current_canvas.painting_metadata.title) && istype(I,/obj/item/pen))
try_rename(user)
else
return ..()
@@ -302,11 +391,11 @@
update_appearance()
/obj/structure/sign/painting/proc/try_rename(mob/user)
- if(current_canvas.painting_name == initial(current_canvas.painting_name))
+ if(current_canvas.painting_metadata.title == initial(current_canvas.painting_metadata.title))
current_canvas.try_rename(user)
/obj/structure/sign/painting/update_name(updates)
- name = current_canvas ? "painting - [current_canvas.painting_name]" : initial(name)
+ name = current_canvas ? "painting - [current_canvas.painting_metadata.title]" : initial(name)
return ..()
/obj/structure/sign/painting/update_desc(updates)
@@ -337,24 +426,13 @@
* Deleting paintings leaves their json, so this proc will remove the json and try again if it finds one of those.
*/
/obj/structure/sign/painting/proc/load_persistent()
- if(!persistence_id || !SSpersistent_paintings.paintings[persistence_id])
+ if(!persistence_id)
return
- var/list/painting_category = SSpersistent_paintings.paintings[persistence_id]
- var/list/painting
- while(!painting)
- if(!length(SSpersistent_paintings.paintings[persistence_id]))
- return //aborts loading anything this category has no usable paintings
- var/list/chosen = pick(painting_category)
- if(!fexists("data/paintings/[persistence_id]/[chosen["md5"]].png")) //shitmin deleted this art, lets remove json entry to avoid errors
- painting_category -= list(chosen)
- continue //and try again
- painting = chosen
- var/title = painting["title"]
- var/author = painting["ckey"]
- var/png = "data/paintings/[persistence_id]/[painting["md5"]].png"
- if(!title)
- title = "Untitled Artwork" //legacy artwork allowed null names which was bad for the json, lets fix that
- painting["title"] = title
+ var/list/valid_paintings = SSpersistent_paintings.get_paintings_with_tag(persistence_id)
+ if(!length(valid_paintings))
+ return //aborts loading anything this category has no usable paintings
+ var/datum/painting/painting = pick(valid_paintings)
+ var/png = "data/paintings/images/[painting.md5].png"
var/icon/I = new(png)
var/obj/item/canvas/new_canvas
var/w = I.Width()
@@ -364,39 +442,43 @@
if(initial(new_canvas.width) == w && initial(new_canvas.height) == h)
new_canvas = new T(src)
break
+ if(!istype(new_canvas))
+ CRASH("Found painting size with no matching canvas type")
+ new_canvas.painting_metadata = painting
new_canvas.fill_grid_from_icon(I)
new_canvas.generated_icon = I
new_canvas.icon_generated = TRUE
new_canvas.finalized = TRUE
- new_canvas.painting_name = title
- new_canvas.author_ckey = author
- new_canvas.name = "painting - [title]"
+ new_canvas.name = "painting - [painting.title]"
current_canvas = new_canvas
+ current_canvas.update_appearance()
update_appearance()
/obj/structure/sign/painting/proc/save_persistent()
- if(!persistence_id || !current_canvas || current_canvas.no_save)
+ if(!persistence_id || !current_canvas || current_canvas.no_save || current_canvas.painting_metadata.loaded_from_json)
return
if(SANITIZE_FILENAME(persistence_id) != persistence_id)
stack_trace("Invalid persistence_id - [persistence_id]")
return
- if(!current_canvas.painting_name)
- current_canvas.painting_name = "Untitled Artwork"
var/data = current_canvas.get_data_string()
var/md5 = md5(lowertext(data))
var/list/current = SSpersistent_paintings.paintings[persistence_id]
if(!current)
current = list()
- for(var/list/entry in current)
- if(entry["md5"] == md5)
+ for(var/datum/painting/entry in SSpersistent_paintings.paintings)
+ if(entry.md5 == md5) // No duplicates
return
- var/png_directory = "data/paintings/[persistence_id]/"
+ current_canvas.painting_metadata.md5 = md5
+ if(!current_canvas.painting_metadata.tags)
+ current_canvas.painting_metadata.tags = list(persistence_id)
+ else
+ current_canvas.painting_metadata.tags |= persistence_id
+ var/png_directory = "data/paintings/images/"
var/png_path = png_directory + "[md5].png"
var/result = rustg_dmi_create_png(png_path,"[current_canvas.width]","[current_canvas.height]",data)
if(result)
CRASH("Error saving persistent painting: [result]")
- current += list(list("title" = current_canvas.painting_name , "md5" = md5, "ckey" = current_canvas.author_ckey))
- SSpersistent_paintings.paintings[persistence_id] = current
+ SSpersistent_paintings.paintings += current_canvas.painting_metadata
/obj/item/canvas/proc/fill_grid_from_icon(icon/I)
var/h = I.Height() + 1
@@ -423,31 +505,20 @@
desc_with_canvas = "A painting hung away from lesser minds."
persistence_id = "library_private"
-/obj/structure/sign/painting/vv_get_dropdown()
- . = ..()
- VV_DROPDOWN_OPTION(VV_HK_REMOVE_PAINTING, "Remove Persistent Painting")
+/// Simple painting utility.
+/obj/item/paint_palette
+ name = "paint palette"
+ desc = "paintbrush included"
+ icon = 'icons/obj/artstuff.dmi'
+ icon_state = "palette"
+ lefthand_file = 'icons/mob/inhands/equipment/palette_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/palette_righthand.dmi'
+ w_class = WEIGHT_CLASS_TINY
+ ///Chosen paint color
+ var/current_color
-/obj/structure/sign/painting/vv_do_topic(list/href_list)
+/obj/item/paint_palette/attack_self(mob/user, modifiers)
. = ..()
- if(href_list[VV_HK_REMOVE_PAINTING])
- if(!check_rights(NONE))
- return
- var/mob/user = usr
- if(!persistence_id || !current_canvas)
- to_chat(user,span_warning("This is not a persistent painting."))
- return
- var/md5 = md5(lowertext(current_canvas.get_data_string()))
- var/author = current_canvas.author_ckey
- var/list/current = SSpersistent_paintings.paintings[persistence_id]
- if(current)
- for(var/list/entry in current)
- if(entry["md5"] == md5)
- current -= entry
- var/png = "data/paintings/[persistence_id]/[md5].png"
- fdel(png)
- for(var/obj/structure/sign/painting/painting as anything in SSpersistent_paintings.painting_frames)
- if(painting.current_canvas && md5(painting.current_canvas.get_data_string()) == md5)
- QDEL_NULL(painting.current_canvas)
- painting.update_appearance()
- log_admin("[key_name(user)] has deleted a persistent painting made by [author].")
- message_admins(span_notice("[key_name_admin(user)] has deleted persistent painting made by [author]."))
+ var/chosen_color = input(user,"Pick new color","Palette") as color|null
+ if(chosen_color)
+ current_color = chosen_color
diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm
index 532a6216392..4e316998596 100644
--- a/code/modules/asset_cache/asset_list_items.dm
+++ b/code/modules/asset_cache/asset_list_items.dm
@@ -442,28 +442,18 @@
..()
/datum/asset/simple/portraits
- var/tab = "use subtypes of this please"
assets = list()
/datum/asset/simple/portraits/New()
- if(!length(SSpersistent_paintings.paintings[tab]))
+ if(!length(SSpersistent_paintings.paintings))
return
- for(var/list/portrait as anything in SSpersistent_paintings.paintings[tab])
- var/png = "data/paintings/[tab]/[portrait["md5"]].png"
+ for(var/datum/painting/portrait as anything in SSpersistent_paintings.paintings)
+ var/png = "data/paintings/images/[portrait.md5].png"
if(fexists(png))
- var/asset_name = "[tab]_[portrait["md5"]]"
+ var/asset_name = "paintings_[portrait.md5]"
assets[asset_name] = png
..() //this is where it registers all these assets we added to the list
-/datum/asset/simple/portraits/library
- tab = "library"
-
-/datum/asset/simple/portraits/library_secure
- tab = "library_secure"
-
-/datum/asset/simple/portraits/library_private
- tab = "library_private"
-
/datum/asset/simple/safe
assets = list(
"safe_dial.png" = 'icons/ui_icons/safe/safe_dial.png'
diff --git a/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm b/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm
index d792ff82bb0..2b7912921ae 100644
--- a/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm
+++ b/code/modules/mob/living/silicon/ai/ai_portrait_picker.dm
@@ -30,16 +30,12 @@
/datum/portrait_picker/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/simple/portraits/library),
- get_asset_datum(/datum/asset/simple/portraits/library_secure),
- get_asset_datum(/datum/asset/simple/portraits/library_private)
+ get_asset_datum(/datum/asset/simple/portraits)
)
/datum/portrait_picker/ui_data(mob/user)
var/list/data = list()
- data["library"] = SSpersistent_paintings.paintings["library"] ? SSpersistent_paintings.paintings["library"] : 0
- data["library_secure"] = SSpersistent_paintings.paintings["library_secure"] ? SSpersistent_paintings.paintings["library_secure"] : 0
- data["library_private"] = SSpersistent_paintings.paintings["library_private"] ? SSpersistent_paintings.paintings["library_private"] : 0 //i'm gonna regret this, won't i?
+ data["paintings"] = SSpersistent_paintings.painting_ui_data(filter = PAINTINGS_FILTER_AI_PORTRAIT)
return data
/datum/portrait_picker/ui_act(action, params)
@@ -48,11 +44,13 @@
return
switch(action)
if("select")
- var/list/tab2key = list(TAB_LIBRARY = "library", TAB_SECURE = "library_secure", TAB_PRIVATE = "library_private")
- var/folder = tab2key[params["tab"]]
- var/list/current_list = SSpersistent_paintings.paintings[folder]
- var/list/chosen_portrait = current_list[params["selected"]]
- var/png = "data/paintings/[folder]/[chosen_portrait["md5"]].png"
+ //var/list/tab2key = list(TAB_LIBRARY = "library", TAB_SECURE = "library_secure", TAB_PRIVATE = "library_private")
+ //var/folder = tab2key[params["tab"]]
+ //var/list/current_list = SSpersistent_paintings.paintings[folder]
+ var/datum/painting/chosen_portrait = locate(params["selected"]) in SSpersistent_paintings.paintings
+ if(!chosen_portrait)
+ return
+ var/png = "data/paintings/images/[chosen_portrait.md5].png"
var/icon/portrait_icon = new(png)
var/mob/living/ai = holder.mob
var/w = portrait_icon.Width()
diff --git a/code/modules/modular_computers/file_system/programs/portrait_printer.dm b/code/modules/modular_computers/file_system/programs/portrait_printer.dm
index 69d40a363f9..1260b761ac2 100644
--- a/code/modules/modular_computers/file_system/programs/portrait_printer.dm
+++ b/code/modules/modular_computers/file_system/programs/portrait_printer.dm
@@ -23,16 +23,12 @@
/datum/computer_file/program/portrait_printer/ui_data(mob/user)
var/list/data = list()
- data["library"] = SSpersistent_paintings.paintings["library"] ? SSpersistent_paintings.paintings["library"] : 0
- data["library_secure"] = SSpersistent_paintings.paintings["library_secure"] ? SSpersistent_paintings.paintings["library_secure"] : 0
- data["library_private"] = SSpersistent_paintings.paintings["library_private"] ? SSpersistent_paintings.paintings["library_private"] : 0 //i'm gonna regret this, won't i?
+ data["paintings"] = SSpersistent_paintings.painting_ui_data()
return data
/datum/computer_file/program/portrait_printer/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/simple/portraits/library),
- get_asset_datum(/datum/asset/simple/portraits/library_secure),
- get_asset_datum(/datum/asset/simple/portraits/library_private)
+ get_asset_datum(/datum/asset/simple/portraits)
)
/datum/computer_file/program/portrait_printer/ui_act(action, params)
@@ -53,13 +49,9 @@
printer.stored_paper -= CANVAS_PAPER_COST
//canvas printing!
- var/list/tab2key = list(TAB_LIBRARY = "library", TAB_SECURE = "library_secure", TAB_PRIVATE = "library_private")
- var/folder = tab2key[params["tab"]]
- var/list/current_list = SSpersistent_paintings.paintings[folder]
- var/list/chosen_portrait = current_list[params["selected"]]
- var/author = chosen_portrait["author"]
- var/title = chosen_portrait["title"]
- var/png = "data/paintings/[folder]/[chosen_portrait["md5"]].png"
+ var/datum/painting/chosen_portrait = locate(params["selected"]) in SSpersistent_paintings.paintings
+
+ var/png = "data/paintings/images/[chosen_portrait.md5].png"
var/icon/art_icon = new(png)
var/obj/item/canvas/printed_canvas
var/art_width = art_icon.Width()
@@ -69,15 +61,17 @@
if(initial(printed_canvas.width) == art_width && initial(printed_canvas.height) == art_height)
printed_canvas = new canvas_type(get_turf(computer.physical))
break
+ printed_canvas = null
+ if(!printed_canvas)
+ return
+ printed_canvas.painting_metadata = chosen_portrait
printed_canvas.fill_grid_from_icon(art_icon)
printed_canvas.generated_icon = art_icon
printed_canvas.icon_generated = TRUE
printed_canvas.finalized = TRUE
- printed_canvas.painting_name = title
- printed_canvas.author_ckey = author
- printed_canvas.name = "painting - [title]"
+ printed_canvas.name = "painting - [chosen_portrait.title]"
///this is a copy of something that is already in the database- it should not be able to be saved.
printed_canvas.no_save = TRUE
printed_canvas.update_icon()
- to_chat(usr, span_notice("You have printed [title] onto a new canvas."))
+ to_chat(usr, span_notice("You have printed [chosen_portrait.title] onto a new canvas."))
playsound(computer.physical, 'sound/items/poster_being_created.ogg', 100, TRUE)
diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm
index 5dff958ec73..22582f95d20 100644
--- a/code/modules/vending/games.dm
+++ b/code/modules/vending/games.dm
@@ -31,7 +31,8 @@
/obj/item/razor=3,
/obj/item/canvas/nineteen_nineteen = 5,
/obj/item/canvas/twentythree_nineteen = 5,
- /obj/item/canvas/twentythree_twentythree = 5
+ /obj/item/canvas/twentythree_twentythree = 5,
+ /obj/item/paint_palette = 3
)
contraband = list(
/obj/item/dice/fudge = 9,
diff --git a/icons/mob/inhands/equipment/palette_lefthand.dmi b/icons/mob/inhands/equipment/palette_lefthand.dmi
new file mode 100644
index 00000000000..e68495cdd6c
Binary files /dev/null and b/icons/mob/inhands/equipment/palette_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/palette_righthand.dmi b/icons/mob/inhands/equipment/palette_righthand.dmi
new file mode 100644
index 00000000000..cce674c3515
Binary files /dev/null and b/icons/mob/inhands/equipment/palette_righthand.dmi differ
diff --git a/icons/obj/artstuff.dmi b/icons/obj/artstuff.dmi
index 847ebd5ca61..17dd9d38886 100644
Binary files a/icons/obj/artstuff.dmi and b/icons/obj/artstuff.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 3603dd12a4f..e0d06483e8b 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1732,6 +1732,7 @@
#include "code\modules\admin\outfit_editor.dm"
#include "code\modules\admin\outfit_manager.dm"
#include "code\modules\admin\outfits.dm"
+#include "code\modules\admin\painting_manager.dm"
#include "code\modules\admin\permissionedit.dm"
#include "code\modules\admin\player_panel.dm"
#include "code\modules\admin\poll_management.dm"
diff --git a/tgui/packages/tgui/interfaces/Canvas.js b/tgui/packages/tgui/interfaces/Canvas.js
deleted file mode 100644
index 4a73fc60558..00000000000
--- a/tgui/packages/tgui/interfaces/Canvas.js
+++ /dev/null
@@ -1,112 +0,0 @@
-import { Component, createRef } from 'inferno';
-import { useBackend } from '../backend';
-import { Box, Button } from '../components';
-import { Window } from '../layouts';
-
-const PX_PER_UNIT = 24;
-
-class PaintCanvas extends Component {
- constructor(props) {
- super(props);
- this.canvasRef = createRef();
- this.onCVClick = props.onCanvasClick;
- }
-
- componentDidMount() {
- this.drawCanvas(this.props);
- }
-
- componentDidUpdate() {
- this.drawCanvas(this.props);
- }
-
- drawCanvas(propSource) {
- const ctx = this.canvasRef.current.getContext("2d");
- const grid = propSource.value;
- const x_size = grid.length;
- if (!x_size) {
- return;
- }
- const y_size = grid[0].length;
- const x_scale = Math.round(this.canvasRef.current.width / x_size);
- const y_scale = Math.round(this.canvasRef.current.height / y_size);
- ctx.save();
- ctx.scale(x_scale, y_scale);
- for (let x = 0; x < grid.length; x++) {
- const element = grid[x];
- for (let y = 0; y < element.length; y++) {
- const color = element[y];
- ctx.fillStyle = color;
- ctx.fillRect(x, y, 1, 1);
- }
- }
- ctx.restore();
- }
-
- clickwrapper(event) {
- const x_size = this.props.value.length;
- if (!x_size) {
- return;
- }
- const y_size = this.props.value[0].length;
- const x_scale = this.canvasRef.current.width / x_size;
- const y_scale = this.canvasRef.current.height / y_size;
- const x = Math.floor(event.offsetX / x_scale)+1;
- const y = Math.floor(event.offsetY / y_scale)+1;
- this.onCVClick(x, y);
- }
-
- render() {
- const {
- res = 1,
- value,
- dotsize = PX_PER_UNIT,
- ...rest
- } = this.props;
- const [width, height] = getImageSize(value);
- return (
-
- );
- }
-}
-
-const getImageSize = value => {
- const width = value.length;
- const height = width !== 0 ? value[0].length : 0;
- return [width, height];
-};
-
-export const Canvas = (props, context) => {
- const { act, data } = useBackend(context);
- const dotsize = PX_PER_UNIT;
- const [width, height] = getImageSize(data.grid);
- return (
-
-
-
- act("paint", { x, y })} />
-
- {!data.finalized && (
- act("finalize")}
- content="Finalize" />
- )}
- {data.name}
-
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/Canvas.tsx b/tgui/packages/tgui/interfaces/Canvas.tsx
new file mode 100644
index 00000000000..50a77cc8340
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Canvas.tsx
@@ -0,0 +1,253 @@
+import { Color } from 'common/color';
+import { Component, createRef, RefObject } from 'inferno';
+import { useBackend } from '../backend';
+import { Box, Button, Flex } from '../components';
+import { Window } from '../layouts';
+
+const PX_PER_UNIT = 24;
+
+type PaintCanvasProps = Partial<{
+ onCanvasModifiedHandler: (data : PointData[]) => void,
+ value: string[][],
+ width: number,
+ height: number,
+ imageWidth: number,
+ imageHeight: number,
+ editable: boolean,
+ drawing_color: string | null,
+}>;
+
+type PointData = {
+ x: number,
+ y: number,
+ color: Color
+}
+
+const fromDM = (data: string[][]) => {
+ return data.map(inner => inner.map(v => Color.fromHex(v)));
+};
+
+const toMassPaintFormat = (data: PointData[]) => {
+ return data.map(p => ({ x: p.x+1, y: p.y+1 })); // 1-based index dm side
+};
+
+class PaintCanvas extends Component {
+ canvasRef: RefObject;
+ baseImageData: Color[][]
+ modifiedElements: PointData[];
+ onCanvasModified: (data: PointData[]) => void;
+ drawing: boolean;
+ drawing_color: string;
+
+ constructor(props) {
+ super(props);
+ this.canvasRef = createRef();
+ this.modifiedElements = [];
+ this.drawing = false;
+ this.onCanvasModified = props.onCanvasModifiedHandler;
+
+ this.handleStartDrawing = this.handleStartDrawing.bind(this);
+ this.handleDrawing = this.handleDrawing.bind(this);
+ this.handleEndDrawing = this.handleEndDrawing.bind(this);
+ }
+
+ componentDidMount() {
+ this.prepareCanvas();
+ this.syncCanvas();
+ }
+
+ componentDidUpdate() {
+ // eslint-disable-next-line max-len
+ if (this.props.value !== undefined && JSON.stringify(this.baseImageData) !== JSON.stringify(fromDM(this.props.value))) {
+ this.syncCanvas();
+ }
+ }
+
+ prepareCanvas() {
+ const canvas = this.canvasRef.current!;
+ const ctx = canvas.getContext("2d");
+ const width = this.props.width || canvas.width || 360;
+ const height = this.props.height || canvas.height || 360;
+ const x_resolution = this.props.imageWidth || 36;
+ const y_resolution = this.props.imageHeight || 36;
+ const x_scale = Math.round(width / x_resolution);
+ const y_scale = Math.round(height / y_resolution);
+ ctx?.setTransform(1, 0, 0, 1, 0, 0);
+ ctx?.scale(x_scale, y_scale); // This clears the canvas.
+ }
+
+ syncCanvas() {
+ if (this.props.value === undefined) {
+ return;
+ }
+ this.baseImageData = fromDM(this.props.value);
+ this.modifiedElements = [];
+
+ const canvas = this.canvasRef.current!;
+ const ctx = canvas.getContext("2d")!;
+ for (let x = 0; x < this.baseImageData.length; x++) {
+ const element = this.baseImageData[x];
+ for (let y = 0; y < element.length; y++) {
+ const color = element[y];
+ ctx.fillStyle = color.toString();
+ ctx.fillRect(x, y, 1, 1);
+ }
+ }
+ }
+
+ eventToCoords(event : MouseEvent) {
+ const canvas = this.canvasRef.current!;
+ const width = this.props.width || canvas.width || 360;
+ const height = this.props.height || canvas.height || 360;
+ const x_resolution = this.props.imageWidth || 36;
+ const y_resolution = this.props.imageHeight || 36;
+ const x_scale = Math.round(width / x_resolution);
+ const y_scale = Math.round(height / y_resolution);
+ const x = Math.floor(event.offsetX / x_scale);
+ const y = Math.floor(event.offsetY / y_scale);
+ return { x, y };
+ }
+
+ handleStartDrawing(event : MouseEvent) {
+ if (!this.props.editable
+ || this.props.drawing_color === undefined
+ || this.props.drawing_color === null) {
+ return;
+ }
+ this.modifiedElements = [];
+ this.drawing = true;
+ this.drawing_color = this.props.drawing_color;
+ const coords = this.eventToCoords(event);
+ this.drawPoint(coords.x, coords.y, this.drawing_color);
+ }
+
+ drawPoint(x: number, y: number, color: any) {
+ let p: PointData = { x, y, color: Color.fromHex(color) };
+ this.modifiedElements.push(p);
+ const canvas = this.canvasRef.current!;
+ const ctx = canvas.getContext("2d")!;
+ ctx.fillStyle = color;
+ ctx.fillRect(x, y, 1, 1);
+ }
+
+ handleDrawing(event: MouseEvent) {
+ if (!this.drawing) {
+ return;
+ }
+ const coords = this.eventToCoords(event);
+ this.drawPoint(coords.x, coords.y, this.drawing_color);
+ }
+
+ handleEndDrawing(event: MouseEvent) {
+ if (!this.drawing) {
+ return;
+ }
+ this.drawing = false;
+ const canvas = this.canvasRef.current!;
+ const ctx = canvas.getContext("2d")!;
+ if (this.onCanvasModified !== undefined) {
+ this.onCanvasModified(this.modifiedElements);
+ }
+ }
+
+ render() {
+ const {
+ value,
+ width = 300,
+ height = 300,
+ imageWidth = 36,
+ imageHeight = 36,
+ ...rest
+ } = this.props;
+ return (
+
+ );
+ }
+}
+
+const getImageSize = value => {
+ const width = value.length;
+ const height = width !== 0 ? value[0].length : 0;
+ return [width, height];
+};
+
+type CanvasData = {
+ grid: string[][],
+ finalized: boolean,
+ name: string,
+ editable: boolean,
+ paint_tool_color: string | null,
+ author: string | null,
+ medium: string | null,
+ patron: string | null,
+ date: string | null,
+ show_plaque: boolean
+}
+
+export const Canvas = (props, context) => {
+ const { act, data } = useBackend(context);
+ const [width, height] = getImageSize(data.grid);
+ const scaled_width = width * PX_PER_UNIT;
+ const scaled_height = height * PX_PER_UNIT;
+ const average_plaque_height = 90;
+ return (
+
+
+
+ act("paint", { data: toMassPaintFormat(changed) })}
+ editable={data.editable}
+ />
+
+ {!data.finalized && (
+
+ act("finalize")}
+ content="Finalize" />
+
+ )}
+ {!!data.finalized && !!data.show_plaque && (
+
+ {data.name}
+
+ {data.author}
+ {!!data.date && `- ${new Date(data.date).getFullYear()+540}`}
+
+ {data.medium}
+
+ {!!data.patron && `Sponsored by ${data.patron} `}
+
+
+ )}
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/NtosPortraitPrinter.js b/tgui/packages/tgui/interfaces/NtosPortraitPrinter.js
index 23312d6a09e..f5ca231e7a4 100644
--- a/tgui/packages/tgui/interfaces/NtosPortraitPrinter.js
+++ b/tgui/packages/tgui/interfaces/NtosPortraitPrinter.js
@@ -1,37 +1,16 @@
import { resolveAsset } from '../assets';
import { useBackend, useLocalState } from '../backend';
-import { Button, NoticeBox, Section, Stack, Tabs } from '../components';
+import { Button, NoticeBox, Section, Stack } from '../components';
import { NtosWindow } from '../layouts';
export const NtosPortraitPrinter = (props, context) => {
const { act, data } = useBackend(context);
- const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
const [listIndex, setListIndex] = useLocalState(context, 'listIndex', 0);
const {
- library,
- library_secure,
- library_private,
+ paintings,
} = data;
- const TABS = [
- {
- name: 'Common Portraits',
- asset_prefix: "library",
- list: library,
- },
- {
- name: 'Secure Portraits',
- asset_prefix: "library_secure",
- list: library_secure,
- },
- {
- name: 'Private Portraits',
- asset_prefix: "library_private",
- list: library_private,
- },
- ];
- const tab2list = TABS[tabIndex].list;
- const current_portrait_title = tab2list[listIndex]["title"];
- const current_portrait_asset_name = TABS[tabIndex].asset_prefix + "_" + tab2list[listIndex]["md5"];
+ const current_portrait_title = paintings[listIndex]["title"];
+ const current_portrait_asset_name = "paintings" + "_" + paintings[listIndex]["md5"];
return (
{
height={406}>
-
-
-
- {TABS.map((tabObj, i) => !!tabObj.list && (
- {
- setListIndex(0);
- setTabIndex(i);
- }}>
- {tabObj.name}
-
- ))}
-
-
-
{
icon="check"
content="Print Portrait"
onClick={() => act("select", {
- tab: tabIndex+1,
- selected: listIndex+1,
+ selected: paintings[listIndex]["ref"],
})}
/>
diff --git a/tgui/packages/tgui/interfaces/PaintingAdminPanel.tsx b/tgui/packages/tgui/interfaces/PaintingAdminPanel.tsx
new file mode 100644
index 00000000000..ba8143c61cb
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PaintingAdminPanel.tsx
@@ -0,0 +1,127 @@
+import { resolveAsset } from '../assets';
+import { useBackend, useLocalState } from '../backend';
+import { Box, Button, LabeledList, Section, Table } from '../components';
+import { Window } from '../layouts';
+
+type PaintingAdminPanelData = {
+ paintings: PaintingData[];
+};
+
+type PaintingData = {
+ md5: string,
+ title: string,
+ creator_ckey: string,
+ creator_name: string | null,
+ creation_date: Date | null,
+ creation_round_id: number | null,
+ patron_ckey: string | null,
+ patron_name: string | null,
+ credit_value: number,
+ width: number,
+ height: number,
+ ref: string,
+ tags: string[] | null,
+ medium: string | null
+}
+
+export const PaintingAdminPanel = (props, context) => {
+ const { act, data } = useBackend(context);
+ const [chosenPaintingRef, setChosenPaintingRef] = useLocalState(context, 'chosenPainting', null);
+ const {
+ paintings,
+ } = data;
+ const chosenPainting = paintings.find(p => p.ref === chosenPaintingRef);
+ return (
+
+
+ {chosenPainting && (
+ setChosenPaintingRef(null)}>Close}>
+
+
+
+
+ {chosenPainting.title}
+
+
+
+ {chosenPainting.creator_name}
+
+
+
+
+
+ {chosenPainting.tags?.map((tag) => (
+
+
+
+
+
+
+
+
+ { setChosenPaintingRef(null); act("delete", { ref: chosenPainting.ref }); }}>Delete
+ act("dumpit", { ref: chosenPainting.ref })}>Reset Patronage
+
+
+ )}
+ {!chosenPainting && (
+
+
+ Title
+ Author
+ Preview
+ Actions
+
+ {paintings.map(painting => (
+
+ {painting.title}
+ {painting.creator_ckey}
+
+
+
+ setChosenPaintingRef(painting.ref)}>
+ Edit
+
+
+
+ ))}
+
+ )}
+
+
+ );
+};
+
+
diff --git a/tgui/packages/tgui/interfaces/PortraitPicker.js b/tgui/packages/tgui/interfaces/PortraitPicker.js
index 59b9b227046..fe0591d1c25 100644
--- a/tgui/packages/tgui/interfaces/PortraitPicker.js
+++ b/tgui/packages/tgui/interfaces/PortraitPicker.js
@@ -1,37 +1,16 @@
import { resolveAsset } from '../assets';
import { useBackend, useLocalState } from '../backend';
-import { Button, Flex, NoticeBox, Section, Tabs } from '../components';
+import { Button, Flex, NoticeBox, Section } from '../components';
import { Window } from '../layouts';
export const PortraitPicker = (props, context) => {
const { act, data } = useBackend(context);
- const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
const [listIndex, setListIndex] = useLocalState(context, 'listIndex', 0);
const {
- library,
- library_secure,
- library_private,
+ paintings,
} = data;
- const TABS = [
- {
- name: 'Common Portraits',
- asset_prefix: "library",
- list: library,
- },
- {
- name: 'Secure Portraits',
- asset_prefix: "library_secure",
- list: library_secure,
- },
- {
- name: 'Private Portraits',
- asset_prefix: "library_private",
- list: library_private,
- },
- ];
- const tab2list = TABS[tabIndex].list;
- const current_portrait_title = tab2list[listIndex]["title"];
- const current_portrait_asset_name = TABS[tabIndex].asset_prefix + "_" + tab2list[listIndex]["md5"];
+ const current_portrait_title = paintings[listIndex]["title"];
+ const current_portrait_asset_name = "paintings" + "_" + paintings[listIndex]["md5"];
return (
{
height={406}>
-
-
-
- {TABS.map((tabObj, i) => (
- {
- setListIndex(0);
- setTabIndex(i);
- }}>
- {tabObj.name}
-
- ))}
-
-
-
{
icon="check"
content="Select Portrait"
onClick={() => act("select", {
- tab: tabIndex+1,
- selected: listIndex+1,
+ selected: paintings[listIndex]["ref"],
})}
/>
setListIndex(listIndex+1)}
/>
setListIndex(tab2list.length-1)}
+ disabled={listIndex === paintings.length-1}
+ onClick={() => setListIndex(paintings.length-1)}
/>