refactor progress

This commit is contained in:
Hampus Kraft
2026-02-17 12:22:36 +00:00
parent cb31608523
commit d5abd1a7e4
8257 changed files with 1190207 additions and 761040 deletions

View File

@@ -0,0 +1,211 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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<GuildFolderIcon, ReactNode> = {
[GuildFolderIcons.FOLDER]: <FolderIcon weight="fill" size={18} />,
[GuildFolderIcons.STAR]: <StarIcon weight="fill" size={18} />,
[GuildFolderIcons.HEART]: <HeartIcon weight="fill" size={18} />,
[GuildFolderIcons.BOOKMARK]: <BookmarkSimpleIcon weight="fill" size={18} />,
[GuildFolderIcons.GAME_CONTROLLER]: <GameControllerIcon weight="fill" size={18} />,
[GuildFolderIcons.SHIELD]: <ShieldIcon weight="fill" size={18} />,
[GuildFolderIcons.MUSIC_NOTE]: <MusicNoteIcon weight="fill" size={18} />,
};
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<HTMLInputElement>();
const [color, setColor] = useState(folder?.color ?? 0);
const [flags, setFlags] = useState(folder?.flags ?? 0);
const [icon, setIcon] = useState<GuildFolderIcon>(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<Array<SelectOption<GuildFolderIcon>>>(
() => [
{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<HTMLInputElement>) => {
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<GuildFolderIcon>, _isSelected: boolean) => (
<span style={{display: 'flex', alignItems: 'center', gap: 8}}>
{FOLDER_ICON_MAP[option.value]}
{option.label}
</span>
),
[],
);
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 (
<Modal.Root size="small" centered>
<Modal.Header title={t`Folder Settings`} />
<Modal.Content>
<Modal.ContentLayout>
<Input
ref={nameRef}
autoFocus={true}
label={t`Folder Name`}
placeholder={autoGeneratedName}
value={name}
onChange={handleNameChange}
autoComplete="off"
maxLength={100}
/>
<ColorPickerField label={t`Folder Color`} value={color} onChange={handleColorChange} />
<Switch
label={t`Show icon when collapsed`}
value={showCollapsedIcon}
onChange={handleShowCollapsedIconChange}
/>
<Select
label={t`Folder Icon`}
value={icon}
options={iconOptions}
onChange={handleIconChange}
isSearchable={false}
renderOption={renderIconOption}
/>
</Modal.ContentLayout>
</Modal.Content>
<Modal.Footer>
<Button onClick={handleCancel} variant="secondary">
{t`Cancel`}
</Button>
<Button onClick={handleSave} submitting={isSaving}>
{t`Save`}
</Button>
</Modal.Footer>
</Modal.Root>
);
});
export function openGuildFolderSettingsModal(folderId: number): void {
ModalActionCreators.push(ModalActionCreators.modal(() => <GuildFolderSettingsModal folderId={folderId} />));
}