Compare commits
	
		
			177 Commits
		
	
	
		
			zsviczian-
			...
			zsviczian-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					11ee139d5c | ||
| 
						 | 
					16c0417f5b | ||
| 
						 | 
					d2f67e619f | ||
| 
						 | 
					22b39277f5 | ||
| 
						 | 
					63dee03ef0 | ||
| 
						 | 
					08b13f971d | ||
| 
						 | 
					69f4cc70cb | ||
| 
						 | 
					860308eb27 | ||
| 
						 | 
					4eb9463f26 | ||
| 
						 | 
					6ed6131169 | ||
| 
						 | 
					1ed98f9c93 | ||
| 
						 | 
					a71bb63d1f | ||
| 
						 | 
					661d6a4a75 | ||
| 
						 | 
					defd34923a | ||
| 
						 | 
					c540bd68aa | ||
| 
						 | 
					eddbe55f50 | ||
| 
						 | 
					2f9526da24 | ||
| 
						 | 
					1b6e3fe05b | ||
| 
						 | 
					afe52c89a7 | ||
| 
						 | 
					be4e127f6c | ||
| 
						 | 
					ff0b4394b1 | ||
| 
						 | 
					7d8b7fc14d | ||
| 
						 | 
					971b4d4ae6 | ||
| 
						 | 
					cc4c51996c | ||
| 
						 | 
					79257a1923 | ||
| 
						 | 
					dc66261c19 | ||
| 
						 | 
					273ba803d9 | ||
| 
						 | 
					301e83805d | ||
| 
						 | 
					ed5ce8d3de | ||
| 
						 | 
					1ed53b153c | ||
| 
						 | 
					c1926f33bb | ||
| 
						 | 
					6539029d2a | ||
| 
						 | 
					d1f37cc64f | ||
| 
						 | 
					f0d25e34c3 | ||
| 
						 | 
					d9bbf1eda6 | ||
| 
						 | 
					f79fb9aae2 | ||
| 
						 | 
					275f6fbe24 | ||
| 
						 | 
					88812e0386 | ||
| 
						 | 
					6e5aeb112d | ||
| 
						 | 
					4d83d1c91e | ||
| 
						 | 
					a04676d423 | ||
| 
						 | 
					c851aaaf7b | ||
| 
						 | 
					1bd2b1fe55 | ||
| 
						 | 
					015b46ab23 | ||
| 
						 | 
					530617be90 | ||
| 
						 | 
					5211b003b8 | ||
| 
						 | 
					bbcca06b94 | ||
| 
						 | 
					f92f04c13c | ||
| 
						 | 
					890ed9f31f | ||
| 
						 | 
					da2e507298 | ||
| 
						 | 
					f59b4f6af4 | ||
| 
						 | 
					afcde542f9 | ||
| 
						 | 
					4689a6b300 | ||
| 
						 | 
					0ae9b383d6 | ||
| 
						 | 
					f597bd3e01 | ||
| 
						 | 
					4987cc53d0 | ||
| 
						 | 
					d917db438e | ||
| 
						 | 
					a33a400f01 | ||
| 
						 | 
					8a162a4cb4 | ||
| 
						 | 
					c6a045d092 | ||
| 
						 | 
					cd50aa719f | ||
| 
						 | 
					92bc08207c | ||
| 
						 | 
					32df5502ae | ||
| 
						 | 
					bbdcd30a73 | ||
| 
						 | 
					3e334a67ed | ||
| 
						 | 
					1d71f84515 | ||
| 
						 | 
					550a388b2b | ||
| 
						 | 
					6b523563d8 | ||
| 
						 | 
					65bc500598 | ||
| 
						 | 
					7949aa1f1c | ||
| 
						 | 
					15bfa626b4 | ||
| 
						 | 
					068895db0e | ||
| 
						 | 
					b7babe554b | ||
| 
						 | 
					6a385d6663 | ||
| 
						 | 
					2382fad4f6 | ||
| 
						 | 
					480572f893 | ||
| 
						 | 
					68b1fdb20e | ||
| 
						 | 
					a38e82f999 | ||
| 
						 | 
					a07f6e9e3a | ||
| 
						 | 
					7e471b55eb | ||
| 
						 | 
					160440b860 | ||
| 
						 | 
					f207bd0a1c | ||
| 
						 | 
					99601baffc | ||
| 
						 | 
					af1a3d5b76 | ||
| 
						 | 
					36e56267c9 | ||
| 
						 | 
					b09b5cb5f4 | ||
| 
						 | 
					dd8529743a | ||
| 
						 | 
					f639d44a95 | ||
| 
						 | 
					f5ab3e4e12 | ||
| 
						 | 
					361a9449bb | ||
| 
						 | 
					2e719ff671 | ||
| 
						 | 
					79d9dc2f8f | ||
| 
						 | 
					9013c84524 | ||
| 
						 | 
					47f87f4ecb | ||
| 
						 | 
					73bf50e8a8 | ||
| 
						 | 
					48c3465b19 | ||
| 
						 | 
					adc4c9f484 | ||
| 
						 | 
					def1df2c68 | ||
| 
						 | 
					0513b647ec | ||
| 
						 | 
					a289c42830 | ||
| 
						 | 
					d67eaa8710 | ||
| 
						 | 
					0c3dffb082 | ||
| 
						 | 
					0e0f34edd8 | ||
| 
						 | 
					4888d9d355 | ||
| 
						 | 
					1c39bd5781 | ||
| 
						 | 
					90ad885446 | ||
| 
						 | 
					1741c234a6 | ||
| 
						 | 
					63b50b3586 | ||
| 
						 | 
					e0fefa8025 | ||
| 
						 | 
					d426cc968d | ||
| 
						 | 
					2409c091ff | ||
| 
						 | 
					626fe252ab | ||
| 
						 | 
					10bd08ef19 | ||
| 
						 | 
					2789d08154 | ||
| 
						 | 
					678bb2b819 | ||
| 
						 | 
					966f9aead9 | ||
| 
						 | 
					4f0a2a9593 | ||
| 
						 | 
					f3f8217125 | ||
| 
						 | 
					89bd6181f2 | ||
| 
						 | 
					c6fdac131b | ||
| 
						 | 
					0415c616b1 | ||
| 
						 | 
					740a165452 | ||
| 
						 | 
					4997624a3a | ||
| 
						 | 
					b66daae1f3 | ||
| 
						 | 
					1e7df58b5b | ||
| 
						 | 
					46da032626 | ||
| 
						 | 
					3b0593baa7 | ||
| 
						 | 
					dd530737a2 | ||
| 
						 | 
					a4e5e46dd1 | ||
| 
						 | 
					0fa5f5de4c | ||
| 
						 | 
					41cc746885 | ||
| 
						 | 
					8ead8559e0 | ||
| 
						 | 
					5245276409 | ||
| 
						 | 
					0c24a7042f | ||
| 
						 | 
					86cfeb714c | ||
| 
						 | 
					872973f145 | ||
| 
						 | 
					3ecf72a507 | ||
| 
						 | 
					1aaa400876 | ||
| 
						 | 
					65047cc2cb | ||
| 
						 | 
					8b993d409e | ||
| 
						 | 
					1cb350b2aa | ||
| 
						 | 
					43ccc875fb | ||
| 
						 | 
					4249b7dec8 | ||
| 
						 | 
					49f15c736b | ||
| 
						 | 
					a8064ba3ee | ||
| 
						 | 
					e6c3c06c2e | ||
| 
						 | 
					d19b51d4f8 | ||
| 
						 | 
					c72e853c85 | ||
| 
						 | 
					5f40a4cad4 | ||
| 
						 | 
					d91c98b82e | ||
| 
						 | 
					57ea4e61d1 | ||
| 
						 | 
					0808532b49 | ||
| 
						 | 
					2a0fe2584e | ||
| 
						 | 
					7bd6496854 | ||
| 
						 | 
					537f6e7f68 | ||
| 
						 | 
					6dfa89e846 | ||
| 
						 | 
					561e919a2e | ||
| 
						 | 
					20e3acf7a6 | ||
| 
						 | 
					2c0929e537 | ||
| 
						 | 
					aad8ab0123 | ||
| 
						 | 
					88a2b286c7 | ||
| 
						 | 
					b635b10b59 | ||
| 
						 | 
					7ebda02b81 | ||
| 
						 | 
					d6cd8b78f1 | ||
| 
						 | 
					b7d7ccc929 | ||
| 
						 | 
					f14ad61bd0 | ||
| 
						 | 
					8963baf5ad | ||
| 
						 | 
					557add5bf7 | ||
| 
						 | 
					b9cfbc2077 | ||
| 
						 | 
					a04cc707c3 | ||
| 
						 | 
					72ea8022bf | ||
| 
						 | 
					4bdeaf999b | ||
| 
						 | 
					42d8c5a040 | ||
| 
						 | 
					f299514e44 | ||
| 
						 | 
					dd220bcaea | ||
| 
						 | 
					fe75f29c15 | ||
| 
						 | 
					14845a343b | 
@@ -4,8 +4,15 @@
 | 
				
			|||||||
!.eslintrc.json
 | 
					!.eslintrc.json
 | 
				
			||||||
!.npmrc
 | 
					!.npmrc
 | 
				
			||||||
!.prettierrc
 | 
					!.prettierrc
 | 
				
			||||||
 | 
					!excalidraw-app/
 | 
				
			||||||
!package.json
 | 
					!package.json
 | 
				
			||||||
!public/
 | 
					!public/
 | 
				
			||||||
!src/
 | 
					!packages/
 | 
				
			||||||
!tsconfig.json
 | 
					!tsconfig.json
 | 
				
			||||||
!yarn.lock
 | 
					!yarn.lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# keep (sub)sub directories at the end to exclude from explicit included
 | 
				
			||||||
 | 
					# e.g. ./packages/excalidraw/{dist,node_modules}
 | 
				
			||||||
 | 
					**/build
 | 
				
			||||||
 | 
					**/dist
 | 
				
			||||||
 | 
					**/node_modules
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,12 +7,11 @@ VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfu
 | 
				
			|||||||
# collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room)
 | 
					# collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room)
 | 
				
			||||||
VITE_APP_WS_SERVER_URL=http://localhost:3002
 | 
					VITE_APP_WS_SERVER_URL=http://localhost:3002
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# set this only if using the collaboration workflow we use on excalidraw.com
 | 
					 | 
				
			||||||
VITE_APP_PORTAL_URL=
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VITE_APP_PLUS_LP=https://plus.excalidraw.com
 | 
					VITE_APP_PLUS_LP=https://plus.excalidraw.com
 | 
				
			||||||
VITE_APP_PLUS_APP=https://app.excalidraw.com
 | 
					VITE_APP_PLUS_APP=https://app.excalidraw.com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					VITE_APP_AI_BACKEND=http://localhost:3015
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
 | 
					VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# put these in your .env.local, or make sure you don't commit!
 | 
					# put these in your .env.local, or make sure you don't commit!
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,14 +4,13 @@ VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
 | 
				
			|||||||
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
 | 
					VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
 | 
				
			||||||
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
 | 
					VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VITE_APP_PORTAL_URL=https://portal.excalidraw.com
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
VITE_APP_PLUS_LP=https://plus.excalidraw.com
 | 
					VITE_APP_PLUS_LP=https://plus.excalidraw.com
 | 
				
			||||||
VITE_APP_PLUS_APP=https://app.excalidraw.com
 | 
					VITE_APP_PLUS_APP=https://app.excalidraw.com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Fill to set socket server URL used for collaboration.
 | 
					VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com
 | 
				
			||||||
# Meant for forks only: excalidraw.com uses custom VITE_APP_PORTAL_URL flow
 | 
					
 | 
				
			||||||
VITE_APP_WS_SERVER_URL=
 | 
					# socket server URL used for collaboration
 | 
				
			||||||
 | 
					VITE_APP_WS_SERVER_URL=https://oss-collab.excalidraw.com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
 | 
					VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,4 +5,4 @@ package-lock.json
 | 
				
			|||||||
firebase/
 | 
					firebase/
 | 
				
			||||||
dist/
 | 
					dist/
 | 
				
			||||||
public/workbox
 | 
					public/workbox
 | 
				
			||||||
src/packages/excalidraw/types
 | 
					packages/excalidraw/types
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
  "extends": ["@excalidraw/eslint-config", "react-app"],
 | 
					  "extends": ["@excalidraw/eslint-config", "react-app"],
 | 
				
			||||||
  "rules": {
 | 
					  "rules": {
 | 
				
			||||||
    "import/no-anonymous-default-export": "off",
 | 
					    "import/no-anonymous-default-export": "off",
 | 
				
			||||||
    "no-restricted-globals": "off"
 | 
					    "no-restricted-globals": "off",
 | 
				
			||||||
 | 
					    "@typescript-eslint/consistent-type-imports": ["error", { "prefer": "type-imports", "disallowTypeAnnotations": false, "fixStyle": "separate-type-imports" }]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/autorelease-excalidraw.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -23,5 +23,5 @@ jobs:
 | 
				
			|||||||
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
 | 
					          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
 | 
				
			||||||
      - name: Auto release
 | 
					      - name: Auto release
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          yarn add @actions/core
 | 
					          yarn add @actions/core -W
 | 
				
			||||||
          yarn autorelease
 | 
					          yarn autorelease
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/autorelease-preview.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -44,7 +44,7 @@ jobs:
 | 
				
			|||||||
      - name: Auto release preview
 | 
					      - name: Auto release preview
 | 
				
			||||||
        id: "autorelease"
 | 
					        id: "autorelease"
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          yarn add @actions/core
 | 
					          yarn add @actions/core -W
 | 
				
			||||||
          yarn autorelease preview ${{ github.event.issue.number }}
 | 
					          yarn autorelease preview ${{ github.event.issue.number }}
 | 
				
			||||||
      - name: Post comment post release
 | 
					      - name: Post comment post release
 | 
				
			||||||
        if: always()
 | 
					        if: always()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -16,7 +16,7 @@ jobs:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      - name: Install and lint
 | 
					      - name: Install and lint
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          yarn --frozen-lockfile
 | 
					          yarn install
 | 
				
			||||||
          yarn test:other
 | 
					          yarn test:other
 | 
				
			||||||
          yarn test:code
 | 
					          yarn test:code
 | 
				
			||||||
          yarn test:typecheck
 | 
					          yarn test:typecheck
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/workflows/locales-coverage.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -22,11 +22,11 @@ jobs:
 | 
				
			|||||||
      - name: Create report file
 | 
					      - name: Create report file
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          yarn locales-coverage
 | 
					          yarn locales-coverage
 | 
				
			||||||
          FILE_CHANGED=$(git diff src/locales/percentages.json)
 | 
					          FILE_CHANGED=$(git diff packages/excalidraw/locales/percentages.json)
 | 
				
			||||||
          if [ ! -z "${FILE_CHANGED}" ]; then
 | 
					          if [ ! -z "${FILE_CHANGED}" ]; then
 | 
				
			||||||
            git config --global user.name 'Excalidraw Bot'
 | 
					            git config --global user.name 'Excalidraw Bot'
 | 
				
			||||||
            git config --global user.email 'bot@excalidraw.com'
 | 
					            git config --global user.email 'bot@excalidraw.com'
 | 
				
			||||||
            git add src/locales/percentages.json
 | 
					            git add packages/excalidraw/locales/percentages.json
 | 
				
			||||||
            git commit -am "Auto commit: Calculate translation coverage"
 | 
					            git commit -am "Auto commit: Calculate translation coverage"
 | 
				
			||||||
            git push
 | 
					            git push
 | 
				
			||||||
          fi
 | 
					          fi
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								.github/workflows/size-limit.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -15,16 +15,14 @@ jobs:
 | 
				
			|||||||
        uses: actions/setup-node@v3
 | 
					        uses: actions/setup-node@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          node-version: 18.x
 | 
					          node-version: 18.x
 | 
				
			||||||
      - name: Install
 | 
					      - name: Install in packages/excalidraw
 | 
				
			||||||
        run: yarn --frozen-lockfile
 | 
					        run: yarn
 | 
				
			||||||
      - name: Install in src/packages/excalidraw
 | 
					        working-directory: packages/excalidraw
 | 
				
			||||||
        run: yarn --frozen-lockfile
 | 
					 | 
				
			||||||
        working-directory: src/packages/excalidraw
 | 
					 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          CI: true
 | 
					          CI: true
 | 
				
			||||||
      - uses: andresz1/size-limit-action@v1
 | 
					      - uses: andresz1/size-limit-action@v1
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          github_token: ${{ secrets.GITHUB_TOKEN }}
 | 
					          github_token: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
          build_script: build:umd
 | 
					          build_script: build:esm
 | 
				
			||||||
          skip_step: install
 | 
					          skip_step: install
 | 
				
			||||||
          directory: src/packages/excalidraw
 | 
					          directory: packages/excalidraw
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/test-coverage-pr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -16,7 +16,7 @@ jobs:
 | 
				
			|||||||
        with:
 | 
					        with:
 | 
				
			||||||
          node-version: "18.x"
 | 
					          node-version: "18.x"
 | 
				
			||||||
      - name: "Install Deps"
 | 
					      - name: "Install Deps"
 | 
				
			||||||
        run: yarn --frozen-lockfile
 | 
					        run: yarn install
 | 
				
			||||||
      - name: "Test Coverage"
 | 
					      - name: "Test Coverage"
 | 
				
			||||||
        run: yarn test:coverage
 | 
					        run: yarn test:coverage
 | 
				
			||||||
      - name: "Report Coverage"
 | 
					      - name: "Report Coverage"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,6 +1,9 @@
 | 
				
			|||||||
name: Tests
 | 
					name: Tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on: pull_request
 | 
					on:
 | 
				
			||||||
 | 
					  pull_request:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches: master
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  test:
 | 
					  test:
 | 
				
			||||||
@@ -13,5 +16,5 @@ jobs:
 | 
				
			|||||||
          node-version: 18.x
 | 
					          node-version: 18.x
 | 
				
			||||||
      - name: Install and test
 | 
					      - name: Install and test
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          yarn --frozen-lockfile
 | 
					          yarn install
 | 
				
			||||||
          yarn test:app
 | 
					          yarn test:app
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -21,10 +21,9 @@ npm-debug.log*
 | 
				
			|||||||
package-lock.json
 | 
					package-lock.json
 | 
				
			||||||
yarn-debug.log*
 | 
					yarn-debug.log*
 | 
				
			||||||
yarn-error.log*
 | 
					yarn-error.log*
 | 
				
			||||||
src/packages/excalidraw/types
 | 
					packages/excalidraw/types
 | 
				
			||||||
src/packages/excalidraw/example/public/bundle.js
 | 
					 | 
				
			||||||
src/packages/excalidraw/example/public/excalidraw-assets-dev
 | 
					 | 
				
			||||||
src/packages/excalidraw/example/public/excalidraw.development.js
 | 
					 | 
				
			||||||
coverage
 | 
					coverage
 | 
				
			||||||
dev-dist
 | 
					dev-dist
 | 
				
			||||||
html
 | 
					html
 | 
				
			||||||
 | 
					examples/**/bundle.*
 | 
				
			||||||
 | 
					meta*.json
 | 
				
			||||||
							
								
								
									
										12
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						@@ -2,16 +2,18 @@ FROM node:18 AS build
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
WORKDIR /opt/node_app
 | 
					WORKDIR /opt/node_app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY package.json yarn.lock ./
 | 
					COPY . .
 | 
				
			||||||
RUN yarn --ignore-optional --network-timeout 600000
 | 
					
 | 
				
			||||||
 | 
					# do not ignore optional dependencies:
 | 
				
			||||||
 | 
					# Error: Cannot find module @rollup/rollup-linux-x64-gnu
 | 
				
			||||||
 | 
					RUN yarn --network-timeout 600000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARG NODE_ENV=production
 | 
					ARG NODE_ENV=production
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY . .
 | 
					 | 
				
			||||||
RUN yarn build:app:docker
 | 
					RUN yarn build:app:docker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM nginx:1.21-alpine
 | 
					FROM nginx:1.24-alpine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=build /opt/node_app/build /usr/share/nginx/html
 | 
					COPY --from=build /opt/node_app/excalidraw-app/build /usr/share/nginx/html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
HEALTHCHECK CMD wget -q -O /dev/null http://localhost || exit 1
 | 
					HEALTHCHECK CMD wget -q -O /dev/null http://localhost || exit 1
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -85,7 +85,7 @@ We'll be adding these features as drop-in plugins for the npm package in the fut
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Quick start
 | 
					## Quick start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Install the [Excalidraw npm package](https://www.npmjs.com/package/@excalidraw/excalidraw):
 | 
					**Note:** following instructions are for installing the Excalidraw [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw) when integrating Excalidraw into your own app. To run the repository locally for development, please refer to our [Development Guide](https://docs.excalidraw.com/docs/introduction/development).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
npm install react react-dom @excalidraw/excalidraw
 | 
					npm install react react-dom @excalidraw/excalidraw
 | 
				
			||||||
@@ -97,7 +97,7 @@ or via yarn
 | 
				
			|||||||
yarn add react react-dom @excalidraw/excalidraw
 | 
					yarn add react react-dom @excalidraw/excalidraw
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Don't forget to check out our [Documentation](https://docs.excalidraw.com)!
 | 
					Check out our [documentation](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/installation) for more details!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Contributing
 | 
					## Contributing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,3 @@
 | 
				
			|||||||
files:
 | 
					files:
 | 
				
			||||||
  - source: /src/locales/en.json
 | 
					  - source: /packages/excalidraw/locales/en.json
 | 
				
			||||||
    translation: /src/locales/%locale%.json
 | 
					    translation: /packages/excalidraw/locales/%locale%.json
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -133,7 +133,7 @@ function App() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Here is a [complete list](https://github.com/excalidraw/excalidraw/blob/master/src/components/mainMenu/DefaultItems.tsx) of the default items.
 | 
					Here is a [complete list](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/components/mainMenu/DefaultItems.tsx) of the default items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### MainMenu.Group
 | 
					### MainMenu.Group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ Defaults to `THEME.LIGHT` unless passed in `initialData.appState.theme`
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### MIME_TYPES
 | 
					### MIME_TYPES
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L101) contains all the mime types supported by `Excalidraw`.
 | 
					[`MIME_TYPES`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L101) contains all the mime types supported by `Excalidraw`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**How to use **
 | 
					**How to use **
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,9 +2,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
We support a simplified API to make it easier to generate Excalidraw elements programmatically. This API is in beta and subject to change before stable. You can check the [PR](https://github.com/excalidraw/excalidraw/pull/6546) for more details.
 | 
					We support a simplified API to make it easier to generate Excalidraw elements programmatically. This API is in beta and subject to change before stable. You can check the [PR](https://github.com/excalidraw/excalidraw/pull/6546) for more details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
For this purpose we introduced a new type [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133). This is the simplified version of [`ExcalidrawElement`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L134) type with the minimum possible attributes so that creating elements programmatically is much easier (especially for cases like binding arrows or creating text containers).
 | 
					For this purpose we introduced a new type [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133). This is the simplified version of [`ExcalidrawElement`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L134) type with the minimum possible attributes so that creating elements programmatically is much easier (especially for cases like binding arrows or creating text containers).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) can be converted to fully qualified Excalidraw elements by using [`convertToExcalidrawElements`](/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements).
 | 
					The [`ExcalidrawElementSkeleton`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133) can be converted to fully qualified Excalidraw elements by using [`convertToExcalidrawElements`](/docs/@excalidraw/excalidraw/api/excalidraw-element-skeleton#converttoexcalidrawelements).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## convertToExcalidrawElements
 | 
					## convertToExcalidrawElements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,7 +19,7 @@ convertToExcalidrawElements(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
| Name | Type | Default | Description |
 | 
					| Name | Type | Default | Description |
 | 
				
			||||||
| --- | --- | --- | --- |
 | 
					| --- | --- | --- | --- |
 | 
				
			||||||
| `elements` | [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L137) |  | The Excalidraw element Skeleton which needs to be converted to Excalidraw elements. |
 | 
					| `elements` | [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L137) |  | The Excalidraw element Skeleton which needs to be converted to Excalidraw elements. |
 | 
				
			||||||
| `opts` | `{ regenerateIds: boolean }` | ` {regenerateIds: true}` | By default `id` will be regenerated for all the elements irrespective of whether you pass the `id` so if you don't want the ids to regenerated, you can set this attribute to `false`. |
 | 
					| `opts` | `{ regenerateIds: boolean }` | ` {regenerateIds: true}` | By default `id` will be regenerated for all the elements irrespective of whether you pass the `id` so if you don't want the ids to regenerated, you can set this attribute to `false`. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**_How to use_**
 | 
					**_How to use_**
 | 
				
			||||||
@@ -71,7 +71,7 @@ function App() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can pass additional [`properties`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L27) as well to decorate the shapes.
 | 
					You can pass additional [`properties`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L27) as well to decorate the shapes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
:::info
 | 
					:::info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -192,7 +192,7 @@ convertToExcalidrawElements([
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Text Containers
 | 
					### Text Containers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
In addition to `type`, `x` and `y` properties, [`label`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L124C7-L130C59) property is required for text containers. The `text` property in `label` is required, rest of the attributes are optional.
 | 
					In addition to `type`, `x` and `y` properties, [`label`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L124C7-L130C59) property is required for text containers. The `text` property in `label` is required, rest of the attributes are optional.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If you don't provide the dimensions of container, we calculate it based of the label dimensions.
 | 
					If you don't provide the dimensions of container, we calculate it based of the label dimensions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -326,7 +326,7 @@ convertToExcalidrawElements([
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Arrow bindings
 | 
					### Arrow bindings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To bind arrow to a shape you need to specify its [`start`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L86) and [`end`](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L54) properties. You need to pass either `type` or `id` property in `start` and `end` properties, rest of the attributes are optional
 | 
					To bind arrow to a shape you need to specify its [`start`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L86) and [`end`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L54) properties. You need to pass either `type` or `id` property in `start` and `end` properties, rest of the attributes are optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
convertToExcalidrawElements([
 | 
					convertToExcalidrawElements([
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  (api:{" "}
 | 
					  (api:{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L616">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L616">
 | 
				
			||||||
    ExcalidrawAPI
 | 
					    ExcalidrawAPI
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  ) => void;
 | 
					  ) => void;
 | 
				
			||||||
@@ -13,16 +13,16 @@ Once the callback is triggered, you will need to store the api in state to acces
 | 
				
			|||||||
```jsx showLineNumbers
 | 
					```jsx showLineNumbers
 | 
				
			||||||
export default function App() {
 | 
					export default function App() {
 | 
				
			||||||
  const [excalidrawAPI, setExcalidrawAPI] = useState(null);
 | 
					  const [excalidrawAPI, setExcalidrawAPI] = useState(null);
 | 
				
			||||||
  return <Excalidraw excalidrawAPI={{(api)=> setExcalidrawAPI(api)}} />;
 | 
					  return <Excalidraw excalidrawAPI={(api)=> setExcalidrawAPI(api)} />;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can use this prop when you want to access some [Excalidraw APIs](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L616). We expose the below APIs :point_down:
 | 
					You can use this prop when you want to access some [Excalidraw APIs](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L616). We expose the below APIs :point_down:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| API | Signature | Usage |
 | 
					| API | Signature | Usage |
 | 
				
			||||||
| --- | --- | --- |
 | 
					| --- | --- | --- |
 | 
				
			||||||
| [updateScene](#updatescene) | `function` | updates the scene with the sceneData |
 | 
					| [updateScene](#updatescene) | `function` | updates the scene with the sceneData |
 | 
				
			||||||
| [updateLibrary](#updatelibrary) | `function` | updates the scene with the sceneData |
 | 
					| [updateLibrary](#updatelibrary) | `function` | updates the library |
 | 
				
			||||||
| [addFiles](#addfiles) | `function` | add files data to the appState |
 | 
					| [addFiles](#addfiles) | `function` | add files data to the appState |
 | 
				
			||||||
| [resetScene](#resetscene) | `function` | Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false. |
 | 
					| [resetScene](#resetscene) | `function` | Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false. |
 | 
				
			||||||
| [getSceneElementsIncludingDeleted](#getsceneelementsincludingdeleted) | `function` | Returns all the elements including the deleted in the scene |
 | 
					| [getSceneElementsIncludingDeleted](#getsceneelementsincludingdeleted) | `function` | Returns all the elements including the deleted in the scene |
 | 
				
			||||||
@@ -37,7 +37,7 @@ You can use this prop when you want to access some [Excalidraw APIs](https://git
 | 
				
			|||||||
| [setActiveTool](#setactivetool) | `function` | This API can be used to set the active tool |
 | 
					| [setActiveTool](#setactivetool) | `function` | This API can be used to set the active tool |
 | 
				
			||||||
| [setCursor](#setcursor) | `function` | This API can be used to set customise the mouse cursor on the canvas |
 | 
					| [setCursor](#setcursor) | `function` | This API can be used to set customise the mouse cursor on the canvas |
 | 
				
			||||||
| [resetCursor](#resetcursor) | `function` | This API can be used to reset to default mouse cursor on the canvas |
 | 
					| [resetCursor](#resetcursor) | `function` | This API can be used to reset to default mouse cursor on the canvas |
 | 
				
			||||||
| [toggleMenu](#togglemenu) | `function` | Toggles specific menus on/off |
 | 
					| [toggleSidebar](#toggleSidebar) | `function` | Toggles specific sidebar on/off |
 | 
				
			||||||
| [onChange](#onChange) | `function` | Subscribes to change events |
 | 
					| [onChange](#onChange) | `function` | Subscribes to change events |
 | 
				
			||||||
| [onPointerDown](#onPointerDown) | `function` | Subscribes to `pointerdown` events |
 | 
					| [onPointerDown](#onPointerDown) | `function` | Subscribes to `pointerdown` events |
 | 
				
			||||||
| [onPointerUp](#onPointerUp) | `function` | Subscribes to `pointerup` events |
 | 
					| [onPointerUp](#onPointerUp) | `function` | Subscribes to `pointerup` events |
 | 
				
			||||||
@@ -52,7 +52,7 @@ Additionally `ready` and `readyPromise` from the API have been discontinued. The
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  (scene:{" "}
 | 
					  (scene:{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L339">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L339">
 | 
				
			||||||
    sceneData
 | 
					    sceneData
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  ) => void
 | 
					  ) => void
 | 
				
			||||||
@@ -62,10 +62,10 @@ You can use this function to update the scene with the sceneData. It accepts the
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
| Name | Type | Description |
 | 
					| Name | Type | Description |
 | 
				
			||||||
| --- | --- | --- |
 | 
					| --- | --- | --- |
 | 
				
			||||||
| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L38) | The `elements` to be updated in the scene |
 | 
					| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L38) | The `elements` to be updated in the scene |
 | 
				
			||||||
| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L39) | The `appState` to be updated in the scene. |
 | 
					| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L39) | The `appState` to be updated in the scene. |
 | 
				
			||||||
| `collaborators` | <code>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37">Collaborator></a></code> | The list of collaborators to be updated in the scene. |
 | 
					| `collaborators` | <code>Map<string, <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L37">Collaborator></a></code> | The list of collaborators to be updated in the scene. |
 | 
				
			||||||
| `commitToHistory` | `boolean` | Implies if the `history (undo/redo)` should be recorded. Defaults to `false`. |
 | 
					| `commitToStore` | `boolean` | Implies if the change should be captured and commited to the `store`. Commited changes are emmitted and listened to by other components, such as `History` for undo / redo purposes. Defaults to `false`. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```jsx live
 | 
					```jsx live
 | 
				
			||||||
function App() {
 | 
					function App() {
 | 
				
			||||||
@@ -115,7 +115,7 @@ function App() {
 | 
				
			|||||||
      <button className="custom-button" onClick={updateScene}>
 | 
					      <button className="custom-button" onClick={updateScene}>
 | 
				
			||||||
        Update Scene
 | 
					        Update Scene
 | 
				
			||||||
      </button>
 | 
					      </button>
 | 
				
			||||||
      <Excalidraw ref={(api) => setExcalidrawAPI(api)} />
 | 
					      <Excalidraw excalidrawAPI={(api) => setExcalidrawAPI(api)} />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -125,13 +125,13 @@ function App() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  (opts: { <br /> libraryItems:{" "}
 | 
					  (opts: { <br /> libraryItems:{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L249">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L249">
 | 
				
			||||||
    LibraryItemsSource
 | 
					    LibraryItemsSource
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  ;<br /> merge?: boolean; <br /> prompt?: boolean;
 | 
					  ;<br /> merge?: boolean; <br /> prompt?: boolean;
 | 
				
			||||||
  <br /> openLibraryMenu?: boolean;
 | 
					  <br /> openLibraryMenu?: boolean;
 | 
				
			||||||
  <br /> defaultStatus?: "unpublished" | "published"; <br /> }) => Promise<
 | 
					  <br /> defaultStatus?: "unpublished" | "published"; <br /> }) => Promise<
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L246">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L246">
 | 
				
			||||||
    LibraryItems
 | 
					    LibraryItems
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
@@ -141,7 +141,7 @@ You can use this function to update the library. It accepts the below attributes
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
| Name | Type | Default | Description |
 | 
					| Name | Type | Default | Description |
 | 
				
			||||||
| --- | --- | --- | --- |
 | 
					| --- | --- | --- | --- |
 | 
				
			||||||
| `libraryItems` | [LibraryItemsSource](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L249) | \_ | The `libraryItems` to be replaced/merged with current library |
 | 
					| `libraryItems` | [LibraryItemsSource](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L249) | \_ | The `libraryItems` to be replaced/merged with current library |
 | 
				
			||||||
| `merge` | boolean | `false` | Whether to merge with existing library items. |
 | 
					| `merge` | boolean | `false` | Whether to merge with existing library items. |
 | 
				
			||||||
| `prompt` | boolean | `false` | Whether to prompt user for confirmation. |
 | 
					| `prompt` | boolean | `false` | Whether to prompt user for confirmation. |
 | 
				
			||||||
| `openLibraryMenu` | boolean | `false` | Keep the library menu open after library is updated. |
 | 
					| `openLibraryMenu` | boolean | `false` | Keep the library menu open after library is updated. |
 | 
				
			||||||
@@ -188,8 +188,8 @@ function App() {
 | 
				
			|||||||
        Update Library
 | 
					        Update Library
 | 
				
			||||||
      </button>
 | 
					      </button>
 | 
				
			||||||
      <Excalidraw
 | 
					      <Excalidraw
 | 
				
			||||||
        ref={(api) => setExcalidrawAPI(api)}
 | 
					        excalidrawAPI={(api) => setExcalidrawAPI(api)}
 | 
				
			||||||
        // initial data retrieved from https://github.com/excalidraw/excalidraw/blob/master/dev-docs/src/initialData.js
 | 
					        // initial data retrieved from https://github.com/excalidraw/excalidraw/blob/master/dev-docs/packages/excalidraw/initialData.js
 | 
				
			||||||
        initialData={{
 | 
					        initialData={{
 | 
				
			||||||
          libraryItems: initialData.libraryItems,
 | 
					          libraryItems: initialData.libraryItems,
 | 
				
			||||||
          appState: { openSidebar: "library" },
 | 
					          appState: { openSidebar: "library" },
 | 
				
			||||||
@@ -204,7 +204,7 @@ function App() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  (files:{" "}
 | 
					  (files:{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59">
 | 
				
			||||||
    BinaryFileData
 | 
					    BinaryFileData
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  ) => void
 | 
					  ) => void
 | 
				
			||||||
@@ -224,7 +224,7 @@ Resets the scene. If `resetLoadingState` is passed as true then it will also for
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  () =>{" "}
 | 
					  () =>{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115">
 | 
				
			||||||
    ExcalidrawElement[]
 | 
					    ExcalidrawElement[]
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
@@ -235,7 +235,7 @@ Returns all the elements including the deleted in the scene.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  () => NonDeleted<
 | 
					  () => NonDeleted<
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115">
 | 
				
			||||||
    ExcalidrawElement
 | 
					    ExcalidrawElement
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  []>
 | 
					  []>
 | 
				
			||||||
@@ -247,7 +247,7 @@ Returns all the elements excluding the deleted in the scene
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  () =>{" "}
 | 
					  () =>{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
 | 
				
			||||||
    AppState
 | 
					    AppState
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
@@ -288,7 +288,7 @@ Scroll the nearest element out of the elements supplied to the center of the vie
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
| Attribute | type | default | Description |
 | 
					| Attribute | type | default | Description |
 | 
				
			||||||
| --- | --- | --- | --- |
 | 
					| --- | --- | --- | --- |
 | 
				
			||||||
| target | [ExcalidrawElement](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L115) | All scene elements | The element(s) to scroll to. |
 | 
					| target | [ExcalidrawElement](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115) | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L115) | All scene elements | The element(s) to scroll to. |
 | 
				
			||||||
| opts.fitToContent | boolean | false | Whether to fit the elements to viewport by automatically changing zoom as needed. Note that the zoom range is between 10%-100%. |
 | 
					| opts.fitToContent | boolean | false | Whether to fit the elements to viewport by automatically changing zoom as needed. Note that the zoom range is between 10%-100%. |
 | 
				
			||||||
| opts.fitToViewport | boolean | false | Similar to fitToContent but the zoom range is not limited. If elements are smaller than the viewport, zoom will go above 100%. |
 | 
					| opts.fitToViewport | boolean | false | Similar to fitToContent but the zoom range is not limited. If elements are smaller than the viewport, zoom will go above 100%. |
 | 
				
			||||||
| opts.viewportZoomFactor | number | 0.7 | when fitToViewport=true, how much screen should the content cover, between 0.1 (10%) and 1 (100%) |
 | 
					| opts.viewportZoomFactor | number | 0.7 | when fitToViewport=true, how much screen should the content cover, between 0.1 (10%) and 1 (100%) |
 | 
				
			||||||
@@ -336,7 +336,7 @@ The unique id of the excalidraw component. This can be used to identify the exca
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  () =>{" "}
 | 
					  () =>{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L82">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L82">
 | 
				
			||||||
    files
 | 
					    files
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
@@ -364,7 +364,7 @@ This API has the below signature. It sets the `tool` passed in param as the acti
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
| Name | Type | Default | Description |
 | 
					| Name | Type | Default | Description |
 | 
				
			||||||
| --- | --- | --- | --- |
 | 
					| --- | --- | --- | --- |
 | 
				
			||||||
| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L91) | `selection` | The tool type which should be set as active tool. When setting `image` as active tool, the insertion onto canvas when using image tool is disabled by default, so you can enable it by setting `insertOnCanvasDirectly` to `true` |
 | 
					| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool. When setting `image` as active tool, the insertion onto canvas when using image tool is disabled by default, so you can enable it by setting `insertOnCanvasDirectly` to `true` |
 | 
				
			||||||
| `locked` | `boolean` | `false` | Indicates whether the the active tool should be locked. It behaves the same way when using the `lock` tool in the editor interface |
 | 
					| `locked` | `boolean` | `false` | Indicates whether the the active tool should be locked. It behaves the same way when using the `lock` tool in the editor interface |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## setCursor
 | 
					## setCursor
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,18 @@
 | 
				
			|||||||
# initialData
 | 
					# initialData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
{ elements?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>, appState?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> }
 | 
					{ elements?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>, appState?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> }
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This helps to load Excalidraw with `initialData`. It must be an object or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to an object containing the below optional fields.
 | 
					This helps to load Excalidraw with `initialData`. It must be an object or a [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which resolves to an object containing the below optional fields.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Name | Type | Description |
 | 
					| Name | Type | Description |
 | 
				
			||||||
| --- | --- | --- |
 | 
					| --- | --- | --- |
 | 
				
			||||||
| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) | The `elements` with which `Excalidraw` should be mounted. |
 | 
					| `elements` | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) | The `elements` with which `Excalidraw` should be mounted. |
 | 
				
			||||||
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | The `AppState` with which `Excalidraw` should be mounted. |
 | 
					| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) | The `AppState` with which `Excalidraw` should be mounted. |
 | 
				
			||||||
| `scrollToContent` | `boolean` | This attribute indicates whether to `scroll` to the nearest element to center once `Excalidraw` is mounted. By default, it will not scroll the nearest element to the center. Make sure you pass `initialData.appState.scrollX` and `initialData.appState.scrollY` when `scrollToContent` is false so that scroll positions are retained |
 | 
					| `scrollToContent` | `boolean` | This attribute indicates whether to `scroll` to the nearest element to center once `Excalidraw` is mounted. By default, it will not scroll the nearest element to the center. Make sure you pass `initialData.appState.scrollX` and `initialData.appState.scrollY` when `scrollToContent` is false so that scroll positions are retained |
 | 
				
			||||||
| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L247) | Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)> | This library items with which `Excalidraw` should be mounted. |
 | 
					| `libraryItems` | [LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L247) | Promise<[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200)> | This library items with which `Excalidraw` should be mounted. |
 | 
				
			||||||
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L82) | The `files` added to the scene. |
 | 
					| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L82) | The `files` added to the scene. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You might want to use this when you want to load excalidraw with some initial elements and app state.
 | 
					You might want to use this when you want to load excalidraw with some initial elements and app state.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,7 @@ All `props` are _optional_.
 | 
				
			|||||||
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
 | 
					| [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
 | 
				
			||||||
| [`theme`](#theme) | `"light"` | `"dark"` | `"light"` | The theme of the Excalidraw component |
 | 
					| [`theme`](#theme) | `"light"` | `"dark"` | `"light"` | The theme of the Excalidraw component |
 | 
				
			||||||
| [`name`](#name) | `string` |  | Name of the drawing |
 | 
					| [`name`](#name) | `string` |  | Name of the drawing |
 | 
				
			||||||
| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](#canvasactions) |
 | 
					| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](/docs/@excalidraw/excalidraw/api/props/ui-options#canvasactions) |
 | 
				
			||||||
| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
 | 
					| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
 | 
				
			||||||
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
 | 
					| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
 | 
				
			||||||
| [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load |
 | 
					| [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load |
 | 
				
			||||||
@@ -33,7 +33,7 @@ All `props` are _optional_.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Storing custom data on Excalidraw elements
 | 
					### Storing custom data on Excalidraw elements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Beyond attributes that Excalidraw elements already support, you can store `custom` data on each `element` in a `customData` object. The type of the attribute is [`Record<string, any>`](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L66) and is optional.
 | 
					Beyond attributes that Excalidraw elements already support, you can store `custom` data on each `element` in a `customData` object. The type of the attribute is [`Record<string, any>`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L66) and is optional.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can use this to add any extra information you need to keep track of.
 | 
					You can use this to add any extra information you need to keep track of.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -59,11 +59,11 @@ Every time component updates, this callback if passed will get triggered and has
 | 
				
			|||||||
(excalidrawElements, appState, files) => void;
 | 
					(excalidrawElements, appState, files) => void;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. `excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) in the scene.
 | 
					1. `excalidrawElements`: Array of [excalidrawElements](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) in the scene.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
 | 
					2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) of the scene.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
3. `files`: The [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) which are added to the scene.
 | 
					3. `files`: The [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L64) which are added to the scene.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Here you can try saving the data to your backend or local storage for example.
 | 
					Here you can try saving the data to your backend or local storage for example.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -79,14 +79,14 @@ This callback is triggered when mouse pointer is updated.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
2.`button`: The position of the button. This will be one of `["down", "up"]`
 | 
					2.`button`: The position of the button. This will be one of `["down", "up"]`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
3.`pointersMap`: [`pointers`](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L131) map of the scene
 | 
					3.`pointersMap`: [`pointers`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L131) map of the scene
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
(exportedElements, appState, canvas) => void
 | 
					(exportedElements, appState, canvas) => void
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L87) which needs to be exported.
 | 
					1. `exportedElements`: An array of [non deleted elements](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L87) which needs to be exported.
 | 
				
			||||||
2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) of the scene.
 | 
					2. `appState`: [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) of the scene.
 | 
				
			||||||
3. `canvas`: The `HTMLCanvasElement` of the scene.
 | 
					3. `canvas`: The `HTMLCanvasElement` of the scene.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### onPointerDown
 | 
					### onPointerDown
 | 
				
			||||||
@@ -96,11 +96,11 @@ This prop if passed will be triggered on pointer down events and has the below s
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
(activeTool:{" "}
 | 
					(activeTool:{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L115">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L115">
 | 
				
			||||||
    {" "}
 | 
					    {" "}
 | 
				
			||||||
    AppState["activeTool"]
 | 
					    AppState["activeTool"]
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  , pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L424">
 | 
					  , pointerDownState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L424">
 | 
				
			||||||
    PointerDownState
 | 
					    PointerDownState
 | 
				
			||||||
  </a>) => void
 | 
					  </a>) => void
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
@@ -119,7 +119,7 @@ This callback is triggered if passed when something is pasted into the scene. Yo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  (data:{" "}
 | 
					  (data:{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/clipboard.ts#L18">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/clipboard.ts#L18">
 | 
				
			||||||
    ClipboardData
 | 
					    ClipboardData
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  , event: ClipboardEvent | null) => boolean
 | 
					  , event: ClipboardEvent | null) => boolean
 | 
				
			||||||
@@ -135,7 +135,7 @@ This callback if supplied will get triggered when the library is updated and has
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  (items:{" "}
 | 
					  (items:{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">
 | 
				
			||||||
    LibraryItems
 | 
					    LibraryItems
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  ) => void | Promise<any>
 | 
					  ) => void | Promise<any>
 | 
				
			||||||
@@ -149,7 +149,7 @@ This prop if passed will be triggered when clicked on `link`. To handle the redi
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  (element:{" "}
 | 
					  (element:{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">
 | 
				
			||||||
    ExcalidrawElement
 | 
					    ExcalidrawElement
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  , event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
 | 
					  , event: CustomEvent<{ nativeEvent: MouseEvent }>) => void
 | 
				
			||||||
@@ -182,7 +182,7 @@ const onLinkOpen: ExcalidrawProps["onLinkOpen"] = useCallback(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### langCode
 | 
					### langCode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Determines the `language` of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
 | 
					Determines the `language` of the UI. It should be one of the [available language codes](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L14). Defaults to `en` (English). We also export default language and supported languages which you can import as shown below.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import { defaultLang, languages } from "@excalidraw/excalidraw";
 | 
					import { defaultLang, languages } from "@excalidraw/excalidraw";
 | 
				
			||||||
@@ -191,7 +191,7 @@ import { defaultLang, languages } from "@excalidraw/excalidraw";
 | 
				
			|||||||
| name | type |
 | 
					| name | type |
 | 
				
			||||||
| --- | --- |
 | 
					| --- | --- |
 | 
				
			||||||
| `defaultLang` | `string` |
 | 
					| `defaultLang` | `string` |
 | 
				
			||||||
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
 | 
					| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### viewModeEnabled
 | 
					### viewModeEnabled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  (isMobile: boolean, appState:
 | 
					  (isMobile: boolean, appState:
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
 | 
				
			||||||
    AppState
 | 
					    AppState
 | 
				
			||||||
  </a>) => JSX | null
 | 
					  </a>) => JSX | null
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
@@ -66,7 +66,7 @@ function App() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  (element: NonDeleted<ExcalidrawEmbeddableElement>, appState:{" "}
 | 
					  (element: NonDeleted<ExcalidrawEmbeddableElement>, appState:{" "}
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">
 | 
				
			||||||
    AppState
 | 
					    AppState
 | 
				
			||||||
  </a>
 | 
					  </a>
 | 
				
			||||||
  ) => JSX.Element | null
 | 
					  ) => JSX.Element | null
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ This prop can be used to customise UI of Excalidraw. Currently we support custom
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
  <br /> canvasActions?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L372">
 | 
					  <br /> canvasActions?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L372">
 | 
				
			||||||
    CanvasActions
 | 
					    CanvasActions
 | 
				
			||||||
  </a>, <br /> dockedSidebarBreakpoint?: number, <br /> welcomeScreen?: boolean <br />
 | 
					  </a>, <br /> dockedSidebarBreakpoint?: number, <br /> welcomeScreen?: boolean <br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,7 +55,7 @@ If `UIOptions.canvasActions.export` is `false` the export button will not be ren
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## dockedSidebarBreakpoint
 | 
					## dockedSidebarBreakpoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L161).  
 | 
					This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to [`MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L161).  
 | 
				
			||||||
If the _width_ of the _excalidraw_ container exceeds _dockedSidebarBreakpoint_, the sidebar will be `dockable` and the button to `dock` the sidebar will be shown  
 | 
					If the _width_ of the _excalidraw_ container exceeds _dockedSidebarBreakpoint_, the sidebar will be `dockable` and the button to `dock` the sidebar will be shown  
 | 
				
			||||||
If user choses to `dock` the sidebar, it will push the right part of the UI towards the left, making space for the sidebar as shown below.
 | 
					If user choses to `dock` the sidebar, it will push the right part of the UI towards the left, making space for the sidebar as shown below.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,7 +73,7 @@ function App() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## tools
 | 
					## tools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This `prop ` controls the visibility of the tools in the editor.
 | 
					This `prop` controls the visibility of the tools in the editor.
 | 
				
			||||||
Currently you can control the visibility of `image` tool via this prop.
 | 
					Currently you can control the visibility of `image` tool via this prop.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Prop | Type | Default | Description |
 | 
					| Prop | Type | Default | Description |
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,16 +20,16 @@ exportToCanvas({<br/> 
 | 
				
			|||||||
  getDimensions,<br/> 
 | 
					  getDimensions,<br/> 
 | 
				
			||||||
  files,<br/> 
 | 
					  files,<br/> 
 | 
				
			||||||
  exportPadding?: number;<br/>
 | 
					  exportPadding?: number;<br/>
 | 
				
			||||||
}: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L21">ExportOpts</a>
 | 
					}: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L21">ExportOpts</a>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Name | Type | Default | Description |
 | 
					| Name | Type | Default | Description |
 | 
				
			||||||
| --- | --- | --- | --- |
 | 
					| --- | --- | --- | --- |
 | 
				
			||||||
| `elements` | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) |  | The elements to be exported to canvas. |
 | 
					| `elements` | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) |  | The elements to be exported to canvas. |
 | 
				
			||||||
| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L23) | [Default App State](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L17) | The app state of the scene. |
 | 
					| `appState` | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L23) | [Default App State](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/appState.ts#L17) | The app state of the scene. |
 | 
				
			||||||
| [`getDimensions`](#getdimensions) | `function` | _ | A function which returns the `width`, `height`, and optionally `scale` (defaults to  `1`), with which canvas is to be exported. |
 | 
					| [`getDimensions`](#getdimensions) | `function` | _ | A function which returns the `width`, `height`, and optionally `scale` (defaults to  `1`), with which canvas is to be exported. |
 | 
				
			||||||
| `maxWidthOrHeight` | `number` | _ | The maximum `width` or `height` of the exported image. If provided, `getDimensions` is ignored. |
 | 
					| `maxWidthOrHeight` | `number` | _ | The maximum `width` or `height` of the exported image. If provided, `getDimensions` is ignored. |
 | 
				
			||||||
| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59) | _ | The files added to the scene. |
 | 
					| `files` | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59) | _ | The files added to the scene. |
 | 
				
			||||||
| `exportPadding` | `number` | `10` | The `padding` to be added on canvas. |
 | 
					| `exportPadding` | `number` | `10` | The `padding` to be added on canvas. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -105,7 +105,7 @@ function App() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
exportToBlob(<br/> 
 | 
					exportToBlob(<br/> 
 | 
				
			||||||
  opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L14">ExportOpts</a> & {<br/> 
 | 
					  opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L14">ExportOpts</a> & {<br/> 
 | 
				
			||||||
  mimeType?: string,<br/> 
 | 
					  mimeType?: string,<br/> 
 | 
				
			||||||
  quality?: number,<br/> 
 | 
					  quality?: number,<br/> 
 | 
				
			||||||
  exportPadding?: number;<br/>
 | 
					  exportPadding?: number;<br/>
 | 
				
			||||||
@@ -134,16 +134,16 @@ Returns a promise which resolves with a [blob](https://developer.mozilla.org/en-
 | 
				
			|||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
exportToSvg({<br/> 
 | 
					exportToSvg({<br/> 
 | 
				
			||||||
  elements:  
 | 
					  elements:  
 | 
				
			||||||
    <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">
 | 
					    <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">
 | 
				
			||||||
      ExcalidrawElement[]
 | 
					      ExcalidrawElement[]
 | 
				
			||||||
    </a>,<br/> 
 | 
					    </a>,<br/> 
 | 
				
			||||||
  appState:
 | 
					  appState:
 | 
				
			||||||
    <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95"> AppState
 | 
					    <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95"> AppState
 | 
				
			||||||
    </a>,<br/> 
 | 
					    </a>,<br/> 
 | 
				
			||||||
  exportPadding: number,<br/> 
 | 
					  exportPadding: number,<br/> 
 | 
				
			||||||
  metadata: string,<br/> 
 | 
					  metadata: string,<br/> 
 | 
				
			||||||
  files: 
 | 
					  files: 
 | 
				
			||||||
  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L59">
 | 
					  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L59">
 | 
				
			||||||
      BinaryFiles
 | 
					      BinaryFiles
 | 
				
			||||||
    </a>,<br/>
 | 
					    </a>,<br/>
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@@ -151,10 +151,10 @@ exportToSvg({<br/> 
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
| Name | Type | Default | Description |
 | 
					| Name | Type | Default | Description |
 | 
				
			||||||
| --- | --- | --- | --- |
 | 
					| --- | --- | --- | --- |
 | 
				
			||||||
| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114) |  | The elements to exported as `svg `|
 | 
					| elements | [Excalidraw Element []](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114) |  | The elements to exported as `svg `|
 | 
				
			||||||
| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/src/appState.ts#L11) | The `appState` of the scene |
 | 
					| appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) | [defaultAppState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/appState.ts#L11) | The `appState` of the scene |
 | 
				
			||||||
| exportPadding | number | 10 | The `padding` to be added on canvas |
 | 
					| exportPadding | number | 10 | The `padding` to be added on canvas |
 | 
				
			||||||
| files | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L64) | undefined | The `files` added to the scene. |
 | 
					| files | [BinaryFiles](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L64) | undefined | The `files` added to the scene. |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This function returns a promise which resolves to `svg` of the exported drawing.
 | 
					This function returns a promise which resolves to `svg` of the exported drawing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -164,7 +164,7 @@ This function returns a promise which resolves to `svg` of the exported drawing.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
exportToClipboard(<br/> 
 | 
					exportToClipboard(<br/> 
 | 
				
			||||||
  opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/packages/utils.ts#L21">ExportOpts</a> & {<br/> 
 | 
					  opts: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/utils.ts#L21">ExportOpts</a> & {<br/> 
 | 
				
			||||||
  mimeType?: string,<br/> 
 | 
					  mimeType?: string,<br/> 
 | 
				
			||||||
  quality?: number;<br/> 
 | 
					  quality?: number;<br/> 
 | 
				
			||||||
  type: 'png' | 'svg' |'json'<br/>
 | 
					  type: 'png' | 'svg' |'json'<br/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ id: "restore"
 | 
				
			|||||||
**_Signature_**
 | 
					**_Signature_**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState["appState"]</a>,<br/>  localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>
 | 
					restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState["appState"]</a>,<br/>  localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**_How to use_**
 | 
					**_How to use_**
 | 
				
			||||||
@@ -17,7 +17,7 @@ restoreAppState(appState: <a href="https://github.com/excalidraw/excalidraw/blob
 | 
				
			|||||||
import { restoreAppState } from "@excalidraw/excalidraw";
 | 
					import { restoreAppState } from "@excalidraw/excalidraw";
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This function will make sure all the `keys` have appropriate `values` in [appState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95) and if any key is missing, it will be set to its `default` value.
 | 
					This function will make sure all the `keys` have appropriate `values` in [appState](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95) and if any key is missing, it will be set to its `default` value.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When `localAppState` is supplied, it's used in place of values that are missing (`undefined`) in `appState` instead of the defaults.  
 | 
					When `localAppState` is supplied, it's used in place of values that are missing (`undefined`) in `appState` instead of the defaults.  
 | 
				
			||||||
Use this as a way to not override user's defaults if you persist them.
 | 
					Use this as a way to not override user's defaults if you persist them.
 | 
				
			||||||
@@ -29,16 +29,16 @@ You can pass `null` / `undefined` if not applicable.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
restoreElements(
 | 
					restoreElements(
 | 
				
			||||||
  elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/> 
 | 
					  elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a>,<br/> 
 | 
				
			||||||
  localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>,<br/> 
 | 
					  localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>,<br/> 
 | 
				
			||||||
  opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
 | 
					  opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| Prop | Type | Description |
 | 
					| Prop | Type | Description |
 | 
				
			||||||
| ---- | ---- | ---- |
 | 
					| ---- | ---- | ---- |
 | 
				
			||||||
| `elements` | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ImportedDataState["elements"]</a> | The `elements` to be restored |
 | 
					| `elements` | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ImportedDataState["elements"]</a> | The `elements` to be restored |
 | 
				
			||||||
| [`localElements`](#localelements) | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined |  When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. |
 | 
					| [`localElements`](#localelements) | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined |  When `localElements` are supplied, they are used to ensure that existing restored elements reuse `version` (and increment it), and regenerate `versionNonce`. |
 | 
				
			||||||
| [`opts`](#opts) | `Object` | The extra optional parameter to configure restored elements
 | 
					| [`opts`](#opts) | `Object` | The extra optional parameter to configure restored elements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### localElements
 | 
					#### localElements
 | 
				
			||||||
@@ -70,15 +70,15 @@ Parameter `refreshDimensions` indicates whether we should also `recalculate` tex
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
restore(
 | 
					restore(
 | 
				
			||||||
  data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState</a>,<br/> 
 | 
					  data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState</a>,<br/> 
 | 
				
			||||||
  localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>> | null | undefined,<br/> 
 | 
					  localAppState: Partial<<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>> | null | undefined,<br/> 
 | 
				
			||||||
  localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L4">DataState</a><br/>
 | 
					  localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null | undefined<br/>): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L4">DataState</a><br/>
 | 
				
			||||||
  opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
 | 
					  opts: { refreshDimensions?: boolean, repairBindings?: boolean }<br/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#restoreElements) about `localElements`.
 | 
					See [`restoreAppState()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/excalidraw/README.md#restoreAppState) about `localAppState`, and [`restoreElements()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/packages/excalidraw/README.md#restoreElements) about `localElements`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**_How to use_**
 | 
					**_How to use_**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -93,7 +93,7 @@ This function makes sure elements and state is set to appropriate values and set
 | 
				
			|||||||
**_Signature_**
 | 
					**_Signature_**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
restoreLibraryItems(libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L34">ImportedDataState["libraryItems"]</a>,<br/> 
 | 
					restoreLibraryItems(libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L34">ImportedDataState["libraryItems"]</a>,<br/> 
 | 
				
			||||||
defaultStatus: "published" | "unpublished")
 | 
					defaultStatus: "published" | "unpublished")
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ These are pure Javascript functions exported from the @excalidraw/excalidraw [`@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### serializeAsJSON
 | 
					### serializeAsJSON
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Takes the scene elements and state and returns a JSON string. `Deleted` elements as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/src/data/json.ts#L42) source for details).
 | 
					Takes the scene elements and state and returns a JSON string. `Deleted` elements as well as most properties from `AppState` are removed from the resulting JSON. (see [`serializeAsJSON()`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/json.ts#L42) source for details).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If you want to overwrite the `source` field in the `JSON` string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
 | 
					If you want to overwrite the `source` field in the `JSON` string, you can set `window.EXCALIDRAW_EXPORT_SOURCE` to the desired value.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -16,8 +16,8 @@ If you want to overwrite the `source` field in the `JSON` string, you can set `w
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
serializeAsJSON({<br/> 
 | 
					serializeAsJSON({<br/> 
 | 
				
			||||||
  elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>,<br/> 
 | 
					  elements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>,<br/> 
 | 
				
			||||||
  appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a>,<br/>
 | 
					  appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a>,<br/>
 | 
				
			||||||
}): string
 | 
					}): string
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -37,7 +37,7 @@ If you want to overwrite the source field in the JSON string, you can set `windo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
serializeLibraryAsJSON(
 | 
					serializeLibraryAsJSON(
 | 
				
			||||||
  libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems[]</a>)
 | 
					  libraryItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">LibraryItems[]</a>)
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**How to use**
 | 
					**How to use**
 | 
				
			||||||
@@ -53,7 +53,7 @@ Returns `true` if element is invisibly small (e.g. width & height are zero).
 | 
				
			|||||||
**_Signature_**
 | 
					**_Signature_**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
isInvisiblySmallElement(element:  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement</a>): boolean
 | 
					isInvisiblySmallElement(element:  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement</a>): boolean
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**How to use**
 | 
					**How to use**
 | 
				
			||||||
@@ -80,10 +80,10 @@ excalidrawAPI.updateScene(scene);
 | 
				
			|||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
loadFromBlob(<br/> 
 | 
					loadFromBlob(<br/> 
 | 
				
			||||||
  blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/> 
 | 
					  blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/> 
 | 
				
			||||||
  localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> | null,<br/> 
 | 
					  localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> | null,<br/> 
 | 
				
			||||||
  localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/> 
 | 
					  localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/> 
 | 
				
			||||||
  fileHandle?: FileSystemHandle | null <br/>
 | 
					  fileHandle?: FileSystemHandle | null <br/>
 | 
				
			||||||
) => Promise<<a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/restore.ts#L61">RestoredDataState</a>>
 | 
					) => Promise<<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/restore.ts#L61">RestoredDataState</a>>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### loadLibraryFromBlob
 | 
					### loadLibraryFromBlob
 | 
				
			||||||
@@ -130,10 +130,10 @@ if (contents.type === MIME_TYPES.excalidraw) {
 | 
				
			|||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
loadSceneOrLibraryFromBlob(<br/> 
 | 
					loadSceneOrLibraryFromBlob(<br/> 
 | 
				
			||||||
  blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/> 
 | 
					  blob: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>,<br/> 
 | 
				
			||||||
  localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a> | null,<br/> 
 | 
					  localAppState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a> | null,<br/> 
 | 
				
			||||||
  localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/> 
 | 
					  localElements: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a> | null,<br/> 
 | 
				
			||||||
  fileHandle?: FileSystemHandle | null<br/>
 | 
					  fileHandle?: FileSystemHandle | null<br/>
 | 
				
			||||||
) => Promise<{ type: string, data: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/restore.ts#L53">RestoredDataState</a> | <a href="https://github.com/excalidraw/excalidraw/blob/master/src/data/types.ts#L33">ImportedLibraryState</a>}>
 | 
					) => Promise<{ type: string, data: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/restore.ts#L53">RestoredDataState</a> | <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L33">ImportedLibraryState</a>}>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### getFreeDrawSvgPath
 | 
					### getFreeDrawSvgPath
 | 
				
			||||||
@@ -149,7 +149,7 @@ import { getFreeDrawSvgPath } from "@excalidraw/excalidraw";
 | 
				
			|||||||
**Signature**
 | 
					**Signature**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
getFreeDrawSvgPath(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L182">ExcalidrawFreeDrawElement</a>)
 | 
					getFreeDrawSvgPath(element: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L182">ExcalidrawFreeDrawElement</a>)
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### isLinearElement
 | 
					### isLinearElement
 | 
				
			||||||
@@ -165,7 +165,7 @@ import { isLinearElement } from "@excalidraw/excalidraw";
 | 
				
			|||||||
**Signature**
 | 
					**Signature**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
isLinearElement(elementType?: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L80">ExcalidrawElement</a>): boolean
 | 
					isLinearElement(elementType?: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L80">ExcalidrawElement</a>): boolean
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### getNonDeletedElements
 | 
					### getNonDeletedElements
 | 
				
			||||||
@@ -181,7 +181,7 @@ import { getNonDeletedElements } from "@excalidraw/excalidraw";
 | 
				
			|||||||
**Signature**
 | 
					**Signature**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
getNonDeletedElements(elements:<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114"> readonly ExcalidrawElement[]</a>): as readonly <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L125">NonDeletedExcalidrawElement[]</a>
 | 
					getNonDeletedElements(elements:<a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114"> readonly ExcalidrawElement[]</a>): as readonly <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L125">NonDeletedExcalidrawElement[]</a>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### mergeLibraryItems
 | 
					### mergeLibraryItems
 | 
				
			||||||
@@ -196,9 +196,9 @@ import { mergeLibraryItems } from "@excalidraw/excalidraw";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
mergeLibraryItems(<br/> 
 | 
					mergeLibraryItems(<br/> 
 | 
				
			||||||
  localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>,<br/> 
 | 
					  localItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L250">LibraryItems</a>,<br/> 
 | 
				
			||||||
  otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200">LibraryItems</a><br/>
 | 
					  otherItems: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L200">LibraryItems</a><br/>
 | 
				
			||||||
): <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L250">LibraryItems</a>
 | 
					): <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L250">LibraryItems</a>
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### parseLibraryTokensFromUrl
 | 
					### parseLibraryTokensFromUrl
 | 
				
			||||||
@@ -239,8 +239,8 @@ export const App = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
useHandleLibrary(opts: {<br/> 
 | 
					useHandleLibrary(opts: {<br/> 
 | 
				
			||||||
  excalidrawAPI: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L494">ExcalidrawAPI</a>,<br/> 
 | 
					  excalidrawAPI: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L494">ExcalidrawAPI</a>,<br/> 
 | 
				
			||||||
  getInitialLibraryItems?: () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L253">LibraryItemsSource</a><br/>
 | 
					  getInitialLibraryItems?: () => <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L253">LibraryItemsSource</a><br/>
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -253,7 +253,7 @@ This function returns the current `scene` version.
 | 
				
			|||||||
**_Signature_**
 | 
					**_Signature_**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
getSceneVersion(elements:  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L114">ExcalidrawElement[]</a>)
 | 
					getSceneVersion(elements:  <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/element/types.ts#L114">ExcalidrawElement[]</a>)
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**How to use**
 | 
					**How to use**
 | 
				
			||||||
@@ -274,7 +274,7 @@ import { sceneCoordsToViewportCoords } from "@excalidraw/excalidraw";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
sceneCoordsToViewportCoords({ sceneX: number, sceneY: number },<br/> 
 | 
					sceneCoordsToViewportCoords({ sceneX: number, sceneY: number },<br/> 
 | 
				
			||||||
  appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a><br/>): { x: number, y: number }
 | 
					  appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): { x: number, y: number }
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### viewportCoordsToSceneCoords
 | 
					### viewportCoordsToSceneCoords
 | 
				
			||||||
@@ -289,7 +289,7 @@ import { viewportCoordsToSceneCoords } from "@excalidraw/excalidraw";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<pre>
 | 
					<pre>
 | 
				
			||||||
viewportCoordsToSceneCoords({ clientX: number, clientY: number },<br/> 
 | 
					viewportCoordsToSceneCoords({ clientX: number, clientY: number },<br/> 
 | 
				
			||||||
  appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L95">AppState</a><br/>): {x: number, y: number}
 | 
					  appState: <a href="https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L95">AppState</a><br/>): {x: number, y: number}
 | 
				
			||||||
</pre>
 | 
					</pre>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### useDevice
 | 
					### useDevice
 | 
				
			||||||
@@ -350,8 +350,8 @@ To help with localization, we export the following.
 | 
				
			|||||||
| name | type |
 | 
					| name | type |
 | 
				
			||||||
| --- | --- |
 | 
					| --- | --- |
 | 
				
			||||||
| `defaultLang` | `string` |
 | 
					| `defaultLang` | `string` |
 | 
				
			||||||
| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
 | 
					| `languages` | [`Language[]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
 | 
				
			||||||
| `useI18n` | [`() => { langCode, t }`](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L15) |
 | 
					| `useI18n` | [`() => { langCode, t }`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/i18n.ts#L15) |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import { defaultLang, languages, useI18n } from "@excalidraw/excalidraw";
 | 
					import { defaultLang, languages, useI18n } from "@excalidraw/excalidraw";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ Most notably, you can customize the primary colors, by overriding these variable
 | 
				
			|||||||
- `--color-primary-light`
 | 
					- `--color-primary-light`
 | 
				
			||||||
- `--color-primary-contrast-offset` — a slightly darker (in light mode), or lighter (in dark mode) `--color-primary` color to fix contrast issues (see [Chubb illusion](https://en.wikipedia.org/wiki/Chubb_illusion)). It will fall back to `--color-primary` if not present.
 | 
					- `--color-primary-contrast-offset` — a slightly darker (in light mode), or lighter (in dark mode) `--color-primary` color to fix contrast issues (see [Chubb illusion](https://en.wikipedia.org/wiki/Chubb_illusion)). It will fall back to `--color-primary` if not present.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/src/css/theme.scss), though most of them will not make sense to override.
 | 
					For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/css/theme.scss), though most of them will not make sense to override.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```css showLineNumbers
 | 
					```css showLineNumbers
 | 
				
			||||||
.custom-styles .excalidraw {
 | 
					.custom-styles .excalidraw {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
 | 
				
			|||||||
1. Install the dependencies
 | 
					1. Install the dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   ```bash
 | 
					   ```bash
 | 
				
			||||||
   cd src/packages/excalidraw && yarn
 | 
					   cd packages/excalidraw && yarn
 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
2. Start the example app
 | 
					2. Start the example app
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,7 +39,7 @@ Since Vite removes env variables by default, you can update the vite config to e
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 define: {
 | 
					 define: {
 | 
				
			||||||
    "process.env.IS_PREACT": process.env.IS_PREACT,
 | 
					    "process.env.IS_PREACT": JSON.stringify("true"),
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,15 +32,9 @@ function App() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Next.js
 | 
					### Next.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`.
 | 
					Since Excalidraw doesn't support `server side rendering` so it should be rendered only on `client`. The way to achieve this in next.js is using `next.js dynamic import`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Here are two ways on how you can render **Excalidraw** on **Next.js**.
 | 
					If you want to only import `Excalidraw` component you can do :point_down:
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
1. Using **Next.js Dynamic** import [Recommended].
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Since Excalidraw doesn't support server side rendering so you can also use `dynamic import` to render by setting `ssr` to `false`.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
```jsx showLineNumbers
 | 
					```jsx showLineNumbers
 | 
				
			||||||
import dynamic from "next/dynamic";
 | 
					import dynamic from "next/dynamic";
 | 
				
			||||||
@@ -55,25 +49,88 @@ export default function App() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-dynamic-k8yjq2).
 | 
					However the above component only works for named component exports. If you want to import some util / constant or something else apart from Excalidraw, then this approach will not work. Instead you can write a wrapper over Excalidraw and import the wrapper dynamically.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you are using `pages router` then importing the wrapper dynamically would work, where as if you are using `app router` then you will have to also add `useClient` directive on top of the file in addition to dynamically importing the wrapper as shown :point_down:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
2. Importing Excalidraw once **client** is rendered.
 | 
					<Tabs>
 | 
				
			||||||
 | 
					  <TabItem value="Excalidraw Wrapper" label="Excalidraw Wrapper" >
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```jsx showLineNumbers
 | 
					  ```jsx showLineNumbers
 | 
				
			||||||
import { useState, useEffect } from "react";
 | 
					  "use client";
 | 
				
			||||||
export default function App() {
 | 
					  import { Excalidraw, convertToExcalidrawElements } from "@excalidraw/excalidraw";
 | 
				
			||||||
  const [Excalidraw, setExcalidraw] = useState(null);
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  import "@excalidraw/excalidraw/index.css";
 | 
				
			||||||
    import("@excalidraw/excalidraw").then((comp) =>
 | 
					
 | 
				
			||||||
      setExcalidraw(comp.Excalidraw),
 | 
					  const ExcalidrawWrapper: React.FC = () => {
 | 
				
			||||||
 | 
					    console.info(convertToExcalidrawElements([{
 | 
				
			||||||
 | 
					      type: "rectangle",
 | 
				
			||||||
 | 
					      id: "rect-1",
 | 
				
			||||||
 | 
					      width: 186.47265625,
 | 
				
			||||||
 | 
					      height: 141.9765625,
 | 
				
			||||||
 | 
					    },]));
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div style={{height:"500px", width:"500px"}}>  
 | 
				
			||||||
 | 
					        <Excalidraw />
 | 
				
			||||||
 | 
					      </div> 
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }, []);
 | 
					  };
 | 
				
			||||||
  return <>{Excalidraw && <Excalidraw />}</>;
 | 
					  export default ExcalidrawWrapper;
 | 
				
			||||||
}
 | 
					  ```
 | 
				
			||||||
```
 | 
					
 | 
				
			||||||
 | 
					  </TabItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <TabItem value="pages" label="Pages router">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ```jsx showLineNumbers
 | 
				
			||||||
 | 
					  import dynamic from "next/dynamic";
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Since client components get prerenderd on server as well hence importing 
 | 
				
			||||||
 | 
					  // the excalidraw stuff dynamically with ssr false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const ExcalidrawWrapper = dynamic(
 | 
				
			||||||
 | 
					    async () => (await import("../excalidrawWrapper")).default,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      ssr: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export default function Page() {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <ExcalidrawWrapper />      
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ```
 | 
				
			||||||
 | 
					  </TabItem>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <TabItem value="app" label="App router">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ```jsx showLineNumbers
 | 
				
			||||||
 | 
					  import dynamic from "next/dynamic";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Since client components get prerenderd on server as well hence importing 
 | 
				
			||||||
 | 
					  // the excalidraw stuff dynamically with ssr false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const ExcalidrawWrapper = dynamic(
 | 
				
			||||||
 | 
					    async () => (await import("../excalidrawWrapper")).default,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      ssr: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export default function Page() {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <ExcalidrawWrapper />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </TabItem>
 | 
				
			||||||
 | 
					</Tabs>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Here is a [source code](https://github.com/excalidraw/excalidraw/tree/master/examples/excalidraw/with-nextjs) for the example with app and pages router. You you can try it out [here](https://excalidraw-package-example-with-nextjs-gh6smrdnq-excalidraw.vercel.app/).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-5xb3d)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
 | 
					The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -93,7 +150,7 @@ Since Vite removes env variables by default, you can update the vite config to e
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 define: {
 | 
					 define: {
 | 
				
			||||||
    "process.env.IS_PREACT": process.env.IS_PREACT,
 | 
					    "process.env.IS_PREACT": JSON.stringify("true"),
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
::: 
 | 
					::: 
 | 
				
			||||||
@@ -148,7 +205,7 @@ import TabItem from "@theme/TabItem";
 | 
				
			|||||||
      <h1>Excalidraw Embed Example</h1>
 | 
					      <h1>Excalidraw Embed Example</h1>
 | 
				
			||||||
      <div id="app"></div>
 | 
					      <div id="app"></div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <script type="text/javascript" src="src/index.js"></script>
 | 
					    <script type="text/javascript" src="packages/excalidraw/index.js"></script>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,9 +38,9 @@ Add the diagram type in switch case in [`parseMermaid`](https://github.com/excal
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Writing the Excalidraw Skeleton Convertor
 | 
					## Writing the Excalidraw Skeleton Convertor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133) format.
 | 
					With the completion of previous step, we have all the data, now we need to transform it so to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133) format.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133).
 | 
					Similar to [`FlowChartToExcalidrawSkeletonConverter`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24), you have to write the `{{diagramType}}ToExcalidrawSkeletonConverter` which parses the data received in previous step and returns the [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Thats it, you have added the new diagram type 🥳, now lets test it out!
 | 
					Thats it, you have added the new diagram type 🥳, now lets test it out!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ In this section we will be diving into how the [flowchart parser](https://github
 | 
				
			|||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38).
 | 
					We use `diagram.parser.yy` attribute to parse the data. If you want to know more about how the `diagram.parse.yy` attribute looks like, you can check it [here](https://github.com/mermaid-js/mermaid/blob/00d06c7282a701849793680c1e97da1cfdfcce62/packages/mermaid/src/diagrams/flowchart/flowDb.js#L768), however for scope of flowchart we are using **3** APIs from this parser to compute `vertices`, `edges` and `clusters` as we need these data to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
For computing `vertices` and `edge`s lets consider the below svg generated by mermaid
 | 
					For computing `vertices` and `edge`s lets consider the below svg generated by mermaid
 | 
				
			||||||
@@ -42,7 +42,7 @@ Considering the same example this is the response from the API
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response.
 | 
					The dimensions and position is missing in this response and we need that to transform to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38), for this we have our own parser [`parseVertex`](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/parseMermaid.ts#L178) which takes the above response and uses the `svg` together to compute position, dimensions and cleans up the response.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 The final output from `parseVertex` looks like :point_down:
 | 
					 The final output from `parseVertex` looks like :point_down:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,11 +55,11 @@ If you want to understand how flowchart parser works, you can navigate to [Flowc
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Converting to ExcalidrawElementSkeleton
 | 
					## Converting to ExcalidrawElementSkeleton
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw.
 | 
					Now we have all the data, we just need to transform it to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38) API so it can be rendered in Excalidraw.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
For this we have `converters` which takes the parsed mermaid data and gives back the Excalidraw Skeleton.
 | 
					For this we have `converters` which takes the parsed mermaid data and gives back the Excalidraw Skeleton.
 | 
				
			||||||
For Unsupported types, we have already mentioned above that we convert it to `dataURL` and return the ExcalidrawImageSkeleton.
 | 
					For Unsupported types, we have already mentioned above that we convert it to `dataURL` and return the ExcalidrawImageSkeleton.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/src/data/transform.ts#L133C13-L133C38).
 | 
					For supported types, currently only flowchart, we have [flowchartConverter](https://github.com/excalidraw/mermaid-to-excalidraw/blob/master/src/converter/types/flowchart.ts#L24) which parses the data and converts to [ExcalidrawElementSkeleton](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/transform.ts#L133C13-L133C38).
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					
 | 
				
			||||||
@@ -52,15 +52,6 @@ Make sure the title starts with a semantic prefix:
 | 
				
			|||||||
- **chore**: Other changes that don't modify src or test files
 | 
					- **chore**: Other changes that don't modify src or test files
 | 
				
			||||||
- **revert**: Reverts a previous commit
 | 
					- **revert**: Reverts a previous commit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Changelog
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Add a brief description of your pull request to the changelog located here: [changelog](https://github.com/excalidraw/excalidraw/blob/master/CHANGELOG.md)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Notes:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- Make sure to prepend to the section corresponding with the semantic prefix you selected in the title
 | 
					 | 
				
			||||||
- Link to your pull request - this will require updating the CHANGELOG _after_ creating the pull request
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Testing
 | 
					### Testing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Once you submit your pull request it will automatically be tested. Be sure to check the results of the test and fix any issues that arise.
 | 
					Once you submit your pull request it will automatically be tested. Be sure to check the results of the test and fix any issues that arise.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,10 +41,7 @@ const config = {
 | 
				
			|||||||
          showLastUpdateTime: true,
 | 
					          showLastUpdateTime: true,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        theme: {
 | 
					        theme: {
 | 
				
			||||||
          customCss: [
 | 
					          customCss: [require.resolve("./src/css/custom.scss")],
 | 
				
			||||||
            require.resolve("./src/css/custom.scss"),
 | 
					 | 
				
			||||||
            require.resolve("../src/packages/excalidraw/example/App.scss"),
 | 
					 | 
				
			||||||
          ],
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								dev-docs/vercel.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "outputDirectory": "build",
 | 
				
			||||||
 | 
					  "installCommand": "yarn install"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,14 +15,23 @@
 | 
				
			|||||||
      border-radius: 50%;
 | 
					      border-radius: 50%;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  .app-title {
 | 
				
			||||||
 | 
					    margin-block-start: 0.83em;
 | 
				
			||||||
 | 
					    margin-block-end: 0.83em;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.button-wrapper button {
 | 
					.button-wrapper {
 | 
				
			||||||
 | 
					  input[type="checkbox"] {
 | 
				
			||||||
 | 
					    margin: 5px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  button {
 | 
				
			||||||
    z-index: 1;
 | 
					    z-index: 1;
 | 
				
			||||||
    height: 40px;
 | 
					    height: 40px;
 | 
				
			||||||
    max-width: 200px;
 | 
					    max-width: 200px;
 | 
				
			||||||
    margin: 10px;
 | 
					    margin: 10px;
 | 
				
			||||||
    padding: 5px;
 | 
					    padding: 5px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.excalidraw .App-menu_top .buttonList {
 | 
					.excalidraw .App-menu_top .buttonList {
 | 
				
			||||||
@@ -1,23 +1,31 @@
 | 
				
			|||||||
import { useEffect, useState, useRef, useCallback } from "react";
 | 
					import React, {
 | 
				
			||||||
 | 
					  useEffect,
 | 
				
			||||||
 | 
					  useState,
 | 
				
			||||||
 | 
					  useRef,
 | 
				
			||||||
 | 
					  useCallback,
 | 
				
			||||||
 | 
					  Children,
 | 
				
			||||||
 | 
					  cloneElement,
 | 
				
			||||||
 | 
					} from "react";
 | 
				
			||||||
import ExampleSidebar from "./sidebar/ExampleSidebar";
 | 
					import ExampleSidebar from "./sidebar/ExampleSidebar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type * as TExcalidraw from "../index";
 | 
					import type * as TExcalidraw from "@excalidraw/excalidraw";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "./App.scss";
 | 
					 | 
				
			||||||
import initialData from "./initialData";
 | 
					 | 
				
			||||||
import { nanoid } from "nanoid";
 | 
					import { nanoid } from "nanoid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { ResolvablePromise } from "../utils";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  resolvablePromise,
 | 
					  resolvablePromise,
 | 
				
			||||||
  ResolvablePromise,
 | 
					  distance2d,
 | 
				
			||||||
 | 
					  fileOpen,
 | 
				
			||||||
  withBatchedUpdates,
 | 
					  withBatchedUpdates,
 | 
				
			||||||
  withBatchedUpdatesThrottled,
 | 
					  withBatchedUpdatesThrottled,
 | 
				
			||||||
} from "../../../utils";
 | 
					} from "../utils";
 | 
				
			||||||
import { EVENT, ROUNDNESS } from "../../../constants";
 | 
					
 | 
				
			||||||
import { distance2d } from "../../../math";
 | 
					import CustomFooter from "./CustomFooter";
 | 
				
			||||||
import { fileOpen } from "../../../data/filesystem";
 | 
					import MobileFooter from "./MobileFooter";
 | 
				
			||||||
import { loadSceneOrLibraryFromBlob } from "../../utils";
 | 
					import initialData from "../initialData";
 | 
				
			||||||
import {
 | 
					
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
  AppState,
 | 
					  AppState,
 | 
				
			||||||
  BinaryFileData,
 | 
					  BinaryFileData,
 | 
				
			||||||
  ExcalidrawImperativeAPI,
 | 
					  ExcalidrawImperativeAPI,
 | 
				
			||||||
@@ -25,18 +33,14 @@ import {
 | 
				
			|||||||
  Gesture,
 | 
					  Gesture,
 | 
				
			||||||
  LibraryItems,
 | 
					  LibraryItems,
 | 
				
			||||||
  PointerDownState as ExcalidrawPointerDownState,
 | 
					  PointerDownState as ExcalidrawPointerDownState,
 | 
				
			||||||
} from "../../../types";
 | 
					} from "@excalidraw/excalidraw/dist/excalidraw/types";
 | 
				
			||||||
import { NonDeletedExcalidrawElement, Theme } from "../../../element/types";
 | 
					import type {
 | 
				
			||||||
import { ImportedLibraryData } from "../../../data/types";
 | 
					  NonDeletedExcalidrawElement,
 | 
				
			||||||
import CustomFooter from "./CustomFooter";
 | 
					  Theme,
 | 
				
			||||||
import MobileFooter from "./MobileFooter";
 | 
					} from "@excalidraw/excalidraw/dist/excalidraw/element/types";
 | 
				
			||||||
import { KEYS } from "../../../keys";
 | 
					import type { ImportedLibraryData } from "@excalidraw/excalidraw/dist/excalidraw/data/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
declare global {
 | 
					import "./App.scss";
 | 
				
			||||||
  interface Window {
 | 
					 | 
				
			||||||
    ExcalidrawLib: typeof TExcalidraw;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Comment = {
 | 
					type Comment = {
 | 
				
			||||||
  x: number;
 | 
					  x: number;
 | 
				
			||||||
@@ -57,14 +61,30 @@ type PointerDownState = {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// This is so that we use the bundled excalidraw.development.js file instead
 | 
					const COMMENT_ICON_DIMENSION = 32;
 | 
				
			||||||
// of the actual source code
 | 
					const COMMENT_INPUT_HEIGHT = 50;
 | 
				
			||||||
const {
 | 
					const COMMENT_INPUT_WIDTH = 150;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface AppProps {
 | 
				
			||||||
 | 
					  appTitle: string;
 | 
				
			||||||
 | 
					  useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
 | 
				
			||||||
 | 
					  customArgs?: any[];
 | 
				
			||||||
 | 
					  children: React.ReactNode;
 | 
				
			||||||
 | 
					  excalidrawLib: typeof TExcalidraw;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function App({
 | 
				
			||||||
 | 
					  appTitle,
 | 
				
			||||||
 | 
					  useCustom,
 | 
				
			||||||
 | 
					  customArgs,
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					  excalidrawLib,
 | 
				
			||||||
 | 
					}: AppProps) {
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
    exportToCanvas,
 | 
					    exportToCanvas,
 | 
				
			||||||
    exportToSvg,
 | 
					    exportToSvg,
 | 
				
			||||||
    exportToBlob,
 | 
					    exportToBlob,
 | 
				
			||||||
    exportToClipboard,
 | 
					    exportToClipboard,
 | 
				
			||||||
  Excalidraw,
 | 
					 | 
				
			||||||
    useHandleLibrary,
 | 
					    useHandleLibrary,
 | 
				
			||||||
    MIME_TYPES,
 | 
					    MIME_TYPES,
 | 
				
			||||||
    sceneCoordsToViewportCoords,
 | 
					    sceneCoordsToViewportCoords,
 | 
				
			||||||
@@ -76,19 +96,11 @@ const {
 | 
				
			|||||||
    MainMenu,
 | 
					    MainMenu,
 | 
				
			||||||
    LiveCollaborationTrigger,
 | 
					    LiveCollaborationTrigger,
 | 
				
			||||||
    convertToExcalidrawElements,
 | 
					    convertToExcalidrawElements,
 | 
				
			||||||
} = window.ExcalidrawLib;
 | 
					    TTDDialog,
 | 
				
			||||||
 | 
					    TTDDialogTrigger,
 | 
				
			||||||
const COMMENT_ICON_DIMENSION = 32;
 | 
					    ROUNDNESS,
 | 
				
			||||||
const COMMENT_INPUT_HEIGHT = 50;
 | 
					    loadSceneOrLibraryFromBlob,
 | 
				
			||||||
const COMMENT_INPUT_WIDTH = 150;
 | 
					  } = excalidrawLib;
 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface AppProps {
 | 
					 | 
				
			||||||
  appTitle: string;
 | 
					 | 
				
			||||||
  useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
 | 
					 | 
				
			||||||
  customArgs?: any[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default function App({ appTitle, useCustom, customArgs }: AppProps) {
 | 
					 | 
				
			||||||
  const appRef = useRef<any>(null);
 | 
					  const appRef = useRef<any>(null);
 | 
				
			||||||
  const [viewModeEnabled, setViewModeEnabled] = useState(false);
 | 
					  const [viewModeEnabled, setViewModeEnabled] = useState(false);
 | 
				
			||||||
  const [zenModeEnabled, setZenModeEnabled] = useState(false);
 | 
					  const [zenModeEnabled, setZenModeEnabled] = useState(false);
 | 
				
			||||||
@@ -150,8 +162,105 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
 | 
				
			|||||||
      };
 | 
					      };
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    fetchData();
 | 
					    fetchData();
 | 
				
			||||||
  }, [excalidrawAPI]);
 | 
					  }, [excalidrawAPI, convertToExcalidrawElements, MIME_TYPES]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const renderExcalidraw = (children: React.ReactNode) => {
 | 
				
			||||||
 | 
					    const Excalidraw: any = Children.toArray(children).find(
 | 
				
			||||||
 | 
					      (child) =>
 | 
				
			||||||
 | 
					        React.isValidElement(child) &&
 | 
				
			||||||
 | 
					        typeof child.type !== "string" &&
 | 
				
			||||||
 | 
					        //@ts-ignore
 | 
				
			||||||
 | 
					        child.type.displayName === "Excalidraw",
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (!Excalidraw) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const newElement = cloneElement(
 | 
				
			||||||
 | 
					      Excalidraw,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        excalidrawAPI: (api: ExcalidrawImperativeAPI) => setExcalidrawAPI(api),
 | 
				
			||||||
 | 
					        initialData: initialStatePromiseRef.current.promise,
 | 
				
			||||||
 | 
					        onChange: (
 | 
				
			||||||
 | 
					          elements: NonDeletedExcalidrawElement[],
 | 
				
			||||||
 | 
					          state: AppState,
 | 
				
			||||||
 | 
					        ) => {
 | 
				
			||||||
 | 
					          console.info("Elements :", elements, "State : ", state);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        onPointerUpdate: (payload: {
 | 
				
			||||||
 | 
					          pointer: { x: number; y: number };
 | 
				
			||||||
 | 
					          button: "down" | "up";
 | 
				
			||||||
 | 
					          pointersMap: Gesture["pointers"];
 | 
				
			||||||
 | 
					        }) => setPointerData(payload),
 | 
				
			||||||
 | 
					        viewModeEnabled,
 | 
				
			||||||
 | 
					        zenModeEnabled,
 | 
				
			||||||
 | 
					        gridModeEnabled,
 | 
				
			||||||
 | 
					        theme,
 | 
				
			||||||
 | 
					        name: "Custom name of drawing",
 | 
				
			||||||
 | 
					        UIOptions: {
 | 
				
			||||||
 | 
					          canvasActions: {
 | 
				
			||||||
 | 
					            loadScene: false,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          tools: { image: !disableImageTool },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        renderTopRightUI,
 | 
				
			||||||
 | 
					        onLinkOpen,
 | 
				
			||||||
 | 
					        onPointerDown,
 | 
				
			||||||
 | 
					        onScrollChange: rerenderCommentIcons,
 | 
				
			||||||
 | 
					        validateEmbeddable: true,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      <>
 | 
				
			||||||
 | 
					        {excalidrawAPI && (
 | 
				
			||||||
 | 
					          <Footer>
 | 
				
			||||||
 | 
					            <CustomFooter
 | 
				
			||||||
 | 
					              excalidrawAPI={excalidrawAPI}
 | 
				
			||||||
 | 
					              excalidrawLib={excalidrawLib}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </Footer>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        <WelcomeScreen />
 | 
				
			||||||
 | 
					        <Sidebar name="custom">
 | 
				
			||||||
 | 
					          <Sidebar.Tabs>
 | 
				
			||||||
 | 
					            <Sidebar.Header />
 | 
				
			||||||
 | 
					            <Sidebar.Tab tab="one">Tab one!</Sidebar.Tab>
 | 
				
			||||||
 | 
					            <Sidebar.Tab tab="two">Tab two!</Sidebar.Tab>
 | 
				
			||||||
 | 
					            <Sidebar.TabTriggers>
 | 
				
			||||||
 | 
					              <Sidebar.TabTrigger tab="one">One</Sidebar.TabTrigger>
 | 
				
			||||||
 | 
					              <Sidebar.TabTrigger tab="two">Two</Sidebar.TabTrigger>
 | 
				
			||||||
 | 
					            </Sidebar.TabTriggers>
 | 
				
			||||||
 | 
					          </Sidebar.Tabs>
 | 
				
			||||||
 | 
					        </Sidebar>
 | 
				
			||||||
 | 
					        <Sidebar.Trigger
 | 
				
			||||||
 | 
					          name="custom"
 | 
				
			||||||
 | 
					          tab="one"
 | 
				
			||||||
 | 
					          style={{
 | 
				
			||||||
 | 
					            position: "absolute",
 | 
				
			||||||
 | 
					            left: "50%",
 | 
				
			||||||
 | 
					            transform: "translateX(-50%)",
 | 
				
			||||||
 | 
					            bottom: "20px",
 | 
				
			||||||
 | 
					            zIndex: 9999999999999999,
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          Toggle Custom Sidebar
 | 
				
			||||||
 | 
					        </Sidebar.Trigger>
 | 
				
			||||||
 | 
					        {renderMenu()}
 | 
				
			||||||
 | 
					        {excalidrawAPI && (
 | 
				
			||||||
 | 
					          <TTDDialogTrigger icon={<span>😀</span>}>
 | 
				
			||||||
 | 
					            Text to diagram
 | 
				
			||||||
 | 
					          </TTDDialogTrigger>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        <TTDDialog
 | 
				
			||||||
 | 
					          onTextSubmit={async (_) => {
 | 
				
			||||||
 | 
					            console.info("submit");
 | 
				
			||||||
 | 
					            // sleep for 2s
 | 
				
			||||||
 | 
					            await new Promise((resolve) => setTimeout(resolve, 2000));
 | 
				
			||||||
 | 
					            throw new Error("error, go away now");
 | 
				
			||||||
 | 
					            // return "dummy";
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </>,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return newElement;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
  const renderTopRightUI = (isMobile: boolean) => {
 | 
					  const renderTopRightUI = (isMobile: boolean) => {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <>
 | 
					      <>
 | 
				
			||||||
@@ -335,8 +444,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
 | 
				
			|||||||
    pointerDownState: PointerDownState,
 | 
					    pointerDownState: PointerDownState,
 | 
				
			||||||
  ) => {
 | 
					  ) => {
 | 
				
			||||||
    return withBatchedUpdates((event) => {
 | 
					    return withBatchedUpdates((event) => {
 | 
				
			||||||
      window.removeEventListener(EVENT.POINTER_MOVE, pointerDownState.onMove);
 | 
					      window.removeEventListener("pointermove", pointerDownState.onMove);
 | 
				
			||||||
      window.removeEventListener(EVENT.POINTER_UP, pointerDownState.onUp);
 | 
					      window.removeEventListener("pointerup", pointerDownState.onUp);
 | 
				
			||||||
      excalidrawAPI?.setActiveTool({ type: "selection" });
 | 
					      excalidrawAPI?.setActiveTool({ type: "selection" });
 | 
				
			||||||
      const distance = distance2d(
 | 
					      const distance = distance2d(
 | 
				
			||||||
        pointerDownState.x,
 | 
					        pointerDownState.x,
 | 
				
			||||||
@@ -400,8 +509,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
 | 
				
			|||||||
              onPointerMoveFromPointerDownHandler(pointerDownState);
 | 
					              onPointerMoveFromPointerDownHandler(pointerDownState);
 | 
				
			||||||
            const onPointerUp =
 | 
					            const onPointerUp =
 | 
				
			||||||
              onPointerUpFromPointerDownHandler(pointerDownState);
 | 
					              onPointerUpFromPointerDownHandler(pointerDownState);
 | 
				
			||||||
            window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
 | 
					            window.addEventListener("pointermove", onPointerMove);
 | 
				
			||||||
            window.addEventListener(EVENT.POINTER_UP, onPointerUp);
 | 
					            window.addEventListener("pointerup", onPointerUp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            pointerDownState.onMove = onPointerMove;
 | 
					            pointerDownState.onMove = onPointerMove;
 | 
				
			||||||
            pointerDownState.onUp = onPointerUp;
 | 
					            pointerDownState.onUp = onPointerUp;
 | 
				
			||||||
@@ -493,7 +602,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
 | 
				
			|||||||
        }}
 | 
					        }}
 | 
				
			||||||
        onBlur={saveComment}
 | 
					        onBlur={saveComment}
 | 
				
			||||||
        onKeyDown={(event) => {
 | 
					        onKeyDown={(event) => {
 | 
				
			||||||
          if (!event.shiftKey && event.key === KEYS.ENTER) {
 | 
					          if (!event.shiftKey && event.key === "Enter") {
 | 
				
			||||||
            event.preventDefault();
 | 
					            event.preventDefault();
 | 
				
			||||||
            saveComment();
 | 
					            saveComment();
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@@ -526,7 +635,12 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
 | 
				
			|||||||
        </MainMenu.ItemCustom>
 | 
					        </MainMenu.ItemCustom>
 | 
				
			||||||
        <MainMenu.DefaultItems.Help />
 | 
					        <MainMenu.DefaultItems.Help />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        {excalidrawAPI && <MobileFooter excalidrawAPI={excalidrawAPI} />}
 | 
					        {excalidrawAPI && (
 | 
				
			||||||
 | 
					          <MobileFooter
 | 
				
			||||||
 | 
					            excalidrawLib={excalidrawLib}
 | 
				
			||||||
 | 
					            excalidrawAPI={excalidrawAPI}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
      </MainMenu>
 | 
					      </MainMenu>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -675,69 +789,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
 | 
				
			|||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div className="excalidraw-wrapper">
 | 
					        <div className="excalidraw-wrapper">
 | 
				
			||||||
          <Excalidraw
 | 
					          {renderExcalidraw(children)}
 | 
				
			||||||
            excalidrawAPI={(api: ExcalidrawImperativeAPI) =>
 | 
					 | 
				
			||||||
              setExcalidrawAPI(api)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            initialData={initialStatePromiseRef.current.promise}
 | 
					 | 
				
			||||||
            onChange={(elements, state) => {
 | 
					 | 
				
			||||||
              console.info("Elements :", elements, "State : ", state);
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
            onPointerUpdate={(payload: {
 | 
					 | 
				
			||||||
              pointer: { x: number; y: number };
 | 
					 | 
				
			||||||
              button: "down" | "up";
 | 
					 | 
				
			||||||
              pointersMap: Gesture["pointers"];
 | 
					 | 
				
			||||||
            }) => setPointerData(payload)}
 | 
					 | 
				
			||||||
            viewModeEnabled={viewModeEnabled}
 | 
					 | 
				
			||||||
            zenModeEnabled={zenModeEnabled}
 | 
					 | 
				
			||||||
            gridModeEnabled={gridModeEnabled}
 | 
					 | 
				
			||||||
            theme={theme}
 | 
					 | 
				
			||||||
            name="Custom name of drawing"
 | 
					 | 
				
			||||||
            UIOptions={{
 | 
					 | 
				
			||||||
              canvasActions: {
 | 
					 | 
				
			||||||
                loadScene: false,
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              tools: { image: !disableImageTool },
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
            renderTopRightUI={renderTopRightUI}
 | 
					 | 
				
			||||||
            onLinkOpen={onLinkOpen}
 | 
					 | 
				
			||||||
            onPointerDown={onPointerDown}
 | 
					 | 
				
			||||||
            onScrollChange={rerenderCommentIcons}
 | 
					 | 
				
			||||||
            // allow all urls
 | 
					 | 
				
			||||||
            validateEmbeddable={true}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            {excalidrawAPI && (
 | 
					 | 
				
			||||||
              <Footer>
 | 
					 | 
				
			||||||
                <CustomFooter excalidrawAPI={excalidrawAPI} />
 | 
					 | 
				
			||||||
              </Footer>
 | 
					 | 
				
			||||||
            )}
 | 
					 | 
				
			||||||
            <WelcomeScreen />
 | 
					 | 
				
			||||||
            <Sidebar name="custom">
 | 
					 | 
				
			||||||
              <Sidebar.Tabs>
 | 
					 | 
				
			||||||
                <Sidebar.Header />
 | 
					 | 
				
			||||||
                <Sidebar.Tab tab="one">Tab one!</Sidebar.Tab>
 | 
					 | 
				
			||||||
                <Sidebar.Tab tab="two">Tab two!</Sidebar.Tab>
 | 
					 | 
				
			||||||
                <Sidebar.TabTriggers>
 | 
					 | 
				
			||||||
                  <Sidebar.TabTrigger tab="one">One</Sidebar.TabTrigger>
 | 
					 | 
				
			||||||
                  <Sidebar.TabTrigger tab="two">Two</Sidebar.TabTrigger>
 | 
					 | 
				
			||||||
                </Sidebar.TabTriggers>
 | 
					 | 
				
			||||||
              </Sidebar.Tabs>
 | 
					 | 
				
			||||||
            </Sidebar>
 | 
					 | 
				
			||||||
            <Sidebar.Trigger
 | 
					 | 
				
			||||||
              name="custom"
 | 
					 | 
				
			||||||
              tab="one"
 | 
					 | 
				
			||||||
              style={{
 | 
					 | 
				
			||||||
                position: "absolute",
 | 
					 | 
				
			||||||
                left: "50%",
 | 
					 | 
				
			||||||
                transform: "translateX(-50%)",
 | 
					 | 
				
			||||||
                bottom: "20px",
 | 
					 | 
				
			||||||
                zIndex: 9999999999999999,
 | 
					 | 
				
			||||||
              }}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              Toggle Custom Sidebar
 | 
					 | 
				
			||||||
            </Sidebar.Trigger>
 | 
					 | 
				
			||||||
            {renderMenu()}
 | 
					 | 
				
			||||||
          </Excalidraw>
 | 
					 | 
				
			||||||
          {Object.keys(commentIcons || []).length > 0 && renderCommentIcons()}
 | 
					          {Object.keys(commentIcons || []).length > 0 && renderCommentIcons()}
 | 
				
			||||||
          {comment && renderComment()}
 | 
					          {comment && renderComment()}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
import { ExcalidrawImperativeAPI } from "../../../types";
 | 
					import type * as TExcalidraw from "@excalidraw/excalidraw";
 | 
				
			||||||
import { MIME_TYPES } from "../entry";
 | 
					import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
 | 
				
			||||||
import { Button } from "../../../components/Button";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const COMMENT_SVG = (
 | 
					const COMMENT_SVG = (
 | 
				
			||||||
  <svg
 | 
					  <svg
 | 
				
			||||||
@@ -18,24 +17,28 @@ const COMMENT_SVG = (
 | 
				
			|||||||
    <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
 | 
					    <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>
 | 
				
			||||||
  </svg>
 | 
					  </svg>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const CustomFooter = ({
 | 
					const CustomFooter = ({
 | 
				
			||||||
  excalidrawAPI,
 | 
					  excalidrawAPI,
 | 
				
			||||||
 | 
					  excalidrawLib,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  excalidrawAPI: ExcalidrawImperativeAPI;
 | 
					  excalidrawAPI: ExcalidrawImperativeAPI;
 | 
				
			||||||
 | 
					  excalidrawLib: typeof TExcalidraw;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
 | 
					  const { Button, MIME_TYPES } = excalidrawLib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Button
 | 
					      <Button
 | 
				
			||||||
        onSelect={() => alert("General Kenobi!")}
 | 
					        onSelect={() => alert("General Kenobi!")}
 | 
				
			||||||
        className="you are a bold one"
 | 
					        style={{ marginLeft: "1rem", width: "auto" }}
 | 
				
			||||||
        style={{ marginLeft: "1rem" }}
 | 
					 | 
				
			||||||
        title="Hello there!"
 | 
					        title="Hello there!"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {COMMENT_SVG}
 | 
					        Hit me
 | 
				
			||||||
      </Button>
 | 
					      </Button>
 | 
				
			||||||
      <button
 | 
					      <Button
 | 
				
			||||||
        className="custom-element"
 | 
					        className="custom-element"
 | 
				
			||||||
        onClick={() => {
 | 
					        onSelect={() => {
 | 
				
			||||||
          excalidrawAPI?.setActiveTool({
 | 
					          excalidrawAPI?.setActiveTool({
 | 
				
			||||||
            type: "custom",
 | 
					            type: "custom",
 | 
				
			||||||
            customType: "comment",
 | 
					            customType: "comment",
 | 
				
			||||||
@@ -58,15 +61,10 @@ const CustomFooter = ({
 | 
				
			|||||||
          )}`;
 | 
					          )}`;
 | 
				
			||||||
          excalidrawAPI?.setCursor(`url(${url}), auto`);
 | 
					          excalidrawAPI?.setCursor(`url(${url}), auto`);
 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
 | 
					        title="Comments!"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        {COMMENT_SVG}
 | 
					        {COMMENT_SVG}
 | 
				
			||||||
      </button>
 | 
					      </Button>
 | 
				
			||||||
      <button
 | 
					 | 
				
			||||||
        className="custom-footer"
 | 
					 | 
				
			||||||
        onClick={() => alert("This is dummy footer")}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        custom footer
 | 
					 | 
				
			||||||
      </button>
 | 
					 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
							
								
								
									
										27
									
								
								examples/excalidraw/components/MobileFooter.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/dist/excalidraw/types";
 | 
				
			||||||
 | 
					import CustomFooter from "./CustomFooter";
 | 
				
			||||||
 | 
					import type * as TExcalidraw from "@excalidraw/excalidraw";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MobileFooter = ({
 | 
				
			||||||
 | 
					  excalidrawAPI,
 | 
				
			||||||
 | 
					  excalidrawLib,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  excalidrawAPI: ExcalidrawImperativeAPI;
 | 
				
			||||||
 | 
					  excalidrawLib: typeof TExcalidraw;
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  const { useDevice, Footer } = excalidrawLib;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const device = useDevice();
 | 
				
			||||||
 | 
					  if (device.editor.isMobile) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <Footer>
 | 
				
			||||||
 | 
					        <CustomFooter
 | 
				
			||||||
 | 
					          excalidrawAPI={excalidrawAPI}
 | 
				
			||||||
 | 
					          excalidrawLib={excalidrawLib}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </Footer>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return null;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export default MobileFooter;
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import React, { useState } from "react";
 | 
					import { useState } from "react";
 | 
				
			||||||
import "./ExampleSidebar.scss";
 | 
					import "./ExampleSidebar.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function Sidebar({ children }: { children: React.ReactNode }) {
 | 
					export default function Sidebar({ children }: { children: React.ReactNode }) {
 | 
				
			||||||
  const [open, setOpen] = useState(false);
 | 
					  const [open, setOpen] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { ExcalidrawElementSkeleton } from "../../../data/transform";
 | 
					import type { ExcalidrawElementSkeleton } from "@excalidraw/excalidraw/data/transform";
 | 
				
			||||||
import { FileId } from "../../../element/types";
 | 
					import type { FileId } from "@excalidraw/excalidraw/element/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const elements: ExcalidrawElementSkeleton[] = [
 | 
					const elements: ExcalidrawElementSkeleton[] = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
							
								
								
									
										13
									
								
								examples/excalidraw/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "examples",
 | 
				
			||||||
 | 
					  "version": "1.0.0",
 | 
				
			||||||
 | 
					  "private": true,
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "react": "18.2.0",
 | 
				
			||||||
 | 
					    "react-dom": "18.2.0",
 | 
				
			||||||
 | 
					    "@excalidraw/excalidraw": "*"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "typescript": "^5"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								examples/excalidraw/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "extends": "../../tsconfig"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										146
									
								
								examples/excalidraw/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,146 @@
 | 
				
			|||||||
 | 
					import { unstable_batchedUpdates } from "react-dom";
 | 
				
			||||||
 | 
					import { fileOpen as _fileOpen } from "browser-fs-access";
 | 
				
			||||||
 | 
					import { MIME_TYPES } from "@excalidraw/excalidraw";
 | 
				
			||||||
 | 
					import { AbortError } from "../../packages/excalidraw/errors";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const INPUT_CHANGE_INTERVAL_MS = 500;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ResolvablePromise<T> = Promise<T> & {
 | 
				
			||||||
 | 
					  resolve: [T] extends [undefined] ? (value?: T) => void : (value: T) => void;
 | 
				
			||||||
 | 
					  reject: (error: Error) => void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export const resolvablePromise = <T>() => {
 | 
				
			||||||
 | 
					  let resolve!: any;
 | 
				
			||||||
 | 
					  let reject!: any;
 | 
				
			||||||
 | 
					  const promise = new Promise((_resolve, _reject) => {
 | 
				
			||||||
 | 
					    resolve = _resolve;
 | 
				
			||||||
 | 
					    reject = _reject;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  (promise as any).resolve = resolve;
 | 
				
			||||||
 | 
					  (promise as any).reject = reject;
 | 
				
			||||||
 | 
					  return promise as ResolvablePromise<T>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const distance2d = (x1: number, y1: number, x2: number, y2: number) => {
 | 
				
			||||||
 | 
					  const xd = x2 - x1;
 | 
				
			||||||
 | 
					  const yd = y2 - y1;
 | 
				
			||||||
 | 
					  return Math.hypot(xd, yd);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const fileOpen = <M extends boolean | undefined = false>(opts: {
 | 
				
			||||||
 | 
					  extensions?: FILE_EXTENSION[];
 | 
				
			||||||
 | 
					  description: string;
 | 
				
			||||||
 | 
					  multiple?: M;
 | 
				
			||||||
 | 
					}): Promise<M extends false | undefined ? File : File[]> => {
 | 
				
			||||||
 | 
					  // an unsafe TS hack, alas not much we can do AFAIK
 | 
				
			||||||
 | 
					  type RetType = M extends false | undefined ? File : File[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const mimeTypes = opts.extensions?.reduce((mimeTypes, type) => {
 | 
				
			||||||
 | 
					    mimeTypes.push(MIME_TYPES[type]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return mimeTypes;
 | 
				
			||||||
 | 
					  }, [] as string[]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const extensions = opts.extensions?.reduce((acc, ext) => {
 | 
				
			||||||
 | 
					    if (ext === "jpg") {
 | 
				
			||||||
 | 
					      return acc.concat(".jpg", ".jpeg");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return acc.concat(`.${ext}`);
 | 
				
			||||||
 | 
					  }, [] as string[]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return _fileOpen({
 | 
				
			||||||
 | 
					    description: opts.description,
 | 
				
			||||||
 | 
					    extensions,
 | 
				
			||||||
 | 
					    mimeTypes,
 | 
				
			||||||
 | 
					    multiple: opts.multiple ?? false,
 | 
				
			||||||
 | 
					    legacySetup: (resolve, reject, input) => {
 | 
				
			||||||
 | 
					      const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS);
 | 
				
			||||||
 | 
					      const focusHandler = () => {
 | 
				
			||||||
 | 
					        checkForFile();
 | 
				
			||||||
 | 
					        document.addEventListener("keyup", scheduleRejection);
 | 
				
			||||||
 | 
					        document.addEventListener("pointerup", scheduleRejection);
 | 
				
			||||||
 | 
					        scheduleRejection();
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      const checkForFile = () => {
 | 
				
			||||||
 | 
					        // this hack might not work when expecting multiple files
 | 
				
			||||||
 | 
					        if (input.files?.length) {
 | 
				
			||||||
 | 
					          const ret = opts.multiple ? [...input.files] : input.files[0];
 | 
				
			||||||
 | 
					          resolve(ret as RetType);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      requestAnimationFrame(() => {
 | 
				
			||||||
 | 
					        window.addEventListener("focus", focusHandler);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      const interval = window.setInterval(() => {
 | 
				
			||||||
 | 
					        checkForFile();
 | 
				
			||||||
 | 
					      }, INPUT_CHANGE_INTERVAL_MS);
 | 
				
			||||||
 | 
					      return (rejectPromise) => {
 | 
				
			||||||
 | 
					        clearInterval(interval);
 | 
				
			||||||
 | 
					        scheduleRejection.cancel();
 | 
				
			||||||
 | 
					        window.removeEventListener("focus", focusHandler);
 | 
				
			||||||
 | 
					        document.removeEventListener("keyup", scheduleRejection);
 | 
				
			||||||
 | 
					        document.removeEventListener("pointerup", scheduleRejection);
 | 
				
			||||||
 | 
					        if (rejectPromise) {
 | 
				
			||||||
 | 
					          // so that something is shown in console if we need to debug this
 | 
				
			||||||
 | 
					          console.warn("Opening the file was canceled (legacy-fs).");
 | 
				
			||||||
 | 
					          rejectPromise(new AbortError());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }) as Promise<RetType>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const debounce = <T extends any[]>(
 | 
				
			||||||
 | 
					  fn: (...args: T) => void,
 | 
				
			||||||
 | 
					  timeout: number,
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  let handle = 0;
 | 
				
			||||||
 | 
					  let lastArgs: T | null = null;
 | 
				
			||||||
 | 
					  const ret = (...args: T) => {
 | 
				
			||||||
 | 
					    lastArgs = args;
 | 
				
			||||||
 | 
					    clearTimeout(handle);
 | 
				
			||||||
 | 
					    handle = window.setTimeout(() => {
 | 
				
			||||||
 | 
					      lastArgs = null;
 | 
				
			||||||
 | 
					      fn(...args);
 | 
				
			||||||
 | 
					    }, timeout);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  ret.flush = () => {
 | 
				
			||||||
 | 
					    clearTimeout(handle);
 | 
				
			||||||
 | 
					    if (lastArgs) {
 | 
				
			||||||
 | 
					      const _lastArgs = lastArgs;
 | 
				
			||||||
 | 
					      lastArgs = null;
 | 
				
			||||||
 | 
					      fn(..._lastArgs);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  ret.cancel = () => {
 | 
				
			||||||
 | 
					    lastArgs = null;
 | 
				
			||||||
 | 
					    clearTimeout(handle);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  return ret;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const withBatchedUpdates = <
 | 
				
			||||||
 | 
					  TFunction extends ((event: any) => void) | (() => void),
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  func: Parameters<TFunction>["length"] extends 0 | 1 ? TFunction : never,
 | 
				
			||||||
 | 
					) =>
 | 
				
			||||||
 | 
					  ((event) => {
 | 
				
			||||||
 | 
					    unstable_batchedUpdates(func as TFunction, event);
 | 
				
			||||||
 | 
					  }) as TFunction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * barches React state updates and throttles the calls to a single call per
 | 
				
			||||||
 | 
					 * animation frame
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const withBatchedUpdatesThrottled = <
 | 
				
			||||||
 | 
					  TFunction extends ((event: any) => void) | (() => void),
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  func: Parameters<TFunction>["length"] extends 0 | 1 ? TFunction : never,
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  // @ts-ignore
 | 
				
			||||||
 | 
					  return throttleRAF<Parameters<TFunction>>(((event) => {
 | 
				
			||||||
 | 
					    unstable_batchedUpdates(func, event);
 | 
				
			||||||
 | 
					  }) as TFunction);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										36
									
								
								examples/excalidraw/with-nextjs/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# dependencies
 | 
				
			||||||
 | 
					/node_modules
 | 
				
			||||||
 | 
					/.pnp
 | 
				
			||||||
 | 
					.pnp.js
 | 
				
			||||||
 | 
					.yarn/install-state.gz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# testing
 | 
				
			||||||
 | 
					/coverage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# next.js
 | 
				
			||||||
 | 
					/.next/
 | 
				
			||||||
 | 
					/out/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# production
 | 
				
			||||||
 | 
					/build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# misc
 | 
				
			||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					*.pem
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# debug
 | 
				
			||||||
 | 
					npm-debug.log*
 | 
				
			||||||
 | 
					yarn-debug.log*
 | 
				
			||||||
 | 
					yarn-error.log*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# local env files
 | 
				
			||||||
 | 
					.env*.local
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# vercel
 | 
				
			||||||
 | 
					.vercel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# typescript
 | 
				
			||||||
 | 
					*.tsbuildinfo
 | 
				
			||||||
 | 
					next-env.d.ts
 | 
				
			||||||
							
								
								
									
										36
									
								
								examples/excalidraw/with-nextjs/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Getting Started
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					First, run the development server:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					npm run dev
 | 
				
			||||||
 | 
					# or
 | 
				
			||||||
 | 
					yarn dev
 | 
				
			||||||
 | 
					# or
 | 
				
			||||||
 | 
					pnpm dev
 | 
				
			||||||
 | 
					# or
 | 
				
			||||||
 | 
					bun dev
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Open [http://localhost:3000](http://localhost:3005) with your browser to see the result.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Learn More
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To learn more about Next.js, take a look at the following resources:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
 | 
				
			||||||
 | 
					- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Deploy on Vercel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
 | 
				
			||||||
							
								
								
									
										12
									
								
								examples/excalidraw/with-nextjs/next.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					/** @type {import('next').NextConfig} */
 | 
				
			||||||
 | 
					const nextConfig = {
 | 
				
			||||||
 | 
					  distDir: "build",
 | 
				
			||||||
 | 
					  typescript: {
 | 
				
			||||||
 | 
					    // The ts config doesn't work with `jsx: preserve" and if updated to `react-jsx` it gets ovewritten by next js throwing ts errors hence I am ignoring build errors until this is fixed.
 | 
				
			||||||
 | 
					    ignoreBuildErrors: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // This is needed as in pages router the code for importing types throws error as its outside next js app
 | 
				
			||||||
 | 
					  transpilePackages: ["../"],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = nextConfig;
 | 
				
			||||||
							
								
								
									
										25
									
								
								examples/excalidraw/with-nextjs/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "with-nextjs",
 | 
				
			||||||
 | 
					  "version": "0.1.0",
 | 
				
			||||||
 | 
					  "private": true,
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm",
 | 
				
			||||||
 | 
					    "dev": "yarn build:workspace && next dev -p 3005",
 | 
				
			||||||
 | 
					    "build": "yarn build:workspace && next build",
 | 
				
			||||||
 | 
					    "start": "next start -p 3006",
 | 
				
			||||||
 | 
					    "lint": "next lint"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@excalidraw/excalidraw": "*",
 | 
				
			||||||
 | 
					    "next": "14.1",
 | 
				
			||||||
 | 
					    "react": "^18",
 | 
				
			||||||
 | 
					    "react-dom": "^18"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "@types/node": "^20",
 | 
				
			||||||
 | 
					    "@types/react": "^18",
 | 
				
			||||||
 | 
					    "@types/react-dom": "^18",
 | 
				
			||||||
 | 
					    "path2d-polyfill": "2.0.1",
 | 
				
			||||||
 | 
					    "typescript": "^5"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 197 KiB  | 
| 
		 Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB  | 
| 
		 Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB  | 
| 
		 Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								examples/excalidraw/with-nextjs/src/app/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 25 KiB  | 
							
								
								
									
										11
									
								
								examples/excalidraw/with-nextjs/src/app/layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					export default function RootLayout({
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  children: React.ReactNode;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <html lang="en">
 | 
				
			||||||
 | 
					      <body>{children}</body>
 | 
				
			||||||
 | 
					    </html>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								examples/excalidraw/with-nextjs/src/app/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					import dynamic from "next/dynamic";
 | 
				
			||||||
 | 
					import "../common.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
 | 
				
			||||||
 | 
					// with ssr false
 | 
				
			||||||
 | 
					const ExcalidrawWithClientOnly = dynamic(
 | 
				
			||||||
 | 
					  async () => (await import("../excalidrawWrapper")).default,
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    ssr: false,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Page() {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <a href="/excalidraw-in-pages">Switch to Pages router</a>
 | 
				
			||||||
 | 
					      <h1 className="page-title">App Router</h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */}
 | 
				
			||||||
 | 
					      <ExcalidrawWithClientOnly />
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								examples/excalidraw/with-nextjs/src/common.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					* {
 | 
				
			||||||
 | 
					  box-sizing: border-box;
 | 
				
			||||||
 | 
					  font-family: sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a {
 | 
				
			||||||
 | 
					  color: #1c7ed6;
 | 
				
			||||||
 | 
					  font-size: 20px;
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  font-weight: 550;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.page-title {
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								examples/excalidraw/with-nextjs/src/excalidrawWrapper.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					import * as excalidrawLib from "@excalidraw/excalidraw";
 | 
				
			||||||
 | 
					import { Excalidraw } from "@excalidraw/excalidraw";
 | 
				
			||||||
 | 
					import App from "../../components/App";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "@excalidraw/excalidraw/index.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ExcalidrawWrapper: React.FC = () => {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <App
 | 
				
			||||||
 | 
					        appTitle={"Excalidraw with Nextjs Example"}
 | 
				
			||||||
 | 
					        useCustom={(api: any, args?: any[]) => {}}
 | 
				
			||||||
 | 
					        excalidrawLib={excalidrawLib}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <Excalidraw />
 | 
				
			||||||
 | 
					      </App>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ExcalidrawWrapper;
 | 
				
			||||||
@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import dynamic from "next/dynamic";
 | 
				
			||||||
 | 
					import "../common.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
 | 
				
			||||||
 | 
					// with ssr false
 | 
				
			||||||
 | 
					const Excalidraw = dynamic(
 | 
				
			||||||
 | 
					  async () => (await import("../excalidrawWrapper")).default,
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    ssr: false,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Page() {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <a href="/">Switch to App router</a>
 | 
				
			||||||
 | 
					      <h1 className="page-title">Pages Router</h1>
 | 
				
			||||||
 | 
					      {/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */}
 | 
				
			||||||
 | 
					      <Excalidraw />
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								examples/excalidraw/with-nextjs/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "compilerOptions": {
 | 
				
			||||||
 | 
					    "target": "es5",
 | 
				
			||||||
 | 
					    "lib": ["dom", "dom.iterable", "esnext"],
 | 
				
			||||||
 | 
					    "allowJs": true,
 | 
				
			||||||
 | 
					    "skipLibCheck": true,
 | 
				
			||||||
 | 
					    "strict": true,
 | 
				
			||||||
 | 
					    "noEmit": true,
 | 
				
			||||||
 | 
					    "esModuleInterop": true,
 | 
				
			||||||
 | 
					    "module": "esnext",
 | 
				
			||||||
 | 
					    "moduleResolution": "node",
 | 
				
			||||||
 | 
					    "resolveJsonModule": true,
 | 
				
			||||||
 | 
					    "isolatedModules": true,
 | 
				
			||||||
 | 
					    "jsx": "preserve",
 | 
				
			||||||
 | 
					    "incremental": true,
 | 
				
			||||||
 | 
					    "plugins": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "name": "next"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "paths": {
 | 
				
			||||||
 | 
					      "@/*": ["./src/*"]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "forceConsistentCasingInFileNames": true
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "build/types/**/*.ts"],
 | 
				
			||||||
 | 
					  "exclude": ["node_modules"]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								examples/excalidraw/with-nextjs/vercel.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "outputDirectory": "build"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										252
									
								
								examples/excalidraw/with-nextjs/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,252 @@
 | 
				
			|||||||
 | 
					# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 | 
				
			||||||
 | 
					# yarn lockfile v1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@excalidraw/excalidraw@workspace:^":
 | 
				
			||||||
 | 
					  version "0.17.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.17.2.tgz#9a636a1e6bb3c88c5883347d3a7e75e9cce8ab96"
 | 
				
			||||||
 | 
					  integrity sha512-7pqUWD8+mPjDhF4XxG3gw4rvE2JGaLW3Vss5UZfTbITPxAtFaGEc1K081bncitnaYhUwN9ENJE0i87QB3poDwQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/env@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.4.tgz#d5cda0c4a862d70ae760e58c0cd96a8899a2e49a"
 | 
				
			||||||
 | 
					  integrity sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/swc-darwin-arm64@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz#27b1854c2cd04eb1d5e75081a1a792ad91526618"
 | 
				
			||||||
 | 
					  integrity sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/swc-darwin-x64@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz#9940c449e757d0ee50bb9e792d2600cc08a3eb3b"
 | 
				
			||||||
 | 
					  integrity sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/swc-linux-arm64-gnu@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz#0eafd27c8587f68ace7b4fa80695711a8434de21"
 | 
				
			||||||
 | 
					  integrity sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/swc-linux-arm64-musl@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz#2b0072adb213f36dada5394ea67d6e82069ae7dd"
 | 
				
			||||||
 | 
					  integrity sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/swc-linux-x64-gnu@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz#68c67d20ebc8e3f6ced6ff23a4ba2a679dbcec32"
 | 
				
			||||||
 | 
					  integrity sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/swc-linux-x64-musl@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz#67cd81b42fb2caf313f7992fcf6d978af55a1247"
 | 
				
			||||||
 | 
					  integrity sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/swc-win32-arm64-msvc@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz#be06585906b195d755ceda28f33c633e1443f1a3"
 | 
				
			||||||
 | 
					  integrity sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/swc-win32-ia32-msvc@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz#e76cabefa9f2d891599c3d85928475bd8d3f6600"
 | 
				
			||||||
 | 
					  integrity sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@next/swc-win32-x64-msvc@14.0.4":
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz#e74892f1a9ccf41d3bf5979ad6d3d77c07b9cba1"
 | 
				
			||||||
 | 
					  integrity sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@swc/helpers@0.5.2":
 | 
				
			||||||
 | 
					  version "0.5.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
 | 
				
			||||||
 | 
					  integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    tslib "^2.4.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/node@^20":
 | 
				
			||||||
 | 
					  version "20.11.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.0.tgz#8e0b99e70c0c1ade1a86c4a282f7b7ef87c9552f"
 | 
				
			||||||
 | 
					  integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    undici-types "~5.26.4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/prop-types@*":
 | 
				
			||||||
 | 
					  version "15.7.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563"
 | 
				
			||||||
 | 
					  integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/react-dom@^18":
 | 
				
			||||||
 | 
					  version "18.2.18"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd"
 | 
				
			||||||
 | 
					  integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    "@types/react" "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/react@*", "@types/react@^18":
 | 
				
			||||||
 | 
					  version "18.2.47"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.47.tgz#85074b27ab563df01fbc3f68dc64bf7050b0af40"
 | 
				
			||||||
 | 
					  integrity sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    "@types/prop-types" "*"
 | 
				
			||||||
 | 
					    "@types/scheduler" "*"
 | 
				
			||||||
 | 
					    csstype "^3.0.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/scheduler@*":
 | 
				
			||||||
 | 
					  version "0.16.8"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff"
 | 
				
			||||||
 | 
					  integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					busboy@1.6.0:
 | 
				
			||||||
 | 
					  version "1.6.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
 | 
				
			||||||
 | 
					  integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    streamsearch "^1.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					caniuse-lite@^1.0.30001406:
 | 
				
			||||||
 | 
					  version "1.0.30001576"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4"
 | 
				
			||||||
 | 
					  integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					client-only@0.0.1:
 | 
				
			||||||
 | 
					  version "0.0.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
 | 
				
			||||||
 | 
					  integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					csstype@^3.0.2:
 | 
				
			||||||
 | 
					  version "3.1.3"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
 | 
				
			||||||
 | 
					  integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					glob-to-regexp@^0.4.1:
 | 
				
			||||||
 | 
					  version "0.4.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
 | 
				
			||||||
 | 
					  integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					graceful-fs@^4.1.2, graceful-fs@^4.2.11:
 | 
				
			||||||
 | 
					  version "4.2.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
 | 
				
			||||||
 | 
					  integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"js-tokens@^3.0.0 || ^4.0.0":
 | 
				
			||||||
 | 
					  version "4.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
 | 
				
			||||||
 | 
					  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					loose-envify@^1.1.0:
 | 
				
			||||||
 | 
					  version "1.4.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
 | 
				
			||||||
 | 
					  integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    js-tokens "^3.0.0 || ^4.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nanoid@^3.3.6:
 | 
				
			||||||
 | 
					  version "3.3.7"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
 | 
				
			||||||
 | 
					  integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					next@14.0.4:
 | 
				
			||||||
 | 
					  version "14.0.4"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/next/-/next-14.0.4.tgz#bf00b6f835b20d10a5057838fa2dfced1d0d84dc"
 | 
				
			||||||
 | 
					  integrity sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    "@next/env" "14.0.4"
 | 
				
			||||||
 | 
					    "@swc/helpers" "0.5.2"
 | 
				
			||||||
 | 
					    busboy "1.6.0"
 | 
				
			||||||
 | 
					    caniuse-lite "^1.0.30001406"
 | 
				
			||||||
 | 
					    graceful-fs "^4.2.11"
 | 
				
			||||||
 | 
					    postcss "8.4.31"
 | 
				
			||||||
 | 
					    styled-jsx "5.1.1"
 | 
				
			||||||
 | 
					    watchpack "2.4.0"
 | 
				
			||||||
 | 
					  optionalDependencies:
 | 
				
			||||||
 | 
					    "@next/swc-darwin-arm64" "14.0.4"
 | 
				
			||||||
 | 
					    "@next/swc-darwin-x64" "14.0.4"
 | 
				
			||||||
 | 
					    "@next/swc-linux-arm64-gnu" "14.0.4"
 | 
				
			||||||
 | 
					    "@next/swc-linux-arm64-musl" "14.0.4"
 | 
				
			||||||
 | 
					    "@next/swc-linux-x64-gnu" "14.0.4"
 | 
				
			||||||
 | 
					    "@next/swc-linux-x64-musl" "14.0.4"
 | 
				
			||||||
 | 
					    "@next/swc-win32-arm64-msvc" "14.0.4"
 | 
				
			||||||
 | 
					    "@next/swc-win32-ia32-msvc" "14.0.4"
 | 
				
			||||||
 | 
					    "@next/swc-win32-x64-msvc" "14.0.4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					path2d-polyfill@2.0.1:
 | 
				
			||||||
 | 
					  version "2.0.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz#24c554a738f42700d6961992bf5f1049672f2391"
 | 
				
			||||||
 | 
					  integrity sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					picocolors@^1.0.0:
 | 
				
			||||||
 | 
					  version "1.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
 | 
				
			||||||
 | 
					  integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					postcss@8.4.31:
 | 
				
			||||||
 | 
					  version "8.4.31"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
 | 
				
			||||||
 | 
					  integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    nanoid "^3.3.6"
 | 
				
			||||||
 | 
					    picocolors "^1.0.0"
 | 
				
			||||||
 | 
					    source-map-js "^1.0.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					react-dom@^18:
 | 
				
			||||||
 | 
					  version "18.2.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
 | 
				
			||||||
 | 
					  integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    loose-envify "^1.1.0"
 | 
				
			||||||
 | 
					    scheduler "^0.23.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					react@^18:
 | 
				
			||||||
 | 
					  version "18.2.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
 | 
				
			||||||
 | 
					  integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    loose-envify "^1.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					scheduler@^0.23.0:
 | 
				
			||||||
 | 
					  version "0.23.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
 | 
				
			||||||
 | 
					  integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    loose-envify "^1.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					source-map-js@^1.0.2:
 | 
				
			||||||
 | 
					  version "1.0.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
 | 
				
			||||||
 | 
					  integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					streamsearch@^1.1.0:
 | 
				
			||||||
 | 
					  version "1.1.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
 | 
				
			||||||
 | 
					  integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					styled-jsx@5.1.1:
 | 
				
			||||||
 | 
					  version "5.1.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
 | 
				
			||||||
 | 
					  integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    client-only "0.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tslib@^2.4.0:
 | 
				
			||||||
 | 
					  version "2.6.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
 | 
				
			||||||
 | 
					  integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typescript@^5:
 | 
				
			||||||
 | 
					  version "5.3.3"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
 | 
				
			||||||
 | 
					  integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					undici-types@~5.26.4:
 | 
				
			||||||
 | 
					  version "5.26.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
 | 
				
			||||||
 | 
					  integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watchpack@2.4.0:
 | 
				
			||||||
 | 
					  version "2.4.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
 | 
				
			||||||
 | 
					  integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    glob-to-regexp "^0.4.1"
 | 
				
			||||||
 | 
					    graceful-fs "^4.1.2"
 | 
				
			||||||
@@ -12,18 +12,21 @@
 | 
				
			|||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
      window.name = "codesandbox";
 | 
					      window.name = "codesandbox";
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="/dist/browser/dev/index.css" />
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <body>
 | 
					  <body>
 | 
				
			||||||
    <noscript> You need to enable JavaScript to run this app. </noscript>
 | 
					    <noscript> You need to enable JavaScript to run this app. </noscript>
 | 
				
			||||||
    <div id="root"></div>
 | 
					    <div id="root"></div>
 | 
				
			||||||
    <script src="https://unpkg.com/react@18.2.0/umd/react.development.js"></script>
 | 
					 | 
				
			||||||
    <script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js"></script>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- This is so that we use the bundled excalidraw.development.js file instead
 | 
					    <!-- This is so that we use the bundled excalidraw.development.js file instead
 | 
				
			||||||
    of the actual source code -->
 | 
					    of the actual source code -->
 | 
				
			||||||
    <script src="./excalidraw.development.js"></script>
 | 
					    <script type="module">
 | 
				
			||||||
 | 
					      import * as ExcalidrawLib from "@excalidraw/excalidraw";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <script src="./bundle.js"></script>
 | 
					      console.log(ExcalidrawLib);
 | 
				
			||||||
 | 
					      window.ExcalidrawLib = ExcalidrawLib;
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					    <script type="module" src="index.tsx"></script>
 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										28
									
								
								examples/excalidraw/with-script-in-browser/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					import App from "../components/App";
 | 
				
			||||||
 | 
					import React, { StrictMode } from "react";
 | 
				
			||||||
 | 
					import { createRoot } from "react-dom/client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type * as TExcalidraw from "@excalidraw/excalidraw";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "@excalidraw/excalidraw/index.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare global {
 | 
				
			||||||
 | 
					  interface Window {
 | 
				
			||||||
 | 
					    ExcalidrawLib: typeof TExcalidraw;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rootElement = document.getElementById("root")!;
 | 
				
			||||||
 | 
					const root = createRoot(rootElement);
 | 
				
			||||||
 | 
					const { Excalidraw } = window.ExcalidrawLib;
 | 
				
			||||||
 | 
					root.render(
 | 
				
			||||||
 | 
					  <StrictMode>
 | 
				
			||||||
 | 
					    <App
 | 
				
			||||||
 | 
					      appTitle={"Excalidraw Example"}
 | 
				
			||||||
 | 
					      useCustom={(api: any, args?: any[]) => {}}
 | 
				
			||||||
 | 
					      excalidrawLib={window.ExcalidrawLib}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <Excalidraw />
 | 
				
			||||||
 | 
					    </App>
 | 
				
			||||||
 | 
					  </StrictMode>,
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
							
								
								
									
										19
									
								
								examples/excalidraw/with-script-in-browser/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "name": "with-script-in-browser",
 | 
				
			||||||
 | 
					  "version": "1.0.0",
 | 
				
			||||||
 | 
					  "private": true,
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "react": "18.2.0",
 | 
				
			||||||
 | 
					    "react-dom": "18.2.0",
 | 
				
			||||||
 | 
					    "@excalidraw/excalidraw": "*"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "devDependencies": {
 | 
				
			||||||
 | 
					    "vite": "5.0.12",
 | 
				
			||||||
 | 
					    "typescript": "^5"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "start": "yarn workspace @excalidraw/excalidraw run build:esm && vite",
 | 
				
			||||||
 | 
					    "build": "yarn workspace @excalidraw/excalidraw run build:esm && vite build",
 | 
				
			||||||
 | 
					    "build:preview": "yarn build && vite preview --port 5002"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 197 KiB  | 
| 
		 After Width: | Height: | Size: 30 KiB  | 
| 
		 After Width: | Height: | Size: 6.1 KiB  | 
| 
		 After Width: | Height: | Size: 39 KiB  | 
							
								
								
									
										4
									
								
								examples/excalidraw/with-script-in-browser/vercel.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "outputDirectory": "dist",
 | 
				
			||||||
 | 
					  "installCommand": "yarn install"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								examples/excalidraw/with-script-in-browser/vite.config.mts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { defineConfig } from "vite";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://vitejs.dev/config/
 | 
				
			||||||
 | 
					export default defineConfig({
 | 
				
			||||||
 | 
					  server: {
 | 
				
			||||||
 | 
					    port: 3001,
 | 
				
			||||||
 | 
					    // open the browser
 | 
				
			||||||
 | 
					    open: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  publicDir: "public",
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										313
									
								
								examples/excalidraw/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,313 @@
 | 
				
			|||||||
 | 
					# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 | 
				
			||||||
 | 
					# yarn lockfile v1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/aix-ppc64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3"
 | 
				
			||||||
 | 
					  integrity sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/android-arm64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz#b45d000017385c9051a4f03e17078abb935be220"
 | 
				
			||||||
 | 
					  integrity sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/android-arm@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz#f46f55414e1c3614ac682b29977792131238164c"
 | 
				
			||||||
 | 
					  integrity sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/android-x64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz#bfc01e91740b82011ef503c48f548950824922b2"
 | 
				
			||||||
 | 
					  integrity sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/darwin-arm64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz#533fb7f5a08c37121d82c66198263dcc1bed29bf"
 | 
				
			||||||
 | 
					  integrity sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/darwin-x64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz#62f3819eff7e4ddc656b7c6815a31cf9a1e7d98e"
 | 
				
			||||||
 | 
					  integrity sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/freebsd-arm64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz#d478b4195aa3ca44160272dab85ef8baf4175b4a"
 | 
				
			||||||
 | 
					  integrity sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/freebsd-x64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz#7bdcc1917409178257ca6a1a27fe06e797ec18a2"
 | 
				
			||||||
 | 
					  integrity sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/linux-arm64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz#58ad4ff11685fcc735d7ff4ca759ab18fcfe4545"
 | 
				
			||||||
 | 
					  integrity sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/linux-arm@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz#ce82246d873b5534d34de1e5c1b33026f35e60e3"
 | 
				
			||||||
 | 
					  integrity sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/linux-ia32@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz#cbae1f313209affc74b80f4390c4c35c6ab83fa4"
 | 
				
			||||||
 | 
					  integrity sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/linux-loong64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz#5f32aead1c3ec8f4cccdb7ed08b166224d4e9121"
 | 
				
			||||||
 | 
					  integrity sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/linux-mips64el@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz#38eecf1cbb8c36a616261de858b3c10d03419af9"
 | 
				
			||||||
 | 
					  integrity sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/linux-ppc64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz#9c5725a94e6ec15b93195e5a6afb821628afd912"
 | 
				
			||||||
 | 
					  integrity sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/linux-riscv64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz#2dc4486d474a2a62bbe5870522a9a600e2acb916"
 | 
				
			||||||
 | 
					  integrity sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/linux-s390x@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz#4ad8567df48f7dd4c71ec5b1753b6f37561a65a8"
 | 
				
			||||||
 | 
					  integrity sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/linux-x64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz#b7390c4d5184f203ebe7ddaedf073df82a658766"
 | 
				
			||||||
 | 
					  integrity sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/netbsd-x64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz#d633c09492a1721377f3bccedb2d821b911e813d"
 | 
				
			||||||
 | 
					  integrity sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/openbsd-x64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz#17388c76e2f01125bf831a68c03a7ffccb65d1a2"
 | 
				
			||||||
 | 
					  integrity sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/sunos-x64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz#e320636f00bb9f4fdf3a80e548cb743370d41767"
 | 
				
			||||||
 | 
					  integrity sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/win32-arm64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz#c778b45a496e90b6fc373e2a2bb072f1441fe0ee"
 | 
				
			||||||
 | 
					  integrity sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/win32-ia32@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz#481a65fee2e5cce74ec44823e6b09ecedcc5194c"
 | 
				
			||||||
 | 
					  integrity sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@esbuild/win32-x64@0.19.11":
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04"
 | 
				
			||||||
 | 
					  integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-android-arm-eabi@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz#b752b6c88a14ccfcbdf3f48c577ccc3a7f0e66b9"
 | 
				
			||||||
 | 
					  integrity sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-android-arm64@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz#33757c3a448b9ef77b6f6292d8b0ec45c87e9c1a"
 | 
				
			||||||
 | 
					  integrity sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-darwin-arm64@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz#5234ba62665a3f443143bc8bcea9df2cc58f55fb"
 | 
				
			||||||
 | 
					  integrity sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-darwin-x64@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz#981256c054d3247b83313724938d606798a919d1"
 | 
				
			||||||
 | 
					  integrity sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-linux-arm-gnueabihf@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz#120678a5a2b3a283a548dbb4d337f9187a793560"
 | 
				
			||||||
 | 
					  integrity sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-linux-arm64-gnu@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz#c99d857e2372ece544b6f60b85058ad259f64114"
 | 
				
			||||||
 | 
					  integrity sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-linux-arm64-musl@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz#3064060f568a5718c2a06858cd6e6d24f2ff8632"
 | 
				
			||||||
 | 
					  integrity sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-linux-riscv64-gnu@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz#987d30b5d2b992fff07d055015991a57ff55fbad"
 | 
				
			||||||
 | 
					  integrity sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-linux-x64-gnu@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz#85946ee4d068bd12197aeeec2c6f679c94978a49"
 | 
				
			||||||
 | 
					  integrity sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-linux-x64-musl@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz#fe0b20f9749a60eb1df43d20effa96c756ddcbd4"
 | 
				
			||||||
 | 
					  integrity sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-win32-arm64-msvc@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz#422661ef0e16699a234465d15b2c1089ef963b2a"
 | 
				
			||||||
 | 
					  integrity sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-win32-ia32-msvc@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz#7b73a145891c202fbcc08759248983667a035d85"
 | 
				
			||||||
 | 
					  integrity sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@rollup/rollup-win32-x64-msvc@4.9.5":
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz#10491ccf4f63c814d4149e0316541476ea603602"
 | 
				
			||||||
 | 
					  integrity sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/estree@1.0.5":
 | 
				
			||||||
 | 
					  version "1.0.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
 | 
				
			||||||
 | 
					  integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					esbuild@^0.19.3:
 | 
				
			||||||
 | 
					  version "0.19.11"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.11.tgz#4a02dca031e768b5556606e1b468fe72e3325d96"
 | 
				
			||||||
 | 
					  integrity sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==
 | 
				
			||||||
 | 
					  optionalDependencies:
 | 
				
			||||||
 | 
					    "@esbuild/aix-ppc64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/android-arm" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/android-arm64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/android-x64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/darwin-arm64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/darwin-x64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/freebsd-arm64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/freebsd-x64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/linux-arm" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/linux-arm64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/linux-ia32" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/linux-loong64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/linux-mips64el" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/linux-ppc64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/linux-riscv64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/linux-s390x" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/linux-x64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/netbsd-x64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/openbsd-x64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/sunos-x64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/win32-arm64" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/win32-ia32" "0.19.11"
 | 
				
			||||||
 | 
					    "@esbuild/win32-x64" "0.19.11"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fsevents@~2.3.2, fsevents@~2.3.3:
 | 
				
			||||||
 | 
					  version "2.3.3"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
 | 
				
			||||||
 | 
					  integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"js-tokens@^3.0.0 || ^4.0.0":
 | 
				
			||||||
 | 
					  version "4.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
 | 
				
			||||||
 | 
					  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					loose-envify@^1.1.0:
 | 
				
			||||||
 | 
					  version "1.4.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
 | 
				
			||||||
 | 
					  integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    js-tokens "^3.0.0 || ^4.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nanoid@^3.3.7:
 | 
				
			||||||
 | 
					  version "3.3.7"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
 | 
				
			||||||
 | 
					  integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					picocolors@^1.0.0:
 | 
				
			||||||
 | 
					  version "1.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
 | 
				
			||||||
 | 
					  integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					postcss@^8.4.32:
 | 
				
			||||||
 | 
					  version "8.4.33"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742"
 | 
				
			||||||
 | 
					  integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    nanoid "^3.3.7"
 | 
				
			||||||
 | 
					    picocolors "^1.0.0"
 | 
				
			||||||
 | 
					    source-map-js "^1.0.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					react-dom@18.2.0:
 | 
				
			||||||
 | 
					  version "18.2.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
 | 
				
			||||||
 | 
					  integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    loose-envify "^1.1.0"
 | 
				
			||||||
 | 
					    scheduler "^0.23.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					react@18.2.0:
 | 
				
			||||||
 | 
					  version "18.2.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
 | 
				
			||||||
 | 
					  integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    loose-envify "^1.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rollup@^4.2.0:
 | 
				
			||||||
 | 
					  version "4.9.5"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.5.tgz#62999462c90f4c8b5d7c38fc7161e63b29101b05"
 | 
				
			||||||
 | 
					  integrity sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    "@types/estree" "1.0.5"
 | 
				
			||||||
 | 
					  optionalDependencies:
 | 
				
			||||||
 | 
					    "@rollup/rollup-android-arm-eabi" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-android-arm64" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-darwin-arm64" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-darwin-x64" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-linux-arm-gnueabihf" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-linux-arm64-gnu" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-linux-arm64-musl" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-linux-riscv64-gnu" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-linux-x64-gnu" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-linux-x64-musl" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-win32-arm64-msvc" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-win32-ia32-msvc" "4.9.5"
 | 
				
			||||||
 | 
					    "@rollup/rollup-win32-x64-msvc" "4.9.5"
 | 
				
			||||||
 | 
					    fsevents "~2.3.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					scheduler@^0.23.0:
 | 
				
			||||||
 | 
					  version "0.23.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
 | 
				
			||||||
 | 
					  integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    loose-envify "^1.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					source-map-js@^1.0.2:
 | 
				
			||||||
 | 
					  version "1.0.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
 | 
				
			||||||
 | 
					  integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					vite@5.0.6:
 | 
				
			||||||
 | 
					  version "5.0.6"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.6.tgz#f9e13503a4c5ccd67312c67803dec921f3bdea7c"
 | 
				
			||||||
 | 
					  integrity sha512-MD3joyAEBtV7QZPl2JVVUai6zHms3YOmLR+BpMzLlX2Yzjfcc4gTgNi09d/Rua3F4EtC8zdwPU8eQYyib4vVMQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    esbuild "^0.19.3"
 | 
				
			||||||
 | 
					    postcss "^8.4.32"
 | 
				
			||||||
 | 
					    rollup "^4.2.0"
 | 
				
			||||||
 | 
					  optionalDependencies:
 | 
				
			||||||
 | 
					    fsevents "~2.3.3"
 | 
				
			||||||
							
								
								
									
										1167
									
								
								excalidraw-app/App.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -1,14 +1,14 @@
 | 
				
			|||||||
import { useEffect, useState } from "react";
 | 
					import { useEffect, useState } from "react";
 | 
				
			||||||
import { debounce, getVersion, nFormatter } from "../src/utils";
 | 
					import { debounce, getVersion, nFormatter } from "../packages/excalidraw/utils";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  getElementsStorageSize,
 | 
					  getElementsStorageSize,
 | 
				
			||||||
  getTotalStorageSize,
 | 
					  getTotalStorageSize,
 | 
				
			||||||
} from "./data/localStorage";
 | 
					} from "./data/localStorage";
 | 
				
			||||||
import { DEFAULT_VERSION } from "../src/constants";
 | 
					import { DEFAULT_VERSION } from "../packages/excalidraw/constants";
 | 
				
			||||||
import { t } from "../src/i18n";
 | 
					import { t } from "../packages/excalidraw/i18n";
 | 
				
			||||||
import { copyTextToSystemClipboard } from "../src/clipboard";
 | 
					import { copyTextToSystemClipboard } from "../packages/excalidraw/clipboard";
 | 
				
			||||||
import { NonDeletedExcalidrawElement } from "../src/element/types";
 | 
					import type { NonDeletedExcalidrawElement } from "../packages/excalidraw/element/types";
 | 
				
			||||||
import { UIAppState } from "../src/types";
 | 
					import type { UIAppState } from "../packages/excalidraw/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type StorageSizes = { scene: number; total: number };
 | 
					type StorageSizes = { scene: number; total: number };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,11 +15,17 @@ export const FILE_CACHE_MAX_AGE_SEC = 31536000;
 | 
				
			|||||||
export const WS_EVENTS = {
 | 
					export const WS_EVENTS = {
 | 
				
			||||||
  SERVER_VOLATILE: "server-volatile-broadcast",
 | 
					  SERVER_VOLATILE: "server-volatile-broadcast",
 | 
				
			||||||
  SERVER: "server-broadcast",
 | 
					  SERVER: "server-broadcast",
 | 
				
			||||||
};
 | 
					  USER_FOLLOW_CHANGE: "user-follow",
 | 
				
			||||||
 | 
					  USER_FOLLOW_ROOM_CHANGE: "user-follow-room-change",
 | 
				
			||||||
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum WS_SCENE_EVENT_TYPES {
 | 
					export enum WS_SUBTYPES {
 | 
				
			||||||
 | 
					  INVALID_RESPONSE = "INVALID_RESPONSE",
 | 
				
			||||||
  INIT = "SCENE_INIT",
 | 
					  INIT = "SCENE_INIT",
 | 
				
			||||||
  UPDATE = "SCENE_UPDATE",
 | 
					  UPDATE = "SCENE_UPDATE",
 | 
				
			||||||
 | 
					  MOUSE_LOCATION = "MOUSE_LOCATION",
 | 
				
			||||||
 | 
					  IDLE_STATUS = "IDLE_STATUS",
 | 
				
			||||||
 | 
					  USER_VISIBLE_SCENE_BOUNDS = "USER_VISIBLE_SCENE_BOUNDS",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const FIREBASE_STORAGE_PREFIXES = {
 | 
					export const FIREBASE_STORAGE_PREFIXES = {
 | 
				
			||||||
@@ -33,10 +39,14 @@ export const STORAGE_KEYS = {
 | 
				
			|||||||
  LOCAL_STORAGE_ELEMENTS: "excalidraw",
 | 
					  LOCAL_STORAGE_ELEMENTS: "excalidraw",
 | 
				
			||||||
  LOCAL_STORAGE_APP_STATE: "excalidraw-state",
 | 
					  LOCAL_STORAGE_APP_STATE: "excalidraw-state",
 | 
				
			||||||
  LOCAL_STORAGE_COLLAB: "excalidraw-collab",
 | 
					  LOCAL_STORAGE_COLLAB: "excalidraw-collab",
 | 
				
			||||||
  LOCAL_STORAGE_LIBRARY: "excalidraw-library",
 | 
					 | 
				
			||||||
  LOCAL_STORAGE_THEME: "excalidraw-theme",
 | 
					  LOCAL_STORAGE_THEME: "excalidraw-theme",
 | 
				
			||||||
  VERSION_DATA_STATE: "version-dataState",
 | 
					  VERSION_DATA_STATE: "version-dataState",
 | 
				
			||||||
  VERSION_FILES: "version-files",
 | 
					  VERSION_FILES: "version-files",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  IDB_LIBRARY: "excalidraw-library",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // do not use apart from migrations
 | 
				
			||||||
 | 
					  __LEGACY_LOCAL_STORAGE_LIBRARY: "excalidraw-library",
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const COOKIES = {
 | 
					export const COOKIES = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,39 +1,49 @@
 | 
				
			|||||||
import throttle from "lodash.throttle";
 | 
					import throttle from "lodash.throttle";
 | 
				
			||||||
import { PureComponent } from "react";
 | 
					import { PureComponent } from "react";
 | 
				
			||||||
import { ExcalidrawImperativeAPI } from "../../src/types";
 | 
					import type {
 | 
				
			||||||
import { ErrorDialog } from "../../src/components/ErrorDialog";
 | 
					  ExcalidrawImperativeAPI,
 | 
				
			||||||
import { APP_NAME, ENV, EVENT } from "../../src/constants";
 | 
					  SocketId,
 | 
				
			||||||
import { ImportedDataState } from "../../src/data/types";
 | 
					} from "../../packages/excalidraw/types";
 | 
				
			||||||
import {
 | 
					import { ErrorDialog } from "../../packages/excalidraw/components/ErrorDialog";
 | 
				
			||||||
 | 
					import { APP_NAME, ENV, EVENT } from "../../packages/excalidraw/constants";
 | 
				
			||||||
 | 
					import type { ImportedDataState } from "../../packages/excalidraw/data/types";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
  ExcalidrawElement,
 | 
					  ExcalidrawElement,
 | 
				
			||||||
  InitializedExcalidrawImageElement,
 | 
					  InitializedExcalidrawImageElement,
 | 
				
			||||||
} from "../../src/element/types";
 | 
					  OrderedExcalidrawElement,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/element/types";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  StoreAction,
 | 
				
			||||||
  getSceneVersion,
 | 
					  getSceneVersion,
 | 
				
			||||||
  restoreElements,
 | 
					  restoreElements,
 | 
				
			||||||
} from "../../src/packages/excalidraw/index";
 | 
					  zoomToFitBounds,
 | 
				
			||||||
import { Collaborator, Gesture } from "../../src/types";
 | 
					  reconcileElements,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw";
 | 
				
			||||||
 | 
					import type { Collaborator, Gesture } from "../../packages/excalidraw/types";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  assertNever,
 | 
				
			||||||
  preventUnload,
 | 
					  preventUnload,
 | 
				
			||||||
  resolvablePromise,
 | 
					  resolvablePromise,
 | 
				
			||||||
  withBatchedUpdates,
 | 
					  throttleRAF,
 | 
				
			||||||
} from "../../src/utils";
 | 
					} from "../../packages/excalidraw/utils";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  CURSOR_SYNC_TIMEOUT,
 | 
					  CURSOR_SYNC_TIMEOUT,
 | 
				
			||||||
  FILE_UPLOAD_MAX_BYTES,
 | 
					  FILE_UPLOAD_MAX_BYTES,
 | 
				
			||||||
  FIREBASE_STORAGE_PREFIXES,
 | 
					  FIREBASE_STORAGE_PREFIXES,
 | 
				
			||||||
  INITIAL_SCENE_UPDATE_TIMEOUT,
 | 
					  INITIAL_SCENE_UPDATE_TIMEOUT,
 | 
				
			||||||
  LOAD_IMAGES_TIMEOUT,
 | 
					  LOAD_IMAGES_TIMEOUT,
 | 
				
			||||||
  WS_SCENE_EVENT_TYPES,
 | 
					  WS_SUBTYPES,
 | 
				
			||||||
  SYNC_FULL_SCENE_INTERVAL_MS,
 | 
					  SYNC_FULL_SCENE_INTERVAL_MS,
 | 
				
			||||||
 | 
					  WS_EVENTS,
 | 
				
			||||||
} from "../app_constants";
 | 
					} from "../app_constants";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  SocketUpdateDataSource,
 | 
				
			||||||
 | 
					  SyncableExcalidrawElement,
 | 
				
			||||||
 | 
					} from "../data";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  generateCollaborationLinkData,
 | 
					  generateCollaborationLinkData,
 | 
				
			||||||
  getCollaborationLink,
 | 
					  getCollaborationLink,
 | 
				
			||||||
  getCollabServer,
 | 
					 | 
				
			||||||
  getSyncableElements,
 | 
					  getSyncableElements,
 | 
				
			||||||
  SocketUpdateDataSource,
 | 
					 | 
				
			||||||
  SyncableExcalidrawElement,
 | 
					 | 
				
			||||||
} from "../data";
 | 
					} from "../data";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  isSavedToFirebase,
 | 
					  isSavedToFirebase,
 | 
				
			||||||
@@ -47,42 +57,51 @@ import {
 | 
				
			|||||||
  saveUsernameToLocalStorage,
 | 
					  saveUsernameToLocalStorage,
 | 
				
			||||||
} from "../data/localStorage";
 | 
					} from "../data/localStorage";
 | 
				
			||||||
import Portal from "./Portal";
 | 
					import Portal from "./Portal";
 | 
				
			||||||
import RoomDialog from "./RoomDialog";
 | 
					import { t } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
import { t } from "../../src/i18n";
 | 
					import { UserIdleState } from "../../packages/excalidraw/types";
 | 
				
			||||||
import { UserIdleState } from "../../src/types";
 | 
					import {
 | 
				
			||||||
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../src/constants";
 | 
					  IDLE_THRESHOLD,
 | 
				
			||||||
 | 
					  ACTIVE_THRESHOLD,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/constants";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  encodeFilesForUpload,
 | 
					  encodeFilesForUpload,
 | 
				
			||||||
  FileManager,
 | 
					  FileManager,
 | 
				
			||||||
  updateStaleImageStatuses,
 | 
					  updateStaleImageStatuses,
 | 
				
			||||||
} from "../data/FileManager";
 | 
					} from "../data/FileManager";
 | 
				
			||||||
import { AbortError } from "../../src/errors";
 | 
					import { AbortError } from "../../packages/excalidraw/errors";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  isImageElement,
 | 
					  isImageElement,
 | 
				
			||||||
  isInitializedImageElement,
 | 
					  isInitializedImageElement,
 | 
				
			||||||
} from "../../src/element/typeChecks";
 | 
					} from "../../packages/excalidraw/element/typeChecks";
 | 
				
			||||||
import { newElementWith } from "../../src/element/mutateElement";
 | 
					import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
 | 
				
			||||||
import {
 | 
					import { decryptData } from "../../packages/excalidraw/data/encryption";
 | 
				
			||||||
  ReconciledElements,
 | 
					 | 
				
			||||||
  reconcileElements as _reconcileElements,
 | 
					 | 
				
			||||||
} from "./reconciliation";
 | 
					 | 
				
			||||||
import { decryptData } from "../../src/data/encryption";
 | 
					 | 
				
			||||||
import { resetBrowserStateVersions } from "../data/tabSync";
 | 
					import { resetBrowserStateVersions } from "../data/tabSync";
 | 
				
			||||||
import { LocalData } from "../data/LocalData";
 | 
					import { LocalData } from "../data/LocalData";
 | 
				
			||||||
import { atom, useAtom } from "jotai";
 | 
					import { atom } from "jotai";
 | 
				
			||||||
import { appJotaiStore } from "../app-jotai";
 | 
					import { appJotaiStore } from "../app-jotai";
 | 
				
			||||||
 | 
					import type { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
 | 
				
			||||||
 | 
					import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
 | 
				
			||||||
 | 
					import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils";
 | 
				
			||||||
 | 
					import { collabErrorIndicatorAtom } from "./CollabError";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  ReconciledExcalidrawElement,
 | 
				
			||||||
 | 
					  RemoteExcalidrawElement,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/data/reconcile";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
 | 
					export const collabAPIAtom = atom<CollabAPI | null>(null);
 | 
				
			||||||
export const collabDialogShownAtom = atom(false);
 | 
					 | 
				
			||||||
export const isCollaboratingAtom = atom(false);
 | 
					export const isCollaboratingAtom = atom(false);
 | 
				
			||||||
export const isOfflineAtom = atom(false);
 | 
					export const isOfflineAtom = atom(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface CollabState {
 | 
					interface CollabState {
 | 
				
			||||||
  errorMessage: string;
 | 
					  errorMessage: string | null;
 | 
				
			||||||
 | 
					  /** errors related to saving */
 | 
				
			||||||
 | 
					  dialogNotifiedErrors: Record<string, boolean>;
 | 
				
			||||||
  username: string;
 | 
					  username: string;
 | 
				
			||||||
  activeRoomLink: string;
 | 
					  activeRoomLink: string | null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const activeRoomLinkAtom = atom<string | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CollabInstance = InstanceType<typeof Collab>;
 | 
					type CollabInstance = InstanceType<typeof Collab>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface CollabAPI {
 | 
					export interface CollabAPI {
 | 
				
			||||||
@@ -93,32 +112,34 @@ export interface CollabAPI {
 | 
				
			|||||||
  stopCollaboration: CollabInstance["stopCollaboration"];
 | 
					  stopCollaboration: CollabInstance["stopCollaboration"];
 | 
				
			||||||
  syncElements: CollabInstance["syncElements"];
 | 
					  syncElements: CollabInstance["syncElements"];
 | 
				
			||||||
  fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"];
 | 
					  fetchImageFilesFromFirebase: CollabInstance["fetchImageFilesFromFirebase"];
 | 
				
			||||||
  setUsername: (username: string) => void;
 | 
					  setUsername: CollabInstance["setUsername"];
 | 
				
			||||||
 | 
					  getUsername: CollabInstance["getUsername"];
 | 
				
			||||||
 | 
					  getActiveRoomLink: CollabInstance["getActiveRoomLink"];
 | 
				
			||||||
 | 
					  setCollabError: CollabInstance["setErrorDialog"];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface PublicProps {
 | 
					interface CollabProps {
 | 
				
			||||||
  excalidrawAPI: ExcalidrawImperativeAPI;
 | 
					  excalidrawAPI: ExcalidrawImperativeAPI;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = PublicProps & { modalIsShown: boolean };
 | 
					class Collab extends PureComponent<CollabProps, CollabState> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
class Collab extends PureComponent<Props, CollabState> {
 | 
					 | 
				
			||||||
  portal: Portal;
 | 
					  portal: Portal;
 | 
				
			||||||
  fileManager: FileManager;
 | 
					  fileManager: FileManager;
 | 
				
			||||||
  excalidrawAPI: Props["excalidrawAPI"];
 | 
					  excalidrawAPI: CollabProps["excalidrawAPI"];
 | 
				
			||||||
  activeIntervalId: number | null;
 | 
					  activeIntervalId: number | null;
 | 
				
			||||||
  idleTimeoutId: number | null;
 | 
					  idleTimeoutId: number | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private socketInitializationTimer?: number;
 | 
					  private socketInitializationTimer?: number;
 | 
				
			||||||
  private lastBroadcastedOrReceivedSceneVersion: number = -1;
 | 
					  private lastBroadcastedOrReceivedSceneVersion: number = -1;
 | 
				
			||||||
  private collaborators = new Map<string, Collaborator>();
 | 
					  private collaborators = new Map<SocketId, Collaborator>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(props: Props) {
 | 
					  constructor(props: CollabProps) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
    this.state = {
 | 
					    this.state = {
 | 
				
			||||||
      errorMessage: "",
 | 
					      errorMessage: null,
 | 
				
			||||||
 | 
					      dialogNotifiedErrors: {},
 | 
				
			||||||
      username: importUsernameFromLocalStorage() || "",
 | 
					      username: importUsernameFromLocalStorage() || "",
 | 
				
			||||||
      activeRoomLink: "",
 | 
					      activeRoomLink: null,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    this.portal = new Portal(this);
 | 
					    this.portal = new Portal(this);
 | 
				
			||||||
    this.fileManager = new FileManager({
 | 
					    this.fileManager = new FileManager({
 | 
				
			||||||
@@ -151,12 +172,28 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    this.idleTimeoutId = null;
 | 
					    this.idleTimeoutId = null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private onUmmount: (() => void) | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentDidMount() {
 | 
					  componentDidMount() {
 | 
				
			||||||
    window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
 | 
					    window.addEventListener(EVENT.BEFORE_UNLOAD, this.beforeUnload);
 | 
				
			||||||
    window.addEventListener("online", this.onOfflineStatusToggle);
 | 
					    window.addEventListener("online", this.onOfflineStatusToggle);
 | 
				
			||||||
    window.addEventListener("offline", this.onOfflineStatusToggle);
 | 
					    window.addEventListener("offline", this.onOfflineStatusToggle);
 | 
				
			||||||
    window.addEventListener(EVENT.UNLOAD, this.onUnload);
 | 
					    window.addEventListener(EVENT.UNLOAD, this.onUnload);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const unsubOnUserFollow = this.excalidrawAPI.onUserFollow((payload) => {
 | 
				
			||||||
 | 
					      this.portal.socket && this.portal.broadcastUserFollowed(payload);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const throttledRelayUserViewportBounds = throttleRAF(
 | 
				
			||||||
 | 
					      this.relayVisibleSceneBounds,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const unsubOnScrollChange = this.excalidrawAPI.onScrollChange(() =>
 | 
				
			||||||
 | 
					      throttledRelayUserViewportBounds(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    this.onUmmount = () => {
 | 
				
			||||||
 | 
					      unsubOnUserFollow();
 | 
				
			||||||
 | 
					      unsubOnScrollChange();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.onOfflineStatusToggle();
 | 
					    this.onOfflineStatusToggle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const collabAPI: CollabAPI = {
 | 
					    const collabAPI: CollabAPI = {
 | 
				
			||||||
@@ -167,6 +204,9 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
      fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase,
 | 
					      fetchImageFilesFromFirebase: this.fetchImageFilesFromFirebase,
 | 
				
			||||||
      stopCollaboration: this.stopCollaboration,
 | 
					      stopCollaboration: this.stopCollaboration,
 | 
				
			||||||
      setUsername: this.setUsername,
 | 
					      setUsername: this.setUsername,
 | 
				
			||||||
 | 
					      getUsername: this.getUsername,
 | 
				
			||||||
 | 
					      getActiveRoomLink: this.getActiveRoomLink,
 | 
				
			||||||
 | 
					      setCollabError: this.setErrorDialog,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    appJotaiStore.set(collabAPIAtom, collabAPI);
 | 
					    appJotaiStore.set(collabAPIAtom, collabAPI);
 | 
				
			||||||
@@ -204,6 +244,7 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
      window.clearTimeout(this.idleTimeoutId);
 | 
					      window.clearTimeout(this.idleTimeoutId);
 | 
				
			||||||
      this.idleTimeoutId = null;
 | 
					      this.idleTimeoutId = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    this.onUmmount?.();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  isCollaborating = () => appJotaiStore.get(isCollaboratingAtom)!;
 | 
					  isCollaborating = () => appJotaiStore.get(isCollaboratingAtom)!;
 | 
				
			||||||
@@ -238,24 +279,39 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    syncableElements: readonly SyncableExcalidrawElement[],
 | 
					    syncableElements: readonly SyncableExcalidrawElement[],
 | 
				
			||||||
  ) => {
 | 
					  ) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const savedData = await saveToFirebase(
 | 
					      const storedElements = await saveToFirebase(
 | 
				
			||||||
        this.portal,
 | 
					        this.portal,
 | 
				
			||||||
        syncableElements,
 | 
					        syncableElements,
 | 
				
			||||||
        this.excalidrawAPI.getAppState(),
 | 
					        this.excalidrawAPI.getAppState(),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.isCollaborating() && savedData && savedData.reconciledElements) {
 | 
					      this.resetErrorIndicator();
 | 
				
			||||||
        this.handleRemoteSceneUpdate(
 | 
					
 | 
				
			||||||
          this.reconcileElements(savedData.reconciledElements),
 | 
					      if (this.isCollaborating() && storedElements) {
 | 
				
			||||||
        );
 | 
					        this.handleRemoteSceneUpdate(this._reconcileElements(storedElements));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (error: any) {
 | 
					    } catch (error: any) {
 | 
				
			||||||
      this.setState({
 | 
					      const errorMessage = /is longer than.*?bytes/.test(error.message)
 | 
				
			||||||
        // firestore doesn't return a specific error code when size exceeded
 | 
					 | 
				
			||||||
        errorMessage: /is longer than.*?bytes/.test(error.message)
 | 
					 | 
				
			||||||
        ? t("errors.collabSaveFailed_sizeExceeded")
 | 
					        ? t("errors.collabSaveFailed_sizeExceeded")
 | 
				
			||||||
          : t("errors.collabSaveFailed"),
 | 
					        : t("errors.collabSaveFailed");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        !this.state.dialogNotifiedErrors[errorMessage] ||
 | 
				
			||||||
 | 
					        !this.isCollaborating()
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        this.setErrorDialog(errorMessage);
 | 
				
			||||||
 | 
					        this.setState({
 | 
				
			||||||
 | 
					          dialogNotifiedErrors: {
 | 
				
			||||||
 | 
					            ...this.state.dialogNotifiedErrors,
 | 
				
			||||||
 | 
					            [errorMessage]: true,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (this.isCollaborating()) {
 | 
				
			||||||
 | 
					        this.setErrorIndicator(errorMessage);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      console.error(error);
 | 
					      console.error(error);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -264,6 +320,7 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    this.queueBroadcastAllElements.cancel();
 | 
					    this.queueBroadcastAllElements.cancel();
 | 
				
			||||||
    this.queueSaveToFirebase.cancel();
 | 
					    this.queueSaveToFirebase.cancel();
 | 
				
			||||||
    this.loadImageFiles.cancel();
 | 
					    this.loadImageFiles.cancel();
 | 
				
			||||||
 | 
					    this.resetErrorIndicator(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.saveCollabRoomToFirebase(
 | 
					    this.saveCollabRoomToFirebase(
 | 
				
			||||||
      getSyncableElements(
 | 
					      getSyncableElements(
 | 
				
			||||||
@@ -302,7 +359,7 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      this.excalidrawAPI.updateScene({
 | 
					      this.excalidrawAPI.updateScene({
 | 
				
			||||||
        elements,
 | 
					        elements,
 | 
				
			||||||
        commitToHistory: false,
 | 
					        storeAction: StoreAction.UPDATE,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -313,9 +370,7 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    this.fileManager.reset();
 | 
					    this.fileManager.reset();
 | 
				
			||||||
    if (!opts?.isUnload) {
 | 
					    if (!opts?.isUnload) {
 | 
				
			||||||
      this.setIsCollaborating(false);
 | 
					      this.setIsCollaborating(false);
 | 
				
			||||||
      this.setState({
 | 
					      this.setActiveRoomLink(null);
 | 
				
			||||||
        activeRoomLink: "",
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      this.collaborators = new Map();
 | 
					      this.collaborators = new Map();
 | 
				
			||||||
      this.excalidrawAPI.updateScene({
 | 
					      this.excalidrawAPI.updateScene({
 | 
				
			||||||
        collaborators: this.collaborators,
 | 
					        collaborators: this.collaborators,
 | 
				
			||||||
@@ -356,7 +411,7 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    iv: Uint8Array,
 | 
					    iv: Uint8Array,
 | 
				
			||||||
    encryptedData: ArrayBuffer,
 | 
					    encryptedData: ArrayBuffer,
 | 
				
			||||||
    decryptionKey: string,
 | 
					    decryptionKey: string,
 | 
				
			||||||
  ) => {
 | 
					  ): Promise<ValueOf<SocketUpdateDataSource>> => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const decrypted = await decryptData(iv, encryptedData, decryptionKey);
 | 
					      const decrypted = await decryptData(iv, encryptedData, decryptionKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -368,7 +423,7 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
      window.alert(t("alerts.decryptFailed"));
 | 
					      window.alert(t("alerts.decryptFailed"));
 | 
				
			||||||
      console.error(error);
 | 
					      console.error(error);
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        type: "INVALID_RESPONSE",
 | 
					        type: WS_SUBTYPES.INVALID_RESPONSE,
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -377,11 +432,11 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  startCollaboration = async (
 | 
					  startCollaboration = async (
 | 
				
			||||||
    existingRoomLinkData: null | { roomId: string; roomKey: string },
 | 
					    existingRoomLinkData: null | { roomId: string; roomKey: string },
 | 
				
			||||||
  ): Promise<ImportedDataState | null> => {
 | 
					  ) => {
 | 
				
			||||||
    if (!this.state.username) {
 | 
					    if (!this.state.username) {
 | 
				
			||||||
      import("@excalidraw/random-username").then(({ getRandomUsername }) => {
 | 
					      import("@excalidraw/random-username").then(({ getRandomUsername }) => {
 | 
				
			||||||
        const username = getRandomUsername();
 | 
					        const username = getRandomUsername();
 | 
				
			||||||
        this.onUsernameChange(username);
 | 
					        this.setUsername(username);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -403,7 +458,11 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const scenePromise = resolvablePromise<ImportedDataState | null>();
 | 
					    // TODO: `ImportedDataState` type here seems abused
 | 
				
			||||||
 | 
					    const scenePromise = resolvablePromise<
 | 
				
			||||||
 | 
					      | (ImportedDataState & { elements: readonly OrderedExcalidrawElement[] })
 | 
				
			||||||
 | 
					      | null
 | 
				
			||||||
 | 
					    >();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.setIsCollaborating(true);
 | 
					    this.setIsCollaborating(true);
 | 
				
			||||||
    LocalData.pauseSave("collaboration");
 | 
					    LocalData.pauseSave("collaboration");
 | 
				
			||||||
@@ -423,13 +482,9 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    this.fallbackInitializationHandler = fallbackInitializationHandler;
 | 
					    this.fallbackInitializationHandler = fallbackInitializationHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const socketServerData = await getCollabServer();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      this.portal.socket = this.portal.open(
 | 
					      this.portal.socket = this.portal.open(
 | 
				
			||||||
        socketIOClient(socketServerData.url, {
 | 
					        socketIOClient(import.meta.env.VITE_APP_WS_SERVER_URL, {
 | 
				
			||||||
          transports: socketServerData.polling
 | 
					          transports: ["websocket", "polling"],
 | 
				
			||||||
            ? ["websocket", "polling"]
 | 
					 | 
				
			||||||
            : ["websocket"],
 | 
					 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
        roomId,
 | 
					        roomId,
 | 
				
			||||||
        roomKey,
 | 
					        roomKey,
 | 
				
			||||||
@@ -438,7 +493,7 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
      this.portal.socket.once("connect_error", fallbackInitializationHandler);
 | 
					      this.portal.socket.once("connect_error", fallbackInitializationHandler);
 | 
				
			||||||
    } catch (error: any) {
 | 
					    } catch (error: any) {
 | 
				
			||||||
      console.error(error);
 | 
					      console.error(error);
 | 
				
			||||||
      this.setState({ errorMessage: error.message });
 | 
					      this.setErrorDialog(error.message);
 | 
				
			||||||
      return null;
 | 
					      return null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -449,14 +504,13 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return element;
 | 
					        return element;
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      // remove deleted elements from elements array & history to ensure we don't
 | 
					      // remove deleted elements from elements array to ensure we don't
 | 
				
			||||||
      // expose potentially sensitive user data in case user manually deletes
 | 
					      // expose potentially sensitive user data in case user manually deletes
 | 
				
			||||||
      // existing elements (or clears scene), which would otherwise be persisted
 | 
					      // existing elements (or clears scene), which would otherwise be persisted
 | 
				
			||||||
      // to database even if deleted before creating the room.
 | 
					      // to database even if deleted before creating the room.
 | 
				
			||||||
      this.excalidrawAPI.history.clear();
 | 
					 | 
				
			||||||
      this.excalidrawAPI.updateScene({
 | 
					      this.excalidrawAPI.updateScene({
 | 
				
			||||||
        elements,
 | 
					        elements,
 | 
				
			||||||
        commitToHistory: true,
 | 
					        storeAction: StoreAction.UPDATE,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.saveCollabRoomToFirebase(getSyncableElements(elements));
 | 
					      this.saveCollabRoomToFirebase(getSyncableElements(elements));
 | 
				
			||||||
@@ -484,16 +538,15 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        switch (decryptedData.type) {
 | 
					        switch (decryptedData.type) {
 | 
				
			||||||
          case "INVALID_RESPONSE":
 | 
					          case WS_SUBTYPES.INVALID_RESPONSE:
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
          case WS_SCENE_EVENT_TYPES.INIT: {
 | 
					          case WS_SUBTYPES.INIT: {
 | 
				
			||||||
            if (!this.portal.socketInitialized) {
 | 
					            if (!this.portal.socketInitialized) {
 | 
				
			||||||
              this.initializeRoom({ fetchScene: false });
 | 
					              this.initializeRoom({ fetchScene: false });
 | 
				
			||||||
              const remoteElements = decryptedData.payload.elements;
 | 
					              const remoteElements = decryptedData.payload.elements;
 | 
				
			||||||
              const reconciledElements = this.reconcileElements(remoteElements);
 | 
					              const reconciledElements =
 | 
				
			||||||
              this.handleRemoteSceneUpdate(reconciledElements, {
 | 
					                this._reconcileElements(remoteElements);
 | 
				
			||||||
                init: true,
 | 
					              this.handleRemoteSceneUpdate(reconciledElements);
 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
              // noop if already resolved via init from firebase
 | 
					              // noop if already resolved via init from firebase
 | 
				
			||||||
              scenePromise.resolve({
 | 
					              scenePromise.resolve({
 | 
				
			||||||
                elements: reconciledElements,
 | 
					                elements: reconciledElements,
 | 
				
			||||||
@@ -502,41 +555,75 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          case WS_SCENE_EVENT_TYPES.UPDATE:
 | 
					          case WS_SUBTYPES.UPDATE:
 | 
				
			||||||
            this.handleRemoteSceneUpdate(
 | 
					            this.handleRemoteSceneUpdate(
 | 
				
			||||||
              this.reconcileElements(decryptedData.payload.elements),
 | 
					              this._reconcileElements(decryptedData.payload.elements),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
          case "MOUSE_LOCATION": {
 | 
					          case WS_SUBTYPES.MOUSE_LOCATION: {
 | 
				
			||||||
            const { pointer, button, username, selectedElementIds } =
 | 
					            const { pointer, button, username, selectedElementIds } =
 | 
				
			||||||
              decryptedData.payload;
 | 
					              decryptedData.payload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] =
 | 
					            const socketId: SocketUpdateDataSource["MOUSE_LOCATION"]["payload"]["socketId"] =
 | 
				
			||||||
              decryptedData.payload.socketId ||
 | 
					              decryptedData.payload.socketId ||
 | 
				
			||||||
              // @ts-ignore legacy, see #2094 (#2097)
 | 
					              // @ts-ignore legacy, see #2094 (#2097)
 | 
				
			||||||
              decryptedData.payload.socketID;
 | 
					              decryptedData.payload.socketID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const collaborators = new Map(this.collaborators);
 | 
					            this.updateCollaborator(socketId, {
 | 
				
			||||||
            const user = collaborators.get(socketId) || {}!;
 | 
					              pointer,
 | 
				
			||||||
            user.pointer = pointer;
 | 
					              button,
 | 
				
			||||||
            user.button = button;
 | 
					              selectedElementIds,
 | 
				
			||||||
            user.selectedElementIds = selectedElementIds;
 | 
					              username,
 | 
				
			||||||
            user.username = username;
 | 
					            });
 | 
				
			||||||
            collaborators.set(socketId, user);
 | 
					
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          case WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS: {
 | 
				
			||||||
 | 
					            const { sceneBounds, socketId } = decryptedData.payload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const appState = this.excalidrawAPI.getAppState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // we're not following the user
 | 
				
			||||||
 | 
					            // (shouldn't happen, but could be late message or bug upstream)
 | 
				
			||||||
 | 
					            if (appState.userToFollow?.socketId !== socketId) {
 | 
				
			||||||
 | 
					              console.warn(
 | 
				
			||||||
 | 
					                `receiving remote client's (from ${socketId}) viewport bounds even though we're not subscribed to it!`,
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					              return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // cross-follow case, ignore updates in this case
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					              appState.userToFollow &&
 | 
				
			||||||
 | 
					              appState.followedBy.has(appState.userToFollow.socketId)
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					              return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.excalidrawAPI.updateScene({
 | 
					            this.excalidrawAPI.updateScene({
 | 
				
			||||||
              collaborators,
 | 
					              appState: zoomToFitBounds({
 | 
				
			||||||
 | 
					                appState,
 | 
				
			||||||
 | 
					                bounds: sceneBounds,
 | 
				
			||||||
 | 
					                fitToViewport: true,
 | 
				
			||||||
 | 
					                viewportZoomFactor: 1,
 | 
				
			||||||
 | 
					              }).appState,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          case WS_SUBTYPES.IDLE_STATUS: {
 | 
				
			||||||
 | 
					            const { userState, socketId, username } = decryptedData.payload;
 | 
				
			||||||
 | 
					            this.updateCollaborator(socketId, {
 | 
				
			||||||
 | 
					              userState,
 | 
				
			||||||
 | 
					              username,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          case "IDLE_STATUS": {
 | 
					
 | 
				
			||||||
            const { userState, socketId, username } = decryptedData.payload;
 | 
					          default: {
 | 
				
			||||||
            const collaborators = new Map(this.collaborators);
 | 
					            assertNever(decryptedData, null);
 | 
				
			||||||
            const user = collaborators.get(socketId) || {}!;
 | 
					 | 
				
			||||||
            user.userState = userState;
 | 
					 | 
				
			||||||
            user.username = username;
 | 
					 | 
				
			||||||
            this.excalidrawAPI.updateScene({
 | 
					 | 
				
			||||||
              collaborators,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -553,11 +640,20 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
      scenePromise.resolve(sceneData);
 | 
					      scenePromise.resolve(sceneData);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.portal.socket.on(
 | 
				
			||||||
 | 
					      WS_EVENTS.USER_FOLLOW_ROOM_CHANGE,
 | 
				
			||||||
 | 
					      (followedBy: SocketId[]) => {
 | 
				
			||||||
 | 
					        this.excalidrawAPI.updateScene({
 | 
				
			||||||
 | 
					          appState: { followedBy: new Set(followedBy) },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.relayVisibleSceneBounds({ force: true });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.initializeIdleDetector();
 | 
					    this.initializeIdleDetector();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.setState({
 | 
					    this.setActiveRoomLink(window.location.href);
 | 
				
			||||||
      activeRoomLink: window.location.href,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return scenePromise;
 | 
					    return scenePromise;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -609,17 +705,15 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private reconcileElements = (
 | 
					  private _reconcileElements = (
 | 
				
			||||||
    remoteElements: readonly ExcalidrawElement[],
 | 
					    remoteElements: readonly ExcalidrawElement[],
 | 
				
			||||||
  ): ReconciledElements => {
 | 
					  ): ReconciledExcalidrawElement[] => {
 | 
				
			||||||
    const localElements = this.getSceneElementsIncludingDeleted();
 | 
					    const localElements = this.getSceneElementsIncludingDeleted();
 | 
				
			||||||
    const appState = this.excalidrawAPI.getAppState();
 | 
					    const appState = this.excalidrawAPI.getAppState();
 | 
				
			||||||
 | 
					    const restoredRemoteElements = restoreElements(remoteElements, null);
 | 
				
			||||||
    remoteElements = restoreElements(remoteElements, null);
 | 
					    const reconciledElements = reconcileElements(
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const reconciledElements = _reconcileElements(
 | 
					 | 
				
			||||||
      localElements,
 | 
					      localElements,
 | 
				
			||||||
      remoteElements,
 | 
					      restoredRemoteElements as RemoteExcalidrawElement[],
 | 
				
			||||||
      appState,
 | 
					      appState,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -650,20 +744,13 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
  }, LOAD_IMAGES_TIMEOUT);
 | 
					  }, LOAD_IMAGES_TIMEOUT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private handleRemoteSceneUpdate = (
 | 
					  private handleRemoteSceneUpdate = (
 | 
				
			||||||
    elements: ReconciledElements,
 | 
					    elements: ReconciledExcalidrawElement[],
 | 
				
			||||||
    { init = false }: { init?: boolean } = {},
 | 
					 | 
				
			||||||
  ) => {
 | 
					  ) => {
 | 
				
			||||||
    this.excalidrawAPI.updateScene({
 | 
					    this.excalidrawAPI.updateScene({
 | 
				
			||||||
      elements,
 | 
					      elements,
 | 
				
			||||||
      commitToHistory: !!init,
 | 
					      storeAction: StoreAction.UPDATE,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // We haven't yet implemented multiplayer undo functionality, so we clear the undo stack
 | 
					 | 
				
			||||||
    // when we receive any messages from another peer. This UX can be pretty rough -- if you
 | 
					 | 
				
			||||||
    // undo, a user makes a change, and then try to redo, your element(s) will be lost. However,
 | 
					 | 
				
			||||||
    // right now we think this is the right tradeoff.
 | 
					 | 
				
			||||||
    this.excalidrawAPI.history.clear();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.loadImageFiles();
 | 
					    this.loadImageFiles();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -721,20 +808,39 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange);
 | 
					    document.addEventListener(EVENT.VISIBILITY_CHANGE, this.onVisibilityChange);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setCollaborators(sockets: string[]) {
 | 
					  setCollaborators(sockets: SocketId[]) {
 | 
				
			||||||
    const collaborators: InstanceType<typeof Collab>["collaborators"] =
 | 
					    const collaborators: InstanceType<typeof Collab>["collaborators"] =
 | 
				
			||||||
      new Map();
 | 
					      new Map();
 | 
				
			||||||
    for (const socketId of sockets) {
 | 
					    for (const socketId of sockets) {
 | 
				
			||||||
      if (this.collaborators.has(socketId)) {
 | 
					      collaborators.set(
 | 
				
			||||||
        collaborators.set(socketId, this.collaborators.get(socketId)!);
 | 
					        socketId,
 | 
				
			||||||
      } else {
 | 
					        Object.assign({}, this.collaborators.get(socketId), {
 | 
				
			||||||
        collaborators.set(socketId, {});
 | 
					          isCurrentUser: socketId === this.portal.socket?.id,
 | 
				
			||||||
      }
 | 
					        }),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.collaborators = collaborators;
 | 
					    this.collaborators = collaborators;
 | 
				
			||||||
    this.excalidrawAPI.updateScene({ collaborators });
 | 
					    this.excalidrawAPI.updateScene({ collaborators });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateCollaborator = (socketId: SocketId, updates: Partial<Collaborator>) => {
 | 
				
			||||||
 | 
					    const collaborators = new Map(this.collaborators);
 | 
				
			||||||
 | 
					    const user: Mutable<Collaborator> = Object.assign(
 | 
				
			||||||
 | 
					      {},
 | 
				
			||||||
 | 
					      collaborators.get(socketId),
 | 
				
			||||||
 | 
					      updates,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        isCurrentUser: socketId === this.portal.socket?.id,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    collaborators.set(socketId, user);
 | 
				
			||||||
 | 
					    this.collaborators = collaborators;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.excalidrawAPI.updateScene({
 | 
				
			||||||
 | 
					      collaborators,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public setLastBroadcastedOrReceivedSceneVersion = (version: number) => {
 | 
					  public setLastBroadcastedOrReceivedSceneVersion = (version: number) => {
 | 
				
			||||||
    this.lastBroadcastedOrReceivedSceneVersion = version;
 | 
					    this.lastBroadcastedOrReceivedSceneVersion = version;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -760,29 +866,42 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    CURSOR_SYNC_TIMEOUT,
 | 
					    CURSOR_SYNC_TIMEOUT,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  relayVisibleSceneBounds = (props?: { force: boolean }) => {
 | 
				
			||||||
 | 
					    const appState = this.excalidrawAPI.getAppState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this.portal.socket && (appState.followedBy.size > 0 || props?.force)) {
 | 
				
			||||||
 | 
					      this.portal.broadcastVisibleSceneBounds(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          sceneBounds: getVisibleSceneBounds(appState),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        `follow@${this.portal.socket.id}`,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onIdleStateChange = (userState: UserIdleState) => {
 | 
					  onIdleStateChange = (userState: UserIdleState) => {
 | 
				
			||||||
    this.portal.broadcastIdleChange(userState);
 | 
					    this.portal.broadcastIdleChange(userState);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  broadcastElements = (elements: readonly ExcalidrawElement[]) => {
 | 
					  broadcastElements = (elements: readonly OrderedExcalidrawElement[]) => {
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
      getSceneVersion(elements) >
 | 
					      getSceneVersion(elements) >
 | 
				
			||||||
      this.getLastBroadcastedOrReceivedSceneVersion()
 | 
					      this.getLastBroadcastedOrReceivedSceneVersion()
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
      this.portal.broadcastScene(WS_SCENE_EVENT_TYPES.UPDATE, elements, false);
 | 
					      this.portal.broadcastScene(WS_SUBTYPES.UPDATE, elements, false);
 | 
				
			||||||
      this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(elements);
 | 
					      this.lastBroadcastedOrReceivedSceneVersion = getSceneVersion(elements);
 | 
				
			||||||
      this.queueBroadcastAllElements();
 | 
					      this.queueBroadcastAllElements();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  syncElements = (elements: readonly ExcalidrawElement[]) => {
 | 
					  syncElements = (elements: readonly OrderedExcalidrawElement[]) => {
 | 
				
			||||||
    this.broadcastElements(elements);
 | 
					    this.broadcastElements(elements);
 | 
				
			||||||
    this.queueSaveToFirebase();
 | 
					    this.queueSaveToFirebase();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  queueBroadcastAllElements = throttle(() => {
 | 
					  queueBroadcastAllElements = throttle(() => {
 | 
				
			||||||
    this.portal.broadcastScene(
 | 
					    this.portal.broadcastScene(
 | 
				
			||||||
      WS_SCENE_EVENT_TYPES.UPDATE,
 | 
					      WS_SUBTYPES.UPDATE,
 | 
				
			||||||
      this.excalidrawAPI.getSceneElementsIncludingDeleted(),
 | 
					      this.excalidrawAPI.getSceneElementsIncludingDeleted(),
 | 
				
			||||||
      true,
 | 
					      true,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
@@ -808,41 +927,49 @@ class Collab extends PureComponent<Props, CollabState> {
 | 
				
			|||||||
    { leading: false },
 | 
					    { leading: false },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleClose = () => {
 | 
					 | 
				
			||||||
    appJotaiStore.set(collabDialogShownAtom, false);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setUsername = (username: string) => {
 | 
					  setUsername = (username: string) => {
 | 
				
			||||||
    this.setState({ username });
 | 
					    this.setState({ username });
 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onUsernameChange = (username: string) => {
 | 
					 | 
				
			||||||
    this.setUsername(username);
 | 
					 | 
				
			||||||
    saveUsernameToLocalStorage(username);
 | 
					    saveUsernameToLocalStorage(username);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render() {
 | 
					  getUsername = () => this.state.username;
 | 
				
			||||||
    const { username, errorMessage, activeRoomLink } = this.state;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { modalIsShown } = this.props;
 | 
					  setActiveRoomLink = (activeRoomLink: string | null) => {
 | 
				
			||||||
 | 
					    this.setState({ activeRoomLink });
 | 
				
			||||||
 | 
					    appJotaiStore.set(activeRoomLinkAtom, activeRoomLink);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getActiveRoomLink = () => this.state.activeRoomLink;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setErrorIndicator = (errorMessage: string | null) => {
 | 
				
			||||||
 | 
					    appJotaiStore.set(collabErrorIndicatorAtom, {
 | 
				
			||||||
 | 
					      message: errorMessage,
 | 
				
			||||||
 | 
					      nonce: Date.now(),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  resetErrorIndicator = (resetDialogNotifiedErrors = false) => {
 | 
				
			||||||
 | 
					    appJotaiStore.set(collabErrorIndicatorAtom, { message: null, nonce: 0 });
 | 
				
			||||||
 | 
					    if (resetDialogNotifiedErrors) {
 | 
				
			||||||
 | 
					      this.setState({
 | 
				
			||||||
 | 
					        dialogNotifiedErrors: {},
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setErrorDialog = (errorMessage: string | null) => {
 | 
				
			||||||
 | 
					    this.setState({
 | 
				
			||||||
 | 
					      errorMessage,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  render() {
 | 
				
			||||||
 | 
					    const { errorMessage } = this.state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <>
 | 
					      <>
 | 
				
			||||||
        {modalIsShown && (
 | 
					        {errorMessage != null && (
 | 
				
			||||||
          <RoomDialog
 | 
					          <ErrorDialog onClose={() => this.setErrorDialog(null)}>
 | 
				
			||||||
            handleClose={this.handleClose}
 | 
					 | 
				
			||||||
            activeRoomLink={activeRoomLink}
 | 
					 | 
				
			||||||
            username={username}
 | 
					 | 
				
			||||||
            onUsernameChange={this.onUsernameChange}
 | 
					 | 
				
			||||||
            onRoomCreate={() => this.startCollaboration(null)}
 | 
					 | 
				
			||||||
            onRoomDestroy={this.stopCollaboration}
 | 
					 | 
				
			||||||
            setErrorMessage={(errorMessage) => {
 | 
					 | 
				
			||||||
              this.setState({ errorMessage });
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
        {errorMessage && (
 | 
					 | 
				
			||||||
          <ErrorDialog onClose={() => this.setState({ errorMessage: "" })}>
 | 
					 | 
				
			||||||
            {errorMessage}
 | 
					            {errorMessage}
 | 
				
			||||||
          </ErrorDialog>
 | 
					          </ErrorDialog>
 | 
				
			||||||
        )}
 | 
					        )}
 | 
				
			||||||
@@ -861,11 +988,6 @@ if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
 | 
				
			|||||||
  window.collab = window.collab || ({} as Window["collab"]);
 | 
					  window.collab = window.collab || ({} as Window["collab"]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const _Collab: React.FC<PublicProps> = (props) => {
 | 
					export default Collab;
 | 
				
			||||||
  const [collabDialogShown] = useAtom(collabDialogShownAtom);
 | 
					 | 
				
			||||||
  return <Collab {...props} modalIsShown={collabDialogShown} />;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default _Collab;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type TCollabClass = Collab;
 | 
					export type TCollabClass = Collab;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								excalidraw-app/collab/CollabError.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					@import "../../packages/excalidraw/css/variables.module.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.excalidraw {
 | 
				
			||||||
 | 
					  .collab-errors-button {
 | 
				
			||||||
 | 
					    width: 26px;
 | 
				
			||||||
 | 
					    height: 26px;
 | 
				
			||||||
 | 
					    margin-inline-end: 1rem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    color: var(--color-danger);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    flex-shrink: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .collab-errors-button-shake {
 | 
				
			||||||
 | 
					    animation: strong-shake 0.15s 6;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @keyframes strong-shake {
 | 
				
			||||||
 | 
					    0% {
 | 
				
			||||||
 | 
					      transform: rotate(0deg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    25% {
 | 
				
			||||||
 | 
					      transform: rotate(10deg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    50% {
 | 
				
			||||||
 | 
					      transform: rotate(0deg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    75% {
 | 
				
			||||||
 | 
					      transform: rotate(-10deg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    100% {
 | 
				
			||||||
 | 
					      transform: rotate(0deg);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								excalidraw-app/collab/CollabError.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
 | 
				
			||||||
 | 
					import { warning } from "../../packages/excalidraw/components/icons";
 | 
				
			||||||
 | 
					import clsx from "clsx";
 | 
				
			||||||
 | 
					import { useEffect, useRef, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./CollabError.scss";
 | 
				
			||||||
 | 
					import { atom } from "jotai";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ErrorIndicator = {
 | 
				
			||||||
 | 
					  message: string | null;
 | 
				
			||||||
 | 
					  /** used to rerun the useEffect responsible for animation */
 | 
				
			||||||
 | 
					  nonce: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const collabErrorIndicatorAtom = atom<ErrorIndicator>({
 | 
				
			||||||
 | 
					  message: null,
 | 
				
			||||||
 | 
					  nonce: 0,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CollabError = ({ collabError }: { collabError: ErrorIndicator }) => {
 | 
				
			||||||
 | 
					  const [isAnimating, setIsAnimating] = useState(false);
 | 
				
			||||||
 | 
					  const clearAnimationRef = useRef<string | number | NodeJS.Timeout>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    setIsAnimating(true);
 | 
				
			||||||
 | 
					    clearAnimationRef.current = setTimeout(() => {
 | 
				
			||||||
 | 
					      setIsAnimating(false);
 | 
				
			||||||
 | 
					    }, 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      clearTimeout(clearAnimationRef.current);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }, [collabError.message, collabError.nonce]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!collabError.message) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Tooltip label={collabError.message} long={true}>
 | 
				
			||||||
 | 
					      <div
 | 
				
			||||||
 | 
					        className={clsx("collab-errors-button", {
 | 
				
			||||||
 | 
					          "collab-errors-button-shake": isAnimating,
 | 
				
			||||||
 | 
					        })}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {warning}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </Tooltip>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CollabError.displayName = "CollabError";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default CollabError;
 | 
				
			||||||
@@ -1,28 +1,29 @@
 | 
				
			|||||||
import {
 | 
					import type {
 | 
				
			||||||
  isSyncableElement,
 | 
					 | 
				
			||||||
  SocketUpdateData,
 | 
					  SocketUpdateData,
 | 
				
			||||||
  SocketUpdateDataSource,
 | 
					  SocketUpdateDataSource,
 | 
				
			||||||
 | 
					  SyncableExcalidrawElement,
 | 
				
			||||||
} from "../data";
 | 
					} from "../data";
 | 
				
			||||||
 | 
					import { isSyncableElement } from "../data";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { TCollabClass } from "./Collab";
 | 
					import type { TCollabClass } from "./Collab";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { ExcalidrawElement } from "../../src/element/types";
 | 
					import type { OrderedExcalidrawElement } from "../../packages/excalidraw/element/types";
 | 
				
			||||||
import {
 | 
					import { WS_EVENTS, FILE_UPLOAD_TIMEOUT, WS_SUBTYPES } from "../app_constants";
 | 
				
			||||||
  WS_EVENTS,
 | 
					import type {
 | 
				
			||||||
  FILE_UPLOAD_TIMEOUT,
 | 
					  OnUserFollowedPayload,
 | 
				
			||||||
  WS_SCENE_EVENT_TYPES,
 | 
					  SocketId,
 | 
				
			||||||
} from "../app_constants";
 | 
					  UserIdleState,
 | 
				
			||||||
import { UserIdleState } from "../../src/types";
 | 
					} from "../../packages/excalidraw/types";
 | 
				
			||||||
import { trackEvent } from "../../src/analytics";
 | 
					import { trackEvent } from "../../packages/excalidraw/analytics";
 | 
				
			||||||
import throttle from "lodash.throttle";
 | 
					import throttle from "lodash.throttle";
 | 
				
			||||||
import { newElementWith } from "../../src/element/mutateElement";
 | 
					import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
 | 
				
			||||||
import { BroadcastedExcalidrawElement } from "./reconciliation";
 | 
					import { encryptData } from "../../packages/excalidraw/data/encryption";
 | 
				
			||||||
import { encryptData } from "../../src/data/encryption";
 | 
					import type { Socket } from "socket.io-client";
 | 
				
			||||||
import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
 | 
					import { StoreAction } from "../../packages/excalidraw";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Portal {
 | 
					class Portal {
 | 
				
			||||||
  collab: TCollabClass;
 | 
					  collab: TCollabClass;
 | 
				
			||||||
  socket: SocketIOClient.Socket | null = null;
 | 
					  socket: Socket | null = null;
 | 
				
			||||||
  socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
 | 
					  socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
 | 
				
			||||||
  roomId: string | null = null;
 | 
					  roomId: string | null = null;
 | 
				
			||||||
  roomKey: string | null = null;
 | 
					  roomKey: string | null = null;
 | 
				
			||||||
@@ -32,7 +33,7 @@ class Portal {
 | 
				
			|||||||
    this.collab = collab;
 | 
					    this.collab = collab;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  open(socket: SocketIOClient.Socket, id: string, key: string) {
 | 
					  open(socket: Socket, id: string, key: string) {
 | 
				
			||||||
    this.socket = socket;
 | 
					    this.socket = socket;
 | 
				
			||||||
    this.roomId = id;
 | 
					    this.roomId = id;
 | 
				
			||||||
    this.roomKey = key;
 | 
					    this.roomKey = key;
 | 
				
			||||||
@@ -46,12 +47,12 @@ class Portal {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
    this.socket.on("new-user", async (_socketId: string) => {
 | 
					    this.socket.on("new-user", async (_socketId: string) => {
 | 
				
			||||||
      this.broadcastScene(
 | 
					      this.broadcastScene(
 | 
				
			||||||
        WS_SCENE_EVENT_TYPES.INIT,
 | 
					        WS_SUBTYPES.INIT,
 | 
				
			||||||
        this.collab.getSceneElementsIncludingDeleted(),
 | 
					        this.collab.getSceneElementsIncludingDeleted(),
 | 
				
			||||||
        /* syncAll */ true,
 | 
					        /* syncAll */ true,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    this.socket.on("room-user-change", (clients: string[]) => {
 | 
					    this.socket.on("room-user-change", (clients: SocketId[]) => {
 | 
				
			||||||
      this.collab.setCollaborators(clients);
 | 
					      this.collab.setCollaborators(clients);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -83,6 +84,7 @@ class Portal {
 | 
				
			|||||||
  async _broadcastSocketData(
 | 
					  async _broadcastSocketData(
 | 
				
			||||||
    data: SocketUpdateData,
 | 
					    data: SocketUpdateData,
 | 
				
			||||||
    volatile: boolean = false,
 | 
					    volatile: boolean = false,
 | 
				
			||||||
 | 
					    roomId?: string,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    if (this.isOpen()) {
 | 
					    if (this.isOpen()) {
 | 
				
			||||||
      const json = JSON.stringify(data);
 | 
					      const json = JSON.stringify(data);
 | 
				
			||||||
@@ -91,7 +93,7 @@ class Portal {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      this.socket?.emit(
 | 
					      this.socket?.emit(
 | 
				
			||||||
        volatile ? WS_EVENTS.SERVER_VOLATILE : WS_EVENTS.SERVER,
 | 
					        volatile ? WS_EVENTS.SERVER_VOLATILE : WS_EVENTS.SERVER,
 | 
				
			||||||
        this.roomId,
 | 
					        roomId ?? this.roomId,
 | 
				
			||||||
        encryptedBuffer,
 | 
					        encryptedBuffer,
 | 
				
			||||||
        iv,
 | 
					        iv,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
@@ -126,40 +128,33 @@ class Portal {
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
          return element;
 | 
					          return element;
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
 | 
					      storeAction: StoreAction.UPDATE,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }, FILE_UPLOAD_TIMEOUT);
 | 
					  }, FILE_UPLOAD_TIMEOUT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  broadcastScene = async (
 | 
					  broadcastScene = async (
 | 
				
			||||||
    updateType: WS_SCENE_EVENT_TYPES.INIT | WS_SCENE_EVENT_TYPES.UPDATE,
 | 
					    updateType: WS_SUBTYPES.INIT | WS_SUBTYPES.UPDATE,
 | 
				
			||||||
    allElements: readonly ExcalidrawElement[],
 | 
					    elements: readonly OrderedExcalidrawElement[],
 | 
				
			||||||
    syncAll: boolean,
 | 
					    syncAll: boolean,
 | 
				
			||||||
  ) => {
 | 
					  ) => {
 | 
				
			||||||
    if (updateType === WS_SCENE_EVENT_TYPES.INIT && !syncAll) {
 | 
					    if (updateType === WS_SUBTYPES.INIT && !syncAll) {
 | 
				
			||||||
      throw new Error("syncAll must be true when sending SCENE.INIT");
 | 
					      throw new Error("syncAll must be true when sending SCENE.INIT");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // sync out only the elements we think we need to to save bandwidth.
 | 
					    // sync out only the elements we think we need to to save bandwidth.
 | 
				
			||||||
    // periodically we'll resync the whole thing to make sure no one diverges
 | 
					    // periodically we'll resync the whole thing to make sure no one diverges
 | 
				
			||||||
    // due to a dropped message (server goes down etc).
 | 
					    // due to a dropped message (server goes down etc).
 | 
				
			||||||
    const syncableElements = allElements.reduce(
 | 
					    const syncableElements = elements.reduce((acc, element) => {
 | 
				
			||||||
      (acc, element: BroadcastedExcalidrawElement, idx, elements) => {
 | 
					 | 
				
			||||||
      if (
 | 
					      if (
 | 
				
			||||||
        (syncAll ||
 | 
					        (syncAll ||
 | 
				
			||||||
          !this.broadcastedElementVersions.has(element.id) ||
 | 
					          !this.broadcastedElementVersions.has(element.id) ||
 | 
				
			||||||
            element.version >
 | 
					          element.version > this.broadcastedElementVersions.get(element.id)!) &&
 | 
				
			||||||
              this.broadcastedElementVersions.get(element.id)!) &&
 | 
					 | 
				
			||||||
        isSyncableElement(element)
 | 
					        isSyncableElement(element)
 | 
				
			||||||
      ) {
 | 
					      ) {
 | 
				
			||||||
          acc.push({
 | 
					        acc.push(element);
 | 
				
			||||||
            ...element,
 | 
					 | 
				
			||||||
            // z-index info for the reconciler
 | 
					 | 
				
			||||||
            [PRECEDING_ELEMENT_KEY]: idx === 0 ? "^" : elements[idx - 1]?.id,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return acc;
 | 
					      return acc;
 | 
				
			||||||
      },
 | 
					    }, [] as SyncableExcalidrawElement[]);
 | 
				
			||||||
      [] as BroadcastedExcalidrawElement[],
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const data: SocketUpdateDataSource[typeof updateType] = {
 | 
					    const data: SocketUpdateDataSource[typeof updateType] = {
 | 
				
			||||||
      type: updateType,
 | 
					      type: updateType,
 | 
				
			||||||
@@ -183,9 +178,9 @@ class Portal {
 | 
				
			|||||||
  broadcastIdleChange = (userState: UserIdleState) => {
 | 
					  broadcastIdleChange = (userState: UserIdleState) => {
 | 
				
			||||||
    if (this.socket?.id) {
 | 
					    if (this.socket?.id) {
 | 
				
			||||||
      const data: SocketUpdateDataSource["IDLE_STATUS"] = {
 | 
					      const data: SocketUpdateDataSource["IDLE_STATUS"] = {
 | 
				
			||||||
        type: "IDLE_STATUS",
 | 
					        type: WS_SUBTYPES.IDLE_STATUS,
 | 
				
			||||||
        payload: {
 | 
					        payload: {
 | 
				
			||||||
          socketId: this.socket.id,
 | 
					          socketId: this.socket.id as SocketId,
 | 
				
			||||||
          userState,
 | 
					          userState,
 | 
				
			||||||
          username: this.collab.state.username,
 | 
					          username: this.collab.state.username,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -203,9 +198,9 @@ class Portal {
 | 
				
			|||||||
  }) => {
 | 
					  }) => {
 | 
				
			||||||
    if (this.socket?.id) {
 | 
					    if (this.socket?.id) {
 | 
				
			||||||
      const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
 | 
					      const data: SocketUpdateDataSource["MOUSE_LOCATION"] = {
 | 
				
			||||||
        type: "MOUSE_LOCATION",
 | 
					        type: WS_SUBTYPES.MOUSE_LOCATION,
 | 
				
			||||||
        payload: {
 | 
					        payload: {
 | 
				
			||||||
          socketId: this.socket.id,
 | 
					          socketId: this.socket.id as SocketId,
 | 
				
			||||||
          pointer: payload.pointer,
 | 
					          pointer: payload.pointer,
 | 
				
			||||||
          button: payload.button || "up",
 | 
					          button: payload.button || "up",
 | 
				
			||||||
          selectedElementIds:
 | 
					          selectedElementIds:
 | 
				
			||||||
@@ -213,12 +208,43 @@ class Portal {
 | 
				
			|||||||
          username: this.collab.state.username,
 | 
					          username: this.collab.state.username,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return this._broadcastSocketData(
 | 
					      return this._broadcastSocketData(
 | 
				
			||||||
        data as SocketUpdateData,
 | 
					        data as SocketUpdateData,
 | 
				
			||||||
        true, // volatile
 | 
					        true, // volatile
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  broadcastVisibleSceneBounds = (
 | 
				
			||||||
 | 
					    payload: {
 | 
				
			||||||
 | 
					      sceneBounds: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"]["payload"]["sceneBounds"];
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    roomId: string,
 | 
				
			||||||
 | 
					  ) => {
 | 
				
			||||||
 | 
					    if (this.socket?.id) {
 | 
				
			||||||
 | 
					      const data: SocketUpdateDataSource["USER_VISIBLE_SCENE_BOUNDS"] = {
 | 
				
			||||||
 | 
					        type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS,
 | 
				
			||||||
 | 
					        payload: {
 | 
				
			||||||
 | 
					          socketId: this.socket.id as SocketId,
 | 
				
			||||||
 | 
					          username: this.collab.state.username,
 | 
				
			||||||
 | 
					          sceneBounds: payload.sceneBounds,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return this._broadcastSocketData(
 | 
				
			||||||
 | 
					        data as SocketUpdateData,
 | 
				
			||||||
 | 
					        true, // volatile
 | 
				
			||||||
 | 
					        roomId,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  broadcastUserFollowed = (payload: OnUserFollowedPayload) => {
 | 
				
			||||||
 | 
					    if (this.socket?.id) {
 | 
				
			||||||
 | 
					      this.socket.emit(WS_EVENTS.USER_FOLLOW_CHANGE, payload);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Portal;
 | 
					export default Portal;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,13 @@
 | 
				
			|||||||
import { useRef, useState } from "react";
 | 
					import { useRef, useState } from "react";
 | 
				
			||||||
import * as Popover from "@radix-ui/react-popover";
 | 
					import * as Popover from "@radix-ui/react-popover";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { copyTextToSystemClipboard } from "../../src/clipboard";
 | 
					import { copyTextToSystemClipboard } from "../../packages/excalidraw/clipboard";
 | 
				
			||||||
import { trackEvent } from "../../src/analytics";
 | 
					import { trackEvent } from "../../packages/excalidraw/analytics";
 | 
				
			||||||
import { getFrame } from "../../src/utils";
 | 
					import { getFrame } from "../../packages/excalidraw/utils";
 | 
				
			||||||
import { useI18n } from "../../src/i18n";
 | 
					import { useI18n } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
import { KEYS } from "../../src/keys";
 | 
					import { KEYS } from "../../packages/excalidraw/keys";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Dialog } from "../../src/components/Dialog";
 | 
					import { Dialog } from "../../packages/excalidraw/components/Dialog";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  copyIcon,
 | 
					  copyIcon,
 | 
				
			||||||
  playerPlayIcon,
 | 
					  playerPlayIcon,
 | 
				
			||||||
@@ -16,11 +16,11 @@ import {
 | 
				
			|||||||
  shareIOS,
 | 
					  shareIOS,
 | 
				
			||||||
  shareWindows,
 | 
					  shareWindows,
 | 
				
			||||||
  tablerCheckIcon,
 | 
					  tablerCheckIcon,
 | 
				
			||||||
} from "../../src/components/icons";
 | 
					} from "../../packages/excalidraw/components/icons";
 | 
				
			||||||
import { TextField } from "../../src/components/TextField";
 | 
					import { TextField } from "../../packages/excalidraw/components/TextField";
 | 
				
			||||||
import { FilledButton } from "../../src/components/FilledButton";
 | 
					import { FilledButton } from "../../packages/excalidraw/components/FilledButton";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { ReactComponent as CollabImage } from "../../src/assets/lock.svg";
 | 
					import { ReactComponent as CollabImage } from "../../packages/excalidraw/assets/lock.svg";
 | 
				
			||||||
import "./RoomDialog.scss";
 | 
					import "./RoomDialog.scss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getShareIcon = () => {
 | 
					const getShareIcon = () => {
 | 
				
			||||||
@@ -65,7 +65,9 @@ export const RoomModal = ({
 | 
				
			|||||||
  const copyRoomLink = async () => {
 | 
					  const copyRoomLink = async () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      await copyTextToSystemClipboard(activeRoomLink);
 | 
					      await copyTextToSystemClipboard(activeRoomLink);
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      setErrorMessage(t("errors.copyToSystemClipboardFailed"));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    setJustCopied(true);
 | 
					    setJustCopied(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (timerRef.current) {
 | 
					    if (timerRef.current) {
 | 
				
			||||||
@@ -75,9 +77,6 @@ export const RoomModal = ({
 | 
				
			|||||||
    timerRef.current = window.setTimeout(() => {
 | 
					    timerRef.current = window.setTimeout(() => {
 | 
				
			||||||
      setJustCopied(false);
 | 
					      setJustCopied(false);
 | 
				
			||||||
    }, 3000);
 | 
					    }, 3000);
 | 
				
			||||||
    } catch (error: any) {
 | 
					 | 
				
			||||||
      setErrorMessage(error.message);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ref.current?.select();
 | 
					    ref.current?.select();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@@ -120,7 +119,7 @@ export const RoomModal = ({
 | 
				
			|||||||
              size="large"
 | 
					              size="large"
 | 
				
			||||||
              variant="icon"
 | 
					              variant="icon"
 | 
				
			||||||
              label="Share"
 | 
					              label="Share"
 | 
				
			||||||
              startIcon={getShareIcon()}
 | 
					              icon={getShareIcon()}
 | 
				
			||||||
              className="RoomDialog__active__share"
 | 
					              className="RoomDialog__active__share"
 | 
				
			||||||
              onClick={shareRoomLink}
 | 
					              onClick={shareRoomLink}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
@@ -130,7 +129,7 @@ export const RoomModal = ({
 | 
				
			|||||||
              <FilledButton
 | 
					              <FilledButton
 | 
				
			||||||
                size="large"
 | 
					                size="large"
 | 
				
			||||||
                label="Copy link"
 | 
					                label="Copy link"
 | 
				
			||||||
                startIcon={copyIcon}
 | 
					                icon={copyIcon}
 | 
				
			||||||
                onClick={copyRoomLink}
 | 
					                onClick={copyRoomLink}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            </Popover.Trigger>
 | 
					            </Popover.Trigger>
 | 
				
			||||||
@@ -166,7 +165,7 @@ export const RoomModal = ({
 | 
				
			|||||||
            variant="outlined"
 | 
					            variant="outlined"
 | 
				
			||||||
            color="danger"
 | 
					            color="danger"
 | 
				
			||||||
            label={t("roomDialog.button_stopSession")}
 | 
					            label={t("roomDialog.button_stopSession")}
 | 
				
			||||||
            startIcon={playerStopFilledIcon}
 | 
					            icon={playerStopFilledIcon}
 | 
				
			||||||
            onClick={() => {
 | 
					            onClick={() => {
 | 
				
			||||||
              trackEvent("share", "room closed");
 | 
					              trackEvent("share", "room closed");
 | 
				
			||||||
              onRoomDestroy();
 | 
					              onRoomDestroy();
 | 
				
			||||||
@@ -195,7 +194,7 @@ export const RoomModal = ({
 | 
				
			|||||||
        <FilledButton
 | 
					        <FilledButton
 | 
				
			||||||
          size="large"
 | 
					          size="large"
 | 
				
			||||||
          label={t("roomDialog.button_startSession")}
 | 
					          label={t("roomDialog.button_startSession")}
 | 
				
			||||||
          startIcon={playerPlayIcon}
 | 
					          icon={playerPlayIcon}
 | 
				
			||||||
          onClick={() => {
 | 
					          onClick={() => {
 | 
				
			||||||
            trackEvent("share", "room creation", `ui (${getFrame()})`);
 | 
					            trackEvent("share", "room creation", `ui (${getFrame()})`);
 | 
				
			||||||
            onRoomCreate();
 | 
					            onRoomCreate();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,154 +0,0 @@
 | 
				
			|||||||
import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
 | 
					 | 
				
			||||||
import { ExcalidrawElement } from "../../src/element/types";
 | 
					 | 
				
			||||||
import { AppState } from "../../src/types";
 | 
					 | 
				
			||||||
import { arrayToMapWithIndex } from "../../src/utils";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type ReconciledElements = readonly ExcalidrawElement[] & {
 | 
					 | 
				
			||||||
  _brand: "reconciledElements";
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type BroadcastedExcalidrawElement = ExcalidrawElement & {
 | 
					 | 
				
			||||||
  [PRECEDING_ELEMENT_KEY]?: string;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const shouldDiscardRemoteElement = (
 | 
					 | 
				
			||||||
  localAppState: AppState,
 | 
					 | 
				
			||||||
  local: ExcalidrawElement | undefined,
 | 
					 | 
				
			||||||
  remote: BroadcastedExcalidrawElement,
 | 
					 | 
				
			||||||
): boolean => {
 | 
					 | 
				
			||||||
  if (
 | 
					 | 
				
			||||||
    local &&
 | 
					 | 
				
			||||||
    // local element is being edited
 | 
					 | 
				
			||||||
    (local.id === localAppState.editingElement?.id ||
 | 
					 | 
				
			||||||
      local.id === localAppState.resizingElement?.id ||
 | 
					 | 
				
			||||||
      local.id === localAppState.draggingElement?.id ||
 | 
					 | 
				
			||||||
      // local element is newer
 | 
					 | 
				
			||||||
      local.version > remote.version ||
 | 
					 | 
				
			||||||
      // resolve conflicting edits deterministically by taking the one with
 | 
					 | 
				
			||||||
      // the lowest versionNonce
 | 
					 | 
				
			||||||
      (local.version === remote.version &&
 | 
					 | 
				
			||||||
        local.versionNonce < remote.versionNonce))
 | 
					 | 
				
			||||||
  ) {
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return false;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const reconcileElements = (
 | 
					 | 
				
			||||||
  localElements: readonly ExcalidrawElement[],
 | 
					 | 
				
			||||||
  remoteElements: readonly BroadcastedExcalidrawElement[],
 | 
					 | 
				
			||||||
  localAppState: AppState,
 | 
					 | 
				
			||||||
): ReconciledElements => {
 | 
					 | 
				
			||||||
  const localElementsData =
 | 
					 | 
				
			||||||
    arrayToMapWithIndex<ExcalidrawElement>(localElements);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const reconciledElements: ExcalidrawElement[] = localElements.slice();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const duplicates = new WeakMap<ExcalidrawElement, true>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let cursor = 0;
 | 
					 | 
				
			||||||
  let offset = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let remoteElementIdx = -1;
 | 
					 | 
				
			||||||
  for (const remoteElement of remoteElements) {
 | 
					 | 
				
			||||||
    remoteElementIdx++;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const local = localElementsData.get(remoteElement.id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (shouldDiscardRemoteElement(localAppState, local?.[0], remoteElement)) {
 | 
					 | 
				
			||||||
      if (remoteElement[PRECEDING_ELEMENT_KEY]) {
 | 
					 | 
				
			||||||
        delete remoteElement[PRECEDING_ELEMENT_KEY];
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      continue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Mark duplicate for removal as it'll be replaced with the remote element
 | 
					 | 
				
			||||||
    if (local) {
 | 
					 | 
				
			||||||
      // Unless the remote and local elements are the same element in which case
 | 
					 | 
				
			||||||
      // we need to keep it as we'd otherwise discard it from the resulting
 | 
					 | 
				
			||||||
      // array.
 | 
					 | 
				
			||||||
      if (local[0] === remoteElement) {
 | 
					 | 
				
			||||||
        continue;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      duplicates.set(local[0], true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // parent may not be defined in case the remote client is running an older
 | 
					 | 
				
			||||||
    // excalidraw version
 | 
					 | 
				
			||||||
    const parent =
 | 
					 | 
				
			||||||
      remoteElement[PRECEDING_ELEMENT_KEY] ||
 | 
					 | 
				
			||||||
      remoteElements[remoteElementIdx - 1]?.id ||
 | 
					 | 
				
			||||||
      null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (parent != null) {
 | 
					 | 
				
			||||||
      delete remoteElement[PRECEDING_ELEMENT_KEY];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // ^ indicates the element is the first in elements array
 | 
					 | 
				
			||||||
      if (parent === "^") {
 | 
					 | 
				
			||||||
        offset++;
 | 
					 | 
				
			||||||
        if (cursor === 0) {
 | 
					 | 
				
			||||||
          reconciledElements.unshift(remoteElement);
 | 
					 | 
				
			||||||
          localElementsData.set(remoteElement.id, [
 | 
					 | 
				
			||||||
            remoteElement,
 | 
					 | 
				
			||||||
            cursor - offset,
 | 
					 | 
				
			||||||
          ]);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          reconciledElements.splice(cursor + 1, 0, remoteElement);
 | 
					 | 
				
			||||||
          localElementsData.set(remoteElement.id, [
 | 
					 | 
				
			||||||
            remoteElement,
 | 
					 | 
				
			||||||
            cursor + 1 - offset,
 | 
					 | 
				
			||||||
          ]);
 | 
					 | 
				
			||||||
          cursor++;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        let idx = localElementsData.has(parent)
 | 
					 | 
				
			||||||
          ? localElementsData.get(parent)![1]
 | 
					 | 
				
			||||||
          : null;
 | 
					 | 
				
			||||||
        if (idx != null) {
 | 
					 | 
				
			||||||
          idx += offset;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (idx != null && idx >= cursor) {
 | 
					 | 
				
			||||||
          reconciledElements.splice(idx + 1, 0, remoteElement);
 | 
					 | 
				
			||||||
          offset++;
 | 
					 | 
				
			||||||
          localElementsData.set(remoteElement.id, [
 | 
					 | 
				
			||||||
            remoteElement,
 | 
					 | 
				
			||||||
            idx + 1 - offset,
 | 
					 | 
				
			||||||
          ]);
 | 
					 | 
				
			||||||
          cursor = idx + 1;
 | 
					 | 
				
			||||||
        } else if (idx != null) {
 | 
					 | 
				
			||||||
          reconciledElements.splice(cursor + 1, 0, remoteElement);
 | 
					 | 
				
			||||||
          offset++;
 | 
					 | 
				
			||||||
          localElementsData.set(remoteElement.id, [
 | 
					 | 
				
			||||||
            remoteElement,
 | 
					 | 
				
			||||||
            cursor + 1 - offset,
 | 
					 | 
				
			||||||
          ]);
 | 
					 | 
				
			||||||
          cursor++;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          reconciledElements.push(remoteElement);
 | 
					 | 
				
			||||||
          localElementsData.set(remoteElement.id, [
 | 
					 | 
				
			||||||
            remoteElement,
 | 
					 | 
				
			||||||
            reconciledElements.length - 1 - offset,
 | 
					 | 
				
			||||||
          ]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      // no parent z-index information, local element exists → replace in place
 | 
					 | 
				
			||||||
    } else if (local) {
 | 
					 | 
				
			||||||
      reconciledElements[local[1]] = remoteElement;
 | 
					 | 
				
			||||||
      localElementsData.set(remoteElement.id, [remoteElement, local[1]]);
 | 
					 | 
				
			||||||
      // otherwise push to the end
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      reconciledElements.push(remoteElement);
 | 
					 | 
				
			||||||
      localElementsData.set(remoteElement.id, [
 | 
					 | 
				
			||||||
        remoteElement,
 | 
					 | 
				
			||||||
        reconciledElements.length - 1 - offset,
 | 
					 | 
				
			||||||
      ]);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const ret: readonly ExcalidrawElement[] = reconciledElements.filter(
 | 
					 | 
				
			||||||
    (element) => !duplicates.has(element),
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return ret as ReconciledElements;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { Footer } from "../../src/packages/excalidraw/index";
 | 
					import { Footer } from "../../packages/excalidraw/index";
 | 
				
			||||||
import { EncryptedIcon } from "./EncryptedIcon";
 | 
					import { EncryptedIcon } from "./EncryptedIcon";
 | 
				
			||||||
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
 | 
					import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
 | 
				
			||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
 | 
					import { isExcalidrawPlusSignedUser } from "../app_constants";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,19 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { PlusPromoIcon } from "../../src/components/icons";
 | 
					import {
 | 
				
			||||||
import { MainMenu } from "../../src/packages/excalidraw/index";
 | 
					  loginIcon,
 | 
				
			||||||
 | 
					  ExcalLogo,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/components/icons";
 | 
				
			||||||
 | 
					import type { Theme } from "../../packages/excalidraw/element/types";
 | 
				
			||||||
 | 
					import { MainMenu } from "../../packages/excalidraw/index";
 | 
				
			||||||
 | 
					import { isExcalidrawPlusSignedUser } from "../app_constants";
 | 
				
			||||||
import { LanguageList } from "./LanguageList";
 | 
					import { LanguageList } from "./LanguageList";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const AppMainMenu: React.FC<{
 | 
					export const AppMainMenu: React.FC<{
 | 
				
			||||||
  setCollabDialogShown: (toggle: boolean) => any;
 | 
					  onCollabDialogOpen: () => any;
 | 
				
			||||||
  isCollaborating: boolean;
 | 
					  isCollaborating: boolean;
 | 
				
			||||||
  isCollabEnabled: boolean;
 | 
					  isCollabEnabled: boolean;
 | 
				
			||||||
 | 
					  theme: Theme | "system";
 | 
				
			||||||
 | 
					  setTheme: (theme: Theme | "system") => void;
 | 
				
			||||||
}> = React.memo((props) => {
 | 
					}> = React.memo((props) => {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <MainMenu>
 | 
					    <MainMenu>
 | 
				
			||||||
@@ -17,25 +24,38 @@ export const AppMainMenu: React.FC<{
 | 
				
			|||||||
      {props.isCollabEnabled && (
 | 
					      {props.isCollabEnabled && (
 | 
				
			||||||
        <MainMenu.DefaultItems.LiveCollaborationTrigger
 | 
					        <MainMenu.DefaultItems.LiveCollaborationTrigger
 | 
				
			||||||
          isCollaborating={props.isCollaborating}
 | 
					          isCollaborating={props.isCollaborating}
 | 
				
			||||||
          onSelect={() => props.setCollabDialogShown(true)}
 | 
					          onSelect={() => props.onCollabDialogOpen()}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 | 
					      <MainMenu.DefaultItems.CommandPalette className="highlighted" />
 | 
				
			||||||
      <MainMenu.DefaultItems.Help />
 | 
					      <MainMenu.DefaultItems.Help />
 | 
				
			||||||
      <MainMenu.DefaultItems.ClearCanvas />
 | 
					      <MainMenu.DefaultItems.ClearCanvas />
 | 
				
			||||||
      <MainMenu.Separator />
 | 
					      <MainMenu.Separator />
 | 
				
			||||||
      <MainMenu.ItemLink
 | 
					      <MainMenu.ItemLink
 | 
				
			||||||
        icon={PlusPromoIcon}
 | 
					        icon={ExcalLogo}
 | 
				
			||||||
        href={`${
 | 
					        href={`${
 | 
				
			||||||
          import.meta.env.VITE_APP_PLUS_LP
 | 
					          import.meta.env.VITE_APP_PLUS_LP
 | 
				
			||||||
        }/plus?utm_source=excalidraw&utm_medium=app&utm_content=hamburger`}
 | 
					        }/plus?utm_source=excalidraw&utm_medium=app&utm_content=hamburger`}
 | 
				
			||||||
        className="ExcalidrawPlus"
 | 
					        className=""
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        Excalidraw+
 | 
					        Excalidraw+
 | 
				
			||||||
      </MainMenu.ItemLink>
 | 
					      </MainMenu.ItemLink>
 | 
				
			||||||
      <MainMenu.DefaultItems.Socials />
 | 
					      <MainMenu.DefaultItems.Socials />
 | 
				
			||||||
 | 
					      <MainMenu.ItemLink
 | 
				
			||||||
 | 
					        icon={loginIcon}
 | 
				
			||||||
 | 
					        href={`${import.meta.env.VITE_APP_PLUS_APP}${
 | 
				
			||||||
 | 
					          isExcalidrawPlusSignedUser ? "" : "/sign-up"
 | 
				
			||||||
 | 
					        }?utm_source=signin&utm_medium=app&utm_content=hamburger`}
 | 
				
			||||||
 | 
					        className="highlighted"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        {isExcalidrawPlusSignedUser ? "Sign in" : "Sign up"}
 | 
				
			||||||
 | 
					      </MainMenu.ItemLink>
 | 
				
			||||||
      <MainMenu.Separator />
 | 
					      <MainMenu.Separator />
 | 
				
			||||||
      <MainMenu.DefaultItems.ToggleTheme />
 | 
					      <MainMenu.DefaultItems.ToggleTheme
 | 
				
			||||||
 | 
					        allowSystemTheme
 | 
				
			||||||
 | 
					        theme={props.theme}
 | 
				
			||||||
 | 
					        onSelect={props.setTheme}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
      <MainMenu.ItemCustom>
 | 
					      <MainMenu.ItemCustom>
 | 
				
			||||||
        <LanguageList style={{ width: "100%" }} />
 | 
					        <LanguageList style={{ width: "100%" }} />
 | 
				
			||||||
      </MainMenu.ItemCustom>
 | 
					      </MainMenu.ItemCustom>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,12 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { PlusPromoIcon } from "../../src/components/icons";
 | 
					import { loginIcon } from "../../packages/excalidraw/components/icons";
 | 
				
			||||||
import { useI18n } from "../../src/i18n";
 | 
					import { useI18n } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
import { WelcomeScreen } from "../../src/packages/excalidraw/index";
 | 
					import { WelcomeScreen } from "../../packages/excalidraw/index";
 | 
				
			||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
 | 
					import { isExcalidrawPlusSignedUser } from "../app_constants";
 | 
				
			||||||
import { POINTER_EVENTS } from "../../src/constants";
 | 
					import { POINTER_EVENTS } from "../../packages/excalidraw/constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const AppWelcomeScreen: React.FC<{
 | 
					export const AppWelcomeScreen: React.FC<{
 | 
				
			||||||
  setCollabDialogShown: (toggle: boolean) => any;
 | 
					  onCollabDialogOpen: () => any;
 | 
				
			||||||
  isCollabEnabled: boolean;
 | 
					  isCollabEnabled: boolean;
 | 
				
			||||||
}> = React.memo((props) => {
 | 
					}> = React.memo((props) => {
 | 
				
			||||||
  const { t } = useI18n();
 | 
					  const { t } = useI18n();
 | 
				
			||||||
@@ -52,7 +52,7 @@ export const AppWelcomeScreen: React.FC<{
 | 
				
			|||||||
          <WelcomeScreen.Center.MenuItemHelp />
 | 
					          <WelcomeScreen.Center.MenuItemHelp />
 | 
				
			||||||
          {props.isCollabEnabled && (
 | 
					          {props.isCollabEnabled && (
 | 
				
			||||||
            <WelcomeScreen.Center.MenuItemLiveCollaborationTrigger
 | 
					            <WelcomeScreen.Center.MenuItemLiveCollaborationTrigger
 | 
				
			||||||
              onSelect={() => props.setCollabDialogShown(true)}
 | 
					              onSelect={() => props.onCollabDialogOpen()}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
          {!isExcalidrawPlusSignedUser && (
 | 
					          {!isExcalidrawPlusSignedUser && (
 | 
				
			||||||
@@ -61,9 +61,9 @@ export const AppWelcomeScreen: React.FC<{
 | 
				
			|||||||
                import.meta.env.VITE_APP_PLUS_LP
 | 
					                import.meta.env.VITE_APP_PLUS_LP
 | 
				
			||||||
              }/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest`}
 | 
					              }/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest`}
 | 
				
			||||||
              shortcut={null}
 | 
					              shortcut={null}
 | 
				
			||||||
              icon={PlusPromoIcon}
 | 
					              icon={loginIcon}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              Try Excalidraw Plus!
 | 
					              Sign up
 | 
				
			||||||
            </WelcomeScreen.Center.MenuItemLink>
 | 
					            </WelcomeScreen.Center.MenuItemLink>
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
        </WelcomeScreen.Center.Menu>
 | 
					        </WelcomeScreen.Center.Menu>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { shield } from "../../src/components/icons";
 | 
					import { shield } from "../../packages/excalidraw/components/icons";
 | 
				
			||||||
import { Tooltip } from "../../src/components/Tooltip";
 | 
					import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
 | 
				
			||||||
import { useI18n } from "../../src/i18n";
 | 
					import { useI18n } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const EncryptedIcon = () => {
 | 
					export const EncryptedIcon = () => {
 | 
				
			||||||
  const { t } = useI18n();
 | 
					  const { t } = useI18n();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,36 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { Card } from "../../src/components/Card";
 | 
					import { Card } from "../../packages/excalidraw/components/Card";
 | 
				
			||||||
import { ToolButton } from "../../src/components/ToolButton";
 | 
					import { ToolButton } from "../../packages/excalidraw/components/ToolButton";
 | 
				
			||||||
import { serializeAsJSON } from "../../src/data/json";
 | 
					import { serializeAsJSON } from "../../packages/excalidraw/data/json";
 | 
				
			||||||
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
 | 
					import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
 | 
				
			||||||
import { FileId, NonDeletedExcalidrawElement } from "../../src/element/types";
 | 
					import type {
 | 
				
			||||||
import { AppState, BinaryFileData, BinaryFiles } from "../../src/types";
 | 
					  FileId,
 | 
				
			||||||
 | 
					  NonDeletedExcalidrawElement,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/element/types";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  AppState,
 | 
				
			||||||
 | 
					  BinaryFileData,
 | 
				
			||||||
 | 
					  BinaryFiles,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/types";
 | 
				
			||||||
import { nanoid } from "nanoid";
 | 
					import { nanoid } from "nanoid";
 | 
				
			||||||
import { useI18n } from "../../src/i18n";
 | 
					import { useI18n } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
import { encryptData, generateEncryptionKey } from "../../src/data/encryption";
 | 
					import {
 | 
				
			||||||
import { isInitializedImageElement } from "../../src/element/typeChecks";
 | 
					  encryptData,
 | 
				
			||||||
 | 
					  generateEncryptionKey,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/data/encryption";
 | 
				
			||||||
 | 
					import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
 | 
				
			||||||
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
 | 
					import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
 | 
				
			||||||
import { encodeFilesForUpload } from "../data/FileManager";
 | 
					import { encodeFilesForUpload } from "../data/FileManager";
 | 
				
			||||||
import { MIME_TYPES } from "../../src/constants";
 | 
					import { MIME_TYPES } from "../../packages/excalidraw/constants";
 | 
				
			||||||
import { trackEvent } from "../../src/analytics";
 | 
					import { trackEvent } from "../../packages/excalidraw/analytics";
 | 
				
			||||||
import { getFrame } from "../../src/utils";
 | 
					import { getFrame } from "../../packages/excalidraw/utils";
 | 
				
			||||||
import { ExcalidrawLogo } from "../../src/components/ExcalidrawLogo";
 | 
					import { ExcalidrawLogo } from "../../packages/excalidraw/components/ExcalidrawLogo";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const exportToExcalidrawPlus = async (
 | 
					export const exportToExcalidrawPlus = async (
 | 
				
			||||||
  elements: readonly NonDeletedExcalidrawElement[],
 | 
					  elements: readonly NonDeletedExcalidrawElement[],
 | 
				
			||||||
  appState: Partial<AppState>,
 | 
					  appState: Partial<AppState>,
 | 
				
			||||||
  files: BinaryFiles,
 | 
					  files: BinaryFiles,
 | 
				
			||||||
 | 
					  name: string,
 | 
				
			||||||
) => {
 | 
					) => {
 | 
				
			||||||
  const firebase = await loadFirebaseStorage();
 | 
					  const firebase = await loadFirebaseStorage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -43,7 +54,7 @@ export const exportToExcalidrawPlus = async (
 | 
				
			|||||||
    .ref(`/migrations/scenes/${id}`)
 | 
					    .ref(`/migrations/scenes/${id}`)
 | 
				
			||||||
    .put(blob, {
 | 
					    .put(blob, {
 | 
				
			||||||
      customMetadata: {
 | 
					      customMetadata: {
 | 
				
			||||||
        data: JSON.stringify({ version: 2, name: appState.name }),
 | 
					        data: JSON.stringify({ version: 2, name }),
 | 
				
			||||||
        created: Date.now().toString(),
 | 
					        created: Date.now().toString(),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -79,9 +90,10 @@ export const ExportToExcalidrawPlus: React.FC<{
 | 
				
			|||||||
  elements: readonly NonDeletedExcalidrawElement[];
 | 
					  elements: readonly NonDeletedExcalidrawElement[];
 | 
				
			||||||
  appState: Partial<AppState>;
 | 
					  appState: Partial<AppState>;
 | 
				
			||||||
  files: BinaryFiles;
 | 
					  files: BinaryFiles;
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
  onError: (error: Error) => void;
 | 
					  onError: (error: Error) => void;
 | 
				
			||||||
  onSuccess: () => void;
 | 
					  onSuccess: () => void;
 | 
				
			||||||
}> = ({ elements, appState, files, onError, onSuccess }) => {
 | 
					}> = ({ elements, appState, files, name, onError, onSuccess }) => {
 | 
				
			||||||
  const { t } = useI18n();
 | 
					  const { t } = useI18n();
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Card color="primary">
 | 
					    <Card color="primary">
 | 
				
			||||||
@@ -107,7 +119,7 @@ export const ExportToExcalidrawPlus: React.FC<{
 | 
				
			|||||||
        onClick={async () => {
 | 
					        onClick={async () => {
 | 
				
			||||||
          try {
 | 
					          try {
 | 
				
			||||||
            trackEvent("export", "eplus", `ui (${getFrame()})`);
 | 
					            trackEvent("export", "eplus", `ui (${getFrame()})`);
 | 
				
			||||||
            await exportToExcalidrawPlus(elements, appState, files);
 | 
					            await exportToExcalidrawPlus(elements, appState, files, name);
 | 
				
			||||||
            onSuccess();
 | 
					            onSuccess();
 | 
				
			||||||
          } catch (error: any) {
 | 
					          } catch (error: any) {
 | 
				
			||||||
            console.error(error);
 | 
					            console.error(error);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import oc from "open-color";
 | 
					import oc from "open-color";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { THEME } from "../../src/constants";
 | 
					import { THEME } from "../../packages/excalidraw/constants";
 | 
				
			||||||
import { Theme } from "../../src/element/types";
 | 
					import type { Theme } from "../../packages/excalidraw/element/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://github.com/tholman/github-corners
 | 
					// https://github.com/tholman/github-corners
 | 
				
			||||||
export const GitHubCorner = React.memo(
 | 
					export const GitHubCorner = React.memo(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
import { useSetAtom } from "jotai";
 | 
					import { useSetAtom } from "jotai";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { appLangCodeAtom } from "..";
 | 
					import { appLangCodeAtom } from "../App";
 | 
				
			||||||
import { useI18n } from "../../src/i18n";
 | 
					import { useI18n } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
import { languages } from "../../src/i18n";
 | 
					import { languages } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
 | 
					export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
 | 
				
			||||||
  const { t, langCode } = useI18n();
 | 
					  const { t, langCode } = useI18n();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import * as Sentry from "@sentry/browser";
 | 
					import * as Sentry from "@sentry/browser";
 | 
				
			||||||
import { t } from "../i18n";
 | 
					import { t } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
import Trans from "./Trans";
 | 
					import Trans from "../../packages/excalidraw/components/Trans";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface TopErrorBoundaryState {
 | 
					interface TopErrorBoundaryState {
 | 
				
			||||||
  hasError: boolean;
 | 
					  hasError: boolean;
 | 
				
			||||||
@@ -67,6 +67,8 @@ export class TopErrorBoundary extends React.Component<
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    window.open(
 | 
					    window.open(
 | 
				
			||||||
      `https://github.com/excalidraw/excalidraw/issues/new?body=${body}`,
 | 
					      `https://github.com/excalidraw/excalidraw/issues/new?body=${body}`,
 | 
				
			||||||
 | 
					      "_blank",
 | 
				
			||||||
 | 
					      "noopener noreferrer",
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,19 +1,20 @@
 | 
				
			|||||||
import { compressData } from "../../src/data/encode";
 | 
					import { StoreAction } from "../../packages/excalidraw";
 | 
				
			||||||
import { newElementWith } from "../../src/element/mutateElement";
 | 
					import { compressData } from "../../packages/excalidraw/data/encode";
 | 
				
			||||||
import { isInitializedImageElement } from "../../src/element/typeChecks";
 | 
					import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
 | 
				
			||||||
import {
 | 
					import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
  ExcalidrawElement,
 | 
					  ExcalidrawElement,
 | 
				
			||||||
  ExcalidrawImageElement,
 | 
					  ExcalidrawImageElement,
 | 
				
			||||||
  FileId,
 | 
					  FileId,
 | 
				
			||||||
  InitializedExcalidrawImageElement,
 | 
					  InitializedExcalidrawImageElement,
 | 
				
			||||||
} from "../../src/element/types";
 | 
					} from "../../packages/excalidraw/element/types";
 | 
				
			||||||
import { t } from "../../src/i18n";
 | 
					import { t } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
import {
 | 
					import type {
 | 
				
			||||||
  BinaryFileData,
 | 
					  BinaryFileData,
 | 
				
			||||||
  BinaryFileMetadata,
 | 
					  BinaryFileMetadata,
 | 
				
			||||||
  ExcalidrawImperativeAPI,
 | 
					  ExcalidrawImperativeAPI,
 | 
				
			||||||
  BinaryFiles,
 | 
					  BinaryFiles,
 | 
				
			||||||
} from "../../src/types";
 | 
					} from "../../packages/excalidraw/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class FileManager {
 | 
					export class FileManager {
 | 
				
			||||||
  /** files being fetched */
 | 
					  /** files being fetched */
 | 
				
			||||||
@@ -238,5 +239,6 @@ export const updateStaleImageStatuses = (params: {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return element;
 | 
					        return element;
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
 | 
					    storeAction: StoreAction.UPDATE,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,12 +10,30 @@
 | 
				
			|||||||
 *   (localStorage, indexedDB).
 | 
					 *   (localStorage, indexedDB).
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { createStore, entries, del, getMany, set, setMany } from "idb-keyval";
 | 
					import {
 | 
				
			||||||
import { clearAppStateForLocalStorage } from "../../src/appState";
 | 
					  createStore,
 | 
				
			||||||
import { clearElementsForLocalStorage } from "../../src/element";
 | 
					  entries,
 | 
				
			||||||
import { ExcalidrawElement, FileId } from "../../src/element/types";
 | 
					  del,
 | 
				
			||||||
import { AppState, BinaryFileData, BinaryFiles } from "../../src/types";
 | 
					  getMany,
 | 
				
			||||||
import { debounce } from "../../src/utils";
 | 
					  set,
 | 
				
			||||||
 | 
					  setMany,
 | 
				
			||||||
 | 
					  get,
 | 
				
			||||||
 | 
					} from "idb-keyval";
 | 
				
			||||||
 | 
					import { clearAppStateForLocalStorage } from "../../packages/excalidraw/appState";
 | 
				
			||||||
 | 
					import type { LibraryPersistedData } from "../../packages/excalidraw/data/library";
 | 
				
			||||||
 | 
					import type { ImportedDataState } from "../../packages/excalidraw/data/types";
 | 
				
			||||||
 | 
					import { clearElementsForLocalStorage } from "../../packages/excalidraw/element";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  ExcalidrawElement,
 | 
				
			||||||
 | 
					  FileId,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/element/types";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  AppState,
 | 
				
			||||||
 | 
					  BinaryFileData,
 | 
				
			||||||
 | 
					  BinaryFiles,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/types";
 | 
				
			||||||
 | 
					import type { MaybePromise } from "../../packages/excalidraw/utility-types";
 | 
				
			||||||
 | 
					import { debounce } from "../../packages/excalidraw/utils";
 | 
				
			||||||
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
 | 
					import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
 | 
				
			||||||
import { FileManager } from "./FileManager";
 | 
					import { FileManager } from "./FileManager";
 | 
				
			||||||
import { Locker } from "./Locker";
 | 
					import { Locker } from "./Locker";
 | 
				
			||||||
@@ -176,3 +194,52 @@ export class LocalData {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					export class LibraryIndexedDBAdapter {
 | 
				
			||||||
 | 
					  /** IndexedDB database and store name */
 | 
				
			||||||
 | 
					  private static idb_name = STORAGE_KEYS.IDB_LIBRARY;
 | 
				
			||||||
 | 
					  /** library data store key */
 | 
				
			||||||
 | 
					  private static key = "libraryData";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static store = createStore(
 | 
				
			||||||
 | 
					    `${LibraryIndexedDBAdapter.idb_name}-db`,
 | 
				
			||||||
 | 
					    `${LibraryIndexedDBAdapter.idb_name}-store`,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static async load() {
 | 
				
			||||||
 | 
					    const IDBData = await get<LibraryPersistedData>(
 | 
				
			||||||
 | 
					      LibraryIndexedDBAdapter.key,
 | 
				
			||||||
 | 
					      LibraryIndexedDBAdapter.store,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return IDBData || null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static save(data: LibraryPersistedData): MaybePromise<void> {
 | 
				
			||||||
 | 
					    return set(
 | 
				
			||||||
 | 
					      LibraryIndexedDBAdapter.key,
 | 
				
			||||||
 | 
					      data,
 | 
				
			||||||
 | 
					      LibraryIndexedDBAdapter.store,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** LS Adapter used only for migrating LS library data
 | 
				
			||||||
 | 
					 * to indexedDB */
 | 
				
			||||||
 | 
					export class LibraryLocalStorageMigrationAdapter {
 | 
				
			||||||
 | 
					  static load() {
 | 
				
			||||||
 | 
					    const LSData = localStorage.getItem(
 | 
				
			||||||
 | 
					      STORAGE_KEYS.__LEGACY_LOCAL_STORAGE_LIBRARY,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (LSData != null) {
 | 
				
			||||||
 | 
					      const libraryItems: ImportedDataState["libraryItems"] =
 | 
				
			||||||
 | 
					        JSON.parse(LSData);
 | 
				
			||||||
 | 
					      if (libraryItems) {
 | 
				
			||||||
 | 
					        return { libraryItems };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  static clear() {
 | 
				
			||||||
 | 
					    localStorage.removeItem(STORAGE_KEYS.__LEGACY_LOCAL_STORAGE_LIBRARY);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,30 @@
 | 
				
			|||||||
import { ExcalidrawElement, FileId } from "../../src/element/types";
 | 
					import { reconcileElements } from "../../packages/excalidraw";
 | 
				
			||||||
import { getSceneVersion } from "../../src/element";
 | 
					import type {
 | 
				
			||||||
import Portal from "../collab/Portal";
 | 
					  ExcalidrawElement,
 | 
				
			||||||
import { restoreElements } from "../../src/data/restore";
 | 
					  FileId,
 | 
				
			||||||
import {
 | 
					  OrderedExcalidrawElement,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/element/types";
 | 
				
			||||||
 | 
					import { getSceneVersion } from "../../packages/excalidraw/element";
 | 
				
			||||||
 | 
					import type Portal from "../collab/Portal";
 | 
				
			||||||
 | 
					import { restoreElements } from "../../packages/excalidraw/data/restore";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
  AppState,
 | 
					  AppState,
 | 
				
			||||||
  BinaryFileData,
 | 
					  BinaryFileData,
 | 
				
			||||||
  BinaryFileMetadata,
 | 
					  BinaryFileMetadata,
 | 
				
			||||||
  DataURL,
 | 
					  DataURL,
 | 
				
			||||||
} from "../../src/types";
 | 
					} from "../../packages/excalidraw/types";
 | 
				
			||||||
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
 | 
					import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
 | 
				
			||||||
import { decompressData } from "../../src/data/encode";
 | 
					import { decompressData } from "../../packages/excalidraw/data/encode";
 | 
				
			||||||
import { encryptData, decryptData } from "../../src/data/encryption";
 | 
					import {
 | 
				
			||||||
import { MIME_TYPES } from "../../src/constants";
 | 
					  encryptData,
 | 
				
			||||||
import { reconcileElements } from "../collab/reconciliation";
 | 
					  decryptData,
 | 
				
			||||||
import { getSyncableElements, SyncableExcalidrawElement } from ".";
 | 
					} from "../../packages/excalidraw/data/encryption";
 | 
				
			||||||
import { ResolutionType } from "../../src/utility-types";
 | 
					import { MIME_TYPES } from "../../packages/excalidraw/constants";
 | 
				
			||||||
 | 
					import type { SyncableExcalidrawElement } from ".";
 | 
				
			||||||
 | 
					import { getSyncableElements } from ".";
 | 
				
			||||||
 | 
					import type { ResolutionType } from "../../packages/excalidraw/utility-types";
 | 
				
			||||||
 | 
					import type { Socket } from "socket.io-client";
 | 
				
			||||||
 | 
					import type { RemoteExcalidrawElement } from "../../packages/excalidraw/data/reconcile";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// private
 | 
					// private
 | 
				
			||||||
// -----------------------------------------------------------------------------
 | 
					// -----------------------------------------------------------------------------
 | 
				
			||||||
@@ -132,12 +142,12 @@ const decryptElements = async (
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FirebaseSceneVersionCache {
 | 
					class FirebaseSceneVersionCache {
 | 
				
			||||||
  private static cache = new WeakMap<SocketIOClient.Socket, number>();
 | 
					  private static cache = new WeakMap<Socket, number>();
 | 
				
			||||||
  static get = (socket: SocketIOClient.Socket) => {
 | 
					  static get = (socket: Socket) => {
 | 
				
			||||||
    return FirebaseSceneVersionCache.cache.get(socket);
 | 
					    return FirebaseSceneVersionCache.cache.get(socket);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  static set = (
 | 
					  static set = (
 | 
				
			||||||
    socket: SocketIOClient.Socket,
 | 
					    socket: Socket,
 | 
				
			||||||
    elements: readonly SyncableExcalidrawElement[],
 | 
					    elements: readonly SyncableExcalidrawElement[],
 | 
				
			||||||
  ) => {
 | 
					  ) => {
 | 
				
			||||||
    FirebaseSceneVersionCache.cache.set(socket, getSceneVersion(elements));
 | 
					    FirebaseSceneVersionCache.cache.set(socket, getSceneVersion(elements));
 | 
				
			||||||
@@ -223,7 +233,7 @@ export const saveToFirebase = async (
 | 
				
			|||||||
    !socket ||
 | 
					    !socket ||
 | 
				
			||||||
    isSavedToFirebase(portal, elements)
 | 
					    isSavedToFirebase(portal, elements)
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    return false;
 | 
					    return null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const firebase = await loadFirestore();
 | 
					  const firebase = await loadFirestore();
 | 
				
			||||||
@@ -231,56 +241,59 @@ export const saveToFirebase = async (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const docRef = firestore.collection("scenes").doc(roomId);
 | 
					  const docRef = firestore.collection("scenes").doc(roomId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const savedData = await firestore.runTransaction(async (transaction) => {
 | 
					  const storedScene = await firestore.runTransaction(async (transaction) => {
 | 
				
			||||||
    const snapshot = await transaction.get(docRef);
 | 
					    const snapshot = await transaction.get(docRef);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!snapshot.exists) {
 | 
					    if (!snapshot.exists) {
 | 
				
			||||||
      const sceneDocument = await createFirebaseSceneDocument(
 | 
					      const storedScene = await createFirebaseSceneDocument(
 | 
				
			||||||
        firebase,
 | 
					        firebase,
 | 
				
			||||||
        elements,
 | 
					        elements,
 | 
				
			||||||
        roomKey,
 | 
					        roomKey,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      transaction.set(docRef, sceneDocument);
 | 
					      transaction.set(docRef, storedScene);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return storedScene;
 | 
				
			||||||
        elements,
 | 
					 | 
				
			||||||
        reconciledElements: null,
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const prevDocData = snapshot.data() as FirebaseStoredScene;
 | 
					    const prevStoredScene = snapshot.data() as FirebaseStoredScene;
 | 
				
			||||||
    const prevElements = getSyncableElements(
 | 
					    const prevStoredElements = getSyncableElements(
 | 
				
			||||||
      await decryptElements(prevDocData, roomKey),
 | 
					      restoreElements(await decryptElements(prevStoredScene, roomKey), null),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const reconciledElements = getSyncableElements(
 | 
					    const reconciledElements = getSyncableElements(
 | 
				
			||||||
      reconcileElements(elements, prevElements, appState),
 | 
					      reconcileElements(
 | 
				
			||||||
 | 
					        elements,
 | 
				
			||||||
 | 
					        prevStoredElements as OrderedExcalidrawElement[] as RemoteExcalidrawElement[],
 | 
				
			||||||
 | 
					        appState,
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sceneDocument = await createFirebaseSceneDocument(
 | 
					    const storedScene = await createFirebaseSceneDocument(
 | 
				
			||||||
      firebase,
 | 
					      firebase,
 | 
				
			||||||
      reconciledElements,
 | 
					      reconciledElements,
 | 
				
			||||||
      roomKey,
 | 
					      roomKey,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    transaction.update(docRef, sceneDocument);
 | 
					    transaction.update(docRef, storedScene);
 | 
				
			||||||
    return {
 | 
					
 | 
				
			||||||
      elements,
 | 
					    // Return the stored elements as the in memory `reconciledElements` could have mutated in the meantime
 | 
				
			||||||
      reconciledElements,
 | 
					    return storedScene;
 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  FirebaseSceneVersionCache.set(socket, savedData.elements);
 | 
					  const storedElements = getSyncableElements(
 | 
				
			||||||
 | 
					    restoreElements(await decryptElements(storedScene, roomKey), null),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return { reconciledElements: savedData.reconciledElements };
 | 
					  FirebaseSceneVersionCache.set(socket, storedElements);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return storedElements;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const loadFromFirebase = async (
 | 
					export const loadFromFirebase = async (
 | 
				
			||||||
  roomId: string,
 | 
					  roomId: string,
 | 
				
			||||||
  roomKey: string,
 | 
					  roomKey: string,
 | 
				
			||||||
  socket: SocketIOClient.Socket | null,
 | 
					  socket: Socket | null,
 | 
				
			||||||
): Promise<readonly ExcalidrawElement[] | null> => {
 | 
					): Promise<readonly SyncableExcalidrawElement[] | null> => {
 | 
				
			||||||
  const firebase = await loadFirestore();
 | 
					  const firebase = await loadFirestore();
 | 
				
			||||||
  const db = firebase.firestore();
 | 
					  const db = firebase.firestore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -291,14 +304,14 @@ export const loadFromFirebase = async (
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  const storedScene = doc.data() as FirebaseStoredScene;
 | 
					  const storedScene = doc.data() as FirebaseStoredScene;
 | 
				
			||||||
  const elements = getSyncableElements(
 | 
					  const elements = getSyncableElements(
 | 
				
			||||||
    await decryptElements(storedScene, roomKey),
 | 
					    restoreElements(await decryptElements(storedScene, roomKey), null),
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (socket) {
 | 
					  if (socket) {
 | 
				
			||||||
    FirebaseSceneVersionCache.set(socket, elements);
 | 
					    FirebaseSceneVersionCache.set(socket, elements);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return restoreElements(elements, null);
 | 
					  return elements;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const loadFilesFromFirebase = async (
 | 
					export const loadFilesFromFirebase = async (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +1,34 @@
 | 
				
			|||||||
import { compressData, decompressData } from "../../src/data/encode";
 | 
					import {
 | 
				
			||||||
 | 
					  compressData,
 | 
				
			||||||
 | 
					  decompressData,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/data/encode";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  decryptData,
 | 
					  decryptData,
 | 
				
			||||||
  generateEncryptionKey,
 | 
					  generateEncryptionKey,
 | 
				
			||||||
  IV_LENGTH_BYTES,
 | 
					  IV_LENGTH_BYTES,
 | 
				
			||||||
} from "../../src/data/encryption";
 | 
					} from "../../packages/excalidraw/data/encryption";
 | 
				
			||||||
import { serializeAsJSON } from "../../src/data/json";
 | 
					import { serializeAsJSON } from "../../packages/excalidraw/data/json";
 | 
				
			||||||
import { restore } from "../../src/data/restore";
 | 
					import { restore } from "../../packages/excalidraw/data/restore";
 | 
				
			||||||
import { ImportedDataState } from "../../src/data/types";
 | 
					import type { ImportedDataState } from "../../packages/excalidraw/data/types";
 | 
				
			||||||
import { isInvisiblySmallElement } from "../../src/element/sizeHelpers";
 | 
					import type { SceneBounds } from "../../packages/excalidraw/element/bounds";
 | 
				
			||||||
import { isInitializedImageElement } from "../../src/element/typeChecks";
 | 
					import { isInvisiblySmallElement } from "../../packages/excalidraw/element/sizeHelpers";
 | 
				
			||||||
import { ExcalidrawElement, FileId } from "../../src/element/types";
 | 
					import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
 | 
				
			||||||
import { t } from "../../src/i18n";
 | 
					import type {
 | 
				
			||||||
import {
 | 
					  ExcalidrawElement,
 | 
				
			||||||
 | 
					  FileId,
 | 
				
			||||||
 | 
					  OrderedExcalidrawElement,
 | 
				
			||||||
 | 
					} from "../../packages/excalidraw/element/types";
 | 
				
			||||||
 | 
					import { t } from "../../packages/excalidraw/i18n";
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
  AppState,
 | 
					  AppState,
 | 
				
			||||||
  BinaryFileData,
 | 
					  BinaryFileData,
 | 
				
			||||||
  BinaryFiles,
 | 
					  BinaryFiles,
 | 
				
			||||||
 | 
					  SocketId,
 | 
				
			||||||
  UserIdleState,
 | 
					  UserIdleState,
 | 
				
			||||||
} from "../../src/types";
 | 
					} from "../../packages/excalidraw/types";
 | 
				
			||||||
import { bytesToHexString } from "../../src/utils";
 | 
					import type { MakeBrand } from "../../packages/excalidraw/utility-types";
 | 
				
			||||||
 | 
					import { bytesToHexString } from "../../packages/excalidraw/utils";
 | 
				
			||||||
 | 
					import type { WS_SUBTYPES } from "../app_constants";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  DELETED_ELEMENT_TIMEOUT,
 | 
					  DELETED_ELEMENT_TIMEOUT,
 | 
				
			||||||
  FILE_UPLOAD_MAX_BYTES,
 | 
					  FILE_UPLOAD_MAX_BYTES,
 | 
				
			||||||
@@ -26,12 +37,11 @@ import {
 | 
				
			|||||||
import { encodeFilesForUpload } from "./FileManager";
 | 
					import { encodeFilesForUpload } from "./FileManager";
 | 
				
			||||||
import { saveFilesToFirebase } from "./firebase";
 | 
					import { saveFilesToFirebase } from "./firebase";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SyncableExcalidrawElement = ExcalidrawElement & {
 | 
					export type SyncableExcalidrawElement = OrderedExcalidrawElement &
 | 
				
			||||||
  _brand: "SyncableExcalidrawElement";
 | 
					  MakeBrand<"SyncableExcalidrawElement">;
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const isSyncableElement = (
 | 
					export const isSyncableElement = (
 | 
				
			||||||
  element: ExcalidrawElement,
 | 
					  element: OrderedExcalidrawElement,
 | 
				
			||||||
): element is SyncableExcalidrawElement => {
 | 
					): element is SyncableExcalidrawElement => {
 | 
				
			||||||
  if (element.isDeleted) {
 | 
					  if (element.isDeleted) {
 | 
				
			||||||
    if (element.updated > Date.now() - DELETED_ELEMENT_TIMEOUT) {
 | 
					    if (element.updated > Date.now() - DELETED_ELEMENT_TIMEOUT) {
 | 
				
			||||||
@@ -42,7 +52,9 @@ export const isSyncableElement = (
 | 
				
			|||||||
  return !isInvisiblySmallElement(element);
 | 
					  return !isInvisiblySmallElement(element);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getSyncableElements = (elements: readonly ExcalidrawElement[]) =>
 | 
					export const getSyncableElements = (
 | 
				
			||||||
 | 
					  elements: readonly OrderedExcalidrawElement[],
 | 
				
			||||||
 | 
					) =>
 | 
				
			||||||
  elements.filter((element) =>
 | 
					  elements.filter((element) =>
 | 
				
			||||||
    isSyncableElement(element),
 | 
					    isSyncableElement(element),
 | 
				
			||||||
  ) as SyncableExcalidrawElement[];
 | 
					  ) as SyncableExcalidrawElement[];
 | 
				
			||||||
@@ -56,67 +68,49 @@ const generateRoomId = async () => {
 | 
				
			|||||||
  return bytesToHexString(buffer);
 | 
					  return bytesToHexString(buffer);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Right now the reason why we resolve connection params (url, polling...)
 | 
					 | 
				
			||||||
 * from upstream is to allow changing the params immediately when needed without
 | 
					 | 
				
			||||||
 * having to wait for clients to update the SW.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * If REACT_APP_WS_SERVER_URL env is set, we use that instead (useful for forks)
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export const getCollabServer = async (): Promise<{
 | 
					 | 
				
			||||||
  url: string;
 | 
					 | 
				
			||||||
  polling: boolean;
 | 
					 | 
				
			||||||
}> => {
 | 
					 | 
				
			||||||
  if (import.meta.env.VITE_APP_WS_SERVER_URL) {
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      url: import.meta.env.VITE_APP_WS_SERVER_URL,
 | 
					 | 
				
			||||||
      polling: true,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const resp = await fetch(
 | 
					 | 
				
			||||||
      `${import.meta.env.VITE_APP_PORTAL_URL}/collab-server`,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    return await resp.json();
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					 | 
				
			||||||
    console.error(error);
 | 
					 | 
				
			||||||
    throw new Error(t("errors.cannotResolveCollabServer"));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type EncryptedData = {
 | 
					export type EncryptedData = {
 | 
				
			||||||
  data: ArrayBuffer;
 | 
					  data: ArrayBuffer;
 | 
				
			||||||
  iv: Uint8Array;
 | 
					  iv: Uint8Array;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SocketUpdateDataSource = {
 | 
					export type SocketUpdateDataSource = {
 | 
				
			||||||
 | 
					  INVALID_RESPONSE: {
 | 
				
			||||||
 | 
					    type: WS_SUBTYPES.INVALID_RESPONSE;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
  SCENE_INIT: {
 | 
					  SCENE_INIT: {
 | 
				
			||||||
    type: "SCENE_INIT";
 | 
					    type: WS_SUBTYPES.INIT;
 | 
				
			||||||
    payload: {
 | 
					    payload: {
 | 
				
			||||||
      elements: readonly ExcalidrawElement[];
 | 
					      elements: readonly ExcalidrawElement[];
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  SCENE_UPDATE: {
 | 
					  SCENE_UPDATE: {
 | 
				
			||||||
    type: "SCENE_UPDATE";
 | 
					    type: WS_SUBTYPES.UPDATE;
 | 
				
			||||||
    payload: {
 | 
					    payload: {
 | 
				
			||||||
      elements: readonly ExcalidrawElement[];
 | 
					      elements: readonly ExcalidrawElement[];
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  MOUSE_LOCATION: {
 | 
					  MOUSE_LOCATION: {
 | 
				
			||||||
    type: "MOUSE_LOCATION";
 | 
					    type: WS_SUBTYPES.MOUSE_LOCATION;
 | 
				
			||||||
    payload: {
 | 
					    payload: {
 | 
				
			||||||
      socketId: string;
 | 
					      socketId: SocketId;
 | 
				
			||||||
      pointer: { x: number; y: number; tool: "pointer" | "laser" };
 | 
					      pointer: { x: number; y: number; tool: "pointer" | "laser" };
 | 
				
			||||||
      button: "down" | "up";
 | 
					      button: "down" | "up";
 | 
				
			||||||
      selectedElementIds: AppState["selectedElementIds"];
 | 
					      selectedElementIds: AppState["selectedElementIds"];
 | 
				
			||||||
      username: string;
 | 
					      username: string;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  IDLE_STATUS: {
 | 
					  USER_VISIBLE_SCENE_BOUNDS: {
 | 
				
			||||||
    type: "IDLE_STATUS";
 | 
					    type: WS_SUBTYPES.USER_VISIBLE_SCENE_BOUNDS;
 | 
				
			||||||
    payload: {
 | 
					    payload: {
 | 
				
			||||||
      socketId: string;
 | 
					      socketId: SocketId;
 | 
				
			||||||
 | 
					      username: string;
 | 
				
			||||||
 | 
					      sceneBounds: SceneBounds;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  IDLE_STATUS: {
 | 
				
			||||||
 | 
					    type: WS_SUBTYPES.IDLE_STATUS;
 | 
				
			||||||
 | 
					    payload: {
 | 
				
			||||||
 | 
					      socketId: SocketId;
 | 
				
			||||||
      userState: UserIdleState;
 | 
					      userState: UserIdleState;
 | 
				
			||||||
      username: string;
 | 
					      username: string;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@@ -124,10 +118,7 @@ export type SocketUpdateDataSource = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SocketUpdateDataIncoming =
 | 
					export type SocketUpdateDataIncoming =
 | 
				
			||||||
  | SocketUpdateDataSource[keyof SocketUpdateDataSource]
 | 
					  SocketUpdateDataSource[keyof SocketUpdateDataSource];
 | 
				
			||||||
  | {
 | 
					 | 
				
			||||||
      type: "INVALID_RESPONSE";
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SocketUpdateData =
 | 
					export type SocketUpdateData =
 | 
				
			||||||
  SocketUpdateDataSource[keyof SocketUpdateDataSource] & {
 | 
					  SocketUpdateDataSource[keyof SocketUpdateDataSource] & {
 | 
				
			||||||
@@ -278,7 +269,6 @@ export const loadScene = async (
 | 
				
			|||||||
    // in the scene database/localStorage, and instead fetch them async
 | 
					    // in the scene database/localStorage, and instead fetch them async
 | 
				
			||||||
    // from a different database
 | 
					    // from a different database
 | 
				
			||||||
    files: data.files,
 | 
					    files: data.files,
 | 
				
			||||||
    commitToHistory: false,
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,11 @@
 | 
				
			|||||||
import { ExcalidrawElement } from "../../src/element/types";
 | 
					import type { ExcalidrawElement } from "../../packages/excalidraw/element/types";
 | 
				
			||||||
import { AppState } from "../../src/types";
 | 
					import type { AppState } from "../../packages/excalidraw/types";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  clearAppStateForLocalStorage,
 | 
					  clearAppStateForLocalStorage,
 | 
				
			||||||
  getDefaultAppState,
 | 
					  getDefaultAppState,
 | 
				
			||||||
} from "../../src/appState";
 | 
					} from "../../packages/excalidraw/appState";
 | 
				
			||||||
import { clearElementsForLocalStorage } from "../../src/element";
 | 
					import { clearElementsForLocalStorage } from "../../packages/excalidraw/element";
 | 
				
			||||||
import { STORAGE_KEYS } from "../app_constants";
 | 
					import { STORAGE_KEYS } from "../app_constants";
 | 
				
			||||||
import { ImportedDataState } from "../../src/data/types";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const saveUsernameToLocalStorage = (username: string) => {
 | 
					export const saveUsernameToLocalStorage = (username: string) => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
@@ -88,28 +87,13 @@ export const getTotalStorageSize = () => {
 | 
				
			|||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const appState = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_APP_STATE);
 | 
					    const appState = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_APP_STATE);
 | 
				
			||||||
    const collab = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_COLLAB);
 | 
					    const collab = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_COLLAB);
 | 
				
			||||||
    const library = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const appStateSize = appState?.length || 0;
 | 
					    const appStateSize = appState?.length || 0;
 | 
				
			||||||
    const collabSize = collab?.length || 0;
 | 
					    const collabSize = collab?.length || 0;
 | 
				
			||||||
    const librarySize = library?.length || 0;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return appStateSize + collabSize + librarySize + getElementsStorageSize();
 | 
					    return appStateSize + collabSize + getElementsStorageSize();
 | 
				
			||||||
  } catch (error: any) {
 | 
					  } catch (error: any) {
 | 
				
			||||||
    console.error(error);
 | 
					    console.error(error);
 | 
				
			||||||
    return 0;
 | 
					    return 0;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
export const getLibraryItemsFromStorage = () => {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    const libraryItems: ImportedDataState["libraryItems"] = JSON.parse(
 | 
					 | 
				
			||||||
      localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY) as string,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return libraryItems || [];
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					 | 
				
			||||||
    console.error(error);
 | 
					 | 
				
			||||||
    return [];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								excalidraw-app/global.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					interface Window {
 | 
				
			||||||
 | 
					  __EXCALIDRAW_SHA__: string | undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -20,7 +20,7 @@
 | 
				
			|||||||
      name="description"
 | 
					      name="description"
 | 
				
			||||||
      content="Excalidraw is a virtual collaborative whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."
 | 
					      content="Excalidraw is a virtual collaborative whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <meta name="image" content="https://excalidraw.com/og-image-2.png" />
 | 
					    <meta name="image" content="https://excalidraw.com/og-image-3.png" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Open Graph / Facebook -->
 | 
					    <!-- Open Graph / Facebook -->
 | 
				
			||||||
    <meta property="og:site_name" content="Excalidraw" />
 | 
					    <meta property="og:site_name" content="Excalidraw" />
 | 
				
			||||||
@@ -35,7 +35,7 @@
 | 
				
			|||||||
      property="og:description"
 | 
					      property="og:description"
 | 
				
			||||||
      content="Excalidraw is a virtual collaborative whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."
 | 
					      content="Excalidraw is a virtual collaborative whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <meta property="og:image" content="https://excalidraw.com/og-image-2.png" />
 | 
					    <meta property="og:image" content="https://excalidraw.com/og-image-3.png" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Twitter -->
 | 
					    <!-- Twitter -->
 | 
				
			||||||
    <meta property="twitter:card" content="summary_large_image" />
 | 
					    <meta property="twitter:card" content="summary_large_image" />
 | 
				
			||||||
@@ -51,7 +51,7 @@
 | 
				
			|||||||
    />
 | 
					    />
 | 
				
			||||||
    <meta
 | 
					    <meta
 | 
				
			||||||
      property="twitter:image"
 | 
					      property="twitter:image"
 | 
				
			||||||
      content="https://excalidraw.com/og-twitter-v2.png"
 | 
					      content="https://excalidraw.com/og-image-3.png"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- General tags -->
 | 
					    <!-- General tags -->
 | 
				
			||||||
@@ -64,12 +64,30 @@
 | 
				
			|||||||
    <!--   to minimize white flash on load when user has dark mode enabled   -->
 | 
					    <!--   to minimize white flash on load when user has dark mode enabled   -->
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        //
 | 
					        function setTheme(theme) {
 | 
				
			||||||
        const theme = window.localStorage.getItem("excalidraw-theme");
 | 
					 | 
				
			||||||
          if (theme === "dark") {
 | 
					          if (theme === "dark") {
 | 
				
			||||||
            document.documentElement.classList.add("dark");
 | 
					            document.documentElement.classList.add("dark");
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            document.documentElement.classList.remove("dark");
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function getTheme() {
 | 
				
			||||||
 | 
					          const theme = window.localStorage.getItem("excalidraw-theme");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (theme && theme === "system") {
 | 
				
			||||||
 | 
					            return window.matchMedia("(prefers-color-scheme: dark)").matches
 | 
				
			||||||
 | 
					              ? "dark"
 | 
				
			||||||
 | 
					              : "light";
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return theme || "light";
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setTheme(getTheme());
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        console.error("Error setting dark mode", e);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      } catch {}
 | 
					 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
    <style>
 | 
					    <style>
 | 
				
			||||||
      html.dark {
 | 
					      html.dark {
 | 
				
			||||||
@@ -78,7 +96,7 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
    <!------------------------------------------------------------------------->
 | 
					    <!------------------------------------------------------------------------->
 | 
				
			||||||
    <% if ("%PROD%" === "true") { %>
 | 
					    <% if (typeof PROD != 'undefined' && PROD == true) { %>
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
      // Redirect Excalidraw+ users which have auto-redirect enabled.
 | 
					      // Redirect Excalidraw+ users which have auto-redirect enabled.
 | 
				
			||||||
      //
 | 
					      //
 | 
				
			||||||
@@ -121,8 +139,9 @@
 | 
				
			|||||||
      crossorigin="anonymous"
 | 
					      crossorigin="anonymous"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <link rel="stylesheet" href="/fonts.css" type="text/css" />
 | 
					    <link rel="stylesheet" href="/fonts/fonts.css" type="text/css" />
 | 
				
			||||||
    <% if ("%VITE_APP_DEV_DISABLE_LIVE_RELOAD%"==="true" ) { %>
 | 
					    <% if (typeof VITE_APP_DEV_DISABLE_LIVE_RELOAD != 'undefined' &&
 | 
				
			||||||
 | 
					    VITE_APP_DEV_DISABLE_LIVE_RELOAD == true) { %>
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        const _WebSocket = window.WebSocket;
 | 
					        const _WebSocket = window.WebSocket;
 | 
				
			||||||
@@ -195,8 +214,7 @@
 | 
				
			|||||||
      <h1 class="visually-hidden">Excalidraw</h1>
 | 
					      <h1 class="visually-hidden">Excalidraw</h1>
 | 
				
			||||||
    </header>
 | 
					    </header>
 | 
				
			||||||
    <div id="root"></div>
 | 
					    <div id="root"></div>
 | 
				
			||||||
    <script type="module" src="/src/index.tsx"></script>
 | 
					    <script type="module" src="index.tsx"></script>
 | 
				
			||||||
    <% if ("%VITE_APP_DEV_DISABLE_LIVE_RELOAD%" !== 'true') { %>
 | 
					 | 
				
			||||||
    <!-- 100% privacy friendly analytics -->
 | 
					    <!-- 100% privacy friendly analytics -->
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
      // need to load this script dynamically bcs. of iframe embed tracking
 | 
					      // need to load this script dynamically bcs. of iframe embed tracking
 | 
				
			||||||
@@ -229,6 +247,5 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
    <!-- end LEGACY GOOGLE ANALYTICS -->
 | 
					    <!-- end LEGACY GOOGLE ANALYTICS -->
 | 
				
			||||||
    <% } %>
 | 
					 | 
				
			||||||
  </body>
 | 
					  </body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
@@ -4,6 +4,13 @@
 | 
				
			|||||||
  &.theme--dark {
 | 
					  &.theme--dark {
 | 
				
			||||||
    --color-primary-contrast-offset: #726dff; // to offset Chubb illusion
 | 
					    --color-primary-contrast-offset: #726dff; // to offset Chubb illusion
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .top-right-ui {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    align-items: flex-start;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .footer-center {
 | 
					  .footer-center {
 | 
				
			||||||
    justify-content: flex-end;
 | 
					    justify-content: flex-end;
 | 
				
			||||||
    margin-top: auto;
 | 
					    margin-top: auto;
 | 
				
			||||||
@@ -18,6 +25,7 @@
 | 
				
			|||||||
    margin-bottom: auto;
 | 
					    margin-bottom: auto;
 | 
				
			||||||
    margin-inline-start: auto;
 | 
					    margin-inline-start: auto;
 | 
				
			||||||
    margin-inline-end: 0.6em;
 | 
					    margin-inline-end: 0.6em;
 | 
				
			||||||
 | 
					    z-index: var(--zIndex-layerUI);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    svg {
 | 
					    svg {
 | 
				
			||||||
      width: 1.2rem;
 | 
					      width: 1.2rem;
 | 
				
			||||||
@@ -31,8 +39,12 @@
 | 
				
			|||||||
        background-color: #ecfdf5;
 | 
					        background-color: #ecfdf5;
 | 
				
			||||||
        color: #064e3c;
 | 
					        color: #064e3c;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      &.ExcalidrawPlus {
 | 
					      &.highlighted {
 | 
				
			||||||
        color: var(--color-promo);
 | 
					        color: var(--color-promo);
 | 
				
			||||||
 | 
					        font-weight: 700;
 | 
				
			||||||
 | 
					        .dropdown-menu-item__icon g {
 | 
				
			||||||
 | 
					          stroke-width: 2;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||