ui/src/components/Modal/BadgeModal.tsx (125 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import { FC, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import classNames from 'classnames'; import type * as Type from '@/common/interface'; import { loggedUserInfoStore } from '@/stores'; import { readNotification, useQueryNotificationStatus } from '@/services'; import AnimateGift from '@/utils/animateGift'; import Icon from '../Icon'; import Modal from './Modal'; interface BadgeModalProps { badge?: Type.NotificationBadgeAward | null; visible: boolean; } let bg1: AnimateGift; let bg2: AnimateGift; let timeout: NodeJS.Timeout; const BadgeModal: FC<BadgeModalProps> = ({ badge, visible }) => { const { t } = useTranslation('translation', { keyPrefix: 'badges.modal' }); const { user } = loggedUserInfoStore(); const navigate = useNavigate(); const { data, mutate } = useQueryNotificationStatus(); const handle = async () => { if (!data) return; await readNotification(badge?.notification_id); await mutate({ ...data, badge_award: null, }); clearTimeout(timeout); bg1?.destroy(); bg2?.destroy(); }; const handleCancel = async () => { await handle(); }; const handleConfirm = async () => { await handle(); const url = `/badges/${badge?.badge_id}?username=${user.username}`; navigate(url); }; const initAnimation = () => { const DURATION = 8000; const LENGTH = 200; const bgNode = document.documentElement || document.body; const badgeModalNode = document.getElementById('badgeModal'); const parentNode = badgeModalNode?.parentNode; badgeModalNode?.setAttribute('style', 'z-index: 1'); if (parentNode) { bg1 = new AnimateGift({ elm: parentNode, width: bgNode.clientWidth, height: bgNode.clientHeight, length: LENGTH, duration: DURATION, isLoop: true, }); timeout = setTimeout(() => { bg2 = new AnimateGift({ elm: parentNode, width: window.innerWidth, height: window.innerHeight, length: LENGTH, duration: DURATION, }); }, DURATION / 2); } }; const destroyAnimation = () => { clearTimeout(timeout); bg1?.destroy(); bg2?.destroy(); }; useEffect(() => { if (visible) { initAnimation(); } else { destroyAnimation(); } const handleVisibilityChange = () => { if (document.visibilityState === 'visible') { initAnimation(); } else { destroyAnimation(); } }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => { document.removeEventListener('visibilitychange', handleVisibilityChange); destroyAnimation(); }; }, [visible]); return ( <Modal id="badgeModal" title={t('title')} visible={visible} onCancel={handleCancel} onConfirm={handleConfirm} cancelText={t('close')} cancelBtnVariant="link" confirmText={t('confirm')} confirmBtnVariant="primary" scrollable={false}> {badge && ( <div className="text-center"> {badge.icon?.startsWith('http') ? ( <img src={badge.icon} width={96} height={96} alt={badge.name} /> ) : ( <Icon name={badge.icon} size="96px" className={classNames( 'lh-1', badge.level === 1 && 'bronze', badge.level === 2 && 'silver', badge.level === 3 && 'gold', )} /> )} <h5 className="mt-3">{badge?.name}</h5> <p>{t('content')}</p> </div> )} </Modal> ); }; export default BadgeModal;