Initial Vite migration setup

This commit is contained in:
BlackWolfNews 2025-04-13 17:46:38 -06:00
parent 7c58477382
commit 35171070a1
88 changed files with 3341 additions and 3664 deletions

13
.github/workflows/deploy.yml vendored Normal file
View file

@ -0,0 +1,13 @@
name: Deploy
on: push
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: yarn && yarn build
- uses: amondnet/vercel-action@v30
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}

1
.gitignore vendored
View file

@ -26,3 +26,4 @@ coverage
dev-dist
html
meta*.json
.vercel

15
index.html Normal file
View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>ExcalidrawFork</title>
<link rel="stylesheet" href="/excalidraw.css" />
</head>
<body>
<div id="app"></div>
<script>
window.EXCALIDRAW_ASSET_PATH = '/assets/';
</script>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

0
memory-bank Normal file
View file

View file

@ -1,88 +1,64 @@
{
"name": "excalidraw-fork",
"type": "module",
"private": true,
"name": "excalidraw-monorepo",
"version": "1.0.0",
"packageManager": "yarn@1.22.22",
"workspaces": [
"excalidraw-app",
"packages/*",
"examples/*"
],
"nohoist": [],
"scripts": {
"dev": "vite",
"build": "yarn build:excalidraw && vite build",
"preview": "vite preview",
"lint": "eslint . --ext .ts,.tsx",
"typecheck": "tsc --noEmit",
"build:excalidraw": "cd packages/excalidraw && yarn build",
"build:package": "vite build --config vite.package.config.ts",
"prebuild": "yarn build:package"
},
"dependencies": {
"@excalidraw/excalidraw": "^0.18.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/preset-env": "7.26.9",
"@excalidraw/eslint-config": "1.0.3",
"@excalidraw/prettier-config": "1.0.2",
"@types/chai": "4.3.0",
"@types/jest": "27.4.0",
"@types/lodash.throttle": "4.1.7",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"@types/socket.io-client": "3.0.0",
"@vitejs/plugin-react": "3.1.0",
"@vitest/coverage-v8": "3.0.7",
"@vitest/ui": "2.0.5",
"chai": "4.3.6",
"dotenv": "16.0.1",
"eslint-config-prettier": "8.5.0",
"eslint-config-react-app": "7.0.1",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "3.3.1",
"http-server": "14.1.1",
"husky": "7.0.4",
"jsdom": "22.1.0",
"lint-staged": "12.3.7",
"pepjs": "0.5.3",
"prettier": "2.6.2",
"rewire": "6.0.0",
"typescript": "4.9.4",
"vite": "5.0.12",
"vite-plugin-checker": "0.7.2",
"vite-plugin-ejs": "1.7.0",
"vite-plugin-pwa": "0.21.1",
"vite-plugin-svgr": "4.2.0",
"vitest": "3.0.6",
"vitest-canvas-mock": "0.3.3"
"@babel/preset-env": "^7.26.9",
"@excalidraw/eslint-config": "^1.0.3",
"@excalidraw/prettier-config": "^1.0.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@types/chai": "^4.3.0",
"@types/jest": "^27.4.0",
"@types/lodash.throttle": "^4.1.7",
"@types/node": "22.14.0",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.6.0",
"@typescript-eslint/parser": "^7.6.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.56.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"sass": "^1.71.1",
"typescript": "^5.3.3",
"vite": "^5.1.4",
"vite-plugin-checker": "^0.9.1",
"vitest": "3.1.1"
},
"engines": {
"node": "18.0.0 - 22.x.x"
"node": ">=18.0.0"
},
"homepage": ".",
"prettier": "@excalidraw/prettier-config",
"scripts": {
"build-node": "node ./scripts/build-node.js",
"build:app:docker": "yarn --cwd ./excalidraw-app build:app:docker",
"build:app": "yarn --cwd ./excalidraw-app build:app",
"build:package": "yarn --cwd ./packages/excalidraw build:esm",
"build:version": "yarn --cwd ./excalidraw-app build:version",
"build": "yarn --cwd ./excalidraw-app build",
"build:preview": "yarn --cwd ./excalidraw-app build:preview",
"start": "yarn --cwd ./excalidraw-app start",
"start:production": "yarn --cwd ./excalidraw-app start:production",
"start:example": "yarn build:package && yarn --cwd ./examples/with-script-in-browser start",
"test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watch=false",
"test:app": "vitest",
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
"test:other": "yarn prettier --list-different",
"test:typecheck": "tsc",
"test:update": "yarn test:app --update --watch=false",
"test": "yarn test:app",
"test:coverage": "vitest --coverage",
"test:coverage:watch": "vitest --coverage --watch",
"test:ui": "yarn test --ui --coverage.enabled=true",
"fix:code": "yarn test:code --fix",
"fix:other": "yarn prettier --write",
"fix": "yarn fix:other && yarn fix:code",
"locales-coverage": "node scripts/build-locales-coverage.js",
"locales-coverage:description": "node scripts/locales-coverage-description.js",
"prepare": "husky install",
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
"autorelease": "node scripts/autorelease.js",
"prerelease:excalidraw": "node scripts/prerelease.js",
"release:excalidraw": "node scripts/release.js",
"rm:build": "rm -rf excalidraw-app/{build,dist,dev-dist} && rm -rf packages/*/{dist,build} && rm -rf examples/*/{build,dist}",
"rm:node_modules": "rm -rf node_modules && rm -rf excalidraw-app/node_modules && rm -rf packages/*/node_modules",
"clean-install": "yarn rm:node_modules && yarn install"
},
"resolutions": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"strip-ansi": "6.0.1"
}
}

1
packages/common/index.js Normal file
View file

@ -0,0 +1 @@
export {}

View file

@ -2,18 +2,18 @@
"name": "@excalidraw/common",
"version": "0.1.0",
"type": "module",
"main": "./dist/common.umd.js",
"module": "./dist/common.es.js",
"types": "./dist/types/common/src/index.d.ts",
"main": "./dist/prod/index.js",
"module": "./dist/prod/index.js",
"dependencies": {
"@excalidraw/math": "0.1.0"
},
"exports": {
".": {
"types": "./dist/types/common/src/index.d.ts",
"development": "./dist/dev/index.js",
"production": "./dist/prod/index.js",
"default": "./dist/prod/index.js"
},
"./*": {
"types": "./../common/dist/types/common/src/*.d.ts"
"import": "./dist/common.es.js",
"require": "./dist/common.umd.js",
"default": "./dist/common.es.js"
}
},
"files": [
@ -50,7 +50,12 @@
"bugs": "https://github.com/excalidraw/excalidraw/issues",
"repository": "https://github.com/excalidraw/excalidraw",
"scripts": {
"gen:types": "rm -rf types && tsc",
"build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types"
"gen:types": "if exist types rmdir /s /q types && tsc",
"build:esm": "if exist dist rmdir /s /q dist && node ../../scripts/buildBase.js && yarn gen:types",
"build": "vite build",
"test": "jest",
"lint": "eslint . --ext .ts,.tsx",
"prettier": "prettier --check .",
"prettier:fix": "prettier --write ."
}
}

View file

@ -4,6 +4,8 @@ import {
type LocalPoint,
} from "@excalidraw/math";
console.log(pointFromPair);
import type { NullableGridSize } from "@excalidraw/excalidraw/types";
export const getSizeFromPoints = (

View file

@ -0,0 +1,34 @@
import { defineConfig } from 'vite';
import { fileURLToPath } from 'node:url';
import path from 'path';
console.log('Alias set to:', path.resolve(__dirname, '../math/dist/math.es.js'));
export default defineConfig({
build: {
lib: {
entry: fileURLToPath(new URL('./src/index.ts', import.meta.url)),
name: 'ExcalidrawCommon',
fileName: (format) => `common.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
},
format: 'es'
},
onwarn: (warning, warn) => {
if (warning.code === 'CIRCULAR_DEPENDENCY') return;
warn(warning);
}
}
},
resolve: {
alias: {
'@excalidraw/math': path.resolve(__dirname, '../math/dist/math.es.js')
}
}
});

View file

@ -2,23 +2,33 @@
"name": "@excalidraw/element",
"version": "0.1.0",
"type": "module",
"types": "./dist/types/element/src/index.d.ts",
"main": "./dist/prod/index.js",
"module": "./dist/prod/index.js",
"types": "./dist/types/element/src/index.d.ts",
"exports": {
".": {
"types": "./dist/types/element/src/index.d.ts",
"development": "./dist/dev/index.js",
"production": "./dist/prod/index.js",
"import": "./dist/prod/index.js",
"require": "./dist/prod/index.js",
"default": "./dist/prod/index.js"
},
"./*": {
"types": "./../element/dist/types/element/src/*.d.ts"
"./textElement": {
"types": "./dist/types/element/src/textElement.d.ts",
"import": "./dist/prod/index.js",
"require": "./dist/prod/index.js",
"default": "./dist/prod/index.js"
},
"./sizeHelpers": {
"types": "./dist/types/element/src/sizeHelpers.d.ts",
"import": "./dist/prod/index.js",
"require": "./dist/prod/index.js",
"default": "./dist/prod/index.js"
}
},
"files": [
"dist/*"
],
"description": "Excalidraw elements-related logic",
"publishConfig": {
"access": "public"
@ -50,7 +60,7 @@
"bugs": "https://github.com/excalidraw/excalidraw/issues",
"repository": "https://github.com/excalidraw/excalidraw",
"scripts": {
"gen:types": "rm -rf types && tsc",
"build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types"
"gen:types": "if exist types rmdir /s /q types && tsc",
"build:esm": "if exist dist rmdir /s /q dist && node ../../scripts/buildBase.cjs && yarn gen:types"
}
}

View file

@ -1,5 +1,6 @@
import { isInvisiblySmallElement } from "./sizeHelpers";
import { isLinearElementType } from "./typeChecks";
import * as textElement from "./textElement";
import type {
ExcalidrawElement,
@ -71,3 +72,6 @@ export const clearElementsForExport = (
export const clearElementsForLocalStorage = (
elements: readonly ExcalidrawElement[],
) => _clearElements(elements);
export { textElement };
export * from "./sizeHelpers";

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.Avatar {

View file

@ -1,7 +1,7 @@
@import "../css/theme";
@use "../css/styles" as styles;
.excalidraw {
.excalidraw-button {
@include outlineButtonStyles;
@include styles.outlineButtonStyles;
}
}

View file

@ -1,4 +1,4 @@
@import "../css/theme";
@use "../css/styles" as *;
.excalidraw {
button.standalone {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.Card {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.Checkbox {

View file

@ -1,4 +1,4 @@
@import "../../css/variables.module.scss";
@use "../../css/styles" as *;
.excalidraw {
.focus-visible-none {

View file

@ -1,4 +1,4 @@
@import "../../css/variables.module.scss";
@use "../../css/styles" as *;
$verticalBreakpoint: 861px;

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.confirm-dialog {

View file

@ -1,10 +1,10 @@
@import "../css/variables.module.scss";
@use "../css/styles" as styles;
.excalidraw {
.context-menu {
position: relative;
border-radius: 4px;
box-shadow: 0 3px 10px transparentize($oc-black, 0.8);
box-shadow: 0 3px 10px transparentize(styles.$oc-black, 0.8);
padding: 0;
list-style: none;
user-select: none;
@ -45,7 +45,7 @@
&.dangerous {
.context-menu-item__label {
color: $oc-red-7;
color: styles.$oc-red-7;
}
}
@ -69,7 +69,7 @@
.context-menu-item__label {
color: var(--popup-bg-color);
}
background-color: $oc-red-6;
background-color: styles.$oc-red-6;
}
}
@ -93,6 +93,6 @@
.context-menu-item-separator {
border: none;
border-top: 1px solid $oc-gray-5;
border-top: 1px solid styles.$oc-gray-5;
}
}

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as styles;
.excalidraw {
.Dialog {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.ElementLinkDialog {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as styles;
.excalidraw {
.ExportDialog__preview {

View file

@ -1,18 +1,4 @@
@import "../css/variables.module.scss";
@keyframes successStatusAnimation {
0% {
transform: scale(0.35);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
@use "../css/styles" as styles;
.excalidraw {
.ExcButton {
@ -315,3 +301,17 @@
}
}
}
@keyframes successStatusAnimation {
0% {
transform: scale(0.35);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.FixedSideContainer {

View file

@ -1,4 +1,4 @@
@import "../../css/variables.module.scss";
@use "../../css/styles" as *;
.excalidraw {
.FontPicker__container {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.HelpDialog {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
// this is loosely based on the longest hint text
$wide-viewport-width: 1000px;

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.picker {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
--ImageExportModal-preview-border: #d6d6d6;

View file

@ -1,5 +1,5 @@
@import "open-color/open-color";
@import "../css/variables.module.scss";
@use "open-color/open-color.scss" as oc;
@use "../css/styles" as styles;
.excalidraw {
.layer-ui__wrapper.animate {

View file

@ -1,4 +1,5 @@
@import "open-color/open-color";
@use "open-color/open-color.scss" as oc;
@use "../css/styles" as styles;
.excalidraw {
.layer-ui__library {

View file

@ -1,4 +1,4 @@
@import "open-color/open-color";
@use "../css/styles" as *;
.excalidraw {
--container-padding-y: 1.5rem;
@ -6,13 +6,13 @@
.library-menu-items__no-items {
text-align: center;
color: var(--color-gray-70);
color: $oc-gray-7;
line-height: 1.5;
font-size: 0.875rem;
width: 100%;
&__label {
color: var(--color-primary);
color: $oc-blue-7;
font-weight: 700;
font-size: 1.125rem;
margin-bottom: 0.75rem;
@ -21,7 +21,7 @@
&.theme--dark {
.library-menu-items__no-items {
color: var(--color-gray-40);
color: $oc-gray-5;
}
}
@ -60,7 +60,7 @@
}
&__header {
color: var(--color-primary);
color: $oc-blue-7;
font-size: 1.125rem;
font-weight: 700;
margin-bottom: 0.75rem;
@ -86,7 +86,7 @@
font-weight: 500;
font-size: 0.9rem;
margin: 0.6em 0.2em;
color: var(--text-primary-color);
color: $text-primary-color;
}
}

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.library-unit {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
&.excalidraw-modal-container {

View file

@ -1,4 +1,4 @@
@import "../../css/variables.module.scss";
@use "../../css/styles" as *;
.excalidraw {
.OverwriteConfirm {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.PasteChartDialog {

View file

@ -1,3 +1,5 @@
@use "../css/styles" as *;
.excalidraw {
.popover {
position: absolute;

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.publish-library {
@ -14,7 +14,7 @@
span {
font-weight: 500;
font-size: 1rem;
color: $oc-gray-6;
color: oc.$oc-gray-6;
}
input,
textarea {
@ -24,7 +24,7 @@
}
.required {
color: $oc-red-8;
color: oc.$oc-red-8;
margin: 0.2rem;
}
}
@ -48,22 +48,22 @@
}
&--confirm.ToolIcon_type_button {
background-color: $oc-blue-6;
background-color: oc.$oc-blue-6;
&:hover {
background-color: $oc-blue-8;
background-color: oc.$oc-blue-8;
}
}
&--cancel.ToolIcon_type_button {
background-color: $oc-gray-5;
background-color: oc.$oc-gray-5;
&:hover {
background-color: $oc-gray-6;
background-color: oc.$oc-gray-6;
}
}
.ToolIcon__icon {
color: $oc-white;
color: oc.$oc-white;
.Spinner {
--spinner-color: #fff;
svg {
@ -83,7 +83,7 @@
}
&-warning {
color: $oc-red-6;
color: oc.$oc-red-6;
}
&-note {
@ -102,14 +102,14 @@
top: 0.3rem;
left: 0.3rem;
font-size: 0.7rem;
color: $oc-red-7;
color: oc.$oc-red-7;
background: rgba(255, 255, 255, 0.9);
padding: 0.1rem 0.2rem;
border-radius: 0.2rem;
}
&__svg {
background-color: $oc-white;
background-color: oc.$oc-white;
padding: 0.3rem;
width: 7.5rem;
height: 7.5rem;
@ -121,7 +121,7 @@
}
.ToolIcon__icon {
background-color: $oc-white;
background-color: oc.$oc-white;
width: auto;
height: auto;
margin: 0 0.5rem;
@ -132,7 +132,7 @@
}
.required,
.error {
color: $oc-red-8;
color: oc.$oc-red-8;
font-weight: 700;
font-size: 1rem;
margin: 0.2rem;
@ -152,16 +152,16 @@
margin: 0;
}
.ToolIcon__icon {
background-color: $oc-red-6;
background-color: oc.$oc-red-6;
&:hover {
background-color: $oc-red-7;
background-color: oc.$oc-red-7;
}
&:active {
background-color: $oc-red-8;
background-color: oc.$oc-red-8;
}
}
svg {
color: $oc-white;
color: oc.$oc-white;
padding: 0.26rem;
border-radius: 0.3em;
width: 1rem;

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
--RadioGroup-background: var(--island-bg-color);

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
--slider-thumb-size: 16px;

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.SVGLayer {

View file

@ -1,4 +1,4 @@
@import "open-color/open-color";
@use "../css/styles" as *;
.excalidraw {
.layer-ui__search {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.ShareableLinkDialog {

View file

@ -1,5 +1,5 @@
@import "open-color/open-color";
@import "../../css/variables.module.scss";
@use "open-color/open-color.scss" as oc;
@use "../../css/styles" as *;
.excalidraw {
.sidebar {

View file

@ -1,4 +1,4 @@
@import "../../css/variables.module.scss";
@use "../../css/styles" as *;
.excalidraw {
.sidebar-trigger {

View file

@ -1,6 +1,4 @@
@import "open-color/open-color.scss";
$duration: 1.6s;
@use "../css/styles" as *;
.excalidraw {
.Spinner {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
--Switch-disabled-color: var(--color-border-outline);

View file

@ -1,4 +1,4 @@
@import "../../css/variables.module.scss";
@use "../../css/styles" as *;
$verticalBreakpoint: 861px;
@ -241,7 +241,7 @@ $verticalBreakpoint: 861px;
height: 2.5rem;
font-size: 12px;
color: $oc-white;
color: oc.$oc-white;
background-color: var(--color-primary);
width: 100%;

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
--ExcTextField--color: var(--color-on-surface);

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.TextInput {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
.Toast {

View file

@ -1,5 +1,5 @@
@import "open-color/open-color.scss";
@import "../css/variables.module.scss";
@use "open-color/open-color.scss" as oc;
@use "../css/styles" as *;
.excalidraw {
.ToolIcon {

View file

@ -1,5 +1,5 @@
@import "open-color/open-color.scss";
@import "../css/variables.module.scss";
@use "open-color/open-color.scss" as oc;
@use "../css/styles" as *;
.excalidraw {
.App-toolbar {

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
// container in body where the actual tooltip is appended to
.excalidraw-tooltip {
@ -14,13 +14,13 @@
pointer-events: none;
word-wrap: break-word;
background: $oc-black;
background: oc.$oc-black;
line-height: 1.5;
text-align: center;
font-size: 13px;
font-weight: 500;
color: $oc-white;
color: oc.$oc-white;
display: none;

View file

@ -1,4 +1,4 @@
@import "../css/variables.module.scss";
@use "../css/styles" as *;
.excalidraw {
--avatar-size: 1.75rem;

View file

@ -1,4 +1,4 @@
@import "../../css/variables.module.scss";
@use "../../css/styles" as *;
.excalidraw {
.dropdown-menu {

View file

@ -1,4 +1,4 @@
@import "../../css/variables.module.scss";
@use "../../css/styles" as *;
.excalidraw-hyperlinkContainer {
display: flex;

View file

@ -1,4 +1,4 @@
@import "../../css/variables.module.scss";
@use "../../css/styles" as styles;
.excalidraw {
.collab-button {

View file

@ -1,4 +1,5 @@
@import "open-color/open-color.scss";
@use "open-color/open-color.scss" as oc;
@use "./styles" as *;
.visually-hidden {
position: absolute !important;

View file

@ -1,5 +1,10 @@
@import "./variables.module.scss";
@import "./theme";
@use "open-color/open-color.scss" as oc;
@forward "open-color/open-color.scss" as oc-* with (
$oc-gray-2: oc.$oc-gray-2,
$oc-red-1: oc.$oc-red-1,
$oc-red-9: oc.$oc-red-9
);
@forward "./_variables.scss" as *;
:root {
--zIndex-canvas: 1;
@ -201,7 +206,7 @@ body.excalidraw-cursor-resize * {
.divider {
width: 1px;
background-color: $oc-gray-2;
background-color: oc.$oc-gray-2;
margin: 1px;
}
@ -227,7 +232,7 @@ body.excalidraw-cursor-resize * {
label,
button,
.zIndexButton {
@include outlineButtonIconStyles;
@include variables.outlineButtonIconStyles;
}
}
@ -469,8 +474,8 @@ body.excalidraw-cursor-resize * {
}
.help-icon {
@include outlineButtonStyles;
@include filledButtonOnCanvas;
@include variables.outlineButtonStyles;
@include variables.filledButtonOnCanvas;
width: var(--lg-button-size);
height: var(--lg-button-size);
@ -503,7 +508,7 @@ body.excalidraw-cursor-resize * {
margin-inline-start: 0.6em;
}
@include isMobile {
@include variables.isMobile {
aside {
display: none;
}
@ -652,7 +657,7 @@ body.excalidraw-cursor-resize * {
}
.main-menu-trigger {
@include filledButtonOnCanvas;
@include variables.filledButtonOnCanvas;
}
.App-mobile-menu,
@ -705,8 +710,8 @@ body.excalidraw-cursor-resize * {
justify-content: center;
padding: 40px;
background-color: $oc-red-1;
border: 3px solid $oc-red-9;
background-color: oc.$oc-red-1;
border: 3px solid oc.$oc-red-9;
}
.ErrorSplash-paragraph {

View file

@ -1,33 +1,34 @@
@import "open-color/open-color.scss";
@import "./variables.module.scss";
@use "sass:color";
@use "open-color/open-color.scss";
@use "variables.module.scss";
.excalidraw {
--theme-filter: none;
--button-destructive-bg-color: #{$oc-red-1};
--button-destructive-color: #{$oc-red-9};
--button-gray-1: #{$oc-gray-2};
--button-gray-2: #{$oc-gray-4};
--button-gray-3: #{$oc-gray-5};
--button-special-active-bg-color: #{$oc-green-0};
--button-destructive-bg-color: #{open-color.$oc-red-1};
--button-destructive-color: #{open-color.$oc-red-9};
--button-gray-1: #{open-color.$oc-gray-2};
--button-gray-2: #{open-color.$oc-gray-4};
--button-gray-3: #{open-color.$oc-gray-5};
--button-special-active-bg-color: #{open-color.$oc-green-0};
--dialog-border-color: var(--color-gray-20);
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
--focus-highlight-color: #{$oc-blue-2};
--focus-highlight-color: #{open-color.$oc-blue-2};
--icon-fill-color: var(--color-on-surface);
--icon-green-fill-color: #{$oc-green-9};
--default-bg-color: #{$oc-white};
--input-bg-color: #{$oc-white};
--input-border-color: #{$oc-gray-4};
--input-hover-bg-color: #{$oc-gray-1};
--input-label-color: #{$oc-gray-7};
--icon-green-fill-color: #{open-color.$oc-green-9};
--default-bg-color: #{open-color.$oc-white};
--input-bg-color: #{open-color.$oc-white};
--input-border-color: #{open-color.$oc-gray-4};
--input-hover-bg-color: #{open-color.$oc-gray-1};
--input-label-color: #{open-color.$oc-gray-7};
--island-bg-color: #ffffff;
--keybinding-color: var(--color-gray-40);
--link-color: #{$oc-blue-7};
--overlay-bg-color: #{transparentize($oc-white, 0.12)};
--link-color: #{open-color.$oc-blue-7};
--overlay-bg-color: #{color.adjust(open-color.$oc-white, $alpha: -0.12)};
--popup-bg-color: var(--island-bg-color);
--popup-secondary-bg-color: #{$oc-gray-1};
--popup-text-color: #{$oc-black};
--popup-text-inverted-color: #{$oc-white};
--select-highlight-color: #{$oc-blue-5};
--popup-secondary-bg-color: #{open-color.$oc-gray-1};
--popup-text-color: #{open-color.$oc-black};
--popup-text-inverted-color: #{open-color.$oc-white};
--select-highlight-color: #{open-color.$oc-blue-5};
--shadow-island: 0px 0px 0.9310142993927002px 0px rgba(0, 0, 0, 0.17),
0px 0px 3.1270833015441895px 0px rgba(0, 0, 0, 0.08),
0px 7px 14px 0px rgba(0, 0, 0, 0.05);
@ -80,7 +81,7 @@
--color-selection: #6965db;
--color-icon-white: #{$oc-white};
--color-icon-white: #{open-color.$oc-white};
--color-primary: #6965db;
--color-primary-darker: #5b57d1;
@ -172,7 +173,7 @@
&.theme--dark {
--theme-filter: invert(93%) hue-rotate(180deg);
--button-destructive-bg-color: #5a0000;
--button-destructive-color: #{$oc-red-3};
--button-destructive-color: #{open-color.$oc-red-3};
--button-gray-1: #363636;
--button-gray-2: #272727;
@ -180,21 +181,21 @@
--button-special-active-bg-color: #204624;
--dialog-border-color: var(--color-gray-80);
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path fill="%23ced4da" d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
--focus-highlight-color: #{$oc-blue-6};
--icon-green-fill-color: #{$oc-green-4};
--focus-highlight-color: #{open-color.$oc-blue-6};
--icon-green-fill-color: #{open-color.$oc-green-4};
--default-bg-color: #121212;
--input-bg-color: #121212;
--input-border-color: #2e2e2e;
--input-hover-bg-color: #181818;
--input-label-color: #{$oc-gray-2};
--input-label-color: #{open-color.$oc-gray-2};
--island-bg-color: #232329;
--keybinding-color: var(--color-gray-60);
--link-color: #{$oc-blue-4};
--overlay-bg-color: #{transparentize($oc-gray-8, 0.88)};
--link-color: #{open-color.$oc-blue-4};
--overlay-bg-color: #{color.adjust(open-color.$oc-gray-8, $alpha: -0.88)};
--popup-secondary-bg-color: #222;
--popup-text-color: #{$oc-gray-4};
--popup-text-color: #{open-color.$oc-gray-4};
--popup-text-inverted-color: #2c2c2c;
--select-highlight-color: #{$oc-blue-4};
--select-highlight-color: #{open-color.$oc-blue-4};
--shadow-island: 0px 0px 0.9310142993927002px 0px rgba(0, 0, 0, 0.17),
0px 0px 3.1270833015441895px 0px rgba(0, 0, 0, 0.08),
0px 7px 14px 0px rgba(0, 0, 0, 0.05);
@ -207,8 +208,8 @@
0px 2.76726px 2.21381px rgba(0, 0, 0, 0.0196802);
--avatar-border-color: var(--color-gray-85);
--scrollbar-thumb: #{$oc-gray-8};
--scrollbar-thumb-hover: #{$oc-gray-7};
--scrollbar-thumb: #{open-color.$oc-gray-8};
--scrollbar-thumb-hover: #{open-color.$oc-gray-7};
--color-slider-track: hsl(244, 23%, 39%);

View file

@ -1,190 +1,79 @@
@import "open-color/open-color.scss";
@mixin isMobile() {
@at-root .excalidraw--mobile#{&} {
@content;
}
:root {
--color-primary: #6965db;
--color-primary-darker: #5653c0;
--color-primary-darkest: #4c44b4;
--color-primary-hover: #7a75d3;
--color-primary-light: #8c7cd5;
--color-primary-lighter: #a29be8;
--color-primary-lightest: #c3c0f9;
--color-on-primary: #ffffff;
--color-on-primary-container: #000000;
--color-surface-lowest: #f7f7f7;
--color-surface-low: #f2f2f2;
--color-surface: #ffffff;
--color-surface-high: #f9f9f9;
--color-surface-highest: #f7f7f7;
--color-surface-primary-container: #4c44b4;
--color-disabled: #c3c0f9;
--color-gray-90: #333333;
--color-on-surface: #333333;
--color-on-surface-high: #333333;
--color-on-surface-highest: #333333;
--color-on-surface-lowest: #333333;
--color-on-surface-low: #333333;
--color-on-surface-inverse: #ffffff;
--color-on-surface-inverse-high: #ffffff;
--color-on-surface-inverse-highest: #ffffff;
--color-on-surface-inverse-lowest: #ffffff;
--color-on-surface-inverse-low: #ffffff;
--color-on-surface-inverse-disabled: #c3c0f9;
--color-on-surface-disabled: #c3c0f9;
--color-on-surface-high-disabled: #c3c0f9;
--color-on-surface-highest-disabled: #c3c0f9;
--color-on-surface-lowest-disabled: #c3c0f9;
--color-on-surface-low-disabled: #c3c0f9;
--color-on-surface-inverse-disabled: #c3c0f9;
--color-on-surface-inverse-high-disabled: #c3c0f9;
--color-on-surface-inverse-highest-disabled: #c3c0f9;
--color-on-surface-inverse-lowest-disabled: #c3c0f9;
--color-on-surface-inverse-low-disabled: #c3c0f9;
--color-on-surface-disabled: #c3c0f9;
--color-on-surface-high-disabled: #c3c0f9;
--color-on-surface-highest-disabled: #c3c0f9;
--color-on-surface-lowest-disabled: #c3c0f9;
--color-on-surface-low-disabled: #c3c0f9;
--color-on-surface-inverse-disabled: #c3c0f9;
--color-on-surface-inverse-high-disabled: #c3c0f9;
--color-on-surface-inverse-highest-disabled: #c3c0f9;
--color-on-surface-inverse-lowest-disabled: #c3c0f9;
--color-on-surface-inverse-low-disabled: #c3c0f9;
--button-width: 2.5rem;
--button-height: 2.5rem;
--default-button-size: 2.5rem;
--default-icon-size: 1.5rem;
--lg-icon-size: 2rem;
--avatar-size: 1.5rem;
--border-radius-lg: 0.5rem;
--island-bg-color: #ffffff;
--text-primary-color: #333333;
--button-bg: #ffffff;
--button-color: #333333;
--button-border: #cccccc;
--button-hover-bg: #f7f7f7;
--button-hover-color: #333333;
--button-hover-border: #cccccc;
--button-active-bg: #f2f2f2;
--button-active-color: #333333;
--button-active-border: #cccccc;
--button-selected-bg: #4c44b4;
--button-selected-color: #ffffff;
--button-selected-border: #4c44b4;
--button-selected-hover-bg: #5653c0;
--button-selected-hover-color: #ffffff;
--button-selected-hover-border: #5653c0;
--ui-font: Arial, sans-serif;
}
@mixin toolbarButtonColorStates {
&.fillable {
.ToolIcon_type_radio,
.ToolIcon_type_checkbox {
&:checked + .ToolIcon__icon {
--icon-fill-color: var(--color-on-primary-container);
svg {
fill: var(--icon-fill-color);
}
}
}
}
.ToolIcon_type_radio,
.ToolIcon_type_checkbox {
&:checked + .ToolIcon__icon {
background: var(--color-surface-primary-container);
--keybinding-color: var(--color-on-primary-container);
svg {
color: var(--color-on-primary-container);
}
}
}
.ToolIcon__keybinding {
bottom: 4px;
right: 4px;
}
.ToolIcon__icon {
&:hover {
background: var(--button-hover-bg);
}
&:active {
background: var(--button-hover-bg);
border: 1px solid var(--button-active-border);
svg {
color: var(--color-on-primary-container);
}
}
&[aria-disabled="true"] {
background: initial;
border: none;
svg {
color: var(--color-disabled);
}
}
}
}
@mixin outlineButtonStyles {
display: flex;
justify-content: center;
align-items: center;
padding: 0.625rem;
width: var(--button-width, var(--default-button-size));
height: var(--button-height, var(--default-button-size));
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: var(--button-border, var(--default-border-color));
border-radius: var(--border-radius-lg);
cursor: pointer;
background-color: var(--button-bg, var(--island-bg-color));
color: var(--button-color, var(--color-on-surface));
font-family: var(--ui-font);
svg {
width: var(--button-width, var(--lg-icon-size));
height: var(--button-height, var(--lg-icon-size));
}
&:hover {
background-color: var(--button-hover-bg, var(--island-bg-color));
border-color: var(
--button-hover-border,
var(--button-border, var(--default-border-color))
);
color: var(
--button-hover-color,
var(--button-color, var(--text-primary-color, inherit))
);
}
&:active {
background-color: var(--button-active-bg, var(--island-bg-color));
border-color: var(--button-active-border, var(--color-primary-darkest));
}
&.active {
background-color: var(
--button-selected-bg,
var(--color-surface-primary-container)
);
border-color: var(
--button-selected-border,
var(--color-surface-primary-container)
);
&:hover {
background-color: var(
--button-selected-hover-bg,
var(--color-surface-primary-container)
);
}
svg {
color: var(--button-color, var(--color-on-primary-container));
}
}
}
@mixin outlineButtonIconStyles {
@include outlineButtonStyles;
padding: 0;
svg {
width: var(--default-icon-size);
height: var(--default-icon-size);
}
}
@mixin avatarStyles {
width: var(--avatar-size, 1.5rem);
height: var(--avatar-size, 1.5rem);
position: relative;
border-radius: 100%;
outline-offset: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-size: 0.75rem;
font-weight: 700;
line-height: 1;
color: var(--color-gray-90);
flex: 0 0 auto;
&:active {
transform: scale(0.94);
}
&-img {
width: 100%;
height: 100%;
border-radius: 100%;
}
&::before {
content: "";
position: absolute;
top: -3px;
right: -3px;
bottom: -3px;
left: -3px;
border-radius: 100%;
}
&.is-followed::before {
border-color: var(--color-primary-hover);
box-shadow: 0 0 0 1px var(--color-primary-hover);
}
&.is-current-user {
cursor: auto;
}
}
@mixin filledButtonOnCanvas {
border: none;
box-shadow: 0 0 0 1px var(--color-surface-lowest);
background-color: var(--color-surface-low);
&:active {
box-shadow: 0 0 0 1px var(--color-brand-active);
}
}
// Theme variables
$theme-filter: none !default;
$theme-filter-darker: none !default;

View file

@ -87,8 +87,8 @@
"image-blob-reduce": "3.0.1",
"jotai": "2.11.0",
"jotai-scope": "0.7.2",
"lodash.throttle": "4.1.1",
"lodash.debounce": "4.0.8",
"lodash.throttle": "4.1.1",
"nanoid": "3.3.3",
"open-color": "1.9.1",
"pako": "2.0.3",
@ -100,7 +100,7 @@
"points-on-curve": "1.0.1",
"pwacompat": "2.0.17",
"roughjs": "4.6.4",
"sass": "1.51.0",
"sass": "^1.71.1",
"tunnel-rat": "0.1.2"
},
"devDependencies": {
@ -116,20 +116,24 @@
"autoprefixer": "10.4.7",
"cross-env": "7.0.3",
"dotenv": "16.0.1",
"esbuild": "0.19.10",
"esbuild-sass-plugin": "2.16.0",
"esbuild": "0.25.2",
"esbuild-sass-plugin": "3.3.1",
"eslint-plugin-react": "7.32.2",
"fake-indexeddb": "3.1.7",
"fonteditor-core": "2.4.1",
"harfbuzzjs": "0.3.6",
"jest-diff": "29.7.0",
"sass-embedded": "1.86.3",
"typescript": "4.9.4"
},
"repository": "https://github.com/excalidraw/excalidraw",
"bugs": "https://github.com/excalidraw/excalidraw/issues",
"homepage": "https://github.com/excalidraw/excalidraw/tree/master/packages/excalidraw",
"scripts": {
"gen:types": "rm -rf types && tsc",
"build:esm": "rm -rf dist && node ../../scripts/buildPackage.js && yarn gen:types"
"gen:types": "if exist types rmdir /s /q types && tsc",
"build:esm": "if exist dist rmdir /s /q dist && node ../../scripts/buildPackage.mjs && yarn gen:types",
"build": "vite build",
"dev": "vite",
"preview": "vite preview"
}
}

View file

@ -1,7 +1,9 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist/types"
"noEmit": true,
"skipLibCheck": true,
"incremental": false
},
"include": ["**/*"],
"exclude": ["**/*.test.*", "tests", "types", "examples", "dist"]

View file

@ -0,0 +1,22 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { fileURLToPath } from 'node:url';
export default defineConfig({
build: {
lib: {
entry: fileURLToPath(new URL('./index.tsx', import.meta.url)),
name: 'Excalidraw',
fileName: (format) => `excalidraw.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
}
}
});

View file

@ -3,14 +3,14 @@
"version": "0.1.0",
"type": "module",
"types": "./dist/types/math/src/index.d.ts",
"main": "./dist/prod/index.js",
"module": "./dist/prod/index.js",
"main": "./dist/math.umd.js",
"module": "./dist/math.es.js",
"exports": {
".": {
"types": "./dist/types/math/src/index.d.ts",
"development": "./dist/dev/index.js",
"production": "./dist/prod/index.js",
"default": "./dist/prod/index.js"
"development": "./dist/math.es.js",
"production": "./dist/math.umd.js",
"default": "./dist/math.umd.js"
},
"./*": {
"types": "./../math/dist/types/math/src/*.d.ts"
@ -55,6 +55,11 @@
"repository": "https://github.com/excalidraw/excalidraw",
"scripts": {
"gen:types": "rm -rf types && tsc",
"build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types"
"build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types",
"build": "vite build",
"test": "jest",
"lint": "eslint . --ext .ts,.tsx",
"prettier": "prettier --check .",
"prettier:fix": "prettier --write ."
}
}

View file

@ -0,0 +1,27 @@
import { defineConfig } from 'vite';
import { fileURLToPath } from 'node:url';
export default defineConfig({
build: {
lib: {
entry: fileURLToPath(new URL('./src/index.ts', import.meta.url)),
name: 'ExcalidrawMath',
fileName: () => 'index.js', // Changed to match package.json
},
outDir: 'dist/prod', // Added to match package.json
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
},
format: 'es' // Still produces ESM; UMD needs separate config if required
},
onwarn: (warning, warn) => {
if (warning.code === 'CIRCULAR_DEPENDENCY') return;
warn(warning);
}
}
}
});

View file

@ -8,12 +8,15 @@
"exports": {
".": {
"types": "./dist/types/utils/src/index.d.ts",
"development": "./dist/dev/index.js",
"production": "./dist/prod/index.js",
"import": "./dist/prod/index.js",
"require": "./dist/prod/index.js",
"default": "./dist/prod/index.js"
},
"./*": {
"types": "./../utils/dist/types/utils/src/*.d.ts"
"./export": {
"types": "./dist/types/utils/src/export.d.ts",
"import": "./dist/prod/index.js",
"require": "./dist/prod/index.js",
"default": "./dist/prod/index.js"
}
},
"files": [
@ -61,6 +64,8 @@
},
"devDependencies": {
"cross-env": "7.0.3",
"esbuild": "0.25.2",
"esbuild-sass-plugin": "3.3.1",
"fonteditor-core": "2.4.0",
"typescript": "4.9.4",
"wawoff2": "2.0.1",
@ -69,7 +74,7 @@
"bugs": "https://github.com/excalidraw/excalidraw/issues",
"repository": "https://github.com/excalidraw/excalidraw",
"scripts": {
"gen:types": "rm -rf types && tsc",
"build:esm": "rm -rf dist && node ../../scripts/buildUtils.js && yarn gen:types"
"gen:types": "if exist types rmdir /s /q types && tsc",
"build:esm": "if exist dist rmdir /s /q dist && node ../../scripts/buildUtils.mjs && yarn gen:types"
}
}

View file

@ -2,3 +2,6 @@ export * from "./export";
export * from "./withinBounds";
export * from "./bbox";
export { getCommonBounds } from "@excalidraw/element/bounds";
export * from "./collision";
export * from "./shape";
export * from "./test-utils";

1
public/excalidraw.css Normal file

File diff suppressed because one or more lines are too long

12
public/index.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Excalidraw Fork</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View file

@ -1,8 +1,8 @@
const path = require("path");
const { build } = require("esbuild");
// contains all dependencies bundled inside
console.log("Starting build...");
const getConfig = (outdir) => ({
outdir,
bundle: true,
@ -20,6 +20,7 @@ const getConfig = (outdir) => ({
});
function buildDev(config) {
console.log("Building dev...");
return build({
...config,
sourcemap: true,
@ -30,6 +31,7 @@ function buildDev(config) {
}
function buildProd(config) {
console.log("Building prod...");
return build({
...config,
minify: true,
@ -40,11 +42,9 @@ function buildProd(config) {
}
const createESMRawBuild = async () => {
// development unminified build with source maps
await buildDev(getConfig("dist/dev"));
// production minified build without sourcemaps
await buildProd(getConfig("dist/prod"));
console.log("Build complete.");
};
(async () => {

View file

@ -1,83 +0,0 @@
const path = require("path");
const { build } = require("esbuild");
const { sassPlugin } = require("esbuild-sass-plugin");
const { parseEnvVariables } = require("../packages/excalidraw/env.cjs");
const ENV_VARS = {
development: {
...parseEnvVariables(`${__dirname}/../.env.development`),
DEV: true,
},
production: {
...parseEnvVariables(`${__dirname}/../.env.production`),
PROD: true,
},
};
// excludes all external dependencies and bundles only the source code
const getConfig = (outdir) => ({
outdir,
bundle: true,
splitting: true,
format: "esm",
packages: "external",
plugins: [sassPlugin()],
target: "es2020",
assetNames: "[dir]/[name]",
chunkNames: "[dir]/[name]-[hash]",
alias: {
"@excalidraw/common": path.resolve(__dirname, "../packages/common/src"),
"@excalidraw/element": path.resolve(__dirname, "../packages/element/src"),
"@excalidraw/excalidraw": path.resolve(__dirname, "../packages/excalidraw"),
"@excalidraw/math": path.resolve(__dirname, "../packages/math/src"),
"@excalidraw/utils": path.resolve(__dirname, "../packages/utils/src"),
},
loader: {
".woff2": "file",
},
});
function buildDev(config) {
return build({
...config,
sourcemap: true,
define: {
"import.meta.env": JSON.stringify(ENV_VARS.development),
},
});
}
function buildProd(config) {
return build({
...config,
minify: true,
define: {
"import.meta.env": JSON.stringify(ENV_VARS.production),
},
});
}
const createESMRawBuild = async () => {
const chunksConfig = {
entryPoints: ["index.tsx", "**/*.chunk.ts"],
entryNames: "[name]",
};
// development unminified build with source maps
await buildDev({
...getConfig("dist/dev"),
...chunksConfig,
});
// production minified buld without sourcemaps
await buildProd({
...getConfig("dist/prod"),
...chunksConfig,
});
};
(async () => {
await createESMRawBuild();
})();

164
scripts/buildPackage.mjs Normal file
View file

@ -0,0 +1,164 @@
import { build } from "esbuild";
import { sassPlugin } from "esbuild-sass-plugin";
import { fileURLToPath } from "url";
import path from "path";
import fs from "fs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const parseEnvVariables = (filePath) => {
try {
const envContent = fs.readFileSync(filePath, "utf8");
const envVars = {};
envContent.split("\n").forEach((line) => {
const [key, value] = line.split("=");
if (key && value) envVars[key.trim()] = value.trim();
});
return envVars;
} catch {
return {};
}
};
const ENV_VARS = {
development: {
...parseEnvVariables(path.join(__dirname, "../.env.development")),
DEV: true,
},
production: {
...parseEnvVariables(path.join(__dirname, "../.env.production")),
PROD: true,
},
};
const getConfig = (outdir) => ({
outdir,
bundle: true,
splitting: true,
format: "esm",
target: "es2020",
plugins: [
sassPlugin({
loadPaths: [
path.resolve(__dirname, "../node_modules"),
path.resolve(__dirname, "../node_modules/open-color"),
path.resolve(__dirname, "../packages/excalidraw/css"),
path.resolve(__dirname, "../packages/excalidraw/src"),
path.resolve(__dirname, "../packages/excalidraw/components"),
],
precompile: (source, pathname) => {
// Skip precompile for open-color and css files to avoid loops
if (
pathname.includes("node_modules/open-color") ||
pathname.includes("css/variables.module.scss") ||
pathname.includes("css/styles.scss") ||
pathname.includes("css/theme.scss")
) {
return source;
}
if (pathname.endsWith(".scss")) {
return `
@use '../css/variables.module.scss' as vars;
@use '../css/styles.scss' as styles;
${source}
`;
}
return source;
},
type: "css",
sourceMap: false,
logger: {
warn: () => {},
error: (msg) => {
console.error("Sass compilation error:", msg);
throw new Error(msg);
},
},
}),
],
assetNames: "[dir]/[name]",
chunkNames: "[dir]/[name]-[hash]",
alias: {
"@excalidraw/common": path.resolve(__dirname, "../packages/common/src"),
"@excalidraw/element": path.resolve(__dirname, "../packages/element/src"),
"@excalidraw/excalidraw": path.resolve(__dirname, "../packages/excalidraw"),
"@excalidraw/math": path.resolve(__dirname, "../packages/math/src"),
"@excalidraw/utils": path.resolve(__dirname, "../packages/utils/src"),
},
loader: { ".woff2": "file" },
});
async function buildDev(config) {
console.log("Starting dev build to", config.outdir);
try {
await build({
...config,
sourcemap: true,
define: { "import.meta.env": JSON.stringify(ENV_VARS.development) },
});
console.log("Dev build completed successfully");
} catch (err) {
console.error("Dev build failed:", err);
throw err;
}
}
async function buildProd(config) {
console.log("Starting prod build to", config.outdir);
try {
await build({
...config,
minify: true,
define: { "import.meta.env": JSON.stringify(ENV_VARS.production) },
});
console.log("Prod build completed successfully");
} catch (err) {
console.error("Prod build failed:", err);
throw err;
}
}
const createESMRawBuild = async () => {
console.log("Creating ESM raw build...");
const prodDir = path.resolve(__dirname, "../packages/excalidraw/dist/prod");
const devDir = path.resolve(__dirname, "../packages/excalidraw/dist/dev");
if (!fs.existsSync(prodDir)) {
fs.mkdirSync(prodDir, { recursive: true });
}
if (!fs.existsSync(devDir)) {
fs.mkdirSync(devDir, { recursive: true });
}
const chunksConfig = {
entryPoints: [
path.resolve(__dirname, "../packages/excalidraw/index.tsx"),
...fs
.readdirSync(path.resolve(__dirname, "../packages/excalidraw"), { recursive: true })
.filter((file) => file.endsWith(".chunk.ts"))
.map((file) => path.resolve(__dirname, "../packages/excalidraw", file)),
],
entryNames: "[name]",
};
await buildDev({
...getConfig(path.resolve(__dirname, "../packages/excalidraw/dist/dev")),
...chunksConfig,
});
await buildProd({
...getConfig(path.resolve(__dirname, "../packages/excalidraw/dist/prod")),
...chunksConfig,
});
console.log("ESM raw build finished");
};
(async () => {
console.log("Build script started");
try {
await createESMRawBuild();
console.log("Build script completed successfully");
} catch (err) {
console.error("Build script failed:", err);
process.exit(1);
}
})();

View file

@ -1,62 +0,0 @@
const path = require("path");
const { build } = require("esbuild");
const { sassPlugin } = require("esbuild-sass-plugin");
const { woff2ServerPlugin } = require("./woff2/woff2-esbuild-plugins");
// contains all dependencies bundled inside
const getConfig = (outdir) => ({
outdir,
bundle: true,
format: "esm",
entryPoints: ["src/index.ts"],
entryNames: "[name]",
assetNames: "[dir]/[name]",
alias: {
"@excalidraw/common": path.resolve(__dirname, "../packages/common/src"),
"@excalidraw/element": path.resolve(__dirname, "../packages/element/src"),
"@excalidraw/excalidraw": path.resolve(__dirname, "../packages/excalidraw"),
"@excalidraw/math": path.resolve(__dirname, "../packages/math/src"),
"@excalidraw/utils": path.resolve(__dirname, "../packages/utils/src"),
},
});
function buildDev(config) {
return build({
...config,
sourcemap: true,
plugins: [sassPlugin(), woff2ServerPlugin()],
define: {
"import.meta.env": JSON.stringify({ DEV: true }),
},
});
}
function buildProd(config) {
return build({
...config,
minify: true,
plugins: [
sassPlugin(),
woff2ServerPlugin({
outdir: `${config.outdir}/assets`,
}),
],
define: {
"import.meta.env": JSON.stringify({ PROD: true }),
},
});
}
const createESMRawBuild = async () => {
// development unminified build with source maps
await buildDev(getConfig("dist/dev"));
// production minified build without sourcemaps
await buildProd(getConfig("dist/prod"));
};
(async () => {
await createESMRawBuild();
})();

104
scripts/buildUtils.mjs Normal file
View file

@ -0,0 +1,104 @@
import { build } from "esbuild";
import { sassPlugin } from "esbuild-sass-plugin";
import { fileURLToPath } from "url";
import path from "path";
import fs from "fs";
import { woff2ServerPlugin } from "./woff2/woff2-esbuild-plugins.mjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
console.log("Starting build (ESM) - DEBUG VERSION...");
const ensureDir = (dir) => {
console.log(`[DEBUG] Ensuring directory: ${dir}`);
try {
if (!fs.existsSync(dir)) {
console.log(`[DEBUG] Creating directory: ${dir}`);
fs.mkdirSync(dir, { recursive: true });
console.log(`[DEBUG] Successfully created directory: ${dir}`);
} else {
console.log(`[DEBUG] Directory already exists: ${dir}`);
}
return true;
} catch (err) {
console.error(`[ERROR] Failed to create directory ${dir}:`, err);
return false;
}
};
const getConfig = (outdir) => {
ensureDir(outdir);
return {
outdir,
bundle: true,
format: "esm",
entryPoints: ["src/index.ts"],
entryNames: "[name]",
assetNames: "[dir]/[name]",
alias: {
"@excalidraw/common": path.resolve(__dirname, "../packages/common/src"),
"@excalidraw/element": path.resolve(__dirname, "../packages/element/src"),
"@excalidraw/excalidraw": path.resolve(__dirname, "../packages/excalidraw"),
"@excalidraw/math": path.resolve(__dirname, "../packages/math/src"),
"@excalidraw/utils": path.resolve(__dirname, "../packages/utils/src"),
},
};
};
async function buildDev(config) {
console.log("Building dev version...");
try {
await build({
...config,
sourcemap: true,
plugins: [sassPlugin(), woff2ServerPlugin()],
define: {
"import.meta.env": JSON.stringify({ DEV: true }),
},
});
console.log("Dev build completed successfully");
} catch (err) {
console.error("Dev build failed:", err);
throw err;
}
}
async function buildProd(config) {
console.log("Building prod version...");
try {
await build({
...config,
minify: true,
plugins: [
sassPlugin(),
woff2ServerPlugin({
outdir: `${config.outdir}/assets`,
}),
],
define: {
"import.meta.env": JSON.stringify({ PROD: true }),
},
});
console.log("Prod build completed successfully");
} catch (err) {
console.error("Prod build failed:", err);
throw err;
}
}
const createESMRawBuild = async () => {
// development unminified build with source maps
await buildDev(getConfig("dist/dev"));
// production minified build without sourcemaps
await buildProd(getConfig("dist/prod"));
console.log("All builds complete.");
};
(async () => {
try {
await createESMRawBuild();
} catch (error) {
console.error("Build failed:", error);
process.exit(1);
}
})();

View file

@ -1,10 +1,7 @@
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const { Font } = require("fonteditor-core");
const wawoff = require("wawoff2");
const which = require("which");
import { createHash } from "crypto";
import fs from "fs";
import path from "path";
import { promisify } from "util";
/**
* Custom esbuild plugin to:
@ -17,29 +14,27 @@ const which = require("which");
*
* @returns {import("esbuild").Plugin}
*/
module.exports.woff2ServerPlugin = (options = {}) => {
export const woff2ServerPlugin = (options = {}) => {
return {
name: "woff2ServerPlugin",
name: "woff2-server-plugin",
setup(build) {
const fonts = new Map();
build.onResolve({ filter: /\.woff2$/ }, (args) => {
const resolvedPath = path.resolve(args.resolveDir, args.path);
build.onResolve({ filter: /\.(woff|woff2)$/ }, (args) => {
return {
path: resolvedPath,
namespace: "woff2ServerPlugin",
path: path.isAbsolute(args.path)
? args.path
: path.join(path.dirname(args.importer), args.path),
namespace: "woff2-asset",
};
});
build.onLoad(
{ filter: /.*/, namespace: "woff2ServerPlugin" },
async (args) => {
build.onLoad({ filter: /.*/, namespace: "woff2-asset" }, async (args) => {
let woff2Buffer;
if (path.isAbsolute(args.path)) {
// read local woff2 as a buffer (WARN: `readFileSync` does not work!)
woff2Buffer = await fs.promises.readFile(args.path);
woff2Buffer = await promisify(fs.readFile)(args.path);
} else {
throw new Error(`Font path has to be absolute! "${args.path}"`);
}
@ -89,8 +84,7 @@ module.exports.woff2ServerPlugin = (options = {}) => {
)}`,
loader: "text",
};
},
);
});
build.onEnd(async () => {
const { outdir } = options;

158
src/Sidebar.tsx Normal file
View file

@ -0,0 +1,158 @@
import React from 'react';
import type { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/dist/types/excalidraw/types';
import {
FillStyle,
StrokeStyle,
Arrowhead,
ExcalidrawElement,
FractionalIndex,
PointBinding,
ExcalidrawTextElement,
ExcalidrawArrowElement
} from '../packages/element/src/types';
import { Radians, LocalPoint } from '../packages/math/src/types';
import { pointFrom } from '../packages/math/src/point';
interface SidebarProps {
excalidrawAPI: ExcalidrawImperativeAPI | null;
}
const Sidebar: React.FC<SidebarProps> = ({ excalidrawAPI }) => {
const addRectangle = () => {
if (!excalidrawAPI) return;
const elements = [
{
id: 'a1',
type: 'rectangle',
x: 100,
y: 100,
width: 200,
height: 100,
text: '',
strokeColor: '#000000',
backgroundColor: '#ffffff',
fillStyle: 'solid' as FillStyle,
strokeWidth: 1,
strokeStyle: 'solid' as StrokeStyle,
roughness: 0,
opacity: 100,
seed: 1,
angle: 0 as Radians,
startBinding: null,
endBinding: null,
lastCommittedPoint: null,
startArrowhead: null,
endArrowhead: 'arrow',
roundness: null,
version: 1,
versionNonce: 1,
isDeleted: false,
groupIds: [],
frameId: null,
boundElements: [],
updated: Date.now(),
link: null,
locked: false,
index: 'a1' as FractionalIndex
} as ExcalidrawElement,
] as readonly ExcalidrawElement[];
excalidrawAPI.updateScene({ elements });
};
const addTemplate = () => {
if (!excalidrawAPI) return;
const elements = [
{
id: 'text-1',
type: 'text',
x: 150,
y: 150,
width: 100,
height: 30,
text: 'Hello World',
strokeColor: '#000000',
backgroundColor: 'transparent',
fillStyle: 'solid' as FillStyle,
strokeWidth: 1,
strokeStyle: 'solid' as StrokeStyle,
roughness: 1,
opacity: 100,
seed: 1,
angle: 0 as Radians,
originalText: 'Hello World',
lineHeight: 25 as number & { _brand: 'unitlessLineHeight' },
fontSize: 20,
fontFamily: 1,
textAlign: 'left',
verticalAlign: 'top',
containerId: null,
autoResize: true,
roundness: null,
version: 1,
versionNonce: 1,
isDeleted: false,
groupIds: [],
frameId: null,
boundElements: [],
updated: Date.now(),
link: null,
locked: false,
index: 'a0' as FractionalIndex
} as ExcalidrawTextElement,
{
id: 'arrow-1',
type: 'arrow',
x: 100,
y: 100,
width: 100,
height: 100,
strokeColor: '#000000',
backgroundColor: 'transparent',
fillStyle: 'solid' as FillStyle,
strokeWidth: 1,
strokeStyle: 'solid' as StrokeStyle,
roughness: 1,
opacity: 100,
seed: 1,
angle: 0 as Radians,
points: [pointFrom(100, 100), pointFrom(200, 200)] as readonly LocalPoint[],
lastCommittedPoint: pointFrom(200, 200),
startBinding: null,
endBinding: null,
startArrowhead: null,
endArrowhead: null,
elbowed: false,
roundness: null,
version: 1,
versionNonce: 1,
isDeleted: false,
groupIds: [],
frameId: null,
boundElements: [],
updated: Date.now(),
link: null,
locked: false,
customData: {},
index: 'a1' as FractionalIndex
} as ExcalidrawArrowElement
];
excalidrawAPI.updateScene({ elements });
};
const saveDiagram = () => {
if (!excalidrawAPI) return;
const elements = excalidrawAPI.getSceneElements();
console.log('Saving diagram:', elements);
};
return (
<div style={{ width: '200px', padding: '10px', borderRight: '1px solid #ccc' }}>
<button onClick={addRectangle}>Add Rectangle</button>
<button onClick={addTemplate}>Add Template</button>
<button onClick={saveDiagram}>Save Diagram</button>
</div>
);
};
export default Sidebar;

89
src/index.tsx Normal file
View file

@ -0,0 +1,89 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { Excalidraw } from '@excalidraw/excalidraw';
import type { ExcalidrawImperativeAPI, AppState } from '@excalidraw/excalidraw/dist/types/excalidraw/types';
import type { ExcalidrawElement as OrderedExcalidrawElement } from '@excalidraw/element/types';
import type { BinaryFiles } from '@excalidraw/excalidraw/dist/types/excalidraw/types';
import Sidebar from './Sidebar';
// Import CSS directly
import './styles/excalidraw-overrides.css';
import '@excalidraw/excalidraw/dist/excalidraw.css';
console.log('Script loaded - looking for root element');
const rootElement = document.getElementById('app');
if (!rootElement) {
console.error('Failed to find root element');
throw new Error('Failed to find the root element');
}
console.log('Root element found, creating React root');
const root = ReactDOM.createRoot(rootElement);
const App = () => {
console.log('Rendering App component');
const [excalidrawAPI, setExcalidrawAPI] = React.useState<ExcalidrawImperativeAPI | null>(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState<Error | null>(null);
React.useEffect(() => {
try {
// Initialize any required Excalidraw dependencies
setLoading(false);
} catch (err) {
setError(err instanceof Error ? err : new Error(String(err)));
setLoading(false);
}
}, []);
if (loading) {
return <div>Loading Excalidraw...</div>;
}
if (error) {
return <div>Error loading Excalidraw: {error.message}</div>;
}
const onChange = (
elements: readonly OrderedExcalidrawElement[],
appState: AppState,
files: BinaryFiles
) => {
console.log('Excalidraw state changed:', elements, appState, files);
};
const onPointerUpdate = (payload: { pointer: { x: number; y: number } }) => {
console.log('Excalidraw pointer update:', payload);
};
return (
<div style={{ display: 'flex' }}>
<Sidebar excalidrawAPI={excalidrawAPI} />
<div style={{ height: '500px', width: '800px' }}>
<Excalidraw
excalidrawAPI={(api: ExcalidrawImperativeAPI) => {
setExcalidrawAPI(api);
}}
onChange={onChange}
onPointerUpdate={onPointerUpdate}
initialData={{
appState: {
viewBackgroundColor: '#ffffff',
width: 800,
height: 500,
},
}}
/>
</div>
</div>
);
};
console.log('Starting React render');
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
console.log('Render completed');

View file

@ -0,0 +1,16 @@
/* Fix font-face descriptors */
@font-face {
font-family: 'Virgil';
src: url('/node_modules/@excalidraw/excalidraw/dist/Virgil.woff2') format('woff2');
}
/* Fix align-items rule */
.excalidraw {
align-items: normal !important;
}
/* Remove problematic rules */
.excalidraw .invalid-selector,
.excalidraw .another-invalid-selector {
/* These intentionally left blank to override bad selectors */
}

View file

@ -3,7 +3,7 @@
"rootDir": "./",
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["vitest/globals", "@testing-library/jest-dom"],
"types": ["vitest/globals", "@testing-library/jest-dom", "node"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@ -21,8 +21,8 @@
"paths": {
"@excalidraw/common": ["./packages/common/src/index.ts"],
"@excalidraw/common/*": ["./packages/common/src/*"],
"@excalidraw/excalidraw": ["./packages/excalidraw/index.tsx"],
"@excalidraw/excalidraw/*": ["./packages/excalidraw/*"],
"@excalidraw/excalidraw": ["./packages/excalidraw/dist/dev/index.js"],
"@excalidraw/excalidraw/*": ["./packages/excalidraw/dist/types/*"],
"@excalidraw/element": ["./packages/element/src/index.ts"],
"@excalidraw/element/*": ["./packages/element/src/*"],
"@excalidraw/math": ["./packages/math/src/index.ts"],
@ -31,6 +31,6 @@
"@excalidraw/utils/*": ["./packages/utils/src/*"]
}
},
"include": ["packages", "excalidraw-app"],
"exclude": ["examples", "dist", "types", "tests"]
"include": ["src/**/*", "vite.config.ts", "packages/**/*", "excalidraw-app/**/*"],
"exclude": ["examples", "dist", "tests"]
}

1
tsconfig.tsbuildinfo Normal file

File diff suppressed because one or more lines are too long

4
types/esbuild-sass-plugin.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare
module
esbuild-sass-plugin
import { Plugin } from 'esbuild'; export function sassPlugin(options?: any): Plugin;

View file

@ -1,4 +1,5 @@
{
"version": 2,
"public": true,
"headers": [
{
@ -65,6 +66,16 @@
"destination": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor"
}
],
"outputDirectory": "excalidraw-app/build",
"builds": [
{
"src": "package.json",
"use": "@vercel/static-build",
"config": {
"distDir": "packages/excalidraw/dist",
"framework": "vite"
}
}
],
"outputDirectory": "packages/excalidraw/dist",
"installCommand": "yarn install"
}

50
vite.config.ts Normal file
View file

@ -0,0 +1,50 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { fileURLToPath, URL } from 'node:url';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@excalidraw/excalidraw': fileURLToPath(
new URL('node_modules/@excalidraw/excalidraw/dist/excalidraw.production.min.js', import.meta.url)
),
'@excalidraw/excalidraw/types': fileURLToPath(
new URL('node_modules/@excalidraw/excalidraw/dist/types', import.meta.url)
)
},
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']
},
server: {
port: 3000,
open: true,
strictPort: true,
hmr: {
overlay: false
},
headers: {
'Content-Type': 'text/javascript',
'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:"
}
},
build: {
sourcemap: true,
rollupOptions: {
input: {
main: './index.html'
}
}
},
css: {
preprocessorOptions: {
css: {
additionalData: `@import './src/styles/excalidraw-overrides.css';`
}
}
},
optimizeDeps: {
include: ['@excalidraw/excalidraw'],
exclude: ['@excalidraw/excalidraw/dist/excalidraw.production.min.js']
}
});

42
vite.package.config.ts Normal file
View file

@ -0,0 +1,42 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { fileURLToPath } from 'node:url';
import { execSync } from 'node:child_process';
export default defineConfig({
plugins: [
{
name: 'build-packages-first',
async config() {
// First build all packages
await Promise.all([
execSync('cd packages/common && yarn build', { stdio: 'inherit' }),
execSync('cd packages/math && yarn build', { stdio: 'inherit' }),
execSync('cd packages/excalidraw && yarn build', { stdio: 'inherit' })
]);
}
},
react()
],
build: {
lib: {
entry: fileURLToPath(new URL('./packages/excalidraw/src/index.tsx', import.meta.url)),
name: 'Excalidraw',
fileName: (format) => `excalidraw.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
},
format: 'es'
},
onwarn: (warning, warn) => {
if (warning.code === 'CIRCULAR_DEPENDENCY') return;
warn(warning);
}
}
}
});

5162
yarn.lock

File diff suppressed because it is too large Load diff