Polish desktop Pulse and Home views (#764)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
Wes
2026-05-28 08:17:02 -07:00
committed by GitHub
parent 516dc8303f
commit 30654e95f6
29 changed files with 1947 additions and 686 deletions

View File

@@ -1,5 +1,6 @@
import type {
ContactListResponse,
NoteReactionSummary,
PublishNoteResult,
UserNote,
UserNotesResponse,
@@ -12,6 +13,14 @@ type RawUserNote = {
pubkey: string;
created_at: number;
content: string;
tags: string[][];
};
type RawNoteReactionSummary = {
note_id: string;
emoji: string;
count: number;
pubkeys: string[];
};
type RawUserNotesCursor = {
@@ -38,9 +47,32 @@ function fromRawUserNote(note: RawUserNote): UserNote {
pubkey: note.pubkey,
createdAt: note.created_at,
content: note.content,
tags: note.tags,
};
}
export async function getNoteReactions(
noteIds: string[],
): Promise<NoteReactionSummary[]> {
const response = await invokeTauri<RawNoteReactionSummary[]>(
"get_note_reactions",
{ noteIds },
);
return response.map((summary) => ({
noteId: summary.note_id,
emoji: summary.emoji,
count: summary.count,
pubkeys: summary.pubkeys,
}));
}
export async function getNote(noteId: string): Promise<UserNote | null> {
const response = await invokeTauri<RawUserNote | null>("get_note", {
noteId,
});
return response ? fromRawUserNote(response) : null;
}
export async function getUserNotes(
pubkey: string,
options?: {
@@ -137,6 +169,48 @@ export async function setContactList(
};
}
export async function getLikedNotes(
authorPubkey: string,
limit?: number,
): Promise<UserNotesResponse> {
const response = await invokeTauri<RawUserNotesResponse>("get_liked_notes", {
authorPubkey,
limit: limit ?? null,
});
return {
notes: response.notes.map(fromRawUserNote),
nextCursor: response.next_cursor
? {
before: response.next_cursor.before,
beforeId: response.next_cursor.before_id,
}
: null,
};
}
export async function getGlobalNotes(options?: {
limit?: number;
before?: number;
beforeId?: string;
}): Promise<UserNotesResponse> {
const response = await invokeTauri<RawUserNotesResponse>("get_global_notes", {
limit: options?.limit ?? null,
before: options?.before ?? null,
beforeId: options?.beforeId ?? null,
});
return {
notes: response.notes.map(fromRawUserNote),
nextCursor: response.next_cursor
? {
before: response.next_cursor.before,
beforeId: response.next_cursor.before_id,
}
: null,
};
}
export async function getNotesTimeline(
pubkeys: string[],
limitPerUser?: number,

View File

@@ -3,6 +3,14 @@ export type UserNote = {
pubkey: string;
createdAt: number;
content: string;
tags: string[][];
};
export type NoteReactionSummary = {
noteId: string;
emoji: string;
count: number;
pubkeys: string[];
};
export type UserNotesCursor = {

View File

@@ -0,0 +1,97 @@
import * as React from "react";
const THREAD_PANEL_DEFAULT_WIDTH_PX = 380;
const THREAD_PANEL_MIN_WIDTH_PX = 320;
const THREAD_PANEL_MAX_WIDTH_PX = 720;
const THREAD_PANEL_WIDTH_SESSION_KEY = "sprout.desktop.thread-panel-width";
function clampThreadPanelWidth(width: number): number {
return Math.max(
THREAD_PANEL_MIN_WIDTH_PX,
Math.min(THREAD_PANEL_MAX_WIDTH_PX, width),
);
}
function getInitialThreadPanelWidth(): number {
if (typeof window === "undefined") {
return THREAD_PANEL_DEFAULT_WIDTH_PX;
}
try {
const raw = window.sessionStorage.getItem(THREAD_PANEL_WIDTH_SESSION_KEY);
if (!raw) {
return THREAD_PANEL_DEFAULT_WIDTH_PX;
}
const parsed = Number.parseInt(raw, 10);
if (!Number.isFinite(parsed)) {
return THREAD_PANEL_DEFAULT_WIDTH_PX;
}
return clampThreadPanelWidth(parsed);
} catch {
return THREAD_PANEL_DEFAULT_WIDTH_PX;
}
}
export function useThreadPanelWidth() {
const [widthPx, setWidthPx] = React.useState<number>(() =>
getInitialThreadPanelWidth(),
);
React.useEffect(() => {
if (typeof window === "undefined") {
return;
}
try {
window.sessionStorage.setItem(
THREAD_PANEL_WIDTH_SESSION_KEY,
String(widthPx),
);
} catch {
// Ignore storage failures and keep in-memory width for this session.
}
}, [widthPx]);
const onResizeStart = React.useCallback(
(event: React.PointerEvent<HTMLButtonElement>) => {
event.preventDefault();
const startX = event.clientX;
const startWidth = widthPx;
const previousCursor = document.body.style.cursor;
const previousUserSelect = document.body.style.userSelect;
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
const handlePointerMove = (moveEvent: PointerEvent) => {
const deltaX = startX - moveEvent.clientX;
const nextWidth = clampThreadPanelWidth(startWidth + deltaX);
setWidthPx(nextWidth);
};
const handlePointerUp = () => {
document.body.style.cursor = previousCursor;
document.body.style.userSelect = previousUserSelect;
window.removeEventListener("pointermove", handlePointerMove);
};
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp, { once: true });
},
[widthPx],
);
const onResetWidth = React.useCallback(() => {
setWidthPx(THREAD_PANEL_DEFAULT_WIDTH_PX);
}, []);
return {
canReset: widthPx !== THREAD_PANEL_DEFAULT_WIDTH_PX,
onResetWidth,
onResizeStart,
widthPx,
};
}