mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 05:30:05 +01:00
TGUI Select Equipment menu (#57673)
Co-authored-by: Triggeredboi <lucas.moreno2124@gmail.com> Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>
This commit is contained in:
co-authored by
Triggeredboi
Aleksej Komarov
parent
3bbad6e878
commit
8a5c7c12e3
@@ -1061,10 +1061,9 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
|
||||
|
||||
|
||||
var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing")
|
||||
COMPILE_OVERLAYS(body)
|
||||
for(var/D in showDirs)
|
||||
body.setDir(D)
|
||||
COMPILE_OVERLAYS(body)
|
||||
var/icon/partial = getFlatIcon(body)
|
||||
var/icon/partial = getFlatIcon(body, defdir=D)
|
||||
out_icon.Insert(partial,dir=D)
|
||||
|
||||
humanoid_icon_cache[icon_id] = out_icon
|
||||
|
||||
@@ -84,7 +84,7 @@ GLOBAL_PROTECT(admin_verbs_ban)
|
||||
GLOBAL_LIST_INIT(admin_verbs_sounds, list(/client/proc/play_local_sound, /client/proc/play_direct_mob_sound, /client/proc/play_sound, /client/proc/set_round_end_sound))
|
||||
GLOBAL_PROTECT(admin_verbs_sounds)
|
||||
GLOBAL_LIST_INIT(admin_verbs_fun, list(
|
||||
/client/proc/cmd_admin_dress,
|
||||
/client/proc/cmd_select_equipment,
|
||||
/client/proc/cmd_admin_gib_self,
|
||||
/client/proc/drop_bomb,
|
||||
/client/proc/set_dynex_scale,
|
||||
@@ -217,7 +217,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list(
|
||||
/client/proc/play_local_sound,
|
||||
/client/proc/play_sound,
|
||||
/client/proc/set_round_end_sound,
|
||||
/client/proc/cmd_admin_dress,
|
||||
/client/proc/cmd_select_equipment,
|
||||
/client/proc/cmd_admin_gib_self,
|
||||
/client/proc/drop_bomb,
|
||||
/client/proc/drop_dynex_bomb,
|
||||
|
||||
@@ -495,39 +495,6 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
|
||||
set name = "Test Areas (ALL)"
|
||||
cmd_admin_areatest(FALSE)
|
||||
|
||||
/client/proc/cmd_admin_dress(mob/M in GLOB.mob_list)
|
||||
set category = "Admin.Events"
|
||||
set name = "Select equipment"
|
||||
if(!(ishuman(M) || isobserver(M)))
|
||||
alert("Invalid mob")
|
||||
return
|
||||
|
||||
var/dresscode = robust_dress_shop()
|
||||
|
||||
if(!dresscode)
|
||||
return
|
||||
|
||||
var/delete_pocket
|
||||
var/mob/living/carbon/human/H
|
||||
if(isobserver(M))
|
||||
H = M.change_mob_type(/mob/living/carbon/human, null, null, TRUE)
|
||||
else
|
||||
H = M
|
||||
if(H.l_store || H.r_store || H.s_store) //saves a lot of time for admins and coders alike
|
||||
if(alert("Drop Items in Pockets? No will delete them.", "Robust quick dress shop", "Yes", "No") == "No")
|
||||
delete_pocket = TRUE
|
||||
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Select Equipment") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
for (var/obj/item/I in H.get_equipped_items(delete_pocket))
|
||||
qdel(I)
|
||||
if(dresscode != "Naked")
|
||||
H.equipOutfit(dresscode)
|
||||
|
||||
H.regenerate_icons()
|
||||
|
||||
log_admin("[key_name(usr)] changed the equipment of [key_name(H)] to [dresscode].")
|
||||
message_admins("<span class='adminnotice'>[key_name_admin(usr)] changed the equipment of [ADMIN_LOOKUPFLW(H)] to [dresscode].</span>")
|
||||
|
||||
/client/proc/robust_dress_shop()
|
||||
|
||||
var/list/baseoutfits = list("Naked","Custom","As Job...", "As Plasmaman...")
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/client/proc/cmd_select_equipment(mob/target in GLOB.mob_list)
|
||||
set category = "Admin.Events"
|
||||
set name = "Select equipment"
|
||||
|
||||
|
||||
var/datum/select_equipment/ui = new(usr, target)
|
||||
ui.ui_interact(usr)
|
||||
|
||||
/*
|
||||
* This is the datum housing the select equipment UI.
|
||||
*
|
||||
* You may notice some oddities about the way outfits are passed to the UI and vice versa here.
|
||||
* That's because it handles both outfit typepaths (for normal outfits) *and* outfit objects (for custom outfits).
|
||||
*
|
||||
* Custom outfits need to be objects as they're created in runtime.
|
||||
* "Then just handle the normal outfits as objects too and simplify the handling" - you may say.
|
||||
* There are about 300 outfit types at the time of writing this. Initializing all of these to objects would be a huge waste.
|
||||
*
|
||||
*/
|
||||
|
||||
/datum/select_equipment
|
||||
var/client/user
|
||||
var/mob/target_mob
|
||||
|
||||
var/dummy_key
|
||||
var/mob/living/carbon/human/dummy/dummy
|
||||
|
||||
//static list to share all the outfit typepaths between all instances of this datum.
|
||||
var/static/list/cached_outfits
|
||||
|
||||
//a typepath if the selected outfit is a normal outfit;
|
||||
//an object if the selected outfit is a custom outfit
|
||||
var/datum/outfit/selected_outfit = /datum/outfit
|
||||
//serializable string for the UI to keep track of which outfit is selected
|
||||
var/selected_identifier = "/datum/outfit"
|
||||
|
||||
/datum/select_equipment/New(_user, mob/target)
|
||||
user = CLIENT_FROM_VAR(_user)
|
||||
|
||||
if(!ishuman(target) && !isobserver(target))
|
||||
alert("Invalid mob")
|
||||
return
|
||||
target_mob = target
|
||||
|
||||
/datum/select_equipment/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "SelectEquipment", "Select Equipment")
|
||||
ui.open()
|
||||
ui.set_autoupdate(FALSE)
|
||||
|
||||
/datum/select_equipment/ui_state(mob/user)
|
||||
return GLOB.admin_state
|
||||
|
||||
/datum/select_equipment/ui_status(mob/user, datum/ui_state/state)
|
||||
if(QDELETED(target_mob))
|
||||
return UI_CLOSE
|
||||
return ..()
|
||||
|
||||
/datum/select_equipment/ui_close(mob/user)
|
||||
clear_human_dummy(dummy_key)
|
||||
qdel(src)
|
||||
|
||||
/datum/select_equipment/proc/init_dummy()
|
||||
dummy_key = "selectequipmentUI_[target_mob]"
|
||||
dummy = generate_or_wait_for_human_dummy(dummy_key)
|
||||
var/mob/living/carbon/carbon_target = target_mob
|
||||
if(istype(carbon_target))
|
||||
carbon_target.dna.transfer_identity(dummy)
|
||||
dummy.updateappearance()
|
||||
|
||||
unset_busy_human_dummy(dummy_key)
|
||||
return
|
||||
|
||||
/**
|
||||
* Packs up data about an outfit as an assoc list to send to the UI as an outfit entry.
|
||||
*
|
||||
* Args:
|
||||
* * category (string) - The tab it will be under
|
||||
*
|
||||
* * identifier (typepath or ref) - This will sent this back to ui_act to preview or spawn in an outfit.
|
||||
* * Must be unique between all entries.
|
||||
*
|
||||
* * name (string) - Will be the text on the button
|
||||
*
|
||||
* * priority (bool)(optional) - If True, the UI will sort the entry to the top, right below favorites.
|
||||
*
|
||||
* * custom_entry (bool)(optional) - Send the identifier with a "ref" keyword instead of "path",
|
||||
* * for the UI to tell apart custom outfits from normal ones.
|
||||
*
|
||||
* Returns (list) An outfit entry
|
||||
*/
|
||||
|
||||
/datum/select_equipment/proc/outfit_entry(category, identifier, name, priority=FALSE, custom_entry=FALSE)
|
||||
if(custom_entry)
|
||||
return list("category" = category, "ref" = identifier, "name" = name, "priority" = priority)
|
||||
return list("category" = category, "path" = identifier, "name" = name, "priority" = priority)
|
||||
|
||||
/datum/select_equipment/proc/make_outfit_entries(category="General", list/outfit_list)
|
||||
var/list/entries = list()
|
||||
for(var/path as anything in outfit_list)
|
||||
var/datum/outfit/outfit = path
|
||||
entries += list(outfit_entry(category, path, initial(outfit.name)))
|
||||
return entries
|
||||
|
||||
//GLOB.custom_outfits lists outfit *objects* so we'll need to do some custom handling for it
|
||||
/datum/select_equipment/proc/make_custom_outfit_entries(list/outfit_list)
|
||||
var/list/entries = list()
|
||||
for(var/datum/outfit/outfit as anything in outfit_list)
|
||||
entries += list(outfit_entry("Custom", REF(outfit), outfit.name, custom_entry=TRUE)) //it's either this or special handling on the UI side
|
||||
return entries
|
||||
|
||||
/datum/select_equipment/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
if(!dummy)
|
||||
init_dummy()
|
||||
|
||||
var/datum/preferences/prefs = target_mob?.client?.prefs
|
||||
var/icon/dummysprite = get_flat_human_icon(null, prefs=prefs, dummy_key = dummy_key, outfit_override = selected_outfit)
|
||||
data["icon64"] = icon2base64(dummysprite)
|
||||
data["name"] = target_mob
|
||||
|
||||
data["favorites"] = list()
|
||||
if(prefs)
|
||||
data["favorites"] = prefs.favorite_outfits
|
||||
|
||||
var/list/custom
|
||||
custom += make_custom_outfit_entries(GLOB.custom_outfits)
|
||||
data["custom_outfits"] = custom
|
||||
data["current_outfit"] = selected_identifier
|
||||
return data
|
||||
|
||||
|
||||
/datum/select_equipment/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
if(!cached_outfits)
|
||||
cached_outfits = list()
|
||||
cached_outfits += list(outfit_entry("General", /datum/outfit, "Naked", priority=TRUE))
|
||||
cached_outfits += make_outfit_entries("General", subtypesof(/datum/outfit) - typesof(/datum/outfit/job) - typesof(/datum/outfit/plasmaman))
|
||||
cached_outfits += make_outfit_entries("Jobs", typesof(/datum/outfit/job))
|
||||
cached_outfits += make_outfit_entries("Plasmamen Outfits", typesof(/datum/outfit/plasmaman))
|
||||
|
||||
data["outfits"] = cached_outfits
|
||||
return data
|
||||
|
||||
|
||||
/datum/select_equipment/proc/resolve_outfit(text)
|
||||
|
||||
var/path = text2path(text)
|
||||
if(ispath(path, /datum/outfit))
|
||||
return path
|
||||
|
||||
else //don't bail yet - could be a custom outfit
|
||||
var/datum/outfit/custom_outfit = locate(text)
|
||||
if(istype(custom_outfit))
|
||||
return custom_outfit
|
||||
|
||||
|
||||
/datum/select_equipment/ui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
. = TRUE
|
||||
switch(action)
|
||||
if("preview")
|
||||
var/datum/outfit/new_outfit = resolve_outfit(params["path"])
|
||||
|
||||
if(ispath(new_outfit)) //got a typepath - that means we're dealing with a normal outfit
|
||||
selected_identifier = new_outfit //these are keyed by type
|
||||
//by the way, no, they can't be keyed by name because many of them have duplicate names
|
||||
|
||||
else if(istype(new_outfit)) //got an initialized object - means it's a custom outfit
|
||||
selected_identifier = REF(new_outfit) //and the outfit will be keyed by its ref (cause its type will always be /datum/outfit)
|
||||
|
||||
else //we got nothing and should bail
|
||||
return
|
||||
|
||||
selected_outfit = new_outfit
|
||||
|
||||
if("applyoutfit")
|
||||
var/datum/outfit/new_outfit = resolve_outfit(params["path"])
|
||||
if(new_outfit && ispath(new_outfit)) //initialize it
|
||||
new_outfit = new new_outfit
|
||||
if(!istype(new_outfit))
|
||||
return
|
||||
user.admin_apply_outfit(target_mob, new_outfit)
|
||||
|
||||
if("customoutfit")
|
||||
user.outfit_manager()
|
||||
|
||||
if("togglefavorite")
|
||||
var/datum/outfit/outfit_path = resolve_outfit(params["path"])
|
||||
if(!ispath(outfit_path)) //we do *not* want custom outfits (i.e objects) here, they're not even persistent
|
||||
return
|
||||
|
||||
if(user.prefs.favorite_outfits.Find(outfit_path)) //already there, remove it
|
||||
user.prefs.favorite_outfits -= outfit_path
|
||||
else //not there, add it
|
||||
user.prefs.favorite_outfits += outfit_path
|
||||
user.prefs.save_preferences()
|
||||
|
||||
/client/proc/admin_apply_outfit(mob/target, dresscode)
|
||||
if(!ishuman(target) && !isobserver(target))
|
||||
alert("Invalid mob")
|
||||
return
|
||||
|
||||
if(!dresscode)
|
||||
return
|
||||
|
||||
var/delete_pocket
|
||||
var/mob/living/carbon/human/human_target
|
||||
if(isobserver(target))
|
||||
human_target = target.change_mob_type(/mob/living/carbon/human, delete_old_mob = TRUE)
|
||||
else
|
||||
human_target = target
|
||||
if(human_target.l_store || human_target.r_store || human_target.s_store) //saves a lot of time for admins and coders alike
|
||||
if(alert("Drop Items in Pockets? No will delete them.", "Robust quick dress shop", "Yes", "No") == "No")
|
||||
delete_pocket = TRUE
|
||||
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Select Equipment") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
for(var/obj/item/item in human_target.get_equipped_items(delete_pocket))
|
||||
qdel(item)
|
||||
if(dresscode != "Naked")
|
||||
human_target.equipOutfit(dresscode)
|
||||
|
||||
human_target.regenerate_icons()
|
||||
|
||||
log_admin("[key_name(usr)] changed the equipment of [key_name(human_target)] to [dresscode].")
|
||||
message_admins("<span class='adminnotice'>[key_name_admin(usr)] changed the equipment of [ADMIN_LOOKUPFLW(human_target)] to [dresscode].</span>")
|
||||
|
||||
return dresscode
|
||||
@@ -146,6 +146,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
var/persistent_scars = TRUE
|
||||
///If we want to broadcast deadchat connect/disconnect messages
|
||||
var/broadcast_login_logout = TRUE
|
||||
///What outfit typepaths we've favorited in the SelectEquipment menu
|
||||
var/list/favorite_outfits = list()
|
||||
|
||||
/datum/preferences/New(client/C)
|
||||
parent = C
|
||||
@@ -645,7 +647,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
dat += "<b>Set screentip mode:</b> <a href='?_src_=prefs;preference=screentipmode'>[screentip_pref ? "Enabled" : "Disabled"]</a><br>"
|
||||
dat += "<b>Screentip color:</b><span style='border: 1px solid #161616; background-color: [screentip_color];'> </span> <a href='?_src_=prefs;preference=screentipcolor'>Change</a><BR>"
|
||||
dat += "<b>Item Hover Outlines:</b> <a href='?_src_=prefs;preference=itemoutline_pref'>[itemoutline_pref ? "Enabled" : "Disabled"]</a><br>"
|
||||
|
||||
|
||||
|
||||
dat += "<b>Ambient Occlusion:</b> <a href='?_src_=prefs;preference=ambientocclusion'>[ambientocclusion ? "Enabled" : "Disabled"]</a><br>"
|
||||
dat += "<b>Fit Viewport:</b> <a href='?_src_=prefs;preference=auto_fit_viewport'>[auto_fit_viewport ? "Auto" : "Manual"]</a><br>"
|
||||
|
||||
@@ -214,6 +214,15 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
READ_FILE(S["hearted_until"], hearted_until)
|
||||
if(hearted_until > world.realtime)
|
||||
hearted = TRUE
|
||||
//favorite outfits
|
||||
READ_FILE(S["favorite_outfits"], favorite_outfits)
|
||||
|
||||
var/list/parsed_favs = list()
|
||||
for(var/typetext in favorite_outfits)
|
||||
var/datum/outfit/path = text2path(typetext)
|
||||
if(ispath(path)) //whatever typepath fails this check probably doesn't exist anymore
|
||||
parsed_favs += path
|
||||
favorite_outfits = uniqueList(parsed_favs)
|
||||
|
||||
//try to fix any outdated data if necessary
|
||||
if(needs_update >= 0)
|
||||
@@ -262,6 +271,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
pda_style = sanitize_inlist(pda_style, GLOB.pda_styles, initial(pda_style))
|
||||
pda_color = sanitize_hexcolor(pda_color, 6, 1, initial(pda_color))
|
||||
key_bindings = sanitize_keybindings(key_bindings)
|
||||
favorite_outfits = SANITIZE_LIST(favorite_outfits)
|
||||
|
||||
if(needs_update >= 0) //save the updated version
|
||||
var/old_default_slot = default_slot
|
||||
@@ -339,6 +349,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
WRITE_FILE(S["pda_color"], pda_color)
|
||||
WRITE_FILE(S["key_bindings"], key_bindings)
|
||||
WRITE_FILE(S["hearted_until"], (hearted_until > world.realtime ? hearted_until : null))
|
||||
WRITE_FILE(S["favorite_outfits"], favorite_outfits)
|
||||
return TRUE
|
||||
|
||||
/datum/preferences/proc/load_character(slot)
|
||||
|
||||
@@ -40,13 +40,26 @@ GLOBAL_LIST_EMPTY(dummy_mob_list)
|
||||
D = new
|
||||
GLOB.human_dummy_list[slotkey] = D
|
||||
GLOB.dummy_mob_list += D
|
||||
else
|
||||
D.regenerate_icons() //they were cut in wipe_state()
|
||||
D.in_use = TRUE
|
||||
return D
|
||||
|
||||
/proc/unset_busy_human_dummy(slotnumber)
|
||||
if(!slotnumber)
|
||||
/proc/unset_busy_human_dummy(slotkey)
|
||||
if(!slotkey)
|
||||
return
|
||||
var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotnumber]
|
||||
var/mob/living/carbon/human/dummy/D = GLOB.human_dummy_list[slotkey]
|
||||
if(istype(D))
|
||||
D.wipe_state()
|
||||
D.in_use = FALSE
|
||||
|
||||
/proc/clear_human_dummy(slotkey)
|
||||
if(!slotkey)
|
||||
return
|
||||
|
||||
var/mob/living/carbon/human/dummy/dummy = GLOB.human_dummy_list[slotkey]
|
||||
|
||||
GLOB.human_dummy_list -= slotkey
|
||||
if(istype(dummy))
|
||||
GLOB.dummy_mob_list -= dummy
|
||||
qdel(dummy)
|
||||
|
||||
@@ -1541,6 +1541,7 @@
|
||||
#include "code\modules\admin\verbs\randomverbs.dm"
|
||||
#include "code\modules\admin\verbs\reestablish_db_connection.dm"
|
||||
#include "code\modules\admin\verbs\secrets.dm"
|
||||
#include "code\modules\admin\verbs\selectequipment.dm"
|
||||
#include "code\modules\admin\verbs\shuttlepanel.dm"
|
||||
#include "code\modules\admin\verbs\spawnobjasmob.dm"
|
||||
#include "code\modules\admin\verbs\tripAI.dm"
|
||||
|
||||
@@ -171,6 +171,8 @@ export const sortBy = (...iterateeFns) => array => {
|
||||
return mappedArray;
|
||||
};
|
||||
|
||||
export const sort = sortBy();
|
||||
|
||||
/**
|
||||
* A fast implementation of reduce.
|
||||
*/
|
||||
@@ -235,6 +237,8 @@ export const uniqBy = iterateeFn => array => {
|
||||
return result;
|
||||
};
|
||||
|
||||
export const uniq = uniqBy();
|
||||
|
||||
/**
|
||||
* Creates an array of grouped elements, the first of which contains
|
||||
* the first elements of the given arrays, the second of which contains
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
import { filter, map, sortBy, uniq } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { createSearch } from 'common/string';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Icon, Input, Section, Stack, Tabs } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
// here's an important mental define:
|
||||
// custom outfits give a ref keyword instead of path
|
||||
const getOutfitKey = outfit => outfit.path || outfit.ref;
|
||||
|
||||
const useOutfitTabs = (context, categories) => {
|
||||
return useLocalState(context, 'selected-tab', categories[0]);
|
||||
};
|
||||
|
||||
export const SelectEquipment = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
name,
|
||||
icon64,
|
||||
current_outfit,
|
||||
favorites,
|
||||
} = data;
|
||||
|
||||
const isFavorited = entry => favorites?.includes(entry.path);
|
||||
|
||||
const outfits = map(entry => ({
|
||||
...entry,
|
||||
favorite: isFavorited(entry),
|
||||
}))([
|
||||
...data.outfits,
|
||||
...data.custom_outfits,
|
||||
]);
|
||||
|
||||
// even if no custom outfits were sent, we still want to make sure there's
|
||||
// at least a 'Custom' tab so the button to create a new one pops up
|
||||
const categories = uniq([
|
||||
...outfits.map(entry => entry.category),
|
||||
'Custom',
|
||||
]);
|
||||
const [tab] = useOutfitTabs(context, categories);
|
||||
|
||||
const [searchText, setSearchText] = useLocalState(
|
||||
context, 'searchText', '');
|
||||
const searchFilter = createSearch(searchText, entry => (
|
||||
entry.name + entry.path
|
||||
));
|
||||
|
||||
const visibleOutfits = flow([
|
||||
filter(entry => entry.category === tab),
|
||||
filter(searchFilter),
|
||||
sortBy(
|
||||
entry => !entry.favorite,
|
||||
entry => !entry.priority,
|
||||
entry => entry.name
|
||||
),
|
||||
])(outfits);
|
||||
|
||||
const getOutfitEntry = current_outfit => outfits.find(outfit => (
|
||||
getOutfitKey(outfit) === current_outfit
|
||||
));
|
||||
|
||||
const currentOutfitEntry = getOutfitEntry(current_outfit);
|
||||
|
||||
return (
|
||||
<Window
|
||||
width={650}
|
||||
height={415}>
|
||||
<Window.Content>
|
||||
<Stack fill>
|
||||
<Stack.Item>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fluid
|
||||
autoFocus
|
||||
placeholder="Search"
|
||||
value={searchText}
|
||||
onInput={(e, value) => setSearchText(value)} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<DisplayTabs categories={categories} />
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={0} grow={1} basis={0}>
|
||||
<OutfitDisplay
|
||||
entries={visibleOutfits}
|
||||
currentTab={tab} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1} basis={0}>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<Section>
|
||||
<CurrentlySelectedDisplay entry={currentOutfitEntry} />
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
<Section
|
||||
fill
|
||||
title={name}
|
||||
textAlign="center">
|
||||
<Box
|
||||
as="img"
|
||||
m={0}
|
||||
src={`data:image/jpeg;base64,${icon64}`}
|
||||
height="100%"
|
||||
style={{
|
||||
'-ms-interpolation-mode': 'nearest-neighbor',
|
||||
}} />
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const DisplayTabs = (props, context) => {
|
||||
const { categories } = props;
|
||||
const [tab, setTab] = useOutfitTabs(context, categories);
|
||||
return (
|
||||
<Tabs textAlign="center">
|
||||
{categories.map(category => (
|
||||
<Tabs.Tab
|
||||
key={category}
|
||||
selected={tab === category}
|
||||
onClick={() => setTab(category)}>
|
||||
{category}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
const OutfitDisplay = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { current_outfit } = data;
|
||||
const { entries, currentTab } = props;
|
||||
return (
|
||||
<Section fill scrollable>
|
||||
{entries.map(entry => (
|
||||
<Button
|
||||
key={getOutfitKey(entry)}
|
||||
fluid
|
||||
ellipsis
|
||||
icon={entry.favorite && 'star'}
|
||||
iconColor="gold"
|
||||
content={entry.name}
|
||||
title={entry.path || entry.name}
|
||||
selected={getOutfitKey(entry) === current_outfit}
|
||||
onClick={() => act('preview', { path: getOutfitKey(entry) })} />
|
||||
))}
|
||||
{currentTab === "Custom" && (
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="plus"
|
||||
fluid
|
||||
onClick={() => act('customoutfit')}>
|
||||
Create a custom outfit...
|
||||
</Button>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const CurrentlySelectedDisplay = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { current_outfit } = data;
|
||||
const { entry } = props;
|
||||
return (
|
||||
<Stack align="center">
|
||||
{entry?.path && (
|
||||
<Stack.Item>
|
||||
<Icon
|
||||
size={1.6}
|
||||
name={entry.favorite ? 'star' : 'star-o'}
|
||||
color="gold"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => act('togglefavorite', {
|
||||
path: entry.path,
|
||||
})} />
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item grow={1} basis={0}>
|
||||
<Box color="label">
|
||||
Currently selected:
|
||||
</Box>
|
||||
<Box
|
||||
title={entry?.path}
|
||||
style={{
|
||||
'overflow': 'hidden',
|
||||
'white-space': 'nowrap',
|
||||
'text-overflow': 'ellipsis',
|
||||
}}>
|
||||
{entry?.name}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
mr={0.8}
|
||||
lineHeight={2}
|
||||
color="green"
|
||||
onClick={() => act('applyoutfit', {
|
||||
path: current_outfit,
|
||||
})}>
|
||||
Confirm
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user