enhance batching

This commit is contained in:
Shusui MOYATANI
2023-03-20 12:45:07 +09:00
parent cd38f7e976
commit 9a764ba086
16 changed files with 562 additions and 384 deletions

View File

@@ -47,7 +47,7 @@ const Column: Component<ColumnProps> = (props) => {
{/* <span class="column-icon">🏠</span> */}
<span class="column-name">{props.name}</span>
</div>
<ul class="block flex flex-col overflow-y-scroll scroll-smooth">{props.children}</ul>
<ul class="flex flex-col overflow-y-scroll scroll-smooth">{props.children}</ul>
</div>
);
};

View File

@@ -1,5 +1,8 @@
import useConfig, { type Config } from '@/nostr/useConfig';
import { createSignal, For, type JSX } from 'solid-js';
import XMark from 'heroicons/24/outline/x-mark.svg';
import Modal from '@/components/Modal';
type ConfigProps = {
onClose: () => void;
@@ -24,9 +27,11 @@ const RelayConfig = () => {
<For each={config().relayUrls}>
{(relayUrl: string) => {
return (
<li class="flex">
<div class="flex-1">{relayUrl}</div>
<button onClick={() => removeRelay(relayUrl)}>x</button>
<li class="flex items-center">
<div class="flex-1 truncate">{relayUrl}</div>
<button class="h-3 w-3 shrink-0" onClick={() => removeRelay(relayUrl)}>
<XMark />
</button>
</li>
);
}}
@@ -154,33 +159,22 @@ const OtherConfig = () => {
};
const ConfigUI = (props: ConfigProps) => {
let containerRef: HTMLDivElement | undefined;
const handleClickContainer: JSX.EventHandler<HTMLDivElement, MouseEvent> = (ev) => {
if (ev.target === containerRef) {
props.onClose();
}
};
return (
<div
ref={containerRef}
class="absolute top-0 left-0 flex h-screen w-screen cursor-default place-content-center place-items-center bg-black/25"
role="button"
onClick={handleClickContainer}
>
<Modal title="設定" onClose={props.onClose}>
<div class="max-h-[90vh] w-[640px] max-w-[100vw] overflow-y-scroll rounded bg-white p-4 shadow">
<div class="relative">
<h2 class="flex-1 text-center font-bold"></h2>
<button class="absolute top-1 right-0" onClick={() => props.onClose()}>
X
</button>
<div class="flex flex-col gap-1">
<h2 class="flex-1 text-center font-bold"></h2>
<button class="absolute top-1 right-0 h-4 w-4" onClick={() => props.onClose?.()}>
<XMark />
</button>
</div>
</div>
<RelayConfig />
<DateFormatConfig />
<OtherConfig />
</div>
</div>
</Modal>
);
};

28
src/components/Modal.tsx Normal file
View File

@@ -0,0 +1,28 @@
import { type Component, type JSX } from 'solid-js';
export type ModalProps = {
onClose?: () => void;
children?: JSX.Element;
};
const Modal: Component<ModalProps> = (props) => {
let containerRef: HTMLDivElement | undefined;
const handleClickContainer: JSX.EventHandler<HTMLDivElement, MouseEvent> = (ev) => {
if (ev.target === containerRef) {
props.onClose?.();
}
};
return (
<div
ref={containerRef}
class="absolute top-0 left-0 flex h-screen w-screen cursor-default place-content-center place-items-center bg-black/25"
onClick={handleClickContainer}
>
{props.children}
</div>
);
};
export default Modal;

View File

@@ -14,8 +14,6 @@ import uniq from 'lodash/uniq';
import PaperAirplane from 'heroicons/24/solid/paper-airplane.svg';
import Photo from 'heroicons/24/outline/photo.svg';
import Eye from 'heroicons/24/solid/eye.svg';
import EyeSlash from 'heroicons/24/outline/eye-slash.svg';
import XMark from 'heroicons/24/outline/x-mark.svg';
import UserNameDisplay from '@/components/UserDisplayName';
@@ -61,6 +59,11 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
setContentWarning(false);
};
const close = () => {
textAreaRef?.blur();
props.onClose();
};
const { config } = useConfig();
const getPubkey = usePubkey();
const commands = useCommands();
@@ -160,7 +163,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
submit();
} else if (ev.key === 'Escape') {
textAreaRef?.blur();
props.onClose();
close();
}
};
@@ -168,6 +171,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
ev.preventDefault();
const files = [...(ev.currentTarget.files ?? [])];
uploadFilesMutation.mutate(files);
// eslint-disable-next-line no-param-reassign
ev.currentTarget.value = '';
};
@@ -184,7 +188,6 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
const submitDisabled = () =>
text().trim().length === 0 ||
(contentWarning() && contentWarningReason().length === 0) ||
publishTextNoteMutation.isLoading ||
uploadFilesMutation.isLoading;
@@ -192,6 +195,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
onMount(() => {
setTimeout(() => {
textAreaRef?.click();
textAreaRef?.focus();
}, 50);
});
@@ -239,7 +243,7 @@ const NotePostForm: Component<NotePostFormProps> = (props) => {
<div class="flex items-end justify-end gap-1">
<Show when={mode() === 'reply'}>
<div class="flex-1">
<button class="h-5 w-5 text-stone-500" onClick={() => props.onClose()}>
<button class="h-5 w-5 text-stone-500" onClick={() => close()}>
<XMark />
</button>
</div>

View File

@@ -0,0 +1,83 @@
import { Component, createMemo, Show } from 'solid-js';
import { npubEncode } from 'nostr-tools/nip19';
import GlobeAlt from 'heroicons/24/outline/globe-alt.svg';
import XMark from 'heroicons/24/outline/x-mark.svg';
import Modal from '@/components/Modal';
import Copy from '@/components/utils/Copy';
import useProfile from '@/nostr/useProfile';
import useConfig from '@/nostr/useConfig';
export type ProfileDisplayProps = {
pubkey: string;
};
const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
const { config } = useConfig();
const { profile, query } = useProfile(() => ({
relayUrls: config().relayUrls,
pubkey: props.pubkey,
}));
const npub = createMemo(() => npubEncode(props.pubkey));
return (
<Modal>
<div class="max-h-full w-[640px] max-w-full overflow-scroll">
<div class="flex justify-end">
<button class="h-8 w-8 text-stone-700">
<XMark />
</button>
</div>
<div class="flex w-full flex-col overflow-hidden rounded-2xl border bg-white text-stone-700 shadow-lg">
<Show when={query.isFetched} fallback={<>loading</>}>
<div class="h-40 w-full sm:h-52">
<Show when={profile()?.banner} keyed>
{(bannerUrl) => (
<img src={bannerUrl} alt="header" class="h-full w-full object-cover" />
)}
</Show>
</div>
<div class="flex h-[64px] items-center gap-2 px-2">
<div class="mt-[-64px] h-28 w-28 shrink-0 rounded-lg border-2 object-cover">
<Show when={profile()?.picture} keyed>
{(pictureUrl) => <img src={pictureUrl} alt="user icon" class="h-full w-full" />}
</Show>
</div>
<div>
<div class="flex items-center gap-2">
<div class="truncate text-xl font-bold">{profile()?.display_name}</div>
<div class="shrink-0 text-sm">@{profile()?.name}</div>
</div>
<div class="flex gap-1">
<div class="truncate text-xs">{npub()}</div>
<Copy class="h-4 w-4 text-stone-500 hover:text-stone-700" text={npub()} />
</div>
</div>
</div>
<div class="max-h-32 overflow-scroll whitespace-pre-wrap px-4 pt-1 text-sm">
{profile()?.about}
</div>
<ul class="px-4 py-2 text-xs">
<Show when={profile()?.website}>
<li class="flex items-center gap-1">
<span class="inline-block h-4 w-4" area-label="website" title="website">
<GlobeAlt />
</span>
<a href={profile()?.website} target="_blank" rel="noreferrer noopener">
{profile()?.website}
</a>
</li>
</Show>
</ul>
</Show>
<div class="h-16 border" />
</div>
</div>
</Modal>
);
};
export default ProfileDisplay;

View File

@@ -16,14 +16,21 @@ const SideBar: Component = () => {
const [formOpened, setFormOpened] = createSignal(false);
const [configOpened, setConfigOpened] = createSignal(false);
const focusTextArea = () => {
textAreaRef?.focus();
textAreaRef?.click();
};
const openForm = () => setFormOpened(true);
const closeForm = () => setFormOpened(false);
const toggleForm = () => setFormOpened((current) => !current);
useHandleCommand(() => ({
commandType: 'openPostForm',
handler: () => {
openForm();
setTimeout(() => textAreaRef?.focus?.(), 100);
if (textAreaRef != null) {
setTimeout(() => focusTextArea(), 100);
}
},
}));
@@ -32,8 +39,8 @@ const SideBar: Component = () => {
<div class="flex w-14 flex-auto flex-col items-center gap-3 border-r border-rose-200 pt-5">
<div class="flex flex-col items-center gap-3">
<button
class={`h-9 w-9 rounded-full border border-primary bg-primary p-2 text-2xl font-bold text-white`}
onClick={() => setFormOpened((current) => !current)}
class="h-9 w-9 rounded-full border border-primary bg-primary p-2 text-2xl font-bold text-white"
onClick={() => toggleForm()}
>
<PencilSquare />
</button>
@@ -55,14 +62,19 @@ const SideBar: Component = () => {
</button>
</div>
</div>
<Show when={formOpened() || config().keepOpenPostForm}>
<div
classList={{
static: formOpened() || config().keepOpenPostForm,
hidden: !(formOpened() || config().keepOpenPostForm),
}}
>
<NotePostForm
textAreaRef={(el) => {
textAreaRef = el;
}}
onClose={closeForm}
/>
</Show>
</div>
<Show when={configOpened()}>
<Config onClose={() => setConfigOpened(false)} />
</Show>

View File

@@ -0,0 +1,42 @@
import { createSignal, Show, type Component } from 'solid-js';
import ClipboardDocument from 'heroicons/24/outline/clipboard-document.svg';
type CopyProps = {
class: string;
text: string;
};
const Copy: Component<CopyProps> = (props) => {
const [showPopup, setShowPopup] = createSignal(false);
const handleClick = () => {
navigator.clipboard
.writeText(props.text)
.then((e) => {
setShowPopup(true);
setTimeout(() => setShowPopup(false), 1000);
})
.catch((err) => {
console.error('failed to copy', err);
});
};
return (
<div class="relative inline-block">
<button type="button" class={props.class} onClick={handleClick}>
<ClipboardDocument />
</button>
<Show when={showPopup()}>
<div
class="absolute left-[-1rem] top-[-1.5rem] rounded-lg
bg-rose-300 p-1 text-xs font-bold text-white shadow"
>
Copied!
</div>
</Show>
</div>
);
};
export default Copy;