/* * 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 (