From cb28e26695d0b8373b869a5ac724eae8febd7bab Mon Sep 17 00:00:00 2001 From: hyrious Date: Mon, 19 Jul 2021 16:18:25 +0800 Subject: [PATCH 1/3] feat(whiteboard): add drag and drop image support --- .../src/components/Whiteboard.tsx | 29 ++++++++++ desktop/renderer-app/src/utils/dnd/image.ts | 55 +++++++++++++++++++ web/flat-web/src/components/Whiteboard.tsx | 29 ++++++++++ web/flat-web/src/utils/dnd/image.ts | 55 +++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 desktop/renderer-app/src/utils/dnd/image.ts create mode 100644 web/flat-web/src/utils/dnd/image.ts diff --git a/desktop/renderer-app/src/components/Whiteboard.tsx b/desktop/renderer-app/src/components/Whiteboard.tsx index 56174fc6a97..e84a03dfb1c 100644 --- a/desktop/renderer-app/src/components/Whiteboard.tsx +++ b/desktop/renderer-app/src/components/Whiteboard.tsx @@ -9,6 +9,7 @@ import React, { useCallback } from "react"; import { RoomPhase } from "white-web-sdk"; import pagesSVG from "../assets/image/pages.svg"; import { WhiteboardStore } from "../stores/WhiteboardStore"; +import { isSupportedImageType, onDropImage } from "../utils/dnd/image"; import "./Whiteboard.less"; export interface WhiteboardProps { @@ -30,12 +31,40 @@ export const Whiteboard = observer(function Whiteboard({ whiteb [room], ); + const onDragOver = useCallback( + (event: React.DragEvent) => { + event.preventDefault(); + const file = event.dataTransfer.files[0]; + if (room && file && isSupportedImageType(file)) { + event.dataTransfer.dropEffect = "copy"; + } + }, + [room], + ); + + const onDrop = useCallback( + async (event: React.DragEvent) => { + event.preventDefault(); + const file = event.dataTransfer.files[0]; + if (room && file && isSupportedImageType(file)) { + const rect = (event.target as HTMLDivElement).getBoundingClientRect(); + const rx = event.clientX - rect.left; + const ry = event.clientY - rect.top; + const { x, y } = room.convertToPointInWorld({ x: rx, y: ry }); + await onDropImage(file, x, y, room); + } + }, + [room], + ); + return ( room && (
diff --git a/desktop/renderer-app/src/utils/dnd/image.ts b/desktop/renderer-app/src/utils/dnd/image.ts new file mode 100644 index 00000000000..3ce016866d8 --- /dev/null +++ b/desktop/renderer-app/src/utils/dnd/image.ts @@ -0,0 +1,55 @@ +import type { Room, Size } from "white-web-sdk"; +import { listFiles } from "../../apiMiddleware/flatServer/storage"; +import { UploadTask } from "../UploadTaskManager/UploadTask"; + +const ImageFileTypes = [ + "image/png", + "image/jpg", + "image/jpeg", + "image/webp", + "image/apng", + "image/svg+xml", + "image/gif", + "image/bmp", + "image/avif", + "image/tiff", +]; + +export function isSupportedImageType(file: File): boolean { + return ImageFileTypes.includes(file.type); +} + +export async function onDropImage(file: File, x: number, y: number, room: Room): Promise { + if (!isSupportedImageType(file)) { + console.log("[dnd:image] unsupported file type:", file.type, file.name); + return; + } + + const getSize = getImageSize(file); + const task = new UploadTask(file); + await task.upload(); + const { files } = await listFiles({ page: 1 }); + const cloudFile = files.find(f => f.fileUUID === task.fileUUID); + + if (!cloudFile?.fileURL) { + console.log("[dnd:image] upload failed:", file.name); + return; + } + + const uuid = cloudFile.fileUUID; + const { width, height } = await getSize; + room.insertImage({ uuid, centerX: x, centerY: y, width, height, locked: false }); + room.completeImageUpload(uuid, cloudFile.fileURL); +} + +function getImageSize(file: File): Promise { + const image = new Image(); + const url = URL.createObjectURL(file); + return new Promise(resolve => { + image.src = url; + image.onload = () => { + URL.revokeObjectURL(url); + resolve({ width: image.width, height: image.height }); + }; + }); +} diff --git a/web/flat-web/src/components/Whiteboard.tsx b/web/flat-web/src/components/Whiteboard.tsx index 56174fc6a97..e84a03dfb1c 100644 --- a/web/flat-web/src/components/Whiteboard.tsx +++ b/web/flat-web/src/components/Whiteboard.tsx @@ -9,6 +9,7 @@ import React, { useCallback } from "react"; import { RoomPhase } from "white-web-sdk"; import pagesSVG from "../assets/image/pages.svg"; import { WhiteboardStore } from "../stores/WhiteboardStore"; +import { isSupportedImageType, onDropImage } from "../utils/dnd/image"; import "./Whiteboard.less"; export interface WhiteboardProps { @@ -30,12 +31,40 @@ export const Whiteboard = observer(function Whiteboard({ whiteb [room], ); + const onDragOver = useCallback( + (event: React.DragEvent) => { + event.preventDefault(); + const file = event.dataTransfer.files[0]; + if (room && file && isSupportedImageType(file)) { + event.dataTransfer.dropEffect = "copy"; + } + }, + [room], + ); + + const onDrop = useCallback( + async (event: React.DragEvent) => { + event.preventDefault(); + const file = event.dataTransfer.files[0]; + if (room && file && isSupportedImageType(file)) { + const rect = (event.target as HTMLDivElement).getBoundingClientRect(); + const rx = event.clientX - rect.left; + const ry = event.clientY - rect.top; + const { x, y } = room.convertToPointInWorld({ x: rx, y: ry }); + await onDropImage(file, x, y, room); + } + }, + [room], + ); + return ( room && (
diff --git a/web/flat-web/src/utils/dnd/image.ts b/web/flat-web/src/utils/dnd/image.ts new file mode 100644 index 00000000000..3ce016866d8 --- /dev/null +++ b/web/flat-web/src/utils/dnd/image.ts @@ -0,0 +1,55 @@ +import type { Room, Size } from "white-web-sdk"; +import { listFiles } from "../../apiMiddleware/flatServer/storage"; +import { UploadTask } from "../UploadTaskManager/UploadTask"; + +const ImageFileTypes = [ + "image/png", + "image/jpg", + "image/jpeg", + "image/webp", + "image/apng", + "image/svg+xml", + "image/gif", + "image/bmp", + "image/avif", + "image/tiff", +]; + +export function isSupportedImageType(file: File): boolean { + return ImageFileTypes.includes(file.type); +} + +export async function onDropImage(file: File, x: number, y: number, room: Room): Promise { + if (!isSupportedImageType(file)) { + console.log("[dnd:image] unsupported file type:", file.type, file.name); + return; + } + + const getSize = getImageSize(file); + const task = new UploadTask(file); + await task.upload(); + const { files } = await listFiles({ page: 1 }); + const cloudFile = files.find(f => f.fileUUID === task.fileUUID); + + if (!cloudFile?.fileURL) { + console.log("[dnd:image] upload failed:", file.name); + return; + } + + const uuid = cloudFile.fileUUID; + const { width, height } = await getSize; + room.insertImage({ uuid, centerX: x, centerY: y, width, height, locked: false }); + room.completeImageUpload(uuid, cloudFile.fileURL); +} + +function getImageSize(file: File): Promise { + const image = new Image(); + const url = URL.createObjectURL(file); + return new Promise(resolve => { + image.src = url; + image.onload = () => { + URL.revokeObjectURL(url); + resolve({ width: image.width, height: image.height }); + }; + }); +} From 6e1b36e6d9ec6d7f4ff17a2431488baac0393f76 Mon Sep 17 00:00:00 2001 From: hyrious Date: Mon, 19 Jul 2021 17:29:11 +0800 Subject: [PATCH 2/3] refactor: add message --- desktop/renderer-app/src/utils/dnd/image.ts | 6 ++++++ web/flat-web/src/utils/dnd/image.ts | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/desktop/renderer-app/src/utils/dnd/image.ts b/desktop/renderer-app/src/utils/dnd/image.ts index 3ce016866d8..ae6dab10e53 100644 --- a/desktop/renderer-app/src/utils/dnd/image.ts +++ b/desktop/renderer-app/src/utils/dnd/image.ts @@ -1,5 +1,7 @@ +import { message } from "antd"; import type { Room, Size } from "white-web-sdk"; import { listFiles } from "../../apiMiddleware/flatServer/storage"; +import { i18n } from "../i18n"; import { UploadTask } from "../UploadTaskManager/UploadTask"; const ImageFileTypes = [ @@ -25,12 +27,16 @@ export async function onDropImage(file: File, x: number, y: number, room: Room): return; } + const hideLoading = message.loading(i18n.t("Inserting-courseware-tips")); + const getSize = getImageSize(file); const task = new UploadTask(file); await task.upload(); const { files } = await listFiles({ page: 1 }); const cloudFile = files.find(f => f.fileUUID === task.fileUUID); + hideLoading(); + if (!cloudFile?.fileURL) { console.log("[dnd:image] upload failed:", file.name); return; diff --git a/web/flat-web/src/utils/dnd/image.ts b/web/flat-web/src/utils/dnd/image.ts index 3ce016866d8..ae6dab10e53 100644 --- a/web/flat-web/src/utils/dnd/image.ts +++ b/web/flat-web/src/utils/dnd/image.ts @@ -1,5 +1,7 @@ +import { message } from "antd"; import type { Room, Size } from "white-web-sdk"; import { listFiles } from "../../apiMiddleware/flatServer/storage"; +import { i18n } from "../i18n"; import { UploadTask } from "../UploadTaskManager/UploadTask"; const ImageFileTypes = [ @@ -25,12 +27,16 @@ export async function onDropImage(file: File, x: number, y: number, room: Room): return; } + const hideLoading = message.loading(i18n.t("Inserting-courseware-tips")); + const getSize = getImageSize(file); const task = new UploadTask(file); await task.upload(); const { files } = await listFiles({ page: 1 }); const cloudFile = files.find(f => f.fileUUID === task.fileUUID); + hideLoading(); + if (!cloudFile?.fileURL) { console.log("[dnd:image] upload failed:", file.name); return; From a22db969540491e69cc5f4ae4aefb6cad4554d4a Mon Sep 17 00:00:00 2001 From: hyrious Date: Mon, 19 Jul 2021 17:31:34 +0800 Subject: [PATCH 3/3] chore: increase ram --- .github/workflows/code-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-check.yml b/.github/workflows/code-check.yml index 2b8b0ad95b9..64642e604c4 100644 --- a/.github/workflows/code-check.yml +++ b/.github/workflows/code-check.yml @@ -76,7 +76,7 @@ jobs: if: steps.filter.outputs.main == 'true' - name: Build flat-web - run: NODE_OPTIONS="--max-old-space-size=4096" yarn workspace flat-web build + run: NODE_OPTIONS="--max-old-space-size=6144" yarn workspace flat-web build if: steps.filter.outputs.flat-web == 'true' - name: Check i18n