minor fixes

- switch from indices to IDs
- make indexes for the channel view unique
- Fix guilds shuffling around in the channel view by sorting them alphabetically
- Fix possibility to add elements twice from search modal by allowing to hide already added items from the search modal
- Remove invalid channels instead of just not rendering them.
This commit is contained in:
Elvyra 2025-01-23 18:06:16 +01:00
parent de2f1d5159
commit 9cee56e379
2 changed files with 56 additions and 35 deletions

View file

@ -68,7 +68,7 @@ export function SettingArrayComponent({
id id
}: ISettingElementProps<PluginOptionArray>) { }: ISettingElementProps<PluginOptionArray>) {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [items, setItems] = useState<string[]>([]); const [items, setItems] = useState<string[]>(pluginSettings[id] || []);
const [text, setText] = useState<string>(""); const [text, setText] = useState<string>("");
useEffect(() => { useEffect(() => {
@ -87,16 +87,12 @@ export function SettingArrayComponent({
useEffect(() => { useEffect(() => {
pluginSettings[id] = items; pluginSettings[id] = items;
onChange(items); onChange(items);
}, [items, pluginSettings, id]); }, [items]);
useEffect(() => { useEffect(() => {
onError(error !== null); onError(error !== null);
}, [error]); }, [error]);
if (items.length === 0 && pluginSettings[id].length !== 0) {
setItems(pluginSettings[id]);
}
function openSearchModal(val?: string) { function openSearchModal(val?: string) {
return openModal(modalProps => ( return openModal(modalProps => (
<SearchModal <SearchModal
@ -105,16 +101,17 @@ export function SettingArrayComponent({
subText={"All selected items will be added to " + wordsToTitle(wordsFromCamel(id))} subText={"All selected items will be added to " + wordsToTitle(wordsFromCamel(id))}
searchType={option.type === OptionType.USERS ? "USERS" : option.type === OptionType.CHANNELS ? "CHANNELS" : "GUILDS"} searchType={option.type === OptionType.USERS ? "USERS" : option.type === OptionType.CHANNELS ? "CHANNELS" : "GUILDS"}
onSubmit={values => setItems([...items, ...values.map(v => v.id)])} onSubmit={values => setItems([...items, ...values.map(v => v.id)])}
excludeIds={items}
/> />
)); ));
} }
const removeButton = (index: number) => { const removeButton = (id: string) => {
return ( return (
<Button <Button
id={cl("remove-button")} id={cl("remove-button")}
size={Button.Sizes.MIN} size={Button.Sizes.MIN}
onClick={() => removeItem(index)} onClick={() => removeItem(id)}
style={ style={
{ background: "none", color: "red" } { background: "none", color: "red" }
} }
@ -134,17 +131,16 @@ export function SettingArrayComponent({
}; };
const removeItem = (index: number) => { const removeItem = (itemId: string) => {
if (items.length === 1) { if (items.length === 1) {
setItems([]); setItems([]);
pluginSettings[id] = [];
return; return;
} }
setItems(items.filter((_, i) => i !== index)); setItems(items.filter(item => item !== itemId));
}; };
function renderGuildView() { function renderGuildView() {
return items.map(item => GuildStore.getGuild(item)) return items.map(item => GuildStore.getGuild(item) || item)
.map((guild, index) => ( .map((guild, index) => (
<Flex <Flex
flexDirection="row" flexDirection="row"
@ -154,15 +150,15 @@ export function SettingArrayComponent({
marginBottom: "8px" marginBottom: "8px"
}} }}
> >
{guild ? ( {typeof guild !== "string" ? (
<div className={cl("name")} style={{ color: "var(--text-normal)" }}> <div className={cl("name")} style={{ color: "var(--text-normal)" }}>
<span style={{ display: "inline-flex", alignItems: "center" }}> <span style={{ display: "inline-flex", alignItems: "center" }}>
{guildIcon(guild)} {guildIcon(guild)}
<Text variant="text-sm/semibold" style={{ marginLeft: "4px" }}>{guild.name}</Text> <Text variant="text-sm/semibold" style={{ marginLeft: "4px" }}>{guild.name}</Text>
</span> </span>
</div> </div>
) : <Text variant="text-sm/semibold">{"Unknown Guild"}</Text>} ) : <Text variant="text-sm/semibold">{`Unknown Guild (${guild})`}</Text>}
{removeButton(index)} {removeButton(typeof guild !== "string" ? guild.id : guild)}
</Flex> </Flex>
)); ));
} }
@ -206,9 +202,14 @@ export function SettingArrayComponent({
const channels: Record<string, Channel[]> = {}; const channels: Record<string, Channel[]> = {};
const dmChannels: Channel[] = []; const dmChannels: Channel[] = [];
const elements: React.JSX.Element[] = []; const elements: React.JSX.Element[] = [];
// to not remove items while iterating
const invalidChannels: string[] = [];
for (const item of items) { for (const item of items) {
const channel = ChannelStore.getChannel(item); const channel = ChannelStore.getChannel(item);
if (!channel) { if (!channel) {
invalidChannels.push(item);
continue; continue;
} }
if (channel.isDM() || channel.isGroupDM()) { if (channel.isDM() || channel.isGroupDM()) {
@ -221,6 +222,10 @@ export function SettingArrayComponent({
channels[channel.guild_id].push(channel); channels[channel.guild_id].push(channel);
} }
for (const channel of invalidChannels) {
removeItem(channel);
}
const userMention = (channel: Channel) => { const userMention = (channel: Channel) => {
return <UserMentionComponent return <UserMentionComponent
userId={channel.recipients[0]} userId={channel.recipients[0]}
@ -239,33 +244,40 @@ export function SettingArrayComponent({
</span>; </span>;
}; };
let idx = -1;
if (dmChannels.length > 0) { if (dmChannels.length > 0) {
elements.push( elements.push(
<details> <details>
<summary style={{ color: "var(--text-normal)", marginBottom: "8px" }}>DMs</summary> <summary style={{ color: "var(--text-normal)", marginBottom: "8px" }}>DMs</summary>
<div style={{ paddingLeft: "16px" }}> <div style={{ paddingLeft: "16px" }}>
{dmChannels.map((channel, index) => ( {dmChannels.map(channel => {
<Flex idx += 1;
return <Flex
flexDirection="row" flexDirection="row"
key={index} key={idx}
style={{ style={{
gap: "1px", gap: "1px",
marginBottom: "8px", marginBottom: "8px",
}} }}
> >
{channel.recipients.length === 1 ? userMention(channel) : gdmComponent(channel)} {channel.recipients.length === 1 ? userMention(channel) : gdmComponent(channel)}
{removeButton(index)} {removeButton(channel.id)}
</Flex> </Flex>;
))} })}
</div> </div>
</details > </details >
); );
} }
const guilds: { name: string; guild: React.JSX.Element }[] = [];
Object.keys(channels).forEach(guildId => { Object.keys(channels).forEach(guildId => {
const guild = GuildStore.getGuild(guildId); const guild = GuildStore.getGuild(guildId);
elements.push( guilds.push(
{ name: guild?.name ?? `Unknown Guild (${guildId})`, guild: (
<details> <details>
{!guild ? <summary style={{ color: "var(--text-normal)", marginBottom: "8px" }}>Unknown Guild</summary> : ( {!guild ? <summary style={{ color: "var(--text-normal)", marginBottom: "8px" }}>{`Unknown Guild (${guildId})`}</summary> : (
<summary style={{ color: "var(--text-normal)", marginBottom: "8px" }}> <summary style={{ color: "var(--text-normal)", marginBottom: "8px" }}>
<span style={{ display: "inline-flex", alignItems: "center" }}> <span style={{ display: "inline-flex", alignItems: "center" }}>
{guildIcon(guild)} {guildIcon(guild)}
@ -274,26 +286,33 @@ export function SettingArrayComponent({
</summary> </summary>
)} )}
<div style={{ paddingLeft: "16px", color: "var(--text-normal)" }}> <div style={{ paddingLeft: "16px", color: "var(--text-normal)" }}>
{channels[guildId].map((channel, index) => ( {channels[guildId].map(channel => {
<Flex idx += 1;
return <Flex
flexDirection="row" flexDirection="row"
key={index} key={idx}
style={{ style={{
gap: "1px", gap: "1px",
marginBottom: "8px" marginBottom: "8px"
}} }}>
>
<span style={{ display: "inline-flex", alignItems: "center" }}> <span style={{ display: "inline-flex", alignItems: "center" }}>
{getChannelSymbol(channel.type)} {getChannelSymbol(channel.type)}
<Text variant="text-sm/semibold" style={{ marginLeft: "4px" }}>{channel.name}</Text> <Text variant="text-sm/semibold" style={{ marginLeft: "4px" }}>{channel.name}</Text>
{removeButton(index)} {removeButton(channel.id)}
</span> </span>
</Flex> </Flex>;
))} })}
</div> </div>
</details> </details>) }
); );
}); });
guilds.sort((a, b) => a.name.localeCompare(b.name));
for (const guild of guilds) {
elements.push(guild.guild);
}
return elements; return elements;
} }
@ -334,7 +353,7 @@ export function SettingArrayComponent({
) : ( ) : (
<span style={{ color: "var(--text-normal)" }}>{item}</span> <span style={{ color: "var(--text-normal)" }}>{item}</span>
)} )}
{removeButton(index)} {removeButton(item)}
</Flex> </Flex>
)) : option.type === OptionType.CHANNELS ? )) : option.type === OptionType.CHANNELS ?
renderChannelView() : renderGuildView() renderChannelView() : renderGuildView()

View file

@ -147,14 +147,16 @@ function searchTypeToText(type: string | string[]) {
* @param {string} [props.input] - The initial input value for the search bar. * @param {string} [props.input] - The initial input value for the search bar.
* @param {("USERS" | "CHANNELS" | "GUILDS")[] | "USERS" | "CHANNELS" | "GUILDS" | "ALL"} [props.searchType="ALL"] - The type of items to search for. * @param {("USERS" | "CHANNELS" | "GUILDS")[] | "USERS" | "CHANNELS" | "GUILDS" | "ALL"} [props.searchType="ALL"] - The type of items to search for.
* @param {string} [props.subText] - Additional text to display below the heading. * @param {string} [props.subText] - Additional text to display below the heading.
* @param {string[]} [props.excludeIds] - An array of IDs to exclude from the search results.
* @returns The rendered SearchModal component. * @returns The rendered SearchModal component.
*/ */
export default function SearchModal({ modalProps, onSubmit, input, searchType = "ALL", subText }: { export default function SearchModal({ modalProps, onSubmit, input, searchType = "ALL", subText, excludeIds }: {
modalProps: ModalProps; modalProps: ModalProps;
onSubmit(selected: DestinationItem[]): void; onSubmit(selected: DestinationItem[]): void;
input?: string; input?: string;
searchType?: ("USERS" | "CHANNELS" | "GUILDS")[] | "USERS" | "CHANNELS" | "GUILDS" | "ALL"; searchType?: ("USERS" | "CHANNELS" | "GUILDS")[] | "USERS" | "CHANNELS" | "GUILDS" | "ALL";
subText?: string subText?: string
excludeIds?: string[],
}) { }) {
const UserIcon = React.memo(function ({ const UserIcon = React.memo(function ({
user, user,
@ -384,7 +386,7 @@ export default function SearchModal({ modalProps, onSubmit, input, searchType =
const filterItems = (items: any[]) => { const filterItems = (items: any[]) => {
return items.filter( return items.filter(
item => item != null && resultTypes.includes(item.type) item => item != null && resultTypes.includes(item.type) && (excludeIds != null && !excludeIds.includes(item.record.id))
); );
}; };