diff --git a/src/common/layout/LayoutBadgeImageView.tsx b/src/common/layout/LayoutBadgeImageView.tsx index f1639c4..75a6533 100644 --- a/src/common/layout/LayoutBadgeImageView.tsx +++ b/src/common/layout/LayoutBadgeImageView.tsx @@ -67,11 +67,20 @@ export const LayoutBadgeImageView: FC = props => { if(event.badgeId !== badgeCode) return; - const element = await TextureUtils.generateImage(new NitroSprite(event.image)); - - console.log ('boe'); + if(isGroup) + { + const element = await TextureUtils.generateImage(new NitroSprite(event.image)); - element.onload = () => setImageElement(element); + element.onload = () => setImageElement(element); + } + else + { + const badgeUrl = GetConfigurationValue('badge.asset.url').replace('%badgename%', badgeCode.toString()); + const img = new Image(); + + img.onload = () => setImageElement(img); + img.src = badgeUrl; + } didSetBadge = true; @@ -84,13 +93,23 @@ export const LayoutBadgeImageView: FC = props => if(texture && !didSetBadge) { - (async () => + if(isGroup) { - const element = await TextureUtils.generateImage(new NitroSprite(texture)); - + (async () => + { + const element = await TextureUtils.generateImage(new NitroSprite(texture)); - element.onload = () => setImageElement(element); - })(); + element.onload = () => setImageElement(element); + })(); + } + else + { + const badgeUrl = GetConfigurationValue('badge.asset.url').replace('%badgename%', badgeCode.toString()); + const img = new Image(); + + img.onload = () => setImageElement(img); + img.src = badgeUrl; + } } return () => GetEventDispatcher().removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent); diff --git a/src/components/MainView.tsx b/src/components/MainView.tsx index 817bf2a..4b3217f 100644 --- a/src/components/MainView.tsx +++ b/src/components/MainView.tsx @@ -85,7 +85,7 @@ export const MainView: FC<{}> = props => { landingViewVisible && diff --git a/src/components/catalog/views/gift/CatalogGiftView.tsx b/src/components/catalog/views/gift/CatalogGiftView.tsx index c027fba..8104afb 100644 --- a/src/components/catalog/views/gift/CatalogGiftView.tsx +++ b/src/components/catalog/views/gift/CatalogGiftView.tsx @@ -7,6 +7,8 @@ import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../.. import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks'; import { classNames } from '../../../../layout'; +let isBuyingGift = false; + export const CatalogGiftView: FC<{}> = props => { const [ isVisible, setIsVisible ] = useState(false); @@ -32,6 +34,7 @@ export const CatalogGiftView: FC<{}> = props => const onClose = useCallback(() => { + isBuyingGift = false; setIsVisible(false); setPageId(0); setOfferId(0); @@ -122,6 +125,10 @@ export const CatalogGiftView: FC<{}> = props => return; } + if(isBuyingGift) return; + + isBuyingGift = true; + SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, colourId, selectedBoxIndex, selectedRibbonIndex, showMyFace)); return; } @@ -136,6 +143,7 @@ export const CatalogGiftView: FC<{}> = props => switch(event.type) { case CatalogPurchasedEvent.PURCHASE_SUCCESS: + isBuyingGift = false; onClose(); return; case CatalogEvent.INIT_GIFT: diff --git a/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx index 4a62f88..1722db8 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx @@ -6,6 +6,8 @@ import { useCatalog, useMessageEvent, useNavigator, useRoomPromote } from '../.. import { NitroInput } from '../../../../../layout'; import { CatalogLayoutProps } from './CatalogLayout.types'; +let isPurchasingAd = false; + export const CatalogLayoutRoomAdsView: FC = props => { const { page = null } = props; @@ -45,6 +47,10 @@ export const CatalogLayoutRoomAdsView: FC = props => const purchaseAd = () => { + if(isPurchasingAd) return; + + isPurchasingAd = true; + const pageId = page.pageId; const offerId = page.offers.length >= 1 ? page.offers[0].offerId : -1; const flatId = roomId; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx index 897fd58..1cb5283 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx @@ -1,5 +1,5 @@ import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { CatalogPurchaseState, LocalizeText, SendMessageComposer } from '../../../../../api'; import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common'; import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events'; @@ -13,15 +13,18 @@ export const CatalogLayoutVipBuyView: FC = props => const { currentPage = null, catalogOptions = null } = useCatalog(); const { purse = null, getCurrencyAmount = null } = usePurse(); const { clubOffers = null } = catalogOptions; + const isPurchasingRef = useRef(false); const onCatalogEvent = useCallback((event: CatalogEvent) => { switch(event.type) { case CatalogPurchasedEvent.PURCHASE_SUCCESS: + isPurchasingRef.current = false; setPurchaseState(CatalogPurchaseState.NONE); return; case CatalogPurchaseFailureEvent.PURCHASE_FAILED: + isPurchasingRef.current = false; setPurchaseState(CatalogPurchaseState.FAILED); return; } @@ -83,8 +86,9 @@ export const CatalogLayoutVipBuyView: FC = props => const purchaseSubscription = useCallback(() => { - if(!pendingOffer) return; + if(!pendingOffer || isPurchasingRef.current) return; + isPurchasingRef.current = true; setPurchaseState(CatalogPurchaseState.PURCHASE); SendMessageComposer(new PurchaseFromCatalogComposer(currentPage.pageId, pendingOffer.offerId, null, 1)); }, [ pendingOffer, currentPage ]); diff --git a/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceOwnItemsView.tsx b/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceOwnItemsView.tsx index 3e1f847..4acd055 100644 --- a/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceOwnItemsView.tsx +++ b/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceOwnItemsView.tsx @@ -1,5 +1,5 @@ import { CancelMarketplaceOfferMessageComposer, GetMarketplaceOwnOffersMessageComposer, MarketplaceCancelOfferResultEvent, MarketplaceOwnOffersEvent, RedeemMarketplaceOfferCreditsMessageComposer } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { LocalizeText, MarketplaceOfferData, MarketPlaceOfferState, NotificationAlertType, SendMessageComposer } from '../../../../../../api'; import { Button, Column, Text } from '../../../../../../common'; import { useMessageEvent, useNotification } from '../../../../../../hooks'; @@ -11,6 +11,8 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC = prop const [ creditsWaiting, setCreditsWaiting ] = useState(0); const [ offers, setOffers ] = useState([]); const { simpleAlert = null } = useNotification(); + const isRedeemingRef = useRef(false); + const pendingCancelsRef = useRef>(new Set()); useMessageEvent(MarketplaceOwnOffersEvent, event => { @@ -54,6 +56,10 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC = prop const redeemSoldOffers = useCallback(() => { + if(isRedeemingRef.current) return; + + isRedeemingRef.current = true; + setOffers(prevValue => { const idsToDelete = soldOffers.map(value => value.offerId); @@ -62,11 +68,19 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC = prop }); SendMessageComposer(new RedeemMarketplaceOfferCreditsMessageComposer()); + + setTimeout(() => isRedeemingRef.current = false, 3000); }, [ soldOffers ]); const takeItemBack = (offerData: MarketplaceOfferData) => { + if(pendingCancelsRef.current.has(offerData.offerId)) return; + + pendingCancelsRef.current.add(offerData.offerId); + SendMessageComposer(new CancelMarketplaceOfferMessageComposer(offerData.offerId)); + + setTimeout(() => pendingCancelsRef.current.delete(offerData.offerId), 2000); }; useEffect(() => diff --git a/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplacePublicItemsView.tsx b/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplacePublicItemsView.tsx index b95d948..459f2ce 100644 --- a/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplacePublicItemsView.tsx +++ b/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplacePublicItemsView.tsx @@ -1,5 +1,5 @@ import { BuyMarketplaceOfferMessageComposer, GetMarketplaceOffersMessageComposer, MarketplaceBuyOfferResultEvent, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useMemo, useState } from 'react'; +import { FC, useCallback, useMemo, useRef, useState } from 'react'; import { IMarketplaceSearchOptions, LocalizeText, MarketplaceOfferData, MarketplaceSearchType, NotificationAlertType, SendMessageComposer } from '../../../../../../api'; import { Button, Column, Text } from '../../../../../../common'; import { useMessageEvent, useNotification, usePurse } from '../../../../../../hooks'; @@ -23,6 +23,7 @@ export const CatalogLayoutMarketplacePublicItemsView: FC({ minPrice: -1, maxPrice: -1, query: '', type: 3 }); const { getCurrencyAmount = null } = usePurse(); const { simpleAlert = null, showConfirm = null } = useNotification(); + const isBuyingRef = useRef(false); const requestOffers = useCallback((options: IMarketplaceSearchOptions) => { @@ -56,6 +57,9 @@ export const CatalogLayoutMarketplacePublicItemsView: FC { + if(isBuyingRef.current) return; + + isBuyingRef.current = true; SendMessageComposer(new BuyMarketplaceOfferMessageComposer(offerId)); }, null, null, null, LocalizeText('catalog.marketplace.confirm_title')); @@ -83,6 +87,8 @@ export const CatalogLayoutMarketplacePublicItemsView: FC = props => { const [ item, setItem ] = useState(null); @@ -65,10 +67,15 @@ export const MarketplacePostOfferView: FC<{}> = props => const postItem = () => { - if(!item || (askingPrice < marketplaceConfiguration.minimumPrice)) return; + if(!item || (askingPrice < marketplaceConfiguration.minimumPrice) || isPostingMarketplaceOffer) return; showConfirm(LocalizeText('inventory.marketplace.confirm_offer.info', [ 'furniname', 'price' ], [ getFurniTitle, askingPrice.toString() ]), () => { + if(isPostingMarketplaceOffer) return; + + isPostingMarketplaceOffer = true; + setTimeout(() => isPostingMarketplaceOffer = false, 5000); + SendMessageComposer(new MakeOfferMessageComposer(askingPrice, item.isWallItem ? 2 : 1, item.id)); setItem(null); }, diff --git a/src/components/catalog/views/page/layout/vip-gifts/CatalogLayoutVipGiftsView.tsx b/src/components/catalog/views/page/layout/vip-gifts/CatalogLayoutVipGiftsView.tsx index 02daa03..f627250 100644 --- a/src/components/catalog/views/page/layout/vip-gifts/CatalogLayoutVipGiftsView.tsx +++ b/src/components/catalog/views/page/layout/vip-gifts/CatalogLayoutVipGiftsView.tsx @@ -6,6 +6,8 @@ import { useCatalog, useNotification, usePurse } from '../../../../../../hooks'; import { CatalogLayoutProps } from '../CatalogLayout.types'; import { VipGiftItem } from './VipGiftItemView'; +let isSelectingGift = false; + export const CatalogLayoutVipGiftsView: FC = props => { const { purse = null } = usePurse(); @@ -30,6 +32,10 @@ export const CatalogLayoutVipGiftsView: FC = props => { showConfirm(LocalizeText('catalog.club_gift.confirm'), () => { + if(isSelectingGift) return; + + isSelectingGift = true; + SendMessageComposer(new SelectClubGiftComposer(localizationId)); setCatalogOptions(prevValue => @@ -38,6 +44,8 @@ export const CatalogLayoutVipGiftsView: FC = props => return { ...prevValue }; }); + + setTimeout(() => isSelectingGift = false, 5000); }, null); }, [ setCatalogOptions, showConfirm ]); diff --git a/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx index 25cf610..7f2837d 100644 --- a/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx @@ -11,6 +11,8 @@ interface CatalogPurchaseWidgetViewProps purchaseCallback?: () => void; } +let isPurchasingCatalogItem = false; + export const CatalogPurchaseWidgetView: FC = props => { const { noGiftOption = false, purchaseCallback = null } = props; @@ -25,15 +27,19 @@ export const CatalogPurchaseWidgetView: FC = pro switch(event.type) { case CatalogPurchasedEvent.PURCHASE_SUCCESS: + isPurchasingCatalogItem = false; setPurchaseState(CatalogPurchaseState.NONE); return; case CatalogPurchaseFailureEvent.PURCHASE_FAILED: + isPurchasingCatalogItem = false; setPurchaseState(CatalogPurchaseState.FAILED); return; case CatalogPurchaseNotAllowedEvent.NOT_ALLOWED: + isPurchasingCatalogItem = false; setPurchaseState(CatalogPurchaseState.FAILED); return; case CatalogPurchaseSoldOutEvent.SOLD_OUT: + isPurchasingCatalogItem = false; setPurchaseState(CatalogPurchaseState.SOLD_OUT); return; } @@ -62,7 +68,7 @@ export const CatalogPurchaseWidgetView: FC = pro const purchase = (isGift: boolean = false) => { - if(!currentOffer) return; + if(!currentOffer || isPurchasingCatalogItem) return; if(GetClubMemberLevel() < currentOffer.clubLevel) { @@ -78,6 +84,7 @@ export const CatalogPurchaseWidgetView: FC = pro return; } + isPurchasingCatalogItem = true; setPurchaseState(CatalogPurchaseState.PURCHASE); if(purchaseCallback) diff --git a/src/components/catalog/views/targeted-offer/OfferWindowView.tsx b/src/components/catalog/views/targeted-offer/OfferWindowView.tsx index e3052ed..0d00732 100644 --- a/src/components/catalog/views/targeted-offer/OfferWindowView.tsx +++ b/src/components/catalog/views/targeted-offer/OfferWindowView.tsx @@ -4,6 +4,8 @@ import { FriendlyTime, GetConfigurationValue, LocalizeText, SendMessageComposer import { Button, Column, Flex, LayoutCurrencyIcon, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; import { usePurse } from '../../../../hooks'; +let isBuyingOffer = false; + export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Dispatch> }) => { const { offer = null, setOpen = null } = props; @@ -37,8 +39,14 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp const buyOffer = () => { + if(isBuyingOffer) return; + + isBuyingOffer = true; + SendMessageComposer(new PurchaseTargetedOfferComposer(offer.id, amount)); SendMessageComposer(new GetTargetedOfferComposer()); + + setTimeout(() => isBuyingOffer = false, 5000); }; if(!offer) return; diff --git a/src/components/groups/views/GroupCreatorView.tsx b/src/components/groups/views/GroupCreatorView.tsx index 5bdc086..d0d24d1 100644 --- a/src/components/groups/views/GroupCreatorView.tsx +++ b/src/components/groups/views/GroupCreatorView.tsx @@ -15,6 +15,8 @@ interface GroupCreatorViewProps const TABS: number[] = [ 1, 2, 3, 4 ]; +let isBuyingGroup = false; + export const GroupCreatorView: FC = props => { const { onClose = null } = props; @@ -34,7 +36,10 @@ export const GroupCreatorView: FC = props => const buyGroup = () => { - if(!groupData) return; + if(!groupData || isBuyingGroup) return; + + isBuyingGroup = true; + setTimeout(() => isBuyingGroup = false, 5000); const badge = []; diff --git a/src/components/groups/views/GroupMembersView.tsx b/src/components/groups/views/GroupMembersView.tsx index 198c9ca..5adc6db 100644 --- a/src/components/groups/views/GroupMembersView.tsx +++ b/src/components/groups/views/GroupMembersView.tsx @@ -1,5 +1,5 @@ import { AddLinkEventTracker, GetSessionDataManager, GroupAdminGiveComposer, GroupAdminTakeComposer, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembersParser, GroupRank, GroupRemoveMemberComposer, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; import { GetUserProfile, LocalizeText, SendMessageComposer } from '../../../api'; import { Button, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; @@ -16,6 +16,7 @@ export const GroupMembersView: FC<{}> = props => const [ searchQuery, setSearchQuery ] = useState(''); const [ removingMemberName, setRemovingMemberName ] = useState(null); const { showConfirm = null } = useNotification(); + const pendingActionsRef = useRef>(new Set()); const getRankDescription = (member: GroupMemberParser) => { @@ -42,6 +43,11 @@ export const GroupMembersView: FC<{}> = props => { if(!membersData.admin || (member.rank === GroupRank.OWNER)) return; + const key = `admin_${member.id}`; + if(pendingActionsRef.current.has(key)) return; + pendingActionsRef.current.add(key); + setTimeout(() => pendingActionsRef.current.delete(key), 2000); + if(member.rank !== GroupRank.ADMIN) SendMessageComposer(new GroupAdminGiveComposer(membersData.groupId, member.id)); else SendMessageComposer(new GroupAdminTakeComposer(membersData.groupId, member.id)); @@ -52,6 +58,11 @@ export const GroupMembersView: FC<{}> = props => { if(!membersData.admin || (member.rank !== GroupRank.REQUESTED)) return; + const key = `accept_${member.id}`; + if(pendingActionsRef.current.has(key)) return; + pendingActionsRef.current.add(key); + setTimeout(() => pendingActionsRef.current.delete(key), 2000); + SendMessageComposer(new GroupMembershipAcceptComposer(membersData.groupId, member.id)); refreshMembers(); @@ -61,6 +72,11 @@ export const GroupMembersView: FC<{}> = props => { if(!membersData.admin) return; + const key = `remove_${member.id}`; + if(pendingActionsRef.current.has(key)) return; + pendingActionsRef.current.add(key); + setTimeout(() => pendingActionsRef.current.delete(key), 2000); + if(member.rank === GroupRank.REQUESTED) { SendMessageComposer(new GroupMembershipDeclineComposer(membersData.groupId, member.id)); diff --git a/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx index a8de00e..9aaa441 100644 --- a/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx +++ b/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx @@ -1,5 +1,5 @@ import { IssueMessageData, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer'; -import { FC } from 'react'; +import { FC, useRef } from 'react'; import { SendMessageComposer } from '../../../../api'; import { Button, Column, Grid } from '../../../../common'; @@ -12,6 +12,17 @@ interface ModToolsMyIssuesTabViewProps export const ModToolsMyIssuesTabView: FC = props => { const { myIssues = null, handleIssue = null } = props; + const pendingReleasesRef = useRef>(new Set()); + + const releaseIssue = (issueId: number) => + { + if(pendingReleasesRef.current.has(issueId)) return; + + pendingReleasesRef.current.add(issueId); + SendMessageComposer(new ReleaseIssuesMessageComposer([ issueId ])); + + setTimeout(() => pendingReleasesRef.current.delete(issueId), 2000); + }; return ( @@ -36,7 +47,7 @@ export const ModToolsMyIssuesTabView: FC = props =
- +
); diff --git a/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx index 17e4901..387580b 100644 --- a/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx +++ b/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx @@ -1,5 +1,5 @@ import { IssueMessageData, PickIssuesMessageComposer } from '@nitrots/nitro-renderer'; -import { FC } from 'react'; +import { FC, useRef } from 'react'; import { SendMessageComposer } from '../../../../api'; import { Button, Column, Grid } from '../../../../common'; @@ -11,6 +11,17 @@ interface ModToolsOpenIssuesTabViewProps export const ModToolsOpenIssuesTabView: FC = props => { const { openIssues = null } = props; + const pendingPicksRef = useRef>(new Set()); + + const pickIssue = (issueId: number) => + { + if(pendingPicksRef.current.has(issueId)) return; + + pendingPicksRef.current.add(issueId); + SendMessageComposer(new PickIssuesMessageComposer([ issueId ], false, 0, 'pick issue button')); + + setTimeout(() => pendingPicksRef.current.delete(issueId), 2000); + }; return ( @@ -31,7 +42,7 @@ export const ModToolsOpenIssuesTabView: FC = pro
{ issue.reportedUserName }
{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }
- +
); diff --git a/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx b/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx index 2dcdd3e..1bea10a 100644 --- a/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx +++ b/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx @@ -1,5 +1,5 @@ import { CallForHelpTopicData, DefaultSanctionMessageComposer, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModTradingLockMessageComposer } from '@nitrots/nitro-renderer'; -import { FC, useMemo, useState } from 'react'; +import { FC, useMemo, useRef, useState } from 'react'; import { ISelectedUser, LocalizeText, ModActionDefinition, NotificationAlertType, SendMessageComposer } from '../../../../api'; import { Button, DraggableWindowPosition, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; import { useModTools, useNotification } from '../../../../hooks'; @@ -33,6 +33,7 @@ export const ModToolsUserModActionView: FC = pro const [ message, setMessage ] = useState(''); const { cfhCategories = null, settings = null } = useModTools(); const { simpleAlert = null } = useNotification(); + const isSendingRef = useRef(false); const topics = useMemo(() => { @@ -53,6 +54,8 @@ export const ModToolsUserModActionView: FC = pro const sendDefaultSanction = () => { + if(isSendingRef.current) return; + let errorMessage: string = null; const category = topics[selectedTopic]; @@ -63,6 +66,8 @@ export const ModToolsUserModActionView: FC = pro const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message; + isSendingRef.current = true; + SendMessageComposer(new DefaultSanctionMessageComposer(user.userId, selectedTopic, messageOrDefault)); onCloseClick(); @@ -70,6 +75,8 @@ export const ModToolsUserModActionView: FC = pro const sendSanction = () => { + if(isSendingRef.current) return; + let errorMessage: string = null; const category = topics[selectedTopic]; @@ -145,6 +152,8 @@ export const ModToolsUserModActionView: FC = pro } } + isSendingRef.current = true; + onCloseClick(); }; diff --git a/src/components/navigator/views/NavigatorRoomCreatorView.tsx b/src/components/navigator/views/NavigatorRoomCreatorView.tsx index 0b4adf4..9307a4b 100644 --- a/src/components/navigator/views/NavigatorRoomCreatorView.tsx +++ b/src/components/navigator/views/NavigatorRoomCreatorView.tsx @@ -6,6 +6,9 @@ import { Button, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, Text } from '.. import { useNavigator } from '../../../hooks'; import { NitroInput } from '../../../layout'; +let isCreatingRoom = false; +let createRoomTimeout: ReturnType = null; + export const NavigatorRoomCreatorView: FC<{}> = props => { const [ maxVisitorsList, setMaxVisitorsList ] = useState(null); @@ -16,6 +19,7 @@ export const NavigatorRoomCreatorView: FC<{}> = props => const [ tradesSetting, setTradesSetting ] = useState(0); const [ roomModels, setRoomModels ] = useState([]); const [ selectedModelName, setSelectedModelName ] = useState(''); + const [ isCreating, setIsCreating ] = useState(isCreatingRoom); const { categories = null } = useNavigator(); const hcDisabled = GetConfigurationValue('hc.disabled', false); @@ -31,7 +35,19 @@ export const NavigatorRoomCreatorView: FC<{}> = props => const createRoom = () => { + if(isCreatingRoom) return; + + isCreatingRoom = true; + setIsCreating(true); + SendMessageComposer(new CreateFlatMessageComposer(name, description, 'model_' + selectedModelName, Number(category), Number(visitorsCount), tradesSetting)); + + if(createRoomTimeout) clearTimeout(createRoomTimeout); + createRoomTimeout = setTimeout(() => + { + isCreatingRoom = false; + setIsCreating(false); + }, 5000); }; useEffect(() => @@ -117,7 +133,7 @@ export const NavigatorRoomCreatorView: FC<{}> = props => } - + ); }; diff --git a/src/components/navigator/views/room-settings/NavigatorRoomSettingsRightsTabView.tsx b/src/components/navigator/views/room-settings/NavigatorRoomSettingsRightsTabView.tsx index 3f77676..6f166ae 100644 --- a/src/components/navigator/views/room-settings/NavigatorRoomSettingsRightsTabView.tsx +++ b/src/components/navigator/views/room-settings/NavigatorRoomSettingsRightsTabView.tsx @@ -1,5 +1,5 @@ import { FlatControllerAddedEvent, FlatControllerRemovedEvent, FlatControllersEvent, RemoveAllRightsMessageComposer, RoomGiveRightsComposer, RoomTakeRightsComposer, RoomUsersWithRightsComposer } from '@nitrots/nitro-renderer'; -import { FC, useEffect, useState } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import { IRoomData, LocalizeText, SendMessageComposer } from '../../../../api'; import { Button, Column, Flex, Grid, Text, UserProfileIconView } from '../../../../common'; import { useFriends, useMessageEvent } from '../../../../hooks'; @@ -18,6 +18,17 @@ export const NavigatorRoomSettingsRightsTabView: FC>(new Map()); const { onlineFriends = [], offlineFriends = [] } = useFriends(); + const pendingActionsRef = useRef>(new Set()); + + const guardedSend = (key: string, composer: any) => + { + if(pendingActionsRef.current.has(key)) return; + + pendingActionsRef.current.add(key); + SendMessageComposer(composer); + + setTimeout(() => pendingActionsRef.current.delete(key), 2000); + }; const allFriendsRaw = [ ...onlineFriends, ...offlineFriends ]; @@ -115,7 +126,7 @@ export const NavigatorRoomSettingsRightsTabView: FC SendMessageComposer(new RoomTakeRightsComposer(id)) }> + onClick={ () => guardedSend(`take_${id}`, new RoomTakeRightsComposer(id)) }> { name } @@ -127,7 +138,7 @@ export const NavigatorRoomSettingsRightsTabView: FC roomData && SendMessageComposer(new RemoveAllRightsMessageComposer(roomData.roomId)) }> + onClick={ () => roomData && guardedSend('removeAll', new RemoveAllRightsMessageComposer(roomData.roomId)) }> { LocalizeText('navigator.flatctrls.clear') }
@@ -154,7 +165,7 @@ export const NavigatorRoomSettingsRightsTabView: FC SendMessageComposer(new RoomGiveRightsComposer(friend.id)) }> + onClick={ () => guardedSend(`give_${friend.id}`, new RoomGiveRightsComposer(friend.id)) }> { friend.name }