/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
import * as ModalActionCreators from '@app/actions/ModalActionCreators';
import * as UserSettingsActionCreators from '@app/actions/UserSettingsActionCreators';
import {ColorPickerField} from '@app/components/form/ColorPickerField';
import {Input} from '@app/components/form/Input';
import {Select, type SelectOption} from '@app/components/form/Select';
import {Switch} from '@app/components/form/Switch';
import * as Modal from '@app/components/modals/Modal';
import {Button} from '@app/components/uikit/button/Button';
import {useCursorAtEnd} from '@app/hooks/useCursorAtEnd';
import GuildStore from '@app/stores/GuildStore';
import UserSettingsStore from '@app/stores/UserSettingsStore';
import {
DEFAULT_GUILD_FOLDER_ICON,
GuildFolderFlags,
type GuildFolderIcon,
GuildFolderIcons,
} from '@fluxer/constants/src/UserConstants';
import {useLingui} from '@lingui/react/macro';
import {
BookmarkSimpleIcon,
FolderIcon,
GameControllerIcon,
HeartIcon,
MusicNoteIcon,
ShieldIcon,
StarIcon,
} from '@phosphor-icons/react';
import {observer} from 'mobx-react-lite';
import type {ReactNode} from 'react';
import {useCallback, useMemo, useState} from 'react';
const FOLDER_ICON_MAP: Record = {
[GuildFolderIcons.FOLDER]: ,
[GuildFolderIcons.STAR]: ,
[GuildFolderIcons.HEART]: ,
[GuildFolderIcons.BOOKMARK]: ,
[GuildFolderIcons.GAME_CONTROLLER]: ,
[GuildFolderIcons.SHIELD]: ,
[GuildFolderIcons.MUSIC_NOTE]: ,
};
interface GuildFolderSettingsModalProps {
folderId: number;
}
export const GuildFolderSettingsModal = observer(({folderId}: GuildFolderSettingsModalProps) => {
const {t} = useLingui();
const folder = useMemo(() => {
return UserSettingsStore.guildFolders.find((f) => f.id === folderId);
}, [folderId]);
const autoGeneratedName = useMemo(() => {
if (!folder) return '';
const guildNames = folder.guildIds
.slice(0, 3)
.map((guildId) => GuildStore.getGuild(guildId)?.name)
.filter((name): name is string => name != null);
return guildNames.join(', ');
}, [folder]);
const [name, setName] = useState(folder?.name ?? '');
const nameRef = useCursorAtEnd();
const [color, setColor] = useState(folder?.color ?? 0);
const [flags, setFlags] = useState(folder?.flags ?? 0);
const [icon, setIcon] = useState(folder?.icon ?? DEFAULT_GUILD_FOLDER_ICON);
const [isSaving, setIsSaving] = useState(false);
const showCollapsedIcon =
(flags & GuildFolderFlags.SHOW_ICON_WHEN_COLLAPSED) === GuildFolderFlags.SHOW_ICON_WHEN_COLLAPSED;
const iconOptions = useMemo>>(
() => [
{value: GuildFolderIcons.FOLDER, label: t`Folder`},
{value: GuildFolderIcons.STAR, label: t`Star`},
{value: GuildFolderIcons.HEART, label: t`Heart`},
{value: GuildFolderIcons.BOOKMARK, label: t`Bookmark`},
{value: GuildFolderIcons.GAME_CONTROLLER, label: t`Game controller`},
{value: GuildFolderIcons.SHIELD, label: t`Shield`},
{value: GuildFolderIcons.MUSIC_NOTE, label: t`Music note`},
],
[t],
);
const handleNameChange = useCallback((event: React.ChangeEvent) => {
setName(event.target.value);
}, []);
const handleColorChange = useCallback((newColor: number) => {
setColor(newColor);
}, []);
const handleShowCollapsedIconChange = useCallback((value: boolean) => {
setFlags((currentFlags) => {
if (value) {
return currentFlags | GuildFolderFlags.SHOW_ICON_WHEN_COLLAPSED;
}
return currentFlags & ~GuildFolderFlags.SHOW_ICON_WHEN_COLLAPSED;
});
}, []);
const handleIconChange = useCallback((value: GuildFolderIcon) => {
setIcon(value);
}, []);
const renderIconOption = useCallback(
(option: SelectOption, _isSelected: boolean) => (
{FOLDER_ICON_MAP[option.value]}
{option.label}
),
[],
);
const handleCancel = useCallback(() => {
ModalActionCreators.pop();
}, []);
const handleSave = useCallback(async () => {
if (!folder) return;
setIsSaving(true);
try {
const updatedFolders = UserSettingsStore.guildFolders.map((f) => {
if (f.id === folderId) {
return {
...f,
name: name.trim() || null,
color: color || null,
flags,
icon,
};
}
return f;
});
await UserSettingsActionCreators.update({guildFolders: updatedFolders});
ModalActionCreators.pop();
} finally {
setIsSaving(false);
}
}, [folder, folderId, name, color, flags, icon]);
if (!folder) {
return null;
}
return (
);
});
export function openGuildFolderSettingsModal(folderId: number): void {
ModalActionCreators.push(ModalActionCreators.modal(() => ));
}