mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-26 06:29:23 +01:00
[Modular] Ports the Character Directory from Coyote Bayou (see inside for link) (#784)
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may not be viewable. --> <!-- You can view Contributing.MD for a detailed description of the pull request process. --> ## About The Pull Request Source Inspiration: https://github.com/ARF-SS13/coyote-bayou/pull/3415 Adds the character directory from like, three different codebases, to Bubberstation! Pr is tested and ready for merging! Big thanks to @lessthnthree for helping me debug the TGUI, a lot. This PR wouldn't have been possible without the assistance. <!-- Describe The Pull Request. Please be sure every change is documented or this can delay review and even discourage maintainers from merging your PR! --> <!-- Please make sure to actually test your PRs. If you have not tested your PR mention it. --> ## Why It's Good For The Game Helps people find other people to roleplay with! ...And get sloppy with.    <!-- Argue for the merits of your changes and how they benefit the game, especially if they are controversial and/or far reaching. If you can't actually explain WHY what you are doing will improve the game, then it probably isn't good for the game in the first place. --> ## Changelog <!-- If your PR modifies aspects of the game that can be concretely observed by players or admins you should add a changelog. If your change does NOT meet this description, remove this section. Be sure to properly mark your PRs to prevent unnecessary GBP loss. You can read up on GBP and it's effects on PRs in the tgstation guides for contributors. Please note that maintainers freely reserve the right to remove and add tags should they deem it appropriate. You can attempt to finagle the system all you want, but it's best to shoot for clear communication right off the bat. --> 🆑 ReturnToZender, LT3, Waterpig, Nevimer add: Character directory from SPLURT, originally, I think? /🆑 <!-- Both 🆑's are required for the changelog to work! You can put your name to the right of the first 🆑 if you want to overwrite your GitHub username as author ingame. --> <!-- You can use multiple of the same prefix (they're only used for the icon ingame) and delete the unneeded ones. Despite some of the tags, changelogs should generally represent how a player might be affected by the changes rather than a summary of the PR's contents. --> <!-- By opening a pull request. You have read and understood the repository rules located on the main README.md on this project. --> --------- Co-authored-by: StrangeWeirdKitten <95130227+StrangeWeirdKitten@users.noreply.github.com>
This commit is contained in:
co-authored by
StrangeWeirdKitten
parent
9be6f0879f
commit
9d3017f2d0
@@ -0,0 +1,218 @@
|
||||
GLOBAL_DATUM(character_directory, /datum/character_directory)
|
||||
#define READ_PREFS(target, pref) (target.client.prefs.read_preference(/datum/preference/pref) || "Unset")
|
||||
|
||||
//We want players to be able to decide whether they show up in the directory or not
|
||||
/datum/preference/toggle/show_in_directory
|
||||
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
|
||||
default_value = TRUE
|
||||
savefile_key = "show_in_directory"
|
||||
savefile_identifier = PREFERENCE_PLAYER
|
||||
|
||||
//The advertisement that you show to people looking through the directory
|
||||
/datum/preference/text/character_ad
|
||||
savefile_key = "character_ad"
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
maximum_value_length = MAX_FLAVOR_LEN
|
||||
|
||||
//TGUI gets angry if you don't define a default on text preferences
|
||||
/datum/preference/text/character_ad/create_default_value()
|
||||
return ""
|
||||
|
||||
//Any text preference needs this for some reason
|
||||
/datum/preference/text/character_ad/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
|
||||
return FALSE
|
||||
|
||||
/datum/preference/choiced/attraction
|
||||
savefile_key = "attraction"
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
|
||||
/datum/preference/choiced/attraction/init_possible_values()
|
||||
return list("Gay", "Lesbian", "Straight", "Skolio", "Bi", "Pan", "Poly", "Omni", "Ace", "Unset", "Check OOC")
|
||||
|
||||
/datum/preference/choiced/attraction/create_default_value()
|
||||
return "Unset"
|
||||
|
||||
/datum/preference/choiced/attraction/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
|
||||
return FALSE
|
||||
|
||||
/datum/preference/choiced/display_gender
|
||||
savefile_key = "display_gender"
|
||||
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
|
||||
/datum/preference/choiced/display_gender/init_possible_values()
|
||||
return list("Male", "Female", "Null", "Plural", "Nonbinary", "Omni", "Trans", "Andro", "Gyno", "Fluid", "Unset", "Check OOC")
|
||||
|
||||
/datum/preference/choiced/display_gender/create_default_value()
|
||||
return "Unset"
|
||||
|
||||
/datum/preference/choiced/display_gender/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
|
||||
return FALSE
|
||||
//Can't believe Bubberstation invented attraction and gender in the year December 2023
|
||||
|
||||
//Add a cooldown for the character directory to the client, primarily to stop server lag from refresh spam
|
||||
/client
|
||||
COOLDOWN_DECLARE(char_directory_cooldown)
|
||||
|
||||
//Make a verb to open the character directory
|
||||
/client/verb/show_character_directory()
|
||||
set name = "Character Directory"
|
||||
set category = "OOC"
|
||||
set desc = "Shows a listing of all active characters, along with their associated OOC notes, flavor text, and more."
|
||||
|
||||
// This is primarily to stop malicious users from trying to lag the server by spamming this verb
|
||||
if(!COOLDOWN_FINISHED(src, char_directory_cooldown))
|
||||
to_chat(src, span_alert("Hold your horses! Its still refreshing!"))
|
||||
return
|
||||
COOLDOWN_START(src, char_directory_cooldown, 10)
|
||||
|
||||
//Check if there's not already a character directory open; open a new one if one is not present
|
||||
if(!GLOB.character_directory)
|
||||
GLOB.character_directory = new
|
||||
GLOB.character_directory.ui_interact(mob)
|
||||
|
||||
// This is a global singleton. Keep in mind that all operations should occur on user, not src.
|
||||
/datum/character_directory
|
||||
|
||||
/datum/character_directory/ui_state(mob/user)
|
||||
return GLOB.always_state
|
||||
|
||||
/datum/character_directory/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ZubbersCharacterDirectory", "Character Directory")
|
||||
ui.open()
|
||||
|
||||
//We want this information to update any time the player updates their preferences, not just when the panel is refreshed
|
||||
/datum/character_directory/ui_data(mob/user)
|
||||
. = ..()
|
||||
var/list/data = .
|
||||
|
||||
//Collect the user's own preferences for the top of the UI
|
||||
if (user?.client?.prefs)
|
||||
data["personalVisibility"] = READ_PREFS(user, toggle/show_in_directory)
|
||||
data["personalAttraction"] = READ_PREFS(user, choiced/attraction)
|
||||
data["personalGender"] = READ_PREFS(user, choiced/display_gender)
|
||||
data["personalErpTag"] = READ_PREFS(user, choiced/erp_status)
|
||||
data["personalVoreTag"] = READ_PREFS(user, choiced/erp_status_v)
|
||||
data["personalHypnoTag"] = READ_PREFS(user, choiced/erp_status_hypno)
|
||||
data["personalNonconTag"] = READ_PREFS(user, choiced/erp_status_nc)
|
||||
data["prefsOnly"] = TRUE
|
||||
|
||||
data["canOrbit"] = isobserver(user)
|
||||
|
||||
return data
|
||||
|
||||
/datum/character_directory/ui_static_data(mob/user)
|
||||
. = ..()
|
||||
var/list/data = .
|
||||
|
||||
var/list/directory_mobs = list()
|
||||
//We want the directory to display only alive players, not observers or people in the lobby
|
||||
for(var/mob/mob in GLOB.alive_player_list)
|
||||
// These are the variables we're trying to display in the directory
|
||||
var/name = null
|
||||
var/species = null
|
||||
var/ooc_notes = ""
|
||||
var/flavor_text = ""
|
||||
var/attraction = null
|
||||
var/gender = null
|
||||
var/erp = null
|
||||
var/vore = null
|
||||
var/hypno = null
|
||||
var/noncon = null
|
||||
var/character_ad = ""
|
||||
var/exploitable = ""
|
||||
var/ref = REF(mob)
|
||||
//Just in case something we get is not a mob
|
||||
if(!mob)
|
||||
continue
|
||||
|
||||
//Different approach for humans and silicons
|
||||
if(ishuman(mob))
|
||||
var/mob/living/carbon/human/human = mob
|
||||
//If someone is obscured without flavor text visible, we don't want them on the Directory.
|
||||
if((human.wear_mask && (human.wear_mask.flags_inv & HIDEFACE) && READ_PREFS(human, toggle/obscurity_examine)) || (human.head && (human.head.flags_inv & HIDEFACE) && READ_PREFS(human, toggle/obscurity_examine)) || (HAS_TRAIT(human, TRAIT_UNKNOWN)))
|
||||
continue
|
||||
//Display custom species, otherwise show base species instead
|
||||
species = (READ_PREFS(human, text/custom_species))
|
||||
if(species == "Unset")
|
||||
species = "[human.dna.species.name]"
|
||||
//Load standard flavor text preference
|
||||
flavor_text = READ_PREFS(human, text/flavor_text)
|
||||
else if(issilicon(mob))
|
||||
var/mob/living/silicon/silicon = mob
|
||||
//If the target is a silicon, we want it to show its brain as its species
|
||||
species = READ_PREFS(silicon, choiced/brain_type)
|
||||
//Load silicon flavor text in place of normal flavor text
|
||||
flavor_text = READ_PREFS(silicon, text/silicon_flavor_text)
|
||||
//Don't show if they are not a human or a silicon
|
||||
else continue
|
||||
//List of all the shown ERP preferences in the Directory. If there is none, return "Unset"
|
||||
attraction = READ_PREFS(mob, choiced/attraction)
|
||||
gender = READ_PREFS(mob, choiced/display_gender)
|
||||
erp = READ_PREFS(mob, choiced/erp_status)
|
||||
vore = READ_PREFS(mob, choiced/erp_status_v)
|
||||
hypno = READ_PREFS(mob, choiced/erp_status_hypno)
|
||||
noncon = READ_PREFS(mob, choiced/erp_status_nc)
|
||||
character_ad = READ_PREFS(mob, text/character_ad)
|
||||
ooc_notes = READ_PREFS(mob, text/ooc_notes)
|
||||
//If the user is an antagonist or Observer, we want them to be able to see exploitables in the Directory.
|
||||
if(user.mind?.has_antag_datum(/datum/antagonist) || isobserver(user))
|
||||
if(exploitable == EXPLOITABLE_DEFAULT_TEXT)
|
||||
exploitable = "Unset"
|
||||
else exploitable = READ_PREFS(mob, text/exploitable)
|
||||
else exploitable = "Obscured"
|
||||
//And finally, we want to get the mob's name, taking into account disguised names.
|
||||
name = mob.real_name ? mob.name : mob.real_name
|
||||
|
||||
directory_mobs.Add(list(list(
|
||||
"name" = name,
|
||||
"species" = species,
|
||||
"ooc_notes" = ooc_notes,
|
||||
"attraction" = attraction,
|
||||
"gender" = gender,
|
||||
"erp" = erp,
|
||||
"vore" = vore,
|
||||
"hypno" = hypno,
|
||||
"noncon" = noncon,
|
||||
"exploitable" = exploitable,
|
||||
"character_ad" = character_ad,
|
||||
"flavor_text" = flavor_text,
|
||||
"ref" = ref
|
||||
)))
|
||||
|
||||
data["directory"] = directory_mobs
|
||||
|
||||
return data
|
||||
|
||||
/datum/character_directory/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
|
||||
if(.)
|
||||
return
|
||||
|
||||
var/mob/user = usr
|
||||
if(!user)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("refresh")
|
||||
// This is primarily to stop malicious users from trying to lag the server by spamming this verb
|
||||
if(!COOLDOWN_FINISHED(user.client, char_directory_cooldown))
|
||||
to_chat(user, "<span class='warning'>Don't spam character directory refresh.</span>")
|
||||
return
|
||||
COOLDOWN_START(user.client, char_directory_cooldown, 10)
|
||||
update_static_data(user, ui)
|
||||
return TRUE
|
||||
if("orbit")
|
||||
var/ref = params["ref"]
|
||||
var/mob/dead/observer/ghost = user
|
||||
var/atom/movable/poi = (locate(ref) in GLOB.mob_list)
|
||||
if (poi == null)
|
||||
return TRUE
|
||||
ghost.ManualFollow(poi)
|
||||
ghost.reset_perspective(null)
|
||||
return TRUE
|
||||
@@ -8045,6 +8045,7 @@
|
||||
#include "modular_zubbers\code\modules\cargo\packs\metalsheets.dm"
|
||||
#include "modular_zubbers\code\modules\cargo\packs\security.dm"
|
||||
#include "modular_zubbers\code\modules\cargo\packs\service.dm"
|
||||
#include "modular_zubbers\code\modules\client\verbs\character_directory.dm"
|
||||
#include "modular_zubbers\code\modules\clothing\gloves\syndicate.dm"
|
||||
#include "modular_zubbers\code\modules\clothing\head\helmet.dm"
|
||||
#include "modular_zubbers\code\modules\clothing\head\jobs.dm"
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { Feature, FeatureChoiced, FeatureDropdownInput, FeatureTextInput } from '../../base';
|
||||
|
||||
export const character_ad: Feature<string> = {
|
||||
name: 'Character Advert',
|
||||
description:
|
||||
'An advertisement for your character. Give information on how to approach for those interested, for either regular and erotic roleplay.',
|
||||
component: FeatureTextInput,
|
||||
};
|
||||
|
||||
export const attraction: FeatureChoiced = {
|
||||
name: 'Character Attraction',
|
||||
description:
|
||||
'What classifies what your character is attracted to. This is displayed in the Directory.',
|
||||
component: FeatureDropdownInput,
|
||||
};
|
||||
|
||||
export const display_gender: FeatureChoiced = {
|
||||
name: 'Character Gender',
|
||||
description:
|
||||
'What classifies as the gender for your character. This is displayed in the Directory.',
|
||||
component: FeatureDropdownInput,
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { CheckboxInput, FeatureToggle } from '../../base';
|
||||
|
||||
export const show_in_directory: FeatureToggle = {
|
||||
name: 'Show in Directory',
|
||||
category: 'ERP',
|
||||
description: 'When enabled, character will be shown in Directory',
|
||||
component: CheckboxInput,
|
||||
};
|
||||
@@ -0,0 +1,272 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Icon, LabeledList, Section, Table } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const erpTagColor = {
|
||||
'Unset': 'label',
|
||||
'Yes - Dom': 'red',
|
||||
'Yes - Sub': 'blue',
|
||||
'Yes - Switch': 'yellow',
|
||||
'Yes': 'yellow',
|
||||
'Check OOC': 'black',
|
||||
'Ask': 'black',
|
||||
'No': 'black',
|
||||
};
|
||||
|
||||
export const ZubbersCharacterDirectory = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
personalVisibility,
|
||||
personalAttraction,
|
||||
personalGender,
|
||||
personalErpTag,
|
||||
personalVoreTag,
|
||||
personalHypnoTag,
|
||||
personalNonconTag,
|
||||
prefsOnly,
|
||||
} = data;
|
||||
|
||||
const [overlay, setOverlay] = useLocalState(context, 'overlay', null);
|
||||
|
||||
const [overwritePrefs, setOverwritePrefs] = useLocalState(
|
||||
context,
|
||||
'overwritePrefs',
|
||||
prefsOnly
|
||||
);
|
||||
|
||||
return (
|
||||
<Window width={640} height={480} resizeable>
|
||||
<Window.Content scrollable>
|
||||
{(overlay && <ViewCharacter />) || (
|
||||
<Fragment>
|
||||
<Section title="Controls">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Visibility">
|
||||
<Button
|
||||
fluid
|
||||
content={personalVisibility ? 'Shown' : 'Not Shown'}
|
||||
onClick={() =>
|
||||
act('setVisible', { overwrite_prefs: overwritePrefs })
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Attraction">
|
||||
<Button fluid content={personalAttraction} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Gender">
|
||||
<Button fluid content={personalGender} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="ERP">
|
||||
<Button
|
||||
fluid
|
||||
content={personalErpTag}
|
||||
onClick={() =>
|
||||
act('setErpTag', { overwrite_prefs: overwritePrefs })
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Vore">
|
||||
<Button
|
||||
fluid
|
||||
content={personalVoreTag}
|
||||
onClick={() =>
|
||||
act('setTag', { overwrite_prefs: overwritePrefs })
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Hypno">
|
||||
<Button
|
||||
fluid
|
||||
content={personalHypnoTag}
|
||||
onClick={() =>
|
||||
act('setHypnoTag', { overwrite_prefs: overwritePrefs })
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Noncon">
|
||||
<Button
|
||||
fluid
|
||||
content={personalNonconTag}
|
||||
onClick={() =>
|
||||
act('setNonconTag', { overwrite_prefs: overwritePrefs })
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<CharacterDirectoryList />
|
||||
</Fragment>
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const ViewCharacter = (props, context) => {
|
||||
const [overlay, setOverlay] = useLocalState(context, 'overlay', null);
|
||||
|
||||
return (
|
||||
<Section
|
||||
title={overlay.name}
|
||||
buttons={
|
||||
<Button
|
||||
icon="arrow-left"
|
||||
content="Back"
|
||||
onClick={() => setOverlay(null)}
|
||||
/>
|
||||
}>
|
||||
<Section level={2} title="Species">
|
||||
<Box>{overlay.species}</Box>
|
||||
</Section>
|
||||
<Section level={2} title="Attraction">
|
||||
<Box>{overlay.attraction}</Box>
|
||||
</Section>
|
||||
<Section level={2} title="Gender">
|
||||
<Box>{overlay.gender}</Box>
|
||||
</Section>
|
||||
<Section level={2} title="ERP">
|
||||
<Box p={1} backgroundColor={erpTagColor[overlay.erp]}>
|
||||
{overlay.erp}
|
||||
</Box>
|
||||
<Section level={2} title="Vore">
|
||||
<Box>{overlay.vore}</Box>
|
||||
</Section>
|
||||
</Section>
|
||||
<Section level={2} title="Hypno">
|
||||
<Box>{overlay.hypno}</Box>
|
||||
</Section>
|
||||
<Section level={2} title="Noncon">
|
||||
<Box>{overlay.noncon}</Box>
|
||||
</Section>
|
||||
<Section level={2} title="Character Ad">
|
||||
<Box style={{ 'word-break': 'break-all' }} preserveWhitespace>
|
||||
{overlay.character_ad || 'Unset.'}
|
||||
</Box>
|
||||
</Section>
|
||||
<Section level={2} title="Exploitable">
|
||||
<Box style={{ 'word-break': 'break-all' }} preserveWhitespace>
|
||||
{overlay.exploitable || 'Unset.'}
|
||||
</Box>
|
||||
</Section>
|
||||
<Section level={2} title="OOC Notes">
|
||||
<Box style={{ 'word-break': 'break-all' }} preserveWhitespace>
|
||||
{overlay.ooc_notes || 'Unset.'}
|
||||
</Box>
|
||||
</Section>
|
||||
<Section level={2} title="Flavor Text">
|
||||
<Box style={{ 'word-break': 'break-all' }} preserveWhitespace>
|
||||
{overlay.flavor_text || 'Unset.'}
|
||||
</Box>
|
||||
</Section>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const CharacterDirectoryList = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const { directory, canOrbit } = data;
|
||||
|
||||
const [sortId, _setSortId] = useLocalState(context, 'sortId', 'name');
|
||||
const [sortOrder, _setSortOrder] = useLocalState(
|
||||
context,
|
||||
'sortOrder',
|
||||
'name'
|
||||
);
|
||||
const [overlay, setOverlay] = useLocalState(context, 'overlay', null);
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Directory"
|
||||
buttons={
|
||||
<Button icon="sync" content="Refresh" onClick={() => act('refresh')} />
|
||||
}>
|
||||
<Table>
|
||||
<Table.Row bold>
|
||||
<SortButton id="name">Name</SortButton>
|
||||
<SortButton id="species">Species</SortButton>
|
||||
<SortButton id="attraction">Attraction</SortButton>
|
||||
<SortButton id="gender">Gender</SortButton>
|
||||
<SortButton id="erp">ERP</SortButton>
|
||||
<SortButton id="vore">Vore</SortButton>
|
||||
<SortButton id="hypno">Hypno</SortButton>
|
||||
<SortButton id="noncon">Noncon</SortButton>
|
||||
<Table.Cell collapsing textAlign="right">
|
||||
Advert
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{directory
|
||||
.sort((a, b) => {
|
||||
const i = sortOrder ? 1 : -1;
|
||||
return a[sortId].localeCompare(b[sortId]) * i;
|
||||
})
|
||||
.map((character, i) => (
|
||||
<Table.Row key={i} backgroundColor={erpTagColor[character.erp]}>
|
||||
<Table.Cell p={1}>
|
||||
{canOrbit ? (
|
||||
<Button
|
||||
color={erpTagColor[character.erp]}
|
||||
icon="ghost"
|
||||
tooltip="Orbit"
|
||||
content={character.name}
|
||||
onClick={() => act('orbit', { ref: character.ref })}
|
||||
/>
|
||||
) : (
|
||||
character.name
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>{character.species}</Table.Cell>
|
||||
<Table.Cell>{character.attraction}</Table.Cell>
|
||||
<Table.Cell>{character.gender}</Table.Cell>
|
||||
<Table.Cell>{character.erp}</Table.Cell>
|
||||
<Table.Cell>{character.vore}</Table.Cell>
|
||||
<Table.Cell>{character.hypno}</Table.Cell>
|
||||
<Table.Cell>{character.noncon}</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="right">
|
||||
<Button
|
||||
onClick={() => setOverlay(character)}
|
||||
color="transparent"
|
||||
icon="sticky-note"
|
||||
mr={1}
|
||||
content="View"
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const SortButton = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const { id, children } = props;
|
||||
|
||||
// Hey, same keys mean same data~
|
||||
const [sortId, setSortId] = useLocalState(context, 'sortId', 'name');
|
||||
const [sortOrder, setSortOrder] = useLocalState(context, 'sortOrder', 'name');
|
||||
|
||||
return (
|
||||
<Table.Cell collapsing>
|
||||
<Button
|
||||
width="100%"
|
||||
color={sortId !== id && 'transparent'}
|
||||
onClick={() => {
|
||||
if (sortId === id) {
|
||||
setSortOrder(!sortOrder);
|
||||
} else {
|
||||
setSortId(id);
|
||||
setSortOrder(true);
|
||||
}
|
||||
}}>
|
||||
{children}
|
||||
{sortId === id && (
|
||||
<Icon name={sortOrder ? 'sort-up' : 'sort-down'} ml="0.25rem;" />
|
||||
)}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user