initial commit
This commit is contained in:
45
fluxer_app/src/components/debug/ChannelDebugModal.tsx
Normal file
45
fluxer_app/src/components/debug/ChannelDebugModal.tsx
Normal 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} />;
|
||||
});
|
||||
138
fluxer_app/src/components/debug/DebugModal.module.css
Normal file
138
fluxer_app/src/components/debug/DebugModal.module.css
Normal 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;
|
||||
}
|
||||
149
fluxer_app/src/components/debug/DebugModal.tsx
Normal file
149
fluxer_app/src/components/debug/DebugModal.tsx
Normal 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>
|
||||
));
|
||||
45
fluxer_app/src/components/debug/GuildDebugModal.tsx
Normal file
45
fluxer_app/src/components/debug/GuildDebugModal.tsx
Normal 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} />;
|
||||
});
|
||||
45
fluxer_app/src/components/debug/GuildMemberDebugModal.tsx
Normal file
45
fluxer_app/src/components/debug/GuildMemberDebugModal.tsx
Normal 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} />;
|
||||
});
|
||||
70
fluxer_app/src/components/debug/MessageDebugModal.tsx
Normal file
70
fluxer_app/src/components/debug/MessageDebugModal.tsx
Normal 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} />;
|
||||
});
|
||||
45
fluxer_app/src/components/debug/UserDebugModal.tsx
Normal file
45
fluxer_app/src/components/debug/UserDebugModal.tsx
Normal 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} />;
|
||||
});
|
||||
Reference in New Issue
Block a user