[MIRROR] Paintings improvements. [MDB IGNORE] (#9977)

* 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

* Paintings improvements.

Co-authored-by: AnturK <AnturK@users.noreply.github.com>
This commit is contained in:
SkyratBot
2021-12-09 22:47:52 +01:00
committed by GitHub
parent 5645eac00e
commit e5276a2c6f
17 changed files with 852 additions and 334 deletions

View File

@@ -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))

View File

@@ -83,6 +83,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)

View File

@@ -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

View File

@@ -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)
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,15 +250,38 @@
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
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)"
icon_state = "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]))
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/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/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

View File

@@ -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'

View File

@@ -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()

View File

@@ -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)

View File

@@ -34,7 +34,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
)
//SKYRAT EDIT: Adds Ceramic, Glassblowing, and Fishing Skillchips
contraband = list(

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1775,6 +1775,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"

View File

@@ -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 (
<canvas
ref={this.canvasRef}
width={(width * dotsize) || 300}
height={(height * dotsize) || 300}
{...rest}
onClick={e => this.clickwrapper(e)}>
Canvas failed to render.
</canvas>
);
}
}
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 (
<Window
width={Math.min(700, width * dotsize + 72)}
height={Math.min(700, height * dotsize + 72)}>
<Window.Content>
<Box textAlign="center">
<PaintCanvas
value={data.grid}
dotsize={dotsize}
onCanvasClick={(x, y) => act("paint", { x, y })} />
<Box>
{!data.finalized && (
<Button.Confirm
onClick={() => act("finalize")}
content="Finalize" />
)}
{data.name}
</Box>
</Box>
</Window.Content>
</Window>
);
};

View File

@@ -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<PaintCanvasProps> {
canvasRef: RefObject<HTMLCanvasElement>;
baseImageData: Color[][]
modifiedElements: PointData[];
onCanvasModified: (data: PointData[]) => void;
drawing: boolean;
drawing_color: string;
constructor(props) {
super(props);
this.canvasRef = createRef<HTMLCanvasElement>();
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
ref={this.canvasRef}
width={width}
height={height}
{...rest}
onMouseDown={this.handleStartDrawing}
onMouseMove={this.handleDrawing}
onMouseUp={this.handleEndDrawing}
onMouseOut={this.handleEndDrawing}>
Canvas failed to render.
</canvas>
);
}
}
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<CanvasData>(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 (
<Window
width={scaled_width + 72}
height={scaled_height + 70
+ (data.show_plaque ? average_plaque_height : 0)}>
<Window.Content>
<Box textAlign="center">
<PaintCanvas
value={data.grid}
imageWidth={width}
imageHeight={height}
width={scaled_width}
height={scaled_height}
drawing_color={data.paint_tool_color}
onCanvasModifiedHandler={(changed) => act("paint", { data: toMassPaintFormat(changed) })}
editable={data.editable}
/>
<Flex align="center" justify="center">
{!data.finalized && (
<Flex.Item>
<Button.Confirm
onClick={() => act("finalize")}
content="Finalize" />
</Flex.Item>
)}
{!!data.finalized && !!data.show_plaque && (
<Flex.Item
p={2}
width="60%"
textColor="black"
textAlign="left"
backgroundColor="white"
style={{ "border-style": "inset" }}>
<Box mb={1} fontSize="18px" bold>{data.name}</Box>
<Box bold>
{data.author}
{!!data.date && `- ${new Date(data.date).getFullYear()+540}`}
</Box>
<Box italic>{data.medium}</Box>
<Box italic>
{!!data.patron && `Sponsored by ${data.patron} `}
<Button icon="hand-holding-usd" color="transparent" iconColor="black" onClick={() => act("patronage")} />
</Box>
</Flex.Item>
)}
</Flex>
</Box>
</Window.Content>
</Window>
);
};

View File

@@ -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 (
<NtosWindow
title="Art Galaxy"
@@ -39,23 +18,6 @@ export const NtosPortraitPrinter = (props, context) => {
height={406}>
<NtosWindow.Content>
<Stack vertical fill>
<Stack.Item>
<Section fitted>
<Tabs fluid textAlign="center">
{TABS.map((tabObj, i) => !!tabObj.list && (
<Tabs.Tab
key={i}
selected={i === tabIndex}
onClick={() => {
setListIndex(0);
setTabIndex(i);
}}>
{tabObj.name}
</Tabs.Tab>
))}
</Tabs>
</Section>
</Stack.Item>
<Stack.Item grow={2}>
<Section fill>
<Stack
@@ -103,23 +65,22 @@ export const NtosPortraitPrinter = (props, context) => {
icon="check"
content="Print Portrait"
onClick={() => act("select", {
tab: tabIndex+1,
selected: listIndex+1,
selected: paintings[listIndex]["ref"],
})}
/>
</Stack.Item>
<Stack.Item grow={1}>
<Button
icon="chevron-right"
disabled={listIndex === tab2list.length-1}
disabled={listIndex === paintings.length-1}
onClick={() => setListIndex(listIndex+1)}
/>
</Stack.Item>
<Stack.Item>
<Button
icon="angle-double-right"
disabled={listIndex === tab2list.length-1}
onClick={() => setListIndex(tab2list.length-1)}
disabled={listIndex === paintings.length-1}
onClick={() => setListIndex(paintings.length-1)}
/>
</Stack.Item>
</Stack>

View File

@@ -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<PaintingAdminPanelData>(context);
const [chosenPaintingRef, setChosenPaintingRef] = useLocalState<string|null>(context, 'chosenPainting', null);
const {
paintings,
} = data;
const chosenPainting = paintings.find(p => p.ref === chosenPaintingRef);
return (
<Window
title="Painting Admin Panel"
width={800}
height={600}>
<Window.Content scrollable>
{chosenPainting && (
<Section title="Painting Information" buttons={<Button onClick={() => setChosenPaintingRef(null)}>Close</Button>}>
<img
src={resolveAsset(`paintings_${chosenPainting.md5}`)}
height="96px"
width="96px"
style={{
'vertical-align': 'middle',
'-ms-interpolation-mode': 'nearest-neighbor',
}} />
<LabeledList>
<LabeledList.Item label="md5" content={chosenPainting.md5} />
<LabeledList.Item label="title">
<Box inline>{chosenPainting.title}</Box>
<Button onClick={() => act("rename", { ref: chosenPainting.ref })} icon="edit" />
</LabeledList.Item>
<LabeledList.Item label="creator ckey" content={chosenPainting.creator_ckey} />
<LabeledList.Item label="creator name">
<Box inline>{chosenPainting.creator_name}</Box>
<Button onClick={() => act("rename_author", { ref: chosenPainting.ref })} icon="edit" />
</LabeledList.Item>
<LabeledList.Item label="creation date" content={chosenPainting.creation_date} />
<LabeledList.Item label="creation round id" content={chosenPainting.creation_round_id} />
<LabeledList.Item label="medium" content={chosenPainting.medium} />
<LabeledList.Item label="tags">
{chosenPainting.tags?.map((tag) => (
<Button
key={tag}
color="red"
icon="minus-circle"
iconPosition="right" content={tag}
onClick={() => act("remove_tag", { tag, ref: chosenPainting.ref })} />
))}
<Button
color="green"
icon="plus-circle"
onClick={() => act("add_tag", { ref: chosenPainting.ref })} />
</LabeledList.Item>
<LabeledList.Item label="patron ckey" content={chosenPainting.patron_ckey} />
<LabeledList.Item label="patron name" content={chosenPainting.patron_name} />
<LabeledList.Item label="credit value" content={chosenPainting.credit_value} />
<LabeledList.Item label="width" content={chosenPainting.width} />
<LabeledList.Item label="height" content={chosenPainting.height} />
</LabeledList>
<Section title="Actions">
<Button.Confirm onClick={() => { setChosenPaintingRef(null); act("delete", { ref: chosenPainting.ref }); }}>Delete</Button.Confirm>
<Button onClick={() => act("dumpit", { ref: chosenPainting.ref })}>Reset Patronage</Button>
</Section>
</Section>
)}
{!chosenPainting && (
<Table>
<Table.Row>
<Table.Cell color="label">Title</Table.Cell>
<Table.Cell color="label">Author</Table.Cell>
<Table.Cell color="label">Preview</Table.Cell>
<Table.Cell color="label">Actions</Table.Cell>
</Table.Row>
{paintings.map(painting => (
<Table.Row
key={painting.ref}
className="candystripe">
<Table.Cell>{painting.title}</Table.Cell>
<Table.Cell>{painting.creator_ckey}</Table.Cell>
<Table.Cell><img
src={resolveAsset(`paintings_${painting.md5}`)}
height="36px"
width="36px"
style={{
'vertical-align': 'middle',
'-ms-interpolation-mode': 'nearest-neighbor',
}} />
</Table.Cell>
<Table.Cell>
<Button onClick={() => setChosenPaintingRef(painting.ref)}>
Edit
</Button>
</Table.Cell>
</Table.Row>
))}
</Table>
)}
</Window.Content>
</Window>
);
};

View File

@@ -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 (
<Window
theme="ntos"
@@ -40,23 +19,6 @@ export const PortraitPicker = (props, context) => {
height={406}>
<Window.Content>
<Flex height="100%" direction="column">
<Flex.Item mb={1}>
<Section fitted>
<Tabs fluid textAlign="center">
{TABS.map((tabObj, i) => (
<Tabs.Tab
key={i}
selected={i === tabIndex}
onClick={() => {
setListIndex(0);
setTabIndex(i);
}}>
{tabObj.name}
</Tabs.Tab>
))}
</Tabs>
</Section>
</Flex.Item>
<Flex.Item mb={1} grow={2}>
<Section fill>
<Flex
@@ -104,23 +66,22 @@ export const PortraitPicker = (props, context) => {
icon="check"
content="Select Portrait"
onClick={() => act("select", {
tab: tabIndex+1,
selected: listIndex+1,
selected: paintings[listIndex]["ref"],
})}
/>
</Flex.Item>
<Flex.Item grow={1}>
<Button
icon="chevron-right"
disabled={listIndex === tab2list.length-1}
disabled={listIndex === paintings.length-1}
onClick={() => setListIndex(listIndex+1)}
/>
</Flex.Item>
<Flex.Item>
<Button
icon="angle-double-right"
disabled={listIndex === tab2list.length-1}
onClick={() => setListIndex(tab2list.length-1)}
disabled={listIndex === paintings.length-1}
onClick={() => setListIndex(paintings.length-1)}
/>
</Flex.Item>
</Flex>