From 338af097cadbc7cb1303f0169caeeb6e20538a5a Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Wed, 11 Jan 2023 00:01:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=8B=96=E6=8B=BD?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=8F=91=E9=80=81=E5=9B=BE=E7=89=87=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/i18n/langs/en-US/translation.json | 1 + .../shared/i18n/langs/zh-CN/translation.json | 1 + client/web/package.json | 2 + client/web/src/App.tsx | 10 ++- .../ChatBox/ChatInputBox/ChatDropArea.tsx | 62 +++++++++++++++++++ .../components/ChatBox/ChatInputBox/index.tsx | 3 + pnpm-lock.yaml | 55 ++++++++++++++++ 7 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 client/web/src/components/ChatBox/ChatInputBox/ChatDropArea.tsx diff --git a/client/shared/i18n/langs/en-US/translation.json b/client/shared/i18n/langs/en-US/translation.json index 2717dd789f1..02c86cc824f 100644 --- a/client/shared/i18n/langs/en-US/translation.json +++ b/client/shared/i18n/langs/en-US/translation.json @@ -284,6 +284,7 @@ "ke071c620": "Allow members to manage users, such as banning, removing users, etc.", "ke17b2c87": "Do not upload pictures that violate local laws and regulations", "ke187440d": "Panel type cannot be empty", + "ke3d797fd": "Drop files to send into current converse", "ke59ffe49": "Muted, there are {{remain}} left", "kea977d95": "The following users are offline", "kec46a57f": "Add members", diff --git a/client/shared/i18n/langs/zh-CN/translation.json b/client/shared/i18n/langs/zh-CN/translation.json index a3fa2f8e908..9d1cafed444 100644 --- a/client/shared/i18n/langs/zh-CN/translation.json +++ b/client/shared/i18n/langs/zh-CN/translation.json @@ -284,6 +284,7 @@ "ke071c620": "允许成员管理用户,如禁言、移除用户等操作", "ke17b2c87": "请勿上传违反当地法律法规的图片", "ke187440d": "面板类型不能为空", + "ke3d797fd": "拖放文件以发送到当前会话", "ke59ffe49": "禁言中, 还剩 {{remain}}", "kea977d95": "以下用户已离线", "kec46a57f": "添加成员", diff --git a/client/web/package.json b/client/web/package.json index ce8b2fab7c9..e2985b46b23 100644 --- a/client/web/package.json +++ b/client/web/package.json @@ -49,6 +49,8 @@ "qs": "^6.11.0", "rc-tree": "^5.7.2", "react": "18.2.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", "react-dom": "18.2.0", "react-easy-crop": "^3.5.3", "react-helmet": "^6.1.0", diff --git a/client/web/src/App.tsx b/client/web/src/App.tsx index d552cf0085c..34ac9795437 100644 --- a/client/web/src/App.tsx +++ b/client/web/src/App.tsx @@ -23,6 +23,8 @@ import { pluginRootRoute } from './plugin/common'; import { PortalHost as FallbackPortalHost } from './components/Portal'; import isElectron from 'is-electron'; import { AppRouterApi } from './components/AppRouterApi'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; const AppRouter: any = isElectron() ? HashRouter : BrowserRouter; @@ -55,9 +57,11 @@ const AppProvider: React.FC = React.memo((props) => { }> - - {props.children} - + + + {props.children} + + diff --git a/client/web/src/components/ChatBox/ChatInputBox/ChatDropArea.tsx b/client/web/src/components/ChatBox/ChatInputBox/ChatDropArea.tsx new file mode 100644 index 00000000000..7fad1e553e6 --- /dev/null +++ b/client/web/src/components/ChatBox/ChatInputBox/ChatDropArea.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { t, useMemoizedFn } from 'tailchat-shared'; +import { DropTargetMonitor, useDrop } from 'react-dnd'; +import { NativeTypes } from 'react-dnd-html5-backend'; +import { useChatInputActionContext } from './context'; +import { uploadMessageImage } from './utils'; +import { getMessageTextDecorators } from '@/plugin/common'; +import { Icon } from 'tailchat-design'; + +export const ChatDropArea: React.FC = React.memo(() => { + const actionContext = useChatInputActionContext(); + + const handleDrop = useMemoizedFn((files: File[]) => { + const images = files.filter((f) => f.type.startsWith('image/')); + if (images.length > 0) { + // 目前只取一张 + const img = images[0]; + uploadMessageImage(img).then(({ url, width, height }) => { + actionContext?.sendMsg( + getMessageTextDecorators().image(url, { width, height }) + ); + }); + } + }); + + const [collectedProps, ref] = useDrop({ + accept: [NativeTypes.FILE], + drop(item: { files: any[] }) { + handleDrop(item.files); + }, + canDrop(item: any) { + return true; + }, + collect: (monitor: DropTargetMonitor) => { + return { + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }; + }, + }); + + if (!collectedProps.canDrop) { + return; + } + + return ( +
+
+
+ +
+
{t('拖放文件以发送到当前会话')}
+
+
+ ); +}); +ChatDropArea.displayName = 'ChatDropArea'; diff --git a/client/web/src/components/ChatBox/ChatInputBox/index.tsx b/client/web/src/components/ChatBox/ChatInputBox/index.tsx index 1db3f280feb..dc8db19dd17 100644 --- a/client/web/src/components/ChatBox/ChatInputBox/index.tsx +++ b/client/web/src/components/ChatBox/ChatInputBox/index.tsx @@ -14,6 +14,7 @@ import { } from 'tailchat-shared'; import { ChatInputEmotion } from './Emotion'; import _uniq from 'lodash/uniq'; +import { ChatDropArea } from './ChatDropArea'; interface ChatInputBoxProps { onSendMsg: (msg: string, meta?: SendMessagePayloadMeta) => void; @@ -111,6 +112,8 @@ export const ChatInputBox: React.FC = React.memo((props) => { + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 110eeaf5fc8..225da449364 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -392,6 +392,8 @@ importers: qs: ^6.11.0 rc-tree: ^5.7.2 react: 18.2.0 + react-dnd: ^16.0.1 + react-dnd-html5-backend: ^16.0.1 react-dom: 18.2.0 react-easy-crop: ^3.5.3 react-helmet: ^6.1.0 @@ -461,6 +463,8 @@ importers: qs: 6.11.0 rc-tree: 5.7.2_biqbaboplfbrettd7655fr4n2y react: 18.2.0 + react-dnd: 16.0.1_44xqbblv4eynrtx3g5sxpcjfbe + react-dnd-html5-backend: 16.0.1 react-dom: 18.2.0_react@18.2.0 react-easy-crop: 3.5.3_biqbaboplfbrettd7655fr4n2y react-helmet: 6.1.0_react@18.2.0 @@ -6921,6 +6925,18 @@ packages: tslib: 2.4.1 dev: false + /@react-dnd/asap/5.0.2: + resolution: {integrity: sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==} + dev: false + + /@react-dnd/invariant/4.0.2: + resolution: {integrity: sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==} + dev: false + + /@react-dnd/shallowequal/4.0.2: + resolution: {integrity: sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==} + dev: false + /@reduxjs/toolkit/1.8.5_kkwg4cbsojnjnupd3btipussee: resolution: {integrity: sha512-f4D5EXO7A7Xq35T0zRbWq5kJQyXzzscnHKmjnu2+37B3rwHU6mX9PYlbfXdnxcY6P/7zfmjhgan0Z+yuOfeBmA==} peerDependencies: @@ -16987,6 +17003,14 @@ packages: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} dev: false + /dnd-core/16.0.1: + resolution: {integrity: sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==} + dependencies: + '@react-dnd/asap': 5.0.2 + '@react-dnd/invariant': 4.0.2 + redux: 4.2.0 + dev: false + /dns-equal/1.0.0: resolution: {integrity: sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==} @@ -29542,6 +29566,37 @@ packages: - utf-8-validate dev: false + /react-dnd-html5-backend/16.0.1: + resolution: {integrity: sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==} + dependencies: + dnd-core: 16.0.1 + dev: false + + /react-dnd/16.0.1_44xqbblv4eynrtx3g5sxpcjfbe: + resolution: {integrity: sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==} + peerDependencies: + '@types/hoist-non-react-statics': '>= 3.3.1' + '@types/node': '>= 12' + '@types/react': '>= 16' + react: '>= 16.14' + peerDependenciesMeta: + '@types/hoist-non-react-statics': + optional: true + '@types/node': + optional: true + '@types/react': + optional: true + dependencies: + '@react-dnd/invariant': 4.0.2 + '@react-dnd/shallowequal': 4.0.2 + '@types/node': 15.14.9 + '@types/react': 18.0.20 + dnd-core: 16.0.1 + fast-deep-equal: 3.1.3 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + dev: false + /react-docgen-typescript/2.2.2_typescript@4.7.4: resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} peerDependencies: