diff --git a/.gitignore b/.gitignore index 4416880..bf3e11c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ \.idea/ /build/ +/backup/ /js/ /dist/ /css/ diff --git a/src/App.tsx b/src/App.tsx index 905ea4f..b779178 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -96,10 +96,6 @@ export default function App({ if (excalidrawAPI && !collab) { setCollab(new Collab(excalidrawAPI, fileId, publicSharingToken, setViewModeEnabled)) } if (collab && !collab.portal.socket) collab.startCollab() useEffect(() => { - if (excalidrawAPI) { - registerFilesHandler(excalidrawAPI) - } - const extraTools = document.getElementsByClassName( 'App-toolbar__extra-tools-trigger', )[0] diff --git a/src/collaboration/collab.ts b/src/collaboration/collab.ts index 8e1a471..91268ad 100644 --- a/src/collaboration/collab.ts +++ b/src/collaboration/collab.ts @@ -9,6 +9,7 @@ import { Portal } from './Portal' import { restoreElements } from '@excalidraw/excalidraw' import { throttle } from 'lodash' import { hashElementsVersion, reconcileElements } from './util' +import { registerFilesHandler, type FileHandle } from '../files/files' export class Collab { @@ -20,6 +21,7 @@ export class Collab { lastBroadcastedOrReceivedSceneVersion: number = -1 private collaborators = new Map() private files = new Map() + private fileHandle: FileHandle constructor(excalidrawAPI: ExcalidrawImperativeAPI, fileId: number, publicSharingToken: string | null, setViewModeEnabled: React.Dispatch>) { this.excalidrawAPI = excalidrawAPI @@ -28,6 +30,7 @@ export class Collab { this.setViewModeEnabled = setViewModeEnabled this.portal = new Portal(`${fileId}`, this, publicSharingToken) + this.fileHandle = registerFilesHandler(this.excalidrawAPI, this) } async startCollab() { diff --git a/src/files/files.ts b/src/files/files.ts index be731fe..e2f670a 100644 --- a/src/files/files.ts +++ b/src/files/files.ts @@ -1,46 +1,122 @@ import { convertToExcalidrawElements } from '@excalidraw/excalidraw' -import type { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types/types' +import type { + BinaryFileData, + DataURL, + ExcalidrawImperativeAPI, +} from '@excalidraw/excalidraw/types/types' +import { Collab } from '../collaboration/collab' +import type { FileId } from '@excalidraw/excalidraw/types/element/types' -function addCustomFileElement(excalidrawApi: ExcalidrawImperativeAPI, link: string) { - const elements = excalidrawApi.getSceneElementsIncludingDeleted().slice() - const newElements = convertToExcalidrawElements([{ - text: link, - type: 'text', - fontSize: 16, - textAlign: 'left', - fontFamily: 1, - x: 0, - y: 0, - }]) - elements.push(newElements[0]) - excalidrawApi.updateScene({ elements }) -} +export class FileHandle { + private collab: Collab + private excalidrawApi: ExcalidrawImperativeAPI + private types: string[] + constructor( + excalidrawApi: ExcalidrawImperativeAPI, + collab: Collab, + types: string[], + ) { + this.collab = collab + this.excalidrawApi = excalidrawApi + this.types = types + const containerRef = document.getElementsByClassName( + 'excalidraw-container', + )[0] + let constructedFile: BinaryFileData = { + mimeType: 'image/png', + created: 0o0, + id: 'placeholder_image' as FileId, + dataURL: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTE0LDJMMjAsOFYyMEEyLDIgMCAwLDEgMTgsMjJINkEyLDIgMCAwLDEgNCwyMFY0QTIsMiAwIDAsMSA2LDJIMTRNMTgsMjBWOUgxM1Y0SDZWMjBIMThNMTIsMTlMOCwxNUgxMC41VjEySDEzLjVWMTVIMTZMMTIsMTlaIiAvPjwvc3ZnPg==' as DataURL, + } + this.collab.addFile(constructedFile) + if (containerRef) { + containerRef.addEventListener('drop', (ev) => + this.filesDragEventListener(ev, excalidrawApi), + ) + } + this.excalidrawApi.onPointerDown((tool, state, event) => { + }) + } -// TODO: Implement uploading to nextcloud -function UploadFileToNextcloud(file: File) { - return file -} + private filesDragEventListener(ev: Event, excalidrawApi: ExcalidrawImperativeAPI) { + if (ev instanceof DragEvent) { + for (let file of Array.from(ev.dataTransfer?.files || [])) { + this.handleFileInsert(file, ev) + } + } + } + + private handleFileInsert(file: File, ev: Event) { + // if excalidraw can handle it, do nothing + if (this.types.includes(file.type)) { + return + } + ev.stopImmediatePropagation() -function filesEventListener(ev: Event, excalidrawApi: ExcalidrawImperativeAPI) { - if (ev instanceof DragEvent) { - if (ev.dataTransfer?.files[0]) UploadFileToNextcloud(ev.dataTransfer?.files[0]) - const types = ['image/webp'] - if (!types.includes(ev.dataTransfer?.files[0].type || '')) { - addCustomFileElement(excalidrawApi, ev.dataTransfer?.files[0].name || 'no file name') - ev.stopImmediatePropagation() + const fr = new FileReader() + fr.readAsDataURL(file) + fr.onload = () => { + let constructedFile: BinaryFileData = { + mimeType: 'image/png', + created: 0o0, + id: (Math.random() + 1).toString(36).substring(7) as FileId, + dataURL: fr.result as DataURL, + } + this.addCustomFileElement(constructedFile, file.name) } } + + private addCustomFileElement(constructedFile: BinaryFileData, filename: string) { + this.collab.addFile(constructedFile) + const elements = this.excalidrawApi + .getSceneElementsIncludingDeleted() + .slice() + const newElements = convertToExcalidrawElements([ + { + type: 'text', + text: filename, + customData: { filedata: { constructedFile } }, + groupIds: ['1'], + y: 0, + x: 0, + }, + { + type: 'image', + fileId: 'placeholder_image' as FileId, + groupIds: ['1'], + y: 0, + x: 0, + } + ]) + elements.push(...newElements) + this.excalidrawApi.updateScene({ elements }) + } } /** - * adds drop eventlistener to excalidraw - * uploads file to nextcloud server, to be shared with all users - * if filetype not supported by excalidraw inserts link to file - * @param {ExcalidrawImperativeAPI} excalidrawApi excalidrawApi -*/ -export function registerFilesHandler(excalidrawApi: ExcalidrawImperativeAPI) { - const containerRef = document.getElementsByClassName('excalidraw-container')[0] - if (containerRef) { - containerRef.addEventListener('drop', (ev) => filesEventListener(ev, excalidrawApi)) - } + * adds drop eventlistener to excalidraw + * uploads file to nextcloud server, to be shared with all users + * if filetype not supported by excalidraw inserts link to file + * @param {ExcalidrawImperativeAPI} excalidrawApi excalidrawApi + */ +export function registerFilesHandler( + excalidrawApi: ExcalidrawImperativeAPI, + collab: Collab, +): FileHandle { + const types = [ + 'application/vnd.excalidraw+json', + 'application/vnd.excalidrawlib+json', + 'application/json', + 'image/svg+xml', + 'image/svg+xml', + 'image/png', + 'image/png', + 'image/jpeg', + 'image/gif', + 'image/webp', + 'image/bmp', + 'image/x-icon', + 'application/octet-stream', + ] + return new FileHandle(excalidrawApi, collab, types) }