Skip to content

Commit

Permalink
feat(fileuploads): add custom file element for unknown types
Browse files Browse the repository at this point in the history
Signed-off-by: grnd-alt <[email protected]>
  • Loading branch information
grnd-alt committed Dec 2, 2024
1 parent e26b418 commit ed4f629
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
\.idea/

/build/
/backup/
/js/
/dist/
/css/
Expand Down
4 changes: 0 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
3 changes: 3 additions & 0 deletions src/collaboration/collab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -20,6 +21,7 @@ export class Collab {
lastBroadcastedOrReceivedSceneVersion: number = -1
private collaborators = new Map<string, Collaborator>()
private files = new Map<string, BinaryFileData>()
private fileHandle: FileHandle

constructor(excalidrawAPI: ExcalidrawImperativeAPI, fileId: number, publicSharingToken: string | null, setViewModeEnabled: React.Dispatch<React.SetStateAction<boolean>>) {
this.excalidrawAPI = excalidrawAPI
Expand All @@ -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() {
Expand Down
148 changes: 112 additions & 36 deletions src/files/files.ts
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit ed4f629

Please sign in to comment.