initial commit

This commit is contained in:
Hampus Kraft
2026-01-01 20:42:59 +00:00
commit 2f557eda8c
9029 changed files with 1490197 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
/*
* 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 {useLingui} from '@lingui/react/macro';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {useMemo} from 'react';
import type {ChannelRecord} from '~/records/ChannelRecord';
import {DebugModal, type DebugTab} from './DebugModal';
interface ChannelDebugModalProps {
title: string;
channel: ChannelRecord;
}
export const ChannelDebugModal: React.FC<ChannelDebugModalProps> = observer(({title, channel}) => {
const {t} = useLingui();
const recordJsonData = useMemo(() => channel.toJSON(), [channel]);
const tabs: Array<DebugTab> = [
{
id: 'record',
label: t`Channel Record`,
data: recordJsonData,
},
];
return <DebugModal title={title} tabs={tabs} />;
});

View File

@@ -0,0 +1,138 @@
/*
* 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/>.
*/
.content {
min-height: 0;
padding: 0;
display: flex;
flex-direction: column;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.tabsSection {
padding: 0 1.5rem;
border-bottom: 1px solid var(--background-modifier-accent);
}
.tabs {
padding-bottom: 0;
}
.scrollArea {
flex: 1;
min-height: 0;
overflow: auto;
padding: 1.25rem 1.5rem 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.summary {
background: var(--background-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 1rem;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
}
.summaryTitle {
margin: 0 0 0.5rem;
font-size: 0.875rem;
font-weight: 600;
color: var(--text-secondary);
letter-spacing: 0.02em;
text-transform: uppercase;
}
.summaryBody {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.summaryItem {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 0.75rem;
font-size: 0.875rem;
line-height: 1.25rem;
}
.summaryLabel {
font-weight: 500;
color: var(--text-secondary);
}
.summaryValue {
font-family: var(--font-mono);
color: var(--text-primary);
text-align: right;
word-break: break-word;
}
.codeSection {
flex: 1;
min-height: 0;
display: flex;
}
.codeSurface {
width: 100%;
min-height: 18rem;
display: flex;
flex: 1;
}
.codeSurface :global(pre) {
width: 100%;
max-width: 100%;
min-height: inherit;
height: 100%;
}
.codeSurface :global(pre > div) {
min-height: inherit;
height: 100%;
}
.codeSurface div[class*='codeContainer'] {
max-width: unset !important;
width: 100% !important;
}
.codeSurface pre[class*='hljs'] {
max-width: unset !important;
width: 100% !important;
}
.codeSurface pre {
max-width: unset !important;
width: 100% !important;
}
.codeSurface [class*='codeContainer'] {
max-width: unset !important;
width: 100% !important;
}

View File

@@ -0,0 +1,149 @@
/*
* 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 {Trans, useLingui} from '@lingui/react/macro';
import {clsx} from 'clsx';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {useMemo, useState} from 'react';
import * as Modal from '~/components/modals/Modal';
import {type TabItem, Tabs} from '~/components/uikit/Tabs/Tabs';
import {NodeType} from '~/lib/markdown/parser/types/enums';
import type {CodeBlockNode} from '~/lib/markdown/parser/types/nodes';
import {MarkdownContext} from '~/lib/markdown/renderers';
import {CodeBlockRenderer} from '~/lib/markdown/renderers/common/code-elements';
import markupStyles from '~/styles/Markup.module.css';
import styles from './DebugModal.module.css';
export interface DebugTab {
id: string;
label: string;
data: unknown;
summary?: React.ReactNode;
language?: string;
serialize?: (value: unknown) => string;
}
interface DebugModalProps {
title: string;
tabs: Array<DebugTab>;
defaultTab?: string;
}
export const DebugModal: React.FC<DebugModalProps> = observer(({title, tabs, defaultTab}) => {
const {i18n} = useLingui();
const [activeTabId, setActiveTabId] = useState<string>(defaultTab ?? tabs[0]?.id ?? '');
const activeTab = useMemo(() => tabs.find((tab) => tab.id === activeTabId) ?? tabs[0], [tabs, activeTabId]);
const tabItems = useMemo<Array<TabItem<string>>>(() => tabs.map(({id, label}) => ({key: id, label})), [tabs]);
const codeContent = useMemo(() => {
if (!activeTab) return 'No data available';
if (activeTab.serialize) {
try {
return activeTab.serialize(activeTab.data);
} catch (error) {
console.error('Failed to serialize debug tab data via custom serializer:', error);
return 'Unable to serialize data';
}
}
if (activeTab.data == null) {
return 'No data available';
}
if (typeof activeTab.data === 'string') {
return activeTab.data;
}
try {
return JSON.stringify(activeTab.data, null, 2);
} catch (error) {
console.error('Failed to stringify debug tab data:', error);
return 'Unable to serialize data';
}
}, [activeTab]);
const codeNode = useMemo<CodeBlockNode>(
() => ({
type: NodeType.CodeBlock,
content: codeContent,
language: activeTab?.language ?? 'json',
}),
[codeContent, activeTab?.language],
);
return (
<Modal.Root size="large">
<Modal.Header title={title} />
<Modal.Content padding="none" className={styles.content}>
<div className={styles.container}>
{tabs.length > 1 && (
<div className={styles.tabsSection}>
<Tabs
tabs={tabItems}
activeTab={activeTabId}
onTabChange={(tabKey) => setActiveTabId(tabKey)}
className={styles.tabs}
/>
</div>
)}
<div className={styles.scrollArea}>
{activeTab?.summary && (
<section className={styles.summary}>
<h3 className={styles.summaryTitle}>
<Trans>Summary</Trans>
</h3>
<div className={styles.summaryBody}>{activeTab.summary}</div>
</section>
)}
<div className={styles.codeSection}>
<div className={clsx(markupStyles.markup, styles.codeSurface)}>
<CodeBlockRenderer
id={`${activeTabId}-debug`}
node={codeNode}
renderChildren={() => null}
options={{
context: MarkdownContext.STANDARD_WITHOUT_JUMBO,
shouldJumboEmojis: false,
i18n,
}}
/>
</div>
</div>
</div>
</div>
</Modal.Content>
</Modal.Root>
);
});
interface SummaryItemProps {
label: string;
value: React.ReactNode;
}
export const SummaryItem: React.FC<SummaryItemProps> = observer(({label, value}) => (
<div className={styles.summaryItem}>
<span className={styles.summaryLabel}>{label}</span>
<span className={styles.summaryValue}>{value}</span>
</div>
));

View File

@@ -0,0 +1,45 @@
/*
* 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 {useLingui} from '@lingui/react/macro';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {useMemo} from 'react';
import type {GuildRecord} from '~/records/GuildRecord';
import {DebugModal, type DebugTab} from './DebugModal';
interface GuildDebugModalProps {
title: string;
guild: GuildRecord;
}
export const GuildDebugModal: React.FC<GuildDebugModalProps> = observer(({title, guild}) => {
const {t} = useLingui();
const recordJsonData = useMemo(() => guild.toJSON(), [guild]);
const tabs: Array<DebugTab> = [
{
id: 'record',
label: t`Guild Record`,
data: recordJsonData,
},
];
return <DebugModal title={title} tabs={tabs} />;
});

View File

@@ -0,0 +1,45 @@
/*
* 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 {useLingui} from '@lingui/react/macro';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {useMemo} from 'react';
import type {GuildMemberRecord} from '~/records/GuildMemberRecord';
import {DebugModal, type DebugTab} from './DebugModal';
interface GuildMemberDebugModalProps {
title: string;
member: GuildMemberRecord;
}
export const GuildMemberDebugModal: React.FC<GuildMemberDebugModalProps> = observer(({title, member}) => {
const {t} = useLingui();
const recordJsonData = useMemo(() => member.toJSON(), [member]);
const tabs: Array<DebugTab> = [
{
id: 'record',
label: t`Guild Member Record`,
data: recordJsonData,
},
];
return <DebugModal title={title} tabs={tabs} />;
});

View File

@@ -0,0 +1,70 @@
/*
* 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 {useLingui} from '@lingui/react/macro';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {useMemo} from 'react';
import {MarkdownContext, parse} from '~/lib/markdown/renderers';
import type {MessageRecord} from '~/records/MessageRecord';
import {DebugModal, type DebugTab, SummaryItem} from './DebugModal';
interface MessageDebugModalProps {
title: string;
message: MessageRecord;
}
export const MessageDebugModal: React.FC<MessageDebugModalProps> = observer(({title, message}) => {
const {t} = useLingui();
const recordJsonData = useMemo(() => message.toJSON(), [message]);
const astData = useMemo(() => {
if (!message.content) return null;
const startTime = performance.now();
const nodes = parse({
content: message.content,
context: MarkdownContext.STANDARD_WITH_JUMBO,
});
const endTime = performance.now();
return {
nodes,
parseTime: endTime - startTime,
};
}, [message.content]);
const tabs: Array<DebugTab> = [
{
id: 'record',
label: t`Message Record`,
data: recordJsonData,
},
{
id: 'ast',
label: t`Message AST`,
data: astData?.nodes ?? null,
summary: astData ? (
<SummaryItem label={t`Total Parsing Time:`} value={`${astData.parseTime.toFixed(2)} ms`} />
) : null,
},
];
return <DebugModal title={title} tabs={tabs} />;
});

View File

@@ -0,0 +1,45 @@
/*
* 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 {useLingui} from '@lingui/react/macro';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {useMemo} from 'react';
import type {UserRecord} from '~/records/UserRecord';
import {DebugModal, type DebugTab} from './DebugModal';
interface UserDebugModalProps {
title: string;
user: UserRecord;
}
export const UserDebugModal: React.FC<UserDebugModalProps> = observer(({title, user}) => {
const {t} = useLingui();
const recordJsonData = useMemo(() => user.toJSON(), [user]);
const tabs: Array<DebugTab> = [
{
id: 'record',
label: t`User Record`,
data: recordJsonData,
},
];
return <DebugModal title={title} tabs={tabs} />;
});