emojis.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import { Avatar, Button, Card, Col, Row, Tooltip, Typography } from 'antd';
  2. import Upload, { RcFile } from 'antd/lib/upload';
  3. import React, { ReactElement, useEffect, useState } from 'react';
  4. import dynamic from 'next/dynamic';
  5. import FormStatusIndicator from '../../../components/admin/FormStatusIndicator';
  6. import { DELETE_EMOJI, fetchData, UPLOAD_EMOJI } from '../../../utils/apis';
  7. import { ACCEPTED_IMAGE_TYPES, getBase64 } from '../../../utils/images';
  8. import {
  9. createInputStatus,
  10. STATUS_ERROR,
  11. STATUS_PROCESSING,
  12. STATUS_SUCCESS,
  13. } from '../../../utils/input-statuses';
  14. import { RESET_TIMEOUT } from '../../../utils/config-constants';
  15. import { AdminLayout } from '../../../components/layouts/AdminLayout';
  16. const URL_CUSTOM_EMOJIS = `/api/emoji`;
  17. const { Meta } = Card;
  18. // Lazy loaded components
  19. const CloseOutlined = dynamic(() => import('@ant-design/icons/CloseOutlined'), {
  20. ssr: false,
  21. });
  22. type CustomEmoji = {
  23. name: string;
  24. url: string;
  25. };
  26. const { Title, Paragraph } = Typography;
  27. const Emoji = () => {
  28. const [emojis, setEmojis] = useState<CustomEmoji[]>([]);
  29. const [loading, setLoading] = useState(false);
  30. const [submitStatus, setSubmitStatus] = useState(null);
  31. const [uploadFile, setUploadFile] = useState<RcFile>(null);
  32. let resetTimer = null;
  33. const resetStates = () => {
  34. setSubmitStatus(null);
  35. clearTimeout(resetTimer);
  36. resetTimer = null;
  37. };
  38. async function getEmojis() {
  39. setLoading(true);
  40. try {
  41. const response = await fetchData(URL_CUSTOM_EMOJIS);
  42. setEmojis(response);
  43. } catch (error) {
  44. console.error('error fetching emojis', error);
  45. }
  46. setLoading(false);
  47. }
  48. useEffect(() => {
  49. getEmojis();
  50. }, []);
  51. async function handleDelete(fullPath: string) {
  52. const name = `/${fullPath.split('/').slice(3).join('/')}`;
  53. console.log(name);
  54. setLoading(true);
  55. setSubmitStatus(createInputStatus(STATUS_PROCESSING, 'Deleting emoji...'));
  56. try {
  57. const response = await fetchData(DELETE_EMOJI, {
  58. method: 'POST',
  59. data: { name },
  60. });
  61. if (response instanceof Error) {
  62. throw response;
  63. }
  64. setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Emoji deleted'));
  65. resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
  66. } catch (error) {
  67. setSubmitStatus(createInputStatus(STATUS_ERROR, `${error}`));
  68. setLoading(false);
  69. resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
  70. }
  71. getEmojis();
  72. }
  73. async function handleEmojiUpload() {
  74. setLoading(true);
  75. try {
  76. setSubmitStatus(createInputStatus(STATUS_PROCESSING, 'Converting emoji...'));
  77. // eslint-disable-next-line consistent-return
  78. const emojiData = await new Promise<CustomEmoji>((res, rej) => {
  79. if (!ACCEPTED_IMAGE_TYPES.includes(uploadFile.type)) {
  80. const msg = `File type is not supported: ${uploadFile.type}`;
  81. // eslint-disable-next-line no-promise-executor-return
  82. return rej(msg);
  83. }
  84. getBase64(uploadFile, (url: string) =>
  85. res({
  86. name: uploadFile.name,
  87. url,
  88. }),
  89. );
  90. });
  91. setSubmitStatus(createInputStatus(STATUS_PROCESSING, 'Uploading emoji...'));
  92. const response = await fetchData(UPLOAD_EMOJI, {
  93. method: 'POST',
  94. data: {
  95. name: emojiData.name,
  96. data: emojiData.url,
  97. },
  98. });
  99. if (response instanceof Error) {
  100. throw response;
  101. }
  102. setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Emoji uploaded successfully!'));
  103. getEmojis();
  104. } catch (error) {
  105. setSubmitStatus(createInputStatus(STATUS_ERROR, `${error}`));
  106. }
  107. resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
  108. setLoading(false);
  109. }
  110. return (
  111. <div>
  112. <Title>Emojis</Title>
  113. <Paragraph>
  114. Here you can upload new custom emojis for usage in the chat. When uploading a new emoji, the
  115. filename will be used as emoji name.
  116. </Paragraph>
  117. <br />
  118. <Upload
  119. name="emoji"
  120. listType="picture"
  121. className="emoji-uploader"
  122. showUploadList={false}
  123. accept={ACCEPTED_IMAGE_TYPES.join(',')}
  124. beforeUpload={setUploadFile}
  125. customRequest={handleEmojiUpload}
  126. disabled={loading}
  127. >
  128. <Button type="primary" disabled={loading}>
  129. Upload new emoji
  130. </Button>
  131. </Upload>
  132. <FormStatusIndicator status={submitStatus} />
  133. <br />
  134. <Row>
  135. {emojis.map(record => (
  136. <Col style={{ padding: '10px' }} key={record.name}>
  137. <Card style={{ width: 120, marginTop: 16 }} actions={[]}>
  138. <Meta
  139. description={[
  140. <div
  141. style={{
  142. display: 'flex',
  143. justifyItems: 'center',
  144. alignItems: 'center',
  145. flexDirection: 'column',
  146. gap: '20px',
  147. }}
  148. >
  149. <Tooltip title={record.name}>
  150. <Avatar style={{ height: 50, width: 50 }} src={record.url} />
  151. </Tooltip>
  152. <Button
  153. size="small"
  154. type="ghost"
  155. title="Delete emoji"
  156. style={{
  157. position: 'absolute',
  158. right: 0,
  159. top: 0,
  160. height: 24,
  161. width: 24,
  162. border: 'none',
  163. color: 'gray',
  164. }}
  165. onClick={() => handleDelete(record.url)}
  166. icon={<CloseOutlined />}
  167. />
  168. </div>,
  169. ]}
  170. />
  171. </Card>
  172. </Col>
  173. ))}
  174. </Row>
  175. <br />
  176. </div>
  177. );
  178. };
  179. Emoji.getLayout = function getLayout(page: ReactElement) {
  180. return <AdminLayout page={page} />;
  181. };
  182. export default Emoji;