feat: show profile json

This commit is contained in:
Shusui MOYATANI
2024-01-10 20:25:08 +09:00
parent a13fbdd07a
commit ad8f51f511
5 changed files with 119 additions and 26 deletions

View File

@@ -1,4 +1,4 @@
import { Component, createMemo } from 'solid-js';
import { Component, createMemo, Show } from 'solid-js';
import { type Event as NostrEvent } from 'nostr-tools/pure';
@@ -8,6 +8,7 @@ import usePool from '@/nostr/usePool';
export type EventDebugModalProps = {
event: NostrEvent;
extra?: string;
onClose: () => void;
};
@@ -22,17 +23,26 @@ const EventDebugModal: Component<EventDebugModalProps> = (props) => {
return (
<BasicModal onClose={props.onClose}>
<div class="p-2">
<h2 class="text-lg font-bold">JSON</h2>
<div class="p-4">
<h2 class="text-lg font-bold">Event JSON</h2>
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-4 text-xs">
{json()}
</pre>
<div class="flex justify-end">
<Copy class="h-4 w-4 hover:text-primary" text={json()} />
<Copy class="size-4 hover:text-primary" text={json()} />
</div>
</div>
<div class="p-2">
<Show when={props.extra}>
<div class="p-4">
<h2 class="text-lg font-bold">Extra</h2>
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-4 text-xs">
{props.extra}
</pre>
</div>
</Show>
<div class="p-4">
<h2 class="text-lg font-bold">Found in these relays</h2>
<p>If this is empty, it is from the cache.</p>
<pre class="whitespace-pre-wrap break-all rounded-lg border border-border p-2 text-xs">
{seenOn()}
</pre>

View File

@@ -9,6 +9,7 @@ import ExclamationCircle from 'heroicons/24/solid/exclamation-circle.svg';
import TextNoteContentDisplay from '@/components/event/textNote/TextNoteContentDisplay';
import BasicModal from '@/components/modal/BasicModal';
import EventDebugModal from '@/components/modal/EventDebugModal';
import UserList from '@/components/modal/UserList';
import Timeline from '@/components/timeline/Timeline';
import SafeLink from '@/components/utils/SafeLink';
@@ -58,7 +59,7 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
const [updatingContacts, setUpdatingContacts] = createSignal(false);
const [hoverFollowButton, setHoverFollowButton] = createSignal(false);
const [showFollowers, setShowFollowers] = createSignal(false);
const [modal, setModal] = createSignal<'Following' | null>(null);
const [modal, setModal] = createSignal<'Following' | 'EventDebugModal' | null>(null);
const closeModal = () => setModal(null);
const {
@@ -219,22 +220,33 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
},
},
{
content: i18n.t('profile.addUserColumn'),
content: i18n.t('profile.showJSON'),
onSelect: () => {
const columnName = profile()?.name ?? npub();
saveColumn(createPostsColumn({ name: columnName, pubkey: props.pubkey }));
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
props.onClose?.();
setModal('EventDebugModal');
},
},
{
content: i18n.t('profile.addUserHomeColumn'),
onSelect: () => {
const columnName = `${i18n.t('column.home')} / ${profile()?.name ?? npub()}`;
saveColumn(createFollowingColumn({ name: columnName, pubkey: props.pubkey }));
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
props.onClose?.();
},
content: i18n.t('profile.addColumn'),
items: [
{
content: i18n.t('profile.addUserColumn'),
onSelect: () => {
const columnName = profile()?.name ?? npub();
saveColumn(createPostsColumn({ name: columnName, pubkey: props.pubkey }));
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
props.onClose?.();
},
},
{
content: i18n.t('profile.addUserHomeColumn'),
onSelect: () => {
const columnName = `${i18n.t('column.home')} / ${profile()?.name ?? npub()}`;
saveColumn(createFollowingColumn({ name: columnName, pubkey: props.pubkey }));
request({ command: 'moveToLastColumn' }).catch((err) => console.error(err));
props.onClose?.();
},
},
],
},
{
content: !isMuted() ? i18n.t('profile.mute') : i18n.t('profile.unmute'),
@@ -461,6 +473,15 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
<Match when={modal() === 'Following'}>
<UserList data={userFollowingPubkeys()} pubkeyExtractor={(e) => e} onClose={closeModal} />
</Match>
<Match when={modal() === 'EventDebugModal' && profileQuery.data} keyed>
{(event) => (
<EventDebugModal
event={event}
extra={JSON.stringify(profile(), null, 2)}
onClose={closeModal}
/>
)}
</Match>
</Switch>
<ul class="border-t border-border p-1">
<Timeline events={events()} />

View File

@@ -1,23 +1,37 @@
import { createMemo, For, type Component, type JSX } from 'solid-js';
import { createMemo, For, Switch, Match, type Component, type JSX } from 'solid-js';
import ChevronRight from 'heroicons/24/outline/chevron-right.svg';
import usePopup, { type UsePopupProps } from '@/components/utils/usePopup';
export type MenuItem = {
export type SelectableItem = {
content: JSX.Element;
when?: () => boolean;
onSelect?: () => void;
onSelect: () => void;
};
export type SubMenu = Omit<SelectableItem, 'type' | 'onSelect'> & {
content: JSX.Element;
items: (SelectableItem | SubMenu)[];
};
export type MenuItem = SelectableItem | SubMenu;
export type UseContextMenuProps = Omit<UsePopupProps, 'popup'> & {
menu: MenuItem[];
menu: (MenuItem | SubMenu)[];
};
export type MenuItemDisplayProps = {
item: MenuItem;
export type SelectableItemDisplayProps = {
item: SelectableItem;
onClose: () => void;
};
const MenuItemDisplay: Component<MenuItemDisplayProps> = (props) => {
export type SubMenuDisplayProps = {
submenu: SubMenu;
onClose: () => void;
};
const SelectableItemDisplay: Component<SelectableItemDisplayProps> = (props) => {
const handleClick = () => {
props.item?.onSelect?.();
props.onClose();
@@ -32,15 +46,59 @@ const MenuItemDisplay: Component<MenuItemDisplayProps> = (props) => {
);
};
const SubMenuDisplay: Component<SubMenuDisplayProps> = (props) => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
const contextMenuPopup = useContextMenu(() => ({
menu: props.submenu.items,
position: 'right',
}));
const handleClick = () => {
contextMenuPopup.open();
};
return (
<li ref={contextMenuPopup.targetRef} class="border-b border-border hover:bg-bg-tertiary">
<button class="flex w-full items-center py-1 pe-2 ps-4 text-start" onClick={handleClick}>
<span class="flex-1">{props.submenu.content}</span>
<span class="inline-block size-4 shrink-0">
<ChevronRight />
</span>
</button>
{contextMenuPopup.popup()}
</li>
);
};
const ensureSelectableItem = (item: MenuItem): SelectableItem | null => {
if ('onSelect' in item) return item;
return null;
};
const ensureSubMenu = (item: MenuItem): SubMenu | null => {
if ('items' in item) return item;
return null;
};
const useContextMenu = (propsProvider: () => UseContextMenuProps) => {
const props = createMemo(propsProvider);
const popup = usePopup(() => ({
ensureWidth: 300,
...props(),
popup: (
<ul class="min-w-[96px] rounded border border-border bg-bg shadow-md">
<For each={props().menu.filter((e) => e.when == null || e.when())}>
{(item: MenuItem) => <MenuItemDisplay item={item} onClose={() => popup.close()} />}
{(item) => (
<Switch>
<Match when={ensureSelectableItem(item)} keyed>
{(v) => <SelectableItemDisplay item={v} onClose={() => popup.close()} />}
</Match>
<Match when={ensureSubMenu(item)} keyed>
{(v) => <SubMenuDisplay submenu={v} onClose={() => popup.close()} />}
</Match>
</Switch>
)}
</For>
</ul>
),

View File

@@ -59,10 +59,12 @@ export default {
followingCurrently: 'Following',
followsYou: 'follows you',
copyPubkey: 'Copy ID',
showJSON: 'Show JSON',
mute: 'Mute',
unmute: 'Unmute',
followMyself: 'Follow myself',
unfollowMyself: 'Unfollow myself',
addColumn: 'Add column',
addUserColumn: 'Add user column',
addUserHomeColumn: 'Add home column',
confirmUnfollow: 'Do you really want to unfollow?',

View File

@@ -58,10 +58,12 @@ export default {
followingCurrently: 'フォロー中',
followsYou: 'フォローされています',
copyPubkey: 'IDをコピー',
showJSON: 'JSONを確認',
mute: 'ミュート',
unmute: 'ミュート解除',
followMyself: '自分をフォロー',
unfollowMyself: '自分をフォロー解除',
addColumn: 'カラムを追加',
addUserColumn: 'ユーザカラムを追加',
addUserHomeColumn: 'ホームカラムを追加',
confirmUnfollow: '本当にフォロー解除しますか?',