diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e4a4d74..dc62a382 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -159,6 +159,7 @@ importers: axios: ^0.21.1 bundle-stats-webpack-plugin: ^3.2.9 clsx: ^1.1.1 + compressorjs: ^1.1.1 copy-to-clipboard: ^3.3.1 copy-webpack-plugin: ^9.0.1 cross-env: ^7.0.3 @@ -226,6 +227,7 @@ importers: antd: 4.18.2_react-dom@17.0.2+react@17.0.2 axios: 0.21.4 clsx: registry.nlark.com/clsx/1.1.1 + compressorjs: 1.1.1 copy-to-clipboard: 3.3.1 emoji-mart: 3.0.1_react@17.0.2 is-hotkey: 0.2.0 @@ -3467,6 +3469,10 @@ packages: resolution: {integrity: sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/big.js/download/big.js-5.2.2.tgz} dev: true + /blueimp-canvas-to-blob/3.29.0: + resolution: {integrity: sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==} + dev: false + /body-parser/1.19.1: resolution: {integrity: sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npm.taobao.org/body-parser/download/body-parser-1.19.1.tgz} engines: {node: '>= 0.8'} @@ -3831,6 +3837,13 @@ packages: dot-prop: registry.nlark.com/dot-prop/5.3.0 dev: true + /compressorjs/1.1.1: + resolution: {integrity: sha512-SysRuUPfmUNoq+RviE0iMFVUmoX2q/x+7PkEPUmk6NGkd85hDrmvujx0Qtp8UCGA6KMe5kuodsylPQcNaLf60w==} + dependencies: + blueimp-canvas-to-blob: 3.29.0 + is-blob: 2.1.0 + dev: false + /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -5320,6 +5333,11 @@ packages: is-decimal: 1.0.4 dev: false + /is-blob/2.1.0: + resolution: {integrity: sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==} + engines: {node: '>=6'} + dev: false + /is-buffer/2.0.5: resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} engines: {node: '>=4'} diff --git a/shared/i18n/langs/en-US/translation.json b/shared/i18n/langs/en-US/translation.json index e592f099..429516af 100644 --- a/shared/i18n/langs/en-US/translation.json +++ b/shared/i18n/langs/en-US/translation.json @@ -30,11 +30,13 @@ "k3172297b": "This feature is not yet open", "k31a9d6a3": "The connect to the server is broken", "k323b5cc7": "Recall", + "k3279c602": "Add now", "k34b5e3ab": "Send Message", "k34e357ee": "Group Summary", "k35abe359": "Lobby", "k35f486ba": "Nickname", "k35f990b0": "View Detail", + "k393892b6": "Upload original image", "k3ac17670": "An exception occurred, store create failed", "k3b4b656d": "About", "k3bbf3bbd": "Register Account", @@ -46,6 +48,7 @@ "k3f3597fc": "All", "k3fe97dcc": "System settings", "k41064134": "DM", + "k416e301a": "An exception occurred, Socket creation failed", "k419da0ef": "Message explanation", "k4231ab36": "Performance statistics", "k424be044": "This invite expired in <2>{{date}}", @@ -63,11 +66,13 @@ "k517db7e5": "Text Channel", "k51db56bf": "Temporary Meeting", "k551b0348": "Password", + "k56f9469b": "No friends yet", "k57ab4d97": "Please select user", "k58a85592": "Is not a valid plugin configuration", "k5bb71ad7": "Installed", "k5bec387": "Unable to get group information", "k5f91e72c": "Built Plugins", + "k5fc9ccb6": "Operation too frequently", "k61313787": "Login Tailchat", "k61a1db2": "Already applied", "k62051fcc": "Upload failed", @@ -134,6 +139,7 @@ "ka7907771": "Save Successful", "ka7ecc377": "Unpin", "kaa040a8e": "Default Group", + "kaaf56f78": "Debug", "kabb39529": "Save changes", "kabfe9512": "Save", "kad207008": "Edit", diff --git a/shared/i18n/langs/zh-CN/translation.json b/shared/i18n/langs/zh-CN/translation.json index a336e0cf..333b1c95 100644 --- a/shared/i18n/langs/zh-CN/translation.json +++ b/shared/i18n/langs/zh-CN/translation.json @@ -30,11 +30,13 @@ "k3172297b": "该功能暂未开放", "k31a9d6a3": "与服务器的链接已断开", "k323b5cc7": "撤回", + "k3279c602": "立即添加", "k34b5e3ab": "发送消息", "k34e357ee": "群组概述", "k35abe359": "大厅", "k35f486ba": "用户昵称", "k35f990b0": "查看详情", + "k393892b6": "上传原图", "k3ac17670": "出现异常, Store 创建失败", "k3b4b656d": "关于", "k3bbf3bbd": "注册账号", @@ -46,6 +48,7 @@ "k3f3597fc": "全员", "k3fe97dcc": "系统设置", "k41064134": "私信", + "k416e301a": "出现异常, Socket 创建失败", "k419da0ef": "消息解释", "k4231ab36": "性能统计", "k424be044": "该邀请将于 <2>{{date}} 过期", @@ -63,11 +66,13 @@ "k517db7e5": "文字频道", "k51db56bf": "临时会议", "k551b0348": "密码", + "k56f9469b": "暂无好友", "k57ab4d97": "请选择用户", "k58a85592": "不是一个合法的插件配置", "k5bb71ad7": "已安装", "k5bec387": "无法获取到群组信息", "k5f91e72c": "内置插件", + "k5fc9ccb6": "操作过于频繁", "k61313787": "登录 Tailchat", "k61a1db2": "已申请", "k62051fcc": "上传失败", @@ -134,6 +139,7 @@ "ka7907771": "保存成功", "ka7ecc377": "Unpin", "kaa040a8e": "默认群组", + "kaaf56f78": "调试", "kabb39529": "保存变更", "kabfe9512": "保存", "kad207008": "编辑", diff --git a/web/package.json b/web/package.json index 2ab81270..3aad7d3f 100644 --- a/web/package.json +++ b/web/package.json @@ -30,6 +30,7 @@ "antd": "^4.18.2", "axios": "^0.21.1", "clsx": "^1.1.1", + "compressorjs": "^1.1.1", "copy-to-clipboard": "^3.3.1", "emoji-mart": "^3.0.1", "is-hotkey": "^0.2.0", diff --git a/web/src/components/ChatBox/ChatInputBox/utils.tsx b/web/src/components/ChatBox/ChatInputBox/utils.tsx index fd473b5b..2e3e38ce 100644 --- a/web/src/components/ChatBox/ChatInputBox/utils.tsx +++ b/web/src/components/ChatBox/ChatInputBox/utils.tsx @@ -1,6 +1,6 @@ import { closeModal, openModal } from '@/components/Modal'; import { ImageUploadPreviewer } from '@/components/modals/ImageUploadPreviewer'; -import { fileToDataUrl } from '@/utils/file-helper'; +import { compressImage, fileToDataUrl } from '@/utils/file-helper'; import React from 'react'; import { uploadFile } from 'tailchat-shared'; @@ -20,14 +20,28 @@ export function uploadMessageImage(image: File): Promise<{ onCancel={() => { closeModal(key); }} - onConfirm={async (size) => { - const fileInfo = await uploadFile(image); + onConfirm={async (info) => { + let uploadImage = image; + const uploadOriginImage = info.uploadOriginImage; + if (uploadOriginImage === false) { + // 不上传原图 + const originImageSize = image.size; + uploadImage = await compressImage(image); + const compressedImageSize = uploadImage.size; + + console.log( + `压缩结果: ${ + (compressedImageSize / originImageSize) * 100 + }%(${originImageSize} -> ${compressedImageSize})` + ); + } + const fileInfo = await uploadFile(uploadImage); const imageRemoteUrl = fileInfo.url; resolve({ url: imageRemoteUrl, - width: size.width, - height: size.height, + width: info.size.width, + height: info.size.height, }); closeModal(key); }} diff --git a/web/src/components/modals/ImageUploadPreviewer.tsx b/web/src/components/modals/ImageUploadPreviewer.tsx index 3507ac8c..503409eb 100644 --- a/web/src/components/modals/ImageUploadPreviewer.tsx +++ b/web/src/components/modals/ImageUploadPreviewer.tsx @@ -1,9 +1,9 @@ -import { ModalWrapper } from '@/plugin/common'; -import { Button } from '@/plugin/component'; -import React, { useCallback, useRef } from 'react'; -import { t, useAsyncFn } from 'tailchat-shared'; +import React, { useCallback, useRef, useState } from 'react'; +import { showToasts, t, useAsyncFn } from 'tailchat-shared'; import { useGlobalKeyDown } from '../../hooks/useGlobalKeyDown'; import { isEnterHotkey, isEscHotkey } from '../../utils/hot-key'; +import { Switch, Button } from 'antd'; +import { ModalWrapper } from '@/components/Modal'; interface ImageSize { width: number; @@ -12,19 +12,36 @@ interface ImageSize { interface ImageUploadPreviewerProps { imageUrl: string; - onConfirm: (imageSize: ImageSize) => Promise; + onConfirm: (imageUploadInfo: { + size: ImageSize; + uploadOriginImage: boolean; + }) => Promise; onCancel: () => void; } export const ImageUploadPreviewer: React.FC = React.memo((props) => { const { imageUrl } = props; const imageSizeRef = useRef({ width: 0, height: 0 }); + const [uploadOriginImage, setUploadOriginImage] = useState(false); const [{ loading }, handleConfirm] = useAsyncFn(async () => { + if ( + imageSizeRef.current.width === 0 || + imageSizeRef.current.height === 0 + ) { + showToasts(t('操作过于频繁'), 'warning'); + return; + } + if (typeof props.onConfirm === 'function') { - await Promise.resolve(props.onConfirm(imageSizeRef.current)); + await Promise.resolve( + props.onConfirm({ + size: imageSizeRef.current, + uploadOriginImage, + }) + ); } - }, [props.onConfirm]); + }, [props.onConfirm, uploadOriginImage]); useGlobalKeyDown( (e) => { @@ -72,14 +89,26 @@ export const ImageUploadPreviewer: React.FC = - +
+
+ setUploadOriginImage(checked)} + /> + {t('上传原图')} +
+ +
+ +
+
diff --git a/web/src/utils/file-helper.ts b/web/src/utils/file-helper.ts index bb7ad81e..3c7db8de 100644 --- a/web/src/utils/file-helper.ts +++ b/web/src/utils/file-helper.ts @@ -139,3 +139,33 @@ export async function openFile( export const isGIF = (file: File): boolean => { return file.type === 'image/gif'; }; + +/** + * 压缩图片 + * 默认压缩质量为0.6 + * @link https://www.npmjs.com/package/compressorjs + */ +export async function compressImage( + image: File, + options?: { + quality?: number; + maxWidth?: number; + maxHeight?: number; + } +): Promise { + const { default: Compressor } = await import('compressorjs'); + + return new Promise((resolve, reject) => { + new Compressor(image, { + quality: options?.quality ?? 0.6, + maxWidth: options?.maxWidth ?? 1920, + maxHeight: options?.maxHeight ?? 1080, + success(file) { + resolve(file as File); + }, + error(err) { + reject(err); + }, + }); + }); +}