From 39308f9dc61c70043781301e096117a7a421dae2 Mon Sep 17 00:00:00 2001 From: AnturK Date: Thu, 9 Dec 2021 20:46:37 +0100 Subject: [PATCH] Paintings improvements. (#63170) About The Pull Request Paintings can now do stroke painting. Added painting management panel for admins. Paintings now display author's character name, year of painting, medium and patron when hung on wall. You can become new patron by paying more than the previous one. Added painter's palettes to library vendor. (Sprites by @Mickyan ) Backend changes: Images are now stored in /data/paintings/images/*.png instead of /data/paintings/[category]/*.png Old categories are now just tags Screens & Video Changelog cl add: You can now become patron of your favorite painting by buying sponsorship from Nanotrasen Trust Foundation. add: Painter's palettes are now available at library vendor. qol: Can use strokes in paintings now /cl --- .../subsystem/persistent_paintings.dm | 182 ++++++++++++- code/modules/admin/admin_verbs.dm | 1 + code/modules/admin/painting_manager.dm | 96 +++++++ code/modules/art/paintings.dm | 235 ++++++++++------ code/modules/asset_cache/asset_list_items.dm | 18 +- .../living/silicon/ai/ai_portrait_picker.dm | 20 +- .../file_system/programs/portrait_printer.dm | 28 +- code/modules/vending/games.dm | 3 +- .../inhands/equipment/palette_lefthand.dmi | Bin 0 -> 383 bytes .../inhands/equipment/palette_righthand.dmi | Bin 0 -> 381 bytes icons/obj/artstuff.dmi | Bin 980 -> 1352 bytes tgstation.dme | 1 + tgui/packages/tgui/interfaces/Canvas.js | 112 -------- tgui/packages/tgui/interfaces/Canvas.tsx | 253 ++++++++++++++++++ .../tgui/interfaces/NtosPortraitPrinter.js | 55 +--- .../tgui/interfaces/PaintingAdminPanel.tsx | 127 +++++++++ .../tgui/interfaces/PortraitPicker.js | 55 +--- 17 files changed, 852 insertions(+), 334 deletions(-) create mode 100644 code/modules/admin/painting_manager.dm create mode 100644 icons/mob/inhands/equipment/palette_lefthand.dmi create mode 100644 icons/mob/inhands/equipment/palette_righthand.dmi delete mode 100644 tgui/packages/tgui/interfaces/Canvas.js create mode 100644 tgui/packages/tgui/interfaces/Canvas.tsx create mode 100644 tgui/packages/tgui/interfaces/PaintingAdminPanel.tsx 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 0000000000000000000000000000000000000000..e68495cdd6cfc0f0cd1e45048fe2f029875ac0cb GIT binary patch literal 383 zcmV-_0f7FAP)V=-0C=2JR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRL zOex6#a*U0*I5Sc+(=$pSoZ^zil2jm5sUR^YwWK6fiHkEOv#1!Pjf*p_C^0t`#5Uwo zR&e!m0UHbedgL1YlBu^r0001vNklsYfU^~_DNr;Cg0mg4DG+gS zQ^2F2fkCLm1-}9b7f+~+3RZb8r7ee`OePj=Zs1}-_yWm4qhJ(_0tx}w0_RbEj5^w dFq|;50RSbV4|g3J;Zpzr002ovPDHLkV1o3+jpzUX literal 0 HcmV?d00001 diff --git a/icons/mob/inhands/equipment/palette_righthand.dmi b/icons/mob/inhands/equipment/palette_righthand.dmi new file mode 100644 index 0000000000000000000000000000000000000000..cce674c35152b438819b80ba98e6e8a45bab5e66 GIT binary patch literal 381 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|e^#Gp`*8>L*Z0buW4cES6?|p7@ z_Vee@KhM`Y=5pcLMJ*xc&B6PW6!t8&b#|RtkAb0LPHgE*^OM1JF*J>D=iykq15)@YOwU|+w zhoS0;)Za-fw>tvu?eTPR4DskpPLN<-%)raRl_)IQuvx;liDiRELBr-FF)<<=X7OBB zdlb|W-`LWvUdP(wZFVZ5m|1y_rpAG0*G2`i>HSTC23#IYHtZWVFKh_(6r9-|xj@6x z&*hm{l7>L(cehikdZGzag4<~HSEsfjy_h75J>k+N=FuwV z(I!~GC|bcZJwrip2xwg^U~5<|08 ze%(D>!KfOqF1n{6yro3LvMFN6D_6R$Q!W(%0004WQchCV=-0C=2@(y?za6aIz!=_O4XkLj) zx21S=wi6Qlb){;DCjZ+nR(HR6F|#=K000ADNkl#c;Nlz%_5!6oGQ)d>6JRXW!r{QyF{ z0OzGL0Du;Y1!@9diSz@YJOMNY;DSu!{h{F1ZZLr4sslKU0H_2&8UY43Qc>*sUK7C3 zgdD*10H_9l>;r$~h2A|lfU^bAlx2YVqN%xMfVONWFz-B`Apq0>Kz0GjH}XnF1l9o1 zP9H$_0VYpiw=K+S0R1Z?O#r9`P@JgNhN^cyeG3;SCnqN-Q`&c&?wn~x0Vrpxe;&YV z$})h5eWCJp&pH5RU-kinRszZ@(gFb1zVG?qkAr@IFw}nxfIUAiJ1}4`=mv;z6bXC% z0G0dHB(P-FkA;6%8(_A~KZFhlaTov)?qV?P z0)Up!dO&|z{LlMV>Mg{}He74L+qxxiN#C;^;K zy;}cr*JKCaqExm3WLF%305IM_lmN2(9DoCG0L6dFzH{~e0kFcW72n`sA0WnscO0|* z1OV}>rYWi*E;5KG1Bjs>*ft#iN8mwB1|ULc1E{*EMgS}YI5@BXfTaM;-U2`obSHp? z1aL2a;RNadw2km3A(IK+lZ zyd*T;Znv+PJ)ufFDZ*Bkpc$|&WzY4=35C-sBpF)zo^whhi+%fVK{qf}zp65|Ngr z{?bpvlNGynhsjY&j7RCt{2mM=k%hnT}Z>2TBiMwFdYTR0aUiwyoF#KtSmM#F&7% z0`M8qb$@F3d^!yfxXS=P5kO1;A|b%(PHLOIJSPG;n-Bxk2M`MYQ66Ay^!ma8zYAci z!T?RXRZ|#XuhI!L-s>*{5E}qd7GQcJpXDMb1|U{rK$Hilp1^;)FFY0i$2+4$0AdH= z-o*BXv1h(?8y6Of#bV{s-Fe!((p&XHa`H&5u5Y1+W2VYCh;p0OJJU1(^MH769dH06_pML2oa4 z5_n?y!TS690NSE|PXmzR$N*s7?PTZ!pq`H-pm!e<15Ax4^KK7-&9~#<_ClQjK&j5y zchm(o0GhHV0&`FZSxV4##@=M$10ZKtfefR-xT|ppYyf}ywJxs(X8^|Nrwn!g?gz#w z(gKVDOai#L97moBV{3tm7(@YBKVG~4axYW{;BQc+077>dzyf6NAW?wO_ZWZy7{I-` zyH@`n055pG%t4p(00TJhVZd_*fZ?@JjS>tlgP|J0Ky|>Q9)LkGK~w{!4e#l;CN?rAHdp& z0U~?Dx*JCpi^XD{QEaZxK99#%E8l$vU;qaARsgr>huZ)rev1JZfB_hQ0geG~J`>(u a3;qE-Gi%0000 this.clickwrapper(e)}> - Canvas failed to render. - - ); - } -} - -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 ( + + Canvas failed to render. + + ); + } +} + +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} `} + }> + + + + + {chosenPainting.title} + + + + )} + {!chosenPainting && ( + + + Title + Author + Preview + Actions + + {paintings.map(painting => ( + + {painting.title} + {painting.creator_ckey} + + + + + + + ))} +
+ )} +
+
+ ); +}; + + 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"], })} />