Skip to content

Commit

Permalink
add quick reaction option
Browse files Browse the repository at this point in the history
  • Loading branch information
hzrd149 committed Oct 17, 2023
1 parent 44985ae commit 9569281
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-carrots-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nostrudel": minor
---

Add option to customize quick reactions
5 changes: 1 addition & 4 deletions src/components/note/components/reaction-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@ import {
PopoverContent,
PopoverTrigger,
} from "@chakra-ui/react";
import dayjs from "dayjs";
import { Kind } from "nostr-tools";

import { useCurrentAccount } from "../../../hooks/use-current-account";
import useEventReactions from "../../../hooks/use-event-reactions";
import { useSigningContext } from "../../../providers/signing-provider";
import clientRelaysService from "../../../services/client-relays";
import eventReactionsService from "../../../services/event-reactions";
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
import { NostrEvent } from "../../../types/nostr-event";
import NostrPublishAction from "../../../classes/nostr-publish-action";
import { AddReactionIcon } from "../../icons";
import ReactionPicker from "../../reaction-picker";
Expand Down
33 changes: 12 additions & 21 deletions src/components/reaction-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Button, Divider, Flex, IconButton, Image, Input, Text } from "@chakra-ui/react";
import { Divider, Flex, IconButton, Image, Text } from "@chakra-ui/react";

import { DislikeIcon, LikeIcon } from "./icons";
import { useCurrentAccount } from "../hooks/use-current-account";
import useReplaceableEvent from "../hooks/use-replaceable-event";
import { getEmojisFromPack, getPackCordsFromFavorites, getPackName } from "../helpers/nostr/emoji-packs";
import useFavoriteEmojiPacks from "../hooks/use-favorite-emoji-packs";
import useAppSettings from "../hooks/use-app-settings";

export type ReactionPickerProps = {
onSelect: (emoji: string, url?: string) => void;
Expand Down Expand Up @@ -40,6 +41,7 @@ function EmojiPack({ cord, onSelect }: { cord: string; onSelect: ReactionPickerP
export default function ReactionPicker({ onSelect }: ReactionPickerProps) {
const account = useCurrentAccount();
const favoritePacks = useFavoriteEmojiPacks(account?.pubkey);
const { quickReactions } = useAppSettings();

return (
<Flex direction="column" gap="2">
Expand All @@ -52,26 +54,15 @@ export default function ReactionPicker({ onSelect }: ReactionPickerProps) {
size="sm"
onClick={() => onSelect("-")}
/>
<IconButton
icon={<span>🤙</span>}
aria-label="Shaka"
variant="outline"
size="sm"
onClick={() => onSelect("🤙")}
/>
<IconButton
icon={<span>🫂</span>}
aria-label="Hug"
variant="outline"
size="sm"
onClick={() => onSelect("🫂")}
/>
<Flex>
<Input placeholder="🔥" display="inline" size="sm" minW="2rem" w="5rem" />
<Button variant="solid" colorScheme="primary" size="sm">
Add
</Button>
</Flex>
{quickReactions.map((emoji) => (
<IconButton
icon={<span>{emoji}</span>}
aria-label="Shaka"
variant="outline"
size="sm"
onClick={() => onSelect(emoji)}
/>
))}
</Flex>
{favoritePacks &&
getPackCordsFromFavorites(favoritePacks).map((cord) => (
Expand Down
1 change: 1 addition & 0 deletions src/providers/emoji-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PropsWithChildren, createContext, useContext } from "react";
import { lib } from "emojilib";

import useReplaceableEvents from "../hooks/use-replaceable-events";
import { useCurrentAccount } from "../hooks/use-current-account";
import { isEmojiTag } from "../types/nostr-event";
Expand Down
23 changes: 16 additions & 7 deletions src/services/settings/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export type AppSettingsV2 = Omit<AppSettingsV1, "version"> & {
version: 2;
theme: string;
};
export type AppSettingsV3 = Omit<AppSettingsV2, "version"> & {
version: 3;
quickReactions: string[];
};

export function isV0(settings: { version: number }): settings is AppSettingsV0 {
return settings.version === undefined || settings.version === 0;
Expand All @@ -41,11 +45,14 @@ export function isV1(settings: { version: number }): settings is AppSettingsV1 {
export function isV2(settings: { version: number }): settings is AppSettingsV2 {
return settings.version === 2;
}
export function isV3(settings: { version: number }): settings is AppSettingsV3 {
return settings.version === 3;
}

export type AppSettings = AppSettingsV2;
export type AppSettings = AppSettingsV3;

export const defaultSettings: AppSettings = {
version: 2,
version: 3,
theme: "default",
colorMode: "system",
maxPageWidth: "none",
Expand All @@ -55,6 +62,8 @@ export const defaultSettings: AppSettings = {
showReactions: true,
showSignatureVerification: false,

quickReactions: ["🤙", "❤️", "🤣", "😍", "🔥"],

autoPayWithWebLN: true,
customZapAmounts: "50,200,500,1000,2000,5000",

Expand All @@ -67,11 +76,11 @@ export const defaultSettings: AppSettings = {
youtubeRedirect: undefined,
};

export function upgradeSettings(settings: { version: number }): AppSettings | null {
if (isV0(settings)) return { ...settings, version: 2, maxPageWidth: "none", theme: "default" };
if (isV1(settings)) return { ...settings, version: 2, theme: "default" };
if (isV2(settings)) return settings;
return null;
export function upgradeSettings(settings: { version: number }): AppSettings {
if (isV0(settings)) return { ...defaultSettings, ...settings, version: 3 };
if (isV1(settings)) return { ...defaultSettings, ...settings, version: 3 };
if (isV2(settings)) return { ...defaultSettings, ...settings, version: 3 };
return settings as AppSettings;
}

export function parseAppSettings(event: NostrEvent): AppSettings {
Expand Down
91 changes: 88 additions & 3 deletions src/views/settings/display-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useFormContext } from "react-hook-form";
import { useMemo, useState } from "react";
import { UseControllerProps, useController, useFormContext } from "react-hook-form";
import {
Flex,
FormControl,
Expand All @@ -13,12 +14,51 @@ import {
Input,
Select,
Textarea,
Divider,
Tag,
TagLabel,
TagCloseButton,
useDisclosure,
IconButton,
Button,
} from "@chakra-ui/react";
import { matchSorter } from "match-sorter";

import { AppSettings } from "../../services/settings/migrations";
import { AppearanceIcon } from "../../components/icons";
import { AppearanceIcon, EditIcon } from "../../components/icons";
import { useContextEmojis } from "../../providers/emoji-provider";

export default function DisplaySettings() {
const { register } = useFormContext<AppSettings>();
const { register, setValue, getValues, watch } = useFormContext<AppSettings>();
const emojiPicker = useDisclosure();

const emojis = useContextEmojis();
const [emojiSearch, setEmojiSearch] = useState("");

watch("quickReactions");
const filteredEmojis = useMemo(() => {
const values = getValues();
if (emojiSearch.trim()) {
const noCustom = emojis.filter((e) => e.char && !e.url && !values.quickReactions.includes(e.char));
return matchSorter(noCustom, emojiSearch.trim(), { keys: ["keywords"] }).slice(0, 10);
}
return [];
}, [emojiSearch, getValues().quickReactions]);

const addEmoji = (char: string) => {
const values = getValues();
if (values.quickReactions.includes(char)) return;
setValue("quickReactions", values.quickReactions.concat(char), { shouldTouch: true, shouldDirty: true });
};
const removeEmoji = (char: string) => {
const values = getValues();
if (!values.quickReactions.includes(char)) return;
setValue(
"quickReactions",
values.quickReactions.filter((e) => e !== char),
{ shouldTouch: true, shouldDirty: true },
);
};

return (
<AccordionItem>
Expand Down Expand Up @@ -63,6 +103,51 @@ export default function DisplaySettings() {
<span>The primary color of the theme</span>
</FormHelperText>
</FormControl>
<FormControl>
<FormLabel htmlFor="quickReactions" mb="0">
Quick Reactions
</FormLabel>
<Flex gap="2" wrap="wrap">
{getValues().quickReactions.map((char, i) => (
<Tag key={char + i}>
<TagLabel>{char}</TagLabel>
{emojiPicker.isOpen && <TagCloseButton onClick={() => removeEmoji(char)} />}
</Tag>
))}
{!emojiPicker.isOpen && (
<Button size="sm" onClick={emojiPicker.onOpen} leftIcon={<EditIcon />}>
Customize
</Button>
)}
</Flex>
{emojiPicker.isOpen && (
<>
<Divider my="2" />
<Input
type="search"
w="sm"
h="8"
value={emojiSearch}
onChange={(e) => setEmojiSearch(e.target.value)}
mb="2"
/>
<Flex gap="2" wrap="wrap">
{filteredEmojis.map((emoji) => (
<IconButton
key={emoji.char}
icon={<span>{emoji.char}</span>}
aria-label={`Add ${emoji.name}`}
title={`Add ${emoji.name}`}
variant="outline"
size="sm"
fontSize="lg"
onClick={() => addEmoji(emoji.char)}
/>
))}
</Flex>
</>
)}
</FormControl>
<FormControl>
<FormLabel htmlFor="maxPageWidth" mb="0">
Max Page width
Expand Down

0 comments on commit 9569281

Please sign in to comment.