Add emoji's to custom zaps and messages to default zaps

This commit is contained in:
Bojan Mojsilovic 2024-01-17 18:17:02 +01:00
parent 0a40efeb57
commit 5277b01a4e
14 changed files with 574 additions and 102 deletions

View File

@ -1,5 +1,3 @@
.feedsRestoreModal { .feedsRestoreModal {
position: fixed; position: fixed;
width: 420px; width: 420px;

View File

@ -109,6 +109,10 @@
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
.emoji, .amount {
margin-right: 6px;
}
.sats { .sats {
color: var(--text-tertiary); color: var(--text-tertiary);
font-size: 12px; font-size: 12px;

View File

@ -1,12 +1,13 @@
import { useIntl } from '@cookbook/solid-intl'; import { useIntl } from '@cookbook/solid-intl';
import { Component, createEffect, createSignal, For } from 'solid-js'; import { Component, createEffect, createSignal, For } from 'solid-js';
import { defaultZap, defaultZapOptions } from '../../constants';
import { useAccountContext } from '../../contexts/AccountContext'; import { useAccountContext } from '../../contexts/AccountContext';
import { useSettingsContext } from '../../contexts/SettingsContext'; import { useSettingsContext } from '../../contexts/SettingsContext';
import { hookForDev } from '../../lib/devTools'; import { hookForDev } from '../../lib/devTools';
import { zapNote } from '../../lib/zap'; import { zapNote } from '../../lib/zap';
import { userName } from '../../stores/profile'; import { userName } from '../../stores/profile';
import { toastZapFail, zapCustomOption, actions as tActions, placeholders as tPlaceholders } from '../../translations'; import { toastZapFail, zapCustomOption, actions as tActions, placeholders as tPlaceholders } from '../../translations';
import { PrimalNote } from '../../types/primal'; import { PrimalNote, ZapOption } from '../../types/primal';
import { debounce } from '../../utils'; import { debounce } from '../../utils';
import ButtonPrimary from '../Buttons/ButtonPrimary'; import ButtonPrimary from '../Buttons/ButtonPrimary';
import Modal from '../Modal/Modal'; import Modal from '../Modal/Modal';
@ -19,9 +20,9 @@ const CustomZap: Component<{
id?: string, id?: string,
open?: boolean, open?: boolean,
note: PrimalNote, note: PrimalNote,
onConfirm: (amount?: number) => void, onConfirm: (zapOption?: ZapOption) => void,
onSuccess: (amount?: number) => void, onSuccess: (zapOption?: ZapOption) => void,
onFail: (amount?: number) => void onFail: (zapOption?: ZapOption) => void
}> = (props) => { }> = (props) => {
const toast = useToastContext(); const toast = useToastContext();
@ -29,14 +30,17 @@ const CustomZap: Component<{
const intl = useIntl(); const intl = useIntl();
const settings = useSettingsContext(); const settings = useSettingsContext();
const [selectedValue, setSelectedValue] = createSignal(settings?.availableZapOptions[0] || 10); const [selectedValue, setSelectedValue] = createSignal(settings?.availableZapOptions[0] || defaultZapOptions[0]);
const [comment, setComment] = createSignal(''); const [comment, setComment] = createSignal(defaultZapOptions[0].message || '');
createEffect(() => { createEffect(() => {
setSelectedValue(settings?.availableZapOptions[0] || 10) setSelectedValue(settings?.availableZapOptions[0] || defaultZapOptions[0])
}); });
const isSelected = (value: number) => value === selectedValue(); const isSelected = (value: ZapOption) => {
const sel = selectedValue();
return value.amount === sel.amount && value.emoji === sel.emoji && value.message === sel.message;
};
const truncateNumber = (amount: number) => { const truncateNumber = (amount: number) => {
const t = 1000; const t = 1000;
@ -78,7 +82,7 @@ const CustomZap: Component<{
const success = await zapNote( const success = await zapNote(
props.note, props.note,
account.publicKey, account.publicKey,
selectedValue(), selectedValue().amount || 0,
comment(), comment(),
account.relays, account.relays,
); );
@ -105,7 +109,7 @@ const CustomZap: Component<{
{intl.formatMessage(tActions.zap)} {intl.formatMessage(tActions.zap)}
</div> </div>
</div> </div>
<button class={styles.close} onClick={() => props.onFail(0)}> <button class={styles.close} onClick={() => props.onFail({ amount: 0, message: '' })}>
</button> </button>
</div> </div>
@ -115,7 +119,7 @@ const CustomZap: Component<{
})} })}
<span class={styles.amount}> <span class={styles.amount}>
{truncateNumber(selectedValue())} {truncateNumber(selectedValue().amount || 0)}
</span> </span>
<span class={styles.units}>sats</span> <span class={styles.units}>sats</span>
</div> </div>
@ -125,10 +129,19 @@ const CustomZap: Component<{
{(value) => {(value) =>
<button <button
class={`${styles.zapOption} ${isSelected(value) ? styles.selected : ''}`} class={`${styles.zapOption} ${isSelected(value) ? styles.selected : ''}`}
onClick={() => setSelectedValue(value)} onClick={() => {
setComment(value.message || '')
setSelectedValue(value);
}}
> >
<div> <div>
{truncateNumber(value)} <span class={styles.sats}>sats</span> <span class={styles.emoji}>
{value.emoji}
</span>
<span class={styles.amount}>
{truncateNumber(value.amount || 0)}
</span>
<span class={styles.sats}>sats</span>
</div> </div>
</button> </button>
} }

View File

@ -0,0 +1,34 @@
.emojiSuggestions {
position: relative;
display: grid;
grid-template-columns: 50px 50px 50px 50px 50px 50px;
width: 322px;
max-height: 200px;
overflow-y: scroll;
padding: 4px;
background-color: var(--background-input);
border: none;
border-radius: 8px;
.emojiOption {
margin-bottom: 5px;
padding: 2px;
background: none;
font-size: 16px;
line-height: 20px;
font-weight: 400;
border: none;
display: flex;
justify-content: center;
align-items: center;
&:hover, &.highlight {
background-color: var(--text-tertiary-2);
}
&:focus {
outline: none;
border: none;
}
}
}

View File

@ -0,0 +1,143 @@
import { Component, createEffect, createSignal, For, onCleanup, onMount } from 'solid-js';
import styles from './EmojiPicker.module.scss';
import { useSettingsContext } from '../../contexts/SettingsContext';
import { debounce, isVisibleInContainer, uuidv4 } from '../../utils';
import { useIntl } from '@cookbook/solid-intl';
import ConfirmModal from '../ConfirmModal/ConfirmModal';
import { settings as t } from '../../translations';
import { hookForDev } from '../../lib/devTools';
import ButtonLink from '../Buttons/ButtonLink';
import Modal from '../Modal/Modal';
import emojiSearch from '@jukben/emoji-search';
import { createStore } from 'solid-js/store';
import { EmojiOption } from '../../types/primal';
import ButtonPrimary from '../Buttons/ButtonPrimary';
const EmojiPicker: Component<{ id?: string, filter: string, onSelect: (emoji: EmojiOption) => void }> = (props) => {
const [emojiResults, setEmojiResults] = createStore<EmojiOption[]>([]);
const [highlightedEmoji, setHighlightedEmoji] = createSignal<number>(0);
let emojiOptions: HTMLDivElement | undefined;
const instanceId = uuidv4();
const emojiChangeKeyboard = (e: KeyboardEvent) => {
if (e.code === 'ArrowDown') {
e.preventDefault();
setHighlightedEmoji(i => {
if (emojiResults.length === 0) {
return 0;
}
return i < emojiResults.length - 7 ? i + 6 : 0;
});
const emojiHolder = document.getElementById(`${instanceId}-${highlightedEmoji()}`);
if (emojiHolder && emojiOptions && !isVisibleInContainer(emojiHolder, emojiOptions)) {
emojiHolder.scrollIntoView({ block: 'end', behavior: 'smooth' });
}
return;
}
if (e.code === 'ArrowUp') {
e.preventDefault();
setHighlightedEmoji(i => {
if (emojiResults.length === 0) {
return 0;
}
return i >= 6 ? i - 6 : emojiResults.length - 1;
});
const emojiHolder = document.getElementById(`${instanceId}-${highlightedEmoji()}`);
if (emojiHolder && emojiOptions && !isVisibleInContainer(emojiHolder, emojiOptions)) {
emojiHolder.scrollIntoView({ block: 'start', behavior: 'smooth' });
}
return;
}
if (e.code === 'ArrowRight') {
e.preventDefault();
setHighlightedEmoji(i => {
if (emojiResults.length === 0) {
return 0;
}
return i < emojiResults.length - 1 ? i + 1 : 0;
});
const emojiHolder = document.getElementById(`${instanceId}-${highlightedEmoji()}`);
if (emojiHolder && emojiOptions && !isVisibleInContainer(emojiHolder, emojiOptions)) {
emojiHolder.scrollIntoView({ block: 'end', behavior: 'smooth' });
}
return;
}
if (e.code === 'ArrowLeft') {
e.preventDefault();
setHighlightedEmoji(i => {
if (emojiResults.length === 0) {
return 0;
}
return i > 0 ? i - 1 : emojiResults.length - 1;
});
const emojiHolder = document.getElementById(`${instanceId}-${highlightedEmoji()}`);
if (emojiHolder && emojiOptions && !isVisibleInContainer(emojiHolder, emojiOptions)) {
emojiHolder.scrollIntoView({ block: 'start', behavior: 'smooth' });
}
return;
}
if (['Enter', 'Space'].includes(e.code)) {
props.onSelect(emojiResults[highlightedEmoji()]);
return;
}
};
onMount(() => {
window.addEventListener('keydown', emojiChangeKeyboard);
});
onCleanup(() => {
window.removeEventListener('keydown', emojiChangeKeyboard);
});
createEffect(() => {
const val = props.filter.trim();
setEmojiResults(emojiSearch(val));
});
return (
<div
class={styles.emojiSuggestions}
ref={emojiOptions}
>
<For each={emojiResults}>
{(emoji, index) => (
<button
id={`${instanceId}-${index()}`}
class={`${styles.emojiOption} ${highlightedEmoji() === index() ? styles.highlight : ''}`}
onClick={() => {
props.onSelect(emoji);
}}
>
{emoji.name}
</button>
)}
</For>
</div>
);
}
export default hookForDev(EmojiPicker);

View File

@ -9,12 +9,17 @@ const Modal: Component<{
open?: boolean, open?: boolean,
id?: string, id?: string,
opaqueBackdrop?: boolean, opaqueBackdrop?: boolean,
onBackdropClick?: () => void,
}> = (props) => { }> = (props) => {
return ( return (
<Show when={props.open}> <Show when={props.open}>
<Portal mount={document.getElementById("modal") as Node}> <Portal mount={document.getElementById("modal") as Node}>
<div id={props.id} class={`${styles.modal} ${props.opaqueBackdrop ? styles.opaque : ''}`}> <div
id={props.id}
class={`${styles.modal} ${props.opaqueBackdrop ? styles.opaque : ''}`}
onClick={props.onBackdropClick}
>
{props.children} {props.children}
</div> </div>
</Portal> </Portal>

View File

@ -269,6 +269,7 @@ const EditBox: Component<{
if (lastEmojiTrigger < 0 || cursor - lastEmojiTrigger <= 1) { if (lastEmojiTrigger < 0 || cursor - lastEmojiTrigger <= 1) {
setEmojiInput(false); setEmojiInput(false);
setEmojiQuery('');
return false; return false;
} }

View File

@ -1,5 +1,5 @@
import { Component, createEffect, createSignal, Show } from 'solid-js'; import { Component, createEffect, createSignal, Show } from 'solid-js';
import { MenuItem, PrimalNote } from '../../../types/primal'; import { MenuItem, PrimalNote, ZapOption } from '../../../types/primal';
import { sendRepost } from '../../../lib/notes'; import { sendRepost } from '../../../lib/notes';
import styles from './NoteFooter.module.scss'; import styles from './NoteFooter.module.scss';
@ -252,17 +252,17 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
return; return;
} }
setZappedAmount(() => settings?.defaultZapAmount || 0); setZappedAmount(() => settings?.defaultZap.amount || 0);
setZappedNow(true); setZappedNow(true);
animateZap(); animateZap();
const success = await zapNote(props.note, account.publicKey, settings?.defaultZapAmount || 10, '', account.relays); const success = await zapNote(props.note, account.publicKey, settings?.defaultZap.amount || 10, settings?.defaultZap.message || '', account.relays);
setIsZapping(false); setIsZapping(false);
if (success) { if (success) {
return; return;
} }
setZappedAmount(() => -(settings?.defaultZapAmount || 0)); setZappedAmount(() => -(settings?.defaultZap.amount || 0));
setZappedNow(true); setZappedNow(true);
setZapped(props.note.post.noteActions.zapped); setZapped(props.note.post.noteActions.zapped);
} }
@ -413,14 +413,14 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
<CustomZap <CustomZap
open={isCustomZap()} open={isCustomZap()}
note={props.note} note={props.note}
onConfirm={(amount: number) => { onConfirm={(zapOption: ZapOption) => {
setIsCustomZap(false); setIsCustomZap(false);
setZappedAmount(() => amount || 0); setZappedAmount(() => zapOption.amount || 0);
setZappedNow(true); setZappedNow(true);
setZapped(true); setZapped(true);
animateZap(); animateZap();
}} }}
onSuccess={(amount: number) => { onSuccess={(zapOption: ZapOption) => {
setIsCustomZap(false); setIsCustomZap(false);
setIsZapping(false); setIsZapping(false);
setZappedNow(false); setZappedNow(false);
@ -428,8 +428,8 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
setHideZapIcon(false); setHideZapIcon(false);
setZapped(true); setZapped(true);
}} }}
onFail={(amount: number) => { onFail={(zapOption: ZapOption) => {
setZappedAmount(() => -(amount || 0)); setZappedAmount(() => -(zapOption.amount || 0));
setZappedNow(true); setZappedNow(true);
setIsCustomZap(false); setIsCustomZap(false);
setIsZapping(false); setIsZapping(false);

View File

@ -6,11 +6,12 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
.zapOptions { .zapOptions {
width: 396px; display: flex;
display: grid; flex-direction: column;
grid-template-columns: 1fr 1fr 1fr; width: 100%;
grid-column-gap: 12px; .zapInput {
grid-row-gap: 12px; margin-bottom: 8px;
}
} }
} }
@ -22,7 +23,42 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
input.zapInput { .zapInput {
width: 100%;
height: 36px;
background-color: var(--background-header-input);
border: none;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: flex-start;
.optEmoji {
display: flex;
align-items: center;
justify-content: center;
width: 120px;
height: 36px;
border-radius: 18px 0 0 18px;
margin: 0;
padding-bottom: 0;
padding-top: 4px;
padding-inline: 16px;
font-size: 16px;
font-weight: 600;
line-height: 20px;
color: var(--text-primary);
background: none;
border: none;
&:hover {
background-color: var(--subtile-devider);
outline: none;
box-shadow: none;
}
}
input {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -30,23 +66,137 @@ input.zapInput {
height: 36px; height: 36px;
text-align: center; text-align: center;
margin: 0; margin: 0;
padding: 0; padding-bottom: 0;
padding-top: 4px; padding-top: 4px;
padding-inline: 16px;
font-size: 16px; font-size: 16px;
font-weight: 600; font-weight: 600;
line-height: 20px; line-height: 20px;
color: var(--text-primary); color: var(--text-primary);
background-color: var(--background-header-input); background: none;
border: none; border: none;
border-radius: 18px;
&:focus { &.defAmount {
border-radius: 18px 0 0 18px;
}
&.defMessage {
border-radius: 0 18px 18px 0;
width: 100%;
justify-content: flex-start;
text-align: left;
}
&.optAmount {
border-radius: 0;
}
&.optMessage {
border-radius: 0 18px 18px 0;
width: 100%;
justify-content: flex-start;
text-align: left;
}
&:focus, &:hover {
background-color: var(--subtile-devider); background-color: var(--subtile-devider);
outline: none;
box-shadow: none;
} }
} }
}
.restoreZaps { .restoreZaps {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
margin-top: 36px; margin-top: 36px;
} }
.zapEmojiChangeModal {
position: fixed;
width: 420px;
height: 344px;
color: var(--text-secondary);
background-color: var(--background-input);
border: 1px solid transparent;
border-radius: 6px;
display: flex;
flex-direction: column;
padding: 22px;
justify-content: flex-start;
align-items: center;
input {
width: 220px;
height: 36px;
text-align: left;
margin: 0;
margin-bottom: 24px;
padding-bottom: 0;
padding-top: 4px;
padding-inline: 16px;
font-size: 16px;
font-weight: 600;
line-height: 20px;
color: var(--text-primary);
background: none;
border: none;
border-radius: 18px;
&:focus {
background-color: var(--subtile-devider);
outline: none;
box-shadow: none;
}
&::placeholder {
color: var(--text-tertiary);
font-size: 16px;
font-weight: 400;
line-height: 20px;
}
}
.title {
font-weight: 800;
font-size: 18px;
line-height: 18px;
color: var(--text-secondary);
text-transform: uppercase;
margin-bottom: 20px;
}
.xClose {
background: none;
border: none;
margin: 0;
padding: 0;
width: fit-content;
position: absolute;
top: 16px;
right: 18px;
.iconClose {
width: 14px;
height: 14px;
display: inline-block;
margin: 0px 0px;
background-color: var(--text-secondary);
-webkit-mask: url(../../assets/icons/close.svg) no-repeat center;
mask: url(../../assets/icons/close.svg) no-repeat center;
}
&:hover {
.iconClose {
background-color: var(--text-primary);
}
}
&:focus {
box-shadow: none;
}
}
}

View File

@ -1,13 +1,20 @@
import { Component, createSignal, For } from 'solid-js'; import { Component, createEffect, createSignal, For } from 'solid-js';
import styles from './SettingsZap.module.scss'; import styles from './SettingsZap.module.scss';
import { useSettingsContext } from '../../contexts/SettingsContext'; import { useSettingsContext } from '../../contexts/SettingsContext';
import { debounce } from '../../utils'; import { debounce, isVisibleInContainer, uuidv4 } from '../../utils';
import { useIntl } from '@cookbook/solid-intl'; import { useIntl } from '@cookbook/solid-intl';
import ConfirmModal from '../ConfirmModal/ConfirmModal'; import ConfirmModal from '../ConfirmModal/ConfirmModal';
import { settings as t } from '../../translations'; import { settings as t } from '../../translations';
import { hookForDev } from '../../lib/devTools'; import { hookForDev } from '../../lib/devTools';
import ButtonLink from '../Buttons/ButtonLink'; import ButtonLink from '../Buttons/ButtonLink';
import Modal from '../Modal/Modal';
import emojiSearch from '@jukben/emoji-search';
import { createStore } from 'solid-js/store';
import { EmojiOption } from '../../types/primal';
import ButtonPrimary from '../Buttons/ButtonPrimary';
import EmojiPicker from '../EmojiPicker/EmojiPicker';
const SettingsZap: Component<{ id?: string }> = (props) => { const SettingsZap: Component<{ id?: string }> = (props) => {
@ -17,49 +24,110 @@ const SettingsZap: Component<{ id?: string }> = (props) => {
const [isRestoringZaps, setIsRestoringZaps] = createSignal(false); const [isRestoringZaps, setIsRestoringZaps] = createSignal(false);
const [isEmojiChange, setIsEmojiChange] = createSignal(-1);
const [emojiSearchTerm, setEmojiSearchTerm] = createSignal('smile');
const onRestoreZaps = () => { const onRestoreZaps = () => {
settings?.actions.resetZapOptionsToDefault(); settings?.actions.resetZapOptionsToDefault();
setIsRestoringZaps(false); setIsRestoringZaps(false);
}; };
const changeDefaultZap = (e: InputEvent) => { const changeDefaultZapAmount = (e: InputEvent) => {
debounce(() => { debounce(() => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
const val = parseInt(target.value); const amount = parseInt(target.value);
if (isNaN(val)) { if (isNaN(amount)) {
return; return;
} }
settings?.actions.setDefaultZapAmount(val); settings?.actions.setDefaultZapAmount({ amount });
}, 500) }, 500)
}; };
const changeZapOptions = (e: InputEvent, index: number) => { const changeDefaultZapMessage = (e: InputEvent) => {
debounce(() => { debounce(() => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
const val = parseInt(target.value); const message = target.value;
if (isNaN(val)) { settings?.actions.setDefaultZapAmount({ message });
}, 500)
};
const changeZapOptionAmount = (e: InputEvent, index: number) => {
debounce(() => {
const target = e.target as HTMLInputElement;
const amount = parseInt(target.value);
if (isNaN(amount)) {
return; return;
} }
settings?.actions.setZapOptions(val, index); settings?.actions.setZapOptions({ amount }, index);
}, 500); }, 500);
}; };
const changeZapOptionMessage = (e: InputEvent, index: number) => {
debounce(() => {
const target = e.target as HTMLInputElement;
const message = target.value;
settings?.actions.setZapOptions({ message }, index);
}, 500);
};
const changeZapOptionEmoji = (emojiOption: EmojiOption) => {
if (isEmojiChange() < 0) return;
settings?.actions.setZapOptions({ emoji: emojiOption.name }, isEmojiChange());
setIsEmojiChange(-1);
};
const onKey = (e: KeyboardEvent) => {
if (e.code === 'Escape') {
setIsEmojiChange(-1);
return;
}
};
let emojiInput: HTMLInputElement | undefined;
createEffect(() => {
if (isEmojiChange() >= 0) {
window.addEventListener('keydown', onKey);
setTimeout(() => {
setEmojiSearchTerm(() => 'smile')
emojiInput?.focus();
}, 10);
}
else {
window.removeEventListener('keydown', onKey);
}
})
return ( return (
<div id={props.id} class={styles.zapSettings}> <div id={props.id} class={styles.zapSettings}>
<div class={styles.defaultZaps}> <div class={styles.defaultZaps}>
<div class={styles.caption}> <div class={styles.caption}>
Set default zap amount: Set default zap amount:
</div> </div>
<input <div
type='text'
class={styles.zapInput} class={styles.zapInput}
value={settings?.defaultZapAmount} >
onInput={changeDefaultZap} <input
class={styles.defAmount}
type='text'
value={settings?.defaultZap.amount}
onInput={changeDefaultZapAmount}
/> />
<input
class={styles.defMessage}
type='text'
value={settings?.defaultZap.message}
onInput={changeDefaultZapMessage}
/>
</div>
</div> </div>
<div class={styles.customZaps}> <div class={styles.customZaps}>
<div class={styles.caption}> <div class={styles.caption}>
@ -67,13 +135,27 @@ const SettingsZap: Component<{ id?: string }> = (props) => {
</div> </div>
<div class={styles.zapOptions}> <div class={styles.zapOptions}>
<For each={settings?.availableZapOptions}> <For each={settings?.availableZapOptions}>
{(value, index) => {(option, index) =>
<div class={styles.zapInput}>
<button
class={styles.optEmoji}
onClick={(e: MouseEvent) => setIsEmojiChange(index())}
>
{option.emoji}
</button>
<input <input
class={styles.optAmount}
type='text' type='text'
class={styles.zapInput} value={option.amount}
value={value} onInput={(e: InputEvent) => changeZapOptionAmount(e, index())}
onInput={(e: InputEvent) => changeZapOptions(e, index())}
/> />
<input
class={styles.optMessage}
type='text'
value={option.message}
onInput={(e: InputEvent) => changeZapOptionMessage(e, index())}
/>
</div>
} }
</For> </For>
</div> </div>
@ -93,6 +175,36 @@ const SettingsZap: Component<{ id?: string }> = (props) => {
onConfirm={onRestoreZaps} onConfirm={onRestoreZaps}
onAbort={() => setIsRestoringZaps(false)} onAbort={() => setIsRestoringZaps(false)}
/> />
<Modal
open={isEmojiChange() >= 0}
onBackdropClick={() => setIsEmojiChange(-1)}
>
<div id={props.id} class={styles.zapEmojiChangeModal}>
<div class={styles.title}>
{intl.formatMessage(t.zapEmojiFilterTitle)}
</div>
<button class={styles.xClose} onClick={() => setIsEmojiChange(-1)}>
<div class={styles.iconClose}></div>
</button>
<input
ref={emojiInput}
onInput={(e: InputEvent) => {
const target = e.target as HTMLInputElement;
setEmojiSearchTerm(() => target.value);
}}
placeholder={intl.formatMessage(t.zapEmojiFilterPlaceholder)}
>
</input>
<EmojiPicker
filter={emojiSearchTerm()}
onSelect={changeZapOptionEmoji}
/>
</div>
</Modal>
</div> </div>
); );
} }

View File

@ -1,8 +1,6 @@
import { ContentModeration, FeedPage, Filterlist, } from "./types/primal"; import { ContentModeration, FeedPage, } from "./types/primal";
import logoFire from './assets/icons/logo_fire.svg'; import logoFire from './assets/icons/logo_fire.svg';
import logoIce from './assets/icons/logo_ice.svg'; import logoIce from './assets/icons/logo_ice.svg';
import { MessageDescriptor } from "@cookbook/solid-intl";
import { linkPreviews } from "./lib/notes";
export const second = 1000; export const second = 1000;
export const minute = 60 * second; export const minute = 60 * second;
@ -308,15 +306,15 @@ export const apkLink = `https://github.com/PrimalHQ/primal-android-app/releases/
// ---------------------------------------- // ----------------------------------------
export const defaultZapAmount = 42; export const defaultZap = { "amount": 42, "message": "Onward 🫡" };
export const defaultZapOptions = [ export const defaultZapOptions = [
21, { emoji: '👍', amount: 21, message: 'Great post 👍' },
420, { emoji: '🚀', amount: 420, message: 'Let\'s go 🚀' },
1_000, { emoji: '☕', amount: 1_000, message: 'Coffie on me ☕' },
5_000, { emoji: '🍻', amount: 5_000, message: 'Cheers 🍻' },
10_000, { emoji: '🍷', amount: 10_000, message: 'Party time 🍷' },
100_000, { emoji: '👑', amount: 100_000, message: 'Generational wealth 👑' },
]; ];
export const contentScope = 'content'; export const contentScope = 'content';

View File

@ -1,6 +1,6 @@
import { createStore } from "solid-js/store"; import { createStore } from "solid-js/store";
import { useToastContext } from "../components/Toaster/Toaster"; import { useToastContext } from "../components/Toaster/Toaster";
import { contentScope, defaultContentModeration, defaultFeeds, defaultNotificationSettings, defaultZapAmount, defaultZapOptions, nostrHighlights, themes, trendingFeed, trendingScope } from "../constants"; import { contentScope, defaultContentModeration, defaultFeeds, defaultNotificationSettings, defaultZap, defaultZapOptions, nostrHighlights, themes, trendingFeed, trendingScope } from "../constants";
import { import {
createContext, createContext,
createEffect, createEffect,
@ -20,6 +20,7 @@ import {
ContextChildren, ContextChildren,
PrimalFeed, PrimalFeed,
PrimalTheme, PrimalTheme,
ZapOption,
} from "../types/primal"; } from "../types/primal";
import { import {
initAvailableFeeds, initAvailableFeeds,
@ -35,8 +36,6 @@ import { APP_ID } from "../App";
import { useIntl } from "@cookbook/solid-intl"; import { useIntl } from "@cookbook/solid-intl";
import { hexToNpub } from "../lib/keys"; import { hexToNpub } from "../lib/keys";
import { settings as t } from "../translations"; import { settings as t } from "../translations";
import { getFilterlists } from "../lib/profile";
export type SettingsContextStore = { export type SettingsContextStore = {
locale: string, locale: string,
@ -44,8 +43,8 @@ export type SettingsContextStore = {
themes: PrimalTheme[], themes: PrimalTheme[],
availableFeeds: PrimalFeed[], availableFeeds: PrimalFeed[],
defaultFeed: PrimalFeed, defaultFeed: PrimalFeed,
defaultZapAmount: number, defaultZap: ZapOption,
availableZapOptions: number[], availableZapOptions: ZapOption[],
notificationSettings: Record<string, boolean>, notificationSettings: Record<string, boolean>,
applyContentModeration: boolean, applyContentModeration: boolean,
contentModeration: ContentModeration[], contentModeration: ContentModeration[],
@ -58,8 +57,8 @@ export type SettingsContextStore = {
renameAvailableFeed: (feed: PrimalFeed, newName: string) => void, renameAvailableFeed: (feed: PrimalFeed, newName: string) => void,
saveSettings: () => void, saveSettings: () => void,
loadSettings: (pubkey: string) => void, loadSettings: (pubkey: string) => void,
setDefaultZapAmount: (amount: number) => void, setDefaultZapAmount: (option: ZapOption, temp?: boolean) => void,
setZapOptions: (amount:number, index: number) => void, setZapOptions: (option: ZapOption, index: number, temp?: boolean) => void,
resetZapOptionsToDefault: (temp?: boolean) => void, resetZapOptionsToDefault: (temp?: boolean) => void,
updateNotificationSettings: (key: string, value: boolean, temp?: boolean) => void, updateNotificationSettings: (key: string, value: boolean, temp?: boolean) => void,
restoreDefaultFeeds: () => void, restoreDefaultFeeds: () => void,
@ -74,7 +73,7 @@ export const initialData = {
themes, themes,
availableFeeds: [], availableFeeds: [],
defaultFeed: defaultFeeds[0], defaultFeed: defaultFeeds[0],
defaultZapAmount: defaultZapAmount, defaultZap: defaultZap,
availableZapOptions: defaultZapOptions, availableZapOptions: defaultZapOptions,
notificationSettings: { ...defaultNotificationSettings }, notificationSettings: { ...defaultNotificationSettings },
applyContentModeration: true, applyContentModeration: true,
@ -92,13 +91,13 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
// ACTIONS -------------------------------------- // ACTIONS --------------------------------------
const setDefaultZapAmount = (amount: number, temp?: boolean) => { const setDefaultZapAmount = (option: ZapOption, temp?: boolean) => {
updateStore('defaultZapAmount', () => amount); updateStore('defaultZap', () => option);
!temp && saveSettings(); !temp && saveSettings();
}; };
const setZapOptions = (amount: number, index: number, temp?: boolean) => { const setZapOptions = (option: ZapOption, index: number, temp?: boolean) => {
updateStore('availableZapOptions', index, () => amount); updateStore('availableZapOptions', index, () => ({ ...option }));
!temp && saveSettings(); !temp && saveSettings();
}; };
@ -111,11 +110,11 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
try { try {
const settings = JSON.parse(content?.content); const settings = JSON.parse(content?.content);
let options = settings.zapOptions as number[]; let options = settings.zapConfig;
let amount = settings.defaultZapAmount as number; let amount = settings.zapDefault;
updateStore('availableZapOptions', () => options); updateStore('availableZapOptions', () => options);
updateStore('defaultZapAmount', () => amount); updateStore('defaultZap', () => amount);
!temp && saveSettings(); !temp && saveSettings();
} }
@ -289,8 +288,8 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
const settings = { const settings = {
theme: store.theme, theme: store.theme,
feeds: store.availableFeeds, feeds: store.availableFeeds,
defaultZapAmount: store.defaultZapAmount, defaultZap: store.defaultZap,
zapOptions: store.availableZapOptions, zapConfig: store.availableZapOptions,
notifications: store.notificationSettings, notifications: store.notificationSettings,
applyContentModeration: store.applyContentModeration, applyContentModeration: store.applyContentModeration,
contentModeration: store.contentModeration, contentModeration: store.contentModeration,
@ -336,11 +335,10 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
updateStore('notificationSettings', () => ({ ...notificationSettings } || { ...defaultNotificationSettings })); updateStore('notificationSettings', () => ({ ...notificationSettings } || { ...defaultNotificationSettings }));
updateStore('applyContentModeration', () => true); updateStore('applyContentModeration', () => true);
let zapOptions = settings.zapConfig;
let zapAmount = settings.zapDefault;
let zapOptions = settings.zapOptions as number[]; updateStore('defaultZap', () => zapAmount);
let zapAmount = settings.defaultZapAmount as number;
updateStore('defaultZapAmount', () => zapAmount);
updateStore('availableZapOptions', () => zapOptions); updateStore('availableZapOptions', () => zapOptions);
} }
catch (e) { catch (e) {
@ -378,16 +376,16 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
const { const {
theme, theme,
feeds, feeds,
defaultZapAmount, zapDefault,
zapOptions, zapConfig,
notifications, notifications,
applyContentModeration, applyContentModeration,
contentModeration, contentModeration,
} = JSON.parse(content?.content); } = JSON.parse(content?.content);
theme && setThemeByName(theme, true); theme && setThemeByName(theme, true);
defaultZapAmount && setDefaultZapAmount(defaultZapAmount, true); zapDefault && setDefaultZapAmount(zapDefault, true);
zapOptions && updateStore('availableZapOptions', () => zapOptions); zapConfig && updateStore('availableZapOptions', () => zapConfig);
if (notifications) { if (notifications) {
updateStore('notificationSettings', () => ({ ...notifications })); updateStore('notificationSettings', () => ({ ...notifications }));

View File

@ -1429,6 +1429,16 @@ export const settings = {
defaultMessage: 'This action will restore all your zap settings to their default values', defaultMessage: 'This action will restore all your zap settings to their default values',
description: 'Label explaining the impact of restoring default zaps', description: 'Label explaining the impact of restoring default zaps',
}, },
zapEmojiFilterTitle: {
id: 'settings.zapEmojiFilterTitle',
defaultMessage: 'Select an emoji',
description: 'Title for the select emoji modal',
},
zapEmojiFilterPlaceholder: {
id: 'settings.zapEmojiFilterPlaceholder',
defaultMessage: 'Type to filter...',
description: 'Placeholder for the emoji modal filter',
},
feedLatest: { feedLatest: {
id: 'feeds.feedLatest', id: 'feeds.feedLatest',
defaultMessage: 'Latest', defaultMessage: 'Latest',

View File

@ -623,3 +623,9 @@ export type SelectionOption = {
} }
export type NotificationGroup = 'all' | 'zaps' | 'replies' | 'mentions' | 'reposts'; export type NotificationGroup = 'all' | 'zaps' | 'replies' | 'mentions' | 'reposts';
export type ZapOption = {
emoji?: string,
amount?: number,
message?: string,
};