diff --git a/package-lock.json b/package-lock.json index 839559a5..c52cdc9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,12 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@excalidraw/excalidraw": "^0.17.6", + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", "@mdi/svg": "^7.4.47", "@nextcloud/auth": "^2.4.0", "@nextcloud/axios": "^2.5.0", + "@nextcloud/dialogs": "^5.3.5", "@nextcloud/event-bus": "^3.3.1", "@nextcloud/files": "^3.8.0", "@nextcloud/initial-state": "^2.2.0", @@ -1242,6 +1245,19 @@ "unist-util-is": "^3.0.0" } }, + "node_modules/@mdi/js": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", + "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==" + }, + "node_modules/@mdi/react": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz", + "integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==", + "dependencies": { + "prop-types": "^15.7.2" + } + }, "node_modules/@mdi/svg": { "version": "7.4.47", "resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-7.4.47.tgz", @@ -1442,6 +1458,37 @@ "npm": "^10.0.0" } }, + "node_modules/@nextcloud/dialogs": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-5.3.5.tgz", + "integrity": "sha512-v2+M2zN90IqkZby7QZ575Ej/VsSQXcI6EurMVp51mRGLTeO2bJw8IVdfumDJhSA+3rn/nSHmkz3zWcHUInqzTg==", + "dependencies": { + "@mdi/js": "^7.4.47", + "@nextcloud/auth": "^2.3.0", + "@nextcloud/axios": "^2.5.0", + "@nextcloud/event-bus": "^3.3.1", + "@nextcloud/files": "^3.5.1", + "@nextcloud/initial-state": "^2.2.0", + "@nextcloud/l10n": "^3.1.0", + "@nextcloud/router": "^3.0.1", + "@nextcloud/sharing": "^0.2.2", + "@nextcloud/typings": "^1.9.0", + "@types/toastify-js": "^1.12.3", + "@vueuse/core": "^10.11.0", + "cancelable-promise": "^4.3.1", + "toastify-js": "^1.12.0", + "vue-frag": "^1.4.3", + "webdav": "^5.6.0" + }, + "engines": { + "node": "^20.0.0", + "npm": "^10.0.0" + }, + "peerDependencies": { + "@nextcloud/vue": "^8.9.1", + "vue": "^2.7.16" + } + }, "node_modules/@nextcloud/eslint-config": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@nextcloud/eslint-config/-/eslint-config-8.4.1.tgz", @@ -1670,13 +1717,11 @@ } }, "node_modules/@nextcloud/typings": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@nextcloud/typings/-/typings-1.8.0.tgz", - "integrity": "sha512-q9goE0wc+1BCI9Ku0MebCHmqOMwz2K7ESKQrcHDs6O+HqbKA8zGiEtXL5XGrMS7Ovtl1YOIwxlP9kEvgvXt52Q==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@nextcloud/typings/-/typings-1.9.1.tgz", + "integrity": "sha512-i0l/L5gKW8EACbXHVxXM6wn3sUhY2qmnL2OijppzU4dENC7/hqySMQDer7/+cJbNSNG7uHF/Z+9JmHtDfRfuGg==", "dependencies": { - "@types/jquery": "3.5.16", - "vue": "^2.7.15", - "vue-router": "<4" + "@types/jquery": "3.5.16" }, "engines": { "node": "^20.0.0", @@ -2521,6 +2566,11 @@ "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==" }, + "node_modules/@types/toastify-js": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/@types/toastify-js/-/toastify-js-1.12.3.tgz", + "integrity": "sha512-9RjLlbAHMSaae/KZNHGv19VG4gcLIm3YjvacCXBtfMfYn26h76YP5oxXI8k26q4iKXCB9LNfv18lsoS0JnFPTg==" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -9845,6 +9895,16 @@ "node": ">=10" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/property-information": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", @@ -10047,6 +10107,11 @@ "react": "^18.3.1" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -11829,6 +11894,11 @@ "node": ">=8.0" } }, + "node_modules/toastify-js": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.12.0.tgz", + "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", diff --git a/package.json b/package.json index a986d818..6c00b662 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,12 @@ }, "dependencies": { "@excalidraw/excalidraw": "^0.17.6", + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", "@mdi/svg": "^7.4.47", "@nextcloud/auth": "^2.4.0", "@nextcloud/axios": "^2.5.0", + "@nextcloud/dialogs": "^5.3.5", "@nextcloud/event-bus": "^3.3.1", "@nextcloud/files": "^3.8.0", "@nextcloud/initial-state": "^2.2.0", @@ -78,4 +81,4 @@ "node": "^20", "npm": "^9" } -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index b9b2756f..4ba46f84 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,10 +8,14 @@ /* eslint-disable no-console */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { useCallback, useEffect, useRef, useState } from 'react' +import { Icon } from '@mdi/react' +import { mdiSlashForwardBox } from '@mdi/js' +import { createRoot } from 'react-dom' import { Excalidraw, MainMenu, useHandleLibrary, + viewportCoordsToSceneCoords, } from '@excalidraw/excalidraw' import './App.scss' import { resolvablePromise } from './utils' @@ -23,6 +27,7 @@ import { Collab } from './collaboration/collab' import Embeddable from './Embeddable' import type { ResolvablePromise } from '@excalidraw/excalidraw/types/utils' import type { NonDeletedExcalidrawElement } from '@excalidraw/excalidraw/types/element/types' +import { getLinkWithPicker } from '@nextcloud/vue/dist/Components/NcRichText.js' interface WhiteboardAppProps { fileId: number; fileName: string; @@ -72,6 +77,16 @@ export default function App({ fileId, isEmbedded, fileName }: WhiteboardAppProps if (excalidrawAPI && !collab) setCollab(new Collab(excalidrawAPI, fileId)) if (collab && !collab.portal.socket) collab.startCollab() + useEffect(() => { + const extraTools = document.getElementsByClassName('App-toolbar__extra-tools-trigger')[0] + const smartPick = document.createElement('label') + smartPick.classList.add(...['ToolIcon', 'Shape']) + if (extraTools) { + extraTools.parentNode?.insertBefore(smartPick, extraTools.nextElementSibling) + const root = createRoot(smartPick) + root.render(renderSmartPicker()) + } + }) useEffect(() => { return () => { @@ -114,7 +129,49 @@ export default function App({ fileId, isEmbedded, fileName }: WhiteboardAppProps }, [], ) - + const addWebEmbed = (link:string) => { + let cords: { x: any; y: any } + if (excalidrawAPI) { + cords = viewportCoordsToSceneCoords({ clientX: 100, clientY: 100 }, excalidrawAPI.getAppState()) + } else { + cords = { x: 0, y: 0 } + } + const elements = excalidrawAPI?.getSceneElementsIncludingDeleted().slice() + elements?.push({ + link, + id: (Math.random() + 1).toString(36).substring(7), + x: cords.x, + y: cords.y, + strokeColor: '#1e1e1e', + backgroundColor: 'transparent', + fillStyle: 'solid', + strokeWidth: 2, + strokeStyle: 'solid', + roundness: null, + roughness: 1, + opacity: 100, + width: 400, + height: 200, + angle: 0, + seed: 0, + version: 0, + versionNonce: 0, + isDeleted: false, + groupIds: [], + frameId: null, + boundElements: null, + updated: 0, + locked: false, + type: 'embeddable', + validated: true, + }) + excalidrawAPI?.updateScene({ elements }) + } + const pickFile = () => { + getLinkWithPicker(null, true).then((link: string) => { + addWebEmbed(link) + }) + } const renderMenu = () => { return ( @@ -126,6 +183,14 @@ export default function App({ fileId, isEmbedded, fileName }: WhiteboardAppProps ) } + const renderSmartPicker = () => { + return ( + + ) + } + return (