mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-10-03 02:10:55 +00:00
Add emoji's to custom zaps and messages to default zaps
This commit is contained in:
parent
0a40efeb57
commit
5277b01a4e
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
.feedsRestoreModal {
|
||||
position: fixed;
|
||||
width: 420px;
|
||||
|
@ -109,6 +109,10 @@
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.emoji, .amount {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.sats {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 12px;
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { useIntl } from '@cookbook/solid-intl';
|
||||
import { Component, createEffect, createSignal, For } from 'solid-js';
|
||||
import { defaultZap, defaultZapOptions } from '../../constants';
|
||||
import { useAccountContext } from '../../contexts/AccountContext';
|
||||
import { useSettingsContext } from '../../contexts/SettingsContext';
|
||||
import { hookForDev } from '../../lib/devTools';
|
||||
import { zapNote } from '../../lib/zap';
|
||||
import { userName } from '../../stores/profile';
|
||||
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 ButtonPrimary from '../Buttons/ButtonPrimary';
|
||||
import Modal from '../Modal/Modal';
|
||||
@ -19,9 +20,9 @@ const CustomZap: Component<{
|
||||
id?: string,
|
||||
open?: boolean,
|
||||
note: PrimalNote,
|
||||
onConfirm: (amount?: number) => void,
|
||||
onSuccess: (amount?: number) => void,
|
||||
onFail: (amount?: number) => void
|
||||
onConfirm: (zapOption?: ZapOption) => void,
|
||||
onSuccess: (zapOption?: ZapOption) => void,
|
||||
onFail: (zapOption?: ZapOption) => void
|
||||
}> = (props) => {
|
||||
|
||||
const toast = useToastContext();
|
||||
@ -29,14 +30,17 @@ const CustomZap: Component<{
|
||||
const intl = useIntl();
|
||||
const settings = useSettingsContext();
|
||||
|
||||
const [selectedValue, setSelectedValue] = createSignal(settings?.availableZapOptions[0] || 10);
|
||||
const [comment, setComment] = createSignal('');
|
||||
const [selectedValue, setSelectedValue] = createSignal(settings?.availableZapOptions[0] || defaultZapOptions[0]);
|
||||
const [comment, setComment] = createSignal(defaultZapOptions[0].message || '');
|
||||
|
||||
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 t = 1000;
|
||||
@ -78,7 +82,7 @@ const CustomZap: Component<{
|
||||
const success = await zapNote(
|
||||
props.note,
|
||||
account.publicKey,
|
||||
selectedValue(),
|
||||
selectedValue().amount || 0,
|
||||
comment(),
|
||||
account.relays,
|
||||
);
|
||||
@ -105,7 +109,7 @@ const CustomZap: Component<{
|
||||
{intl.formatMessage(tActions.zap)}
|
||||
</div>
|
||||
</div>
|
||||
<button class={styles.close} onClick={() => props.onFail(0)}>
|
||||
<button class={styles.close} onClick={() => props.onFail({ amount: 0, message: '' })}>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -115,7 +119,7 @@ const CustomZap: Component<{
|
||||
})}
|
||||
|
||||
<span class={styles.amount}>
|
||||
{truncateNumber(selectedValue())}
|
||||
{truncateNumber(selectedValue().amount || 0)}
|
||||
</span>
|
||||
<span class={styles.units}>sats</span>
|
||||
</div>
|
||||
@ -125,10 +129,19 @@ const CustomZap: Component<{
|
||||
{(value) =>
|
||||
<button
|
||||
class={`${styles.zapOption} ${isSelected(value) ? styles.selected : ''}`}
|
||||
onClick={() => setSelectedValue(value)}
|
||||
onClick={() => {
|
||||
setComment(value.message || '')
|
||||
setSelectedValue(value);
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</button>
|
||||
}
|
||||
|
34
src/components/EmojiPicker/EmojiPicker.module.scss
Normal file
34
src/components/EmojiPicker/EmojiPicker.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
143
src/components/EmojiPicker/EmojiPicker.tsx
Normal file
143
src/components/EmojiPicker/EmojiPicker.tsx
Normal 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);
|
@ -9,12 +9,17 @@ const Modal: Component<{
|
||||
open?: boolean,
|
||||
id?: string,
|
||||
opaqueBackdrop?: boolean,
|
||||
onBackdropClick?: () => void,
|
||||
}> = (props) => {
|
||||
|
||||
return (
|
||||
<Show when={props.open}>
|
||||
<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}
|
||||
</div>
|
||||
</Portal>
|
||||
|
@ -269,6 +269,7 @@ const EditBox: Component<{
|
||||
|
||||
if (lastEmojiTrigger < 0 || cursor - lastEmojiTrigger <= 1) {
|
||||
setEmojiInput(false);
|
||||
setEmojiQuery('');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 styles from './NoteFooter.module.scss';
|
||||
@ -252,17 +252,17 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
|
||||
return;
|
||||
}
|
||||
|
||||
setZappedAmount(() => settings?.defaultZapAmount || 0);
|
||||
setZappedAmount(() => settings?.defaultZap.amount || 0);
|
||||
setZappedNow(true);
|
||||
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);
|
||||
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
|
||||
setZappedAmount(() => -(settings?.defaultZapAmount || 0));
|
||||
setZappedAmount(() => -(settings?.defaultZap.amount || 0));
|
||||
setZappedNow(true);
|
||||
setZapped(props.note.post.noteActions.zapped);
|
||||
}
|
||||
@ -413,14 +413,14 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
|
||||
<CustomZap
|
||||
open={isCustomZap()}
|
||||
note={props.note}
|
||||
onConfirm={(amount: number) => {
|
||||
onConfirm={(zapOption: ZapOption) => {
|
||||
setIsCustomZap(false);
|
||||
setZappedAmount(() => amount || 0);
|
||||
setZappedAmount(() => zapOption.amount || 0);
|
||||
setZappedNow(true);
|
||||
setZapped(true);
|
||||
animateZap();
|
||||
}}
|
||||
onSuccess={(amount: number) => {
|
||||
onSuccess={(zapOption: ZapOption) => {
|
||||
setIsCustomZap(false);
|
||||
setIsZapping(false);
|
||||
setZappedNow(false);
|
||||
@ -428,8 +428,8 @@ const NoteFooter: Component<{ note: PrimalNote, wide?: boolean, id?: string }> =
|
||||
setHideZapIcon(false);
|
||||
setZapped(true);
|
||||
}}
|
||||
onFail={(amount: number) => {
|
||||
setZappedAmount(() => -(amount || 0));
|
||||
onFail={(zapOption: ZapOption) => {
|
||||
setZappedAmount(() => -(zapOption.amount || 0));
|
||||
setZappedNow(true);
|
||||
setIsCustomZap(false);
|
||||
setIsZapping(false);
|
||||
|
@ -6,11 +6,12 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.zapOptions {
|
||||
width: 396px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-column-gap: 12px;
|
||||
grid-row-gap: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
.zapInput {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +23,42 @@
|
||||
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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -30,23 +66,137 @@ input.zapInput {
|
||||
height: 36px;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-bottom: 0;
|
||||
padding-top: 4px;
|
||||
padding-inline: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: var(--text-primary);
|
||||
background-color: var(--background-header-input);
|
||||
background: 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);
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.restoreZaps {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 { useSettingsContext } from '../../contexts/SettingsContext';
|
||||
import { debounce } from '../../utils';
|
||||
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';
|
||||
import EmojiPicker from '../EmojiPicker/EmojiPicker';
|
||||
|
||||
const SettingsZap: Component<{ id?: string }> = (props) => {
|
||||
|
||||
@ -17,49 +24,110 @@ const SettingsZap: Component<{ id?: string }> = (props) => {
|
||||
|
||||
const [isRestoringZaps, setIsRestoringZaps] = createSignal(false);
|
||||
|
||||
const [isEmojiChange, setIsEmojiChange] = createSignal(-1);
|
||||
|
||||
const [emojiSearchTerm, setEmojiSearchTerm] = createSignal('smile');
|
||||
|
||||
const onRestoreZaps = () => {
|
||||
settings?.actions.resetZapOptionsToDefault();
|
||||
setIsRestoringZaps(false);
|
||||
};
|
||||
|
||||
const changeDefaultZap = (e: InputEvent) => {
|
||||
const changeDefaultZapAmount = (e: InputEvent) => {
|
||||
debounce(() => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const val = parseInt(target.value);
|
||||
const amount = parseInt(target.value);
|
||||
|
||||
if (isNaN(val)) {
|
||||
if (isNaN(amount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings?.actions.setDefaultZapAmount(val);
|
||||
settings?.actions.setDefaultZapAmount({ amount });
|
||||
}, 500)
|
||||
};
|
||||
|
||||
const changeZapOptions = (e: InputEvent, index: number) => {
|
||||
const changeDefaultZapMessage = (e: InputEvent) => {
|
||||
debounce(() => {
|
||||
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;
|
||||
}
|
||||
|
||||
settings?.actions.setZapOptions(val, index);
|
||||
settings?.actions.setZapOptions({ amount }, index);
|
||||
}, 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 (
|
||||
<div id={props.id} class={styles.zapSettings}>
|
||||
<div class={styles.defaultZaps}>
|
||||
<div class={styles.caption}>
|
||||
Set default zap amount:
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
<div
|
||||
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 class={styles.customZaps}>
|
||||
<div class={styles.caption}>
|
||||
@ -67,13 +135,27 @@ const SettingsZap: Component<{ id?: string }> = (props) => {
|
||||
</div>
|
||||
<div class={styles.zapOptions}>
|
||||
<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
|
||||
class={styles.optAmount}
|
||||
type='text'
|
||||
class={styles.zapInput}
|
||||
value={value}
|
||||
onInput={(e: InputEvent) => changeZapOptions(e, index())}
|
||||
value={option.amount}
|
||||
onInput={(e: InputEvent) => changeZapOptionAmount(e, index())}
|
||||
/>
|
||||
<input
|
||||
class={styles.optMessage}
|
||||
type='text'
|
||||
value={option.message}
|
||||
onInput={(e: InputEvent) => changeZapOptionMessage(e, index())}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</For>
|
||||
</div>
|
||||
@ -93,6 +175,36 @@ const SettingsZap: Component<{ id?: string }> = (props) => {
|
||||
onConfirm={onRestoreZaps}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
@ -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 logoIce from './assets/icons/logo_ice.svg';
|
||||
import { MessageDescriptor } from "@cookbook/solid-intl";
|
||||
import { linkPreviews } from "./lib/notes";
|
||||
|
||||
export const second = 1000;
|
||||
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 = [
|
||||
21,
|
||||
420,
|
||||
1_000,
|
||||
5_000,
|
||||
10_000,
|
||||
100_000,
|
||||
{ emoji: '👍', amount: 21, message: 'Great post 👍' },
|
||||
{ emoji: '🚀', amount: 420, message: 'Let\'s go 🚀' },
|
||||
{ emoji: '☕', amount: 1_000, message: 'Coffie on me ☕' },
|
||||
{ emoji: '🍻', amount: 5_000, message: 'Cheers 🍻' },
|
||||
{ emoji: '🍷', amount: 10_000, message: 'Party time 🍷' },
|
||||
{ emoji: '👑', amount: 100_000, message: 'Generational wealth 👑' },
|
||||
];
|
||||
|
||||
export const contentScope = 'content';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createStore } from "solid-js/store";
|
||||
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 {
|
||||
createContext,
|
||||
createEffect,
|
||||
@ -20,6 +20,7 @@ import {
|
||||
ContextChildren,
|
||||
PrimalFeed,
|
||||
PrimalTheme,
|
||||
ZapOption,
|
||||
} from "../types/primal";
|
||||
import {
|
||||
initAvailableFeeds,
|
||||
@ -35,8 +36,6 @@ import { APP_ID } from "../App";
|
||||
import { useIntl } from "@cookbook/solid-intl";
|
||||
import { hexToNpub } from "../lib/keys";
|
||||
import { settings as t } from "../translations";
|
||||
import { getFilterlists } from "../lib/profile";
|
||||
|
||||
|
||||
export type SettingsContextStore = {
|
||||
locale: string,
|
||||
@ -44,8 +43,8 @@ export type SettingsContextStore = {
|
||||
themes: PrimalTheme[],
|
||||
availableFeeds: PrimalFeed[],
|
||||
defaultFeed: PrimalFeed,
|
||||
defaultZapAmount: number,
|
||||
availableZapOptions: number[],
|
||||
defaultZap: ZapOption,
|
||||
availableZapOptions: ZapOption[],
|
||||
notificationSettings: Record<string, boolean>,
|
||||
applyContentModeration: boolean,
|
||||
contentModeration: ContentModeration[],
|
||||
@ -58,8 +57,8 @@ export type SettingsContextStore = {
|
||||
renameAvailableFeed: (feed: PrimalFeed, newName: string) => void,
|
||||
saveSettings: () => void,
|
||||
loadSettings: (pubkey: string) => void,
|
||||
setDefaultZapAmount: (amount: number) => void,
|
||||
setZapOptions: (amount:number, index: number) => void,
|
||||
setDefaultZapAmount: (option: ZapOption, temp?: boolean) => void,
|
||||
setZapOptions: (option: ZapOption, index: number, temp?: boolean) => void,
|
||||
resetZapOptionsToDefault: (temp?: boolean) => void,
|
||||
updateNotificationSettings: (key: string, value: boolean, temp?: boolean) => void,
|
||||
restoreDefaultFeeds: () => void,
|
||||
@ -74,7 +73,7 @@ export const initialData = {
|
||||
themes,
|
||||
availableFeeds: [],
|
||||
defaultFeed: defaultFeeds[0],
|
||||
defaultZapAmount: defaultZapAmount,
|
||||
defaultZap: defaultZap,
|
||||
availableZapOptions: defaultZapOptions,
|
||||
notificationSettings: { ...defaultNotificationSettings },
|
||||
applyContentModeration: true,
|
||||
@ -92,13 +91,13 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
|
||||
|
||||
// ACTIONS --------------------------------------
|
||||
|
||||
const setDefaultZapAmount = (amount: number, temp?: boolean) => {
|
||||
updateStore('defaultZapAmount', () => amount);
|
||||
const setDefaultZapAmount = (option: ZapOption, temp?: boolean) => {
|
||||
updateStore('defaultZap', () => option);
|
||||
!temp && saveSettings();
|
||||
};
|
||||
|
||||
const setZapOptions = (amount: number, index: number, temp?: boolean) => {
|
||||
updateStore('availableZapOptions', index, () => amount);
|
||||
const setZapOptions = (option: ZapOption, index: number, temp?: boolean) => {
|
||||
updateStore('availableZapOptions', index, () => ({ ...option }));
|
||||
!temp && saveSettings();
|
||||
};
|
||||
|
||||
@ -111,11 +110,11 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
|
||||
try {
|
||||
const settings = JSON.parse(content?.content);
|
||||
|
||||
let options = settings.zapOptions as number[];
|
||||
let amount = settings.defaultZapAmount as number;
|
||||
let options = settings.zapConfig;
|
||||
let amount = settings.zapDefault;
|
||||
|
||||
updateStore('availableZapOptions', () => options);
|
||||
updateStore('defaultZapAmount', () => amount);
|
||||
updateStore('defaultZap', () => amount);
|
||||
|
||||
!temp && saveSettings();
|
||||
}
|
||||
@ -289,8 +288,8 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
|
||||
const settings = {
|
||||
theme: store.theme,
|
||||
feeds: store.availableFeeds,
|
||||
defaultZapAmount: store.defaultZapAmount,
|
||||
zapOptions: store.availableZapOptions,
|
||||
defaultZap: store.defaultZap,
|
||||
zapConfig: store.availableZapOptions,
|
||||
notifications: store.notificationSettings,
|
||||
applyContentModeration: store.applyContentModeration,
|
||||
contentModeration: store.contentModeration,
|
||||
@ -336,11 +335,10 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
|
||||
updateStore('notificationSettings', () => ({ ...notificationSettings } || { ...defaultNotificationSettings }));
|
||||
updateStore('applyContentModeration', () => true);
|
||||
|
||||
let zapOptions = settings.zapConfig;
|
||||
let zapAmount = settings.zapDefault;
|
||||
|
||||
let zapOptions = settings.zapOptions as number[];
|
||||
let zapAmount = settings.defaultZapAmount as number;
|
||||
|
||||
updateStore('defaultZapAmount', () => zapAmount);
|
||||
updateStore('defaultZap', () => zapAmount);
|
||||
updateStore('availableZapOptions', () => zapOptions);
|
||||
}
|
||||
catch (e) {
|
||||
@ -378,16 +376,16 @@ export const SettingsProvider = (props: { children: ContextChildren }) => {
|
||||
const {
|
||||
theme,
|
||||
feeds,
|
||||
defaultZapAmount,
|
||||
zapOptions,
|
||||
zapDefault,
|
||||
zapConfig,
|
||||
notifications,
|
||||
applyContentModeration,
|
||||
contentModeration,
|
||||
} = JSON.parse(content?.content);
|
||||
|
||||
theme && setThemeByName(theme, true);
|
||||
defaultZapAmount && setDefaultZapAmount(defaultZapAmount, true);
|
||||
zapOptions && updateStore('availableZapOptions', () => zapOptions);
|
||||
zapDefault && setDefaultZapAmount(zapDefault, true);
|
||||
zapConfig && updateStore('availableZapOptions', () => zapConfig);
|
||||
|
||||
if (notifications) {
|
||||
updateStore('notificationSettings', () => ({ ...notifications }));
|
||||
|
@ -1429,6 +1429,16 @@ export const settings = {
|
||||
defaultMessage: 'This action will restore all your zap settings to their default values',
|
||||
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: {
|
||||
id: 'feeds.feedLatest',
|
||||
defaultMessage: 'Latest',
|
||||
|
6
src/types/primal.d.ts
vendored
6
src/types/primal.d.ts
vendored
@ -623,3 +623,9 @@ export type SelectionOption = {
|
||||
}
|
||||
|
||||
export type NotificationGroup = 'all' | 'zaps' | 'replies' | 'mentions' | 'reposts';
|
||||
|
||||
export type ZapOption = {
|
||||
emoji?: string,
|
||||
amount?: number,
|
||||
message?: string,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user