Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
118fd7bafa | ||
|
|
21f492fb13 | ||
|
|
258605d1d5 | ||
|
|
c141500400 |
@@ -24,4 +24,4 @@ jobs:
|
|||||||
- name: Auto release
|
- name: Auto release
|
||||||
run: |
|
run: |
|
||||||
yarn add @actions/core -W
|
yarn add @actions/core -W
|
||||||
yarn autorelease
|
yarn release --tag=next --non-interactive
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
name: Auto release excalidraw preview
|
|
||||||
on:
|
|
||||||
issue_comment:
|
|
||||||
types: [created, edited]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Auto-release-excalidraw-preview:
|
|
||||||
name: Auto release preview
|
|
||||||
if: github.event.comment.body == '@excalibot trigger release' && github.event.issue.pull_request
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: React to release comment
|
|
||||||
uses: peter-evans/create-or-update-comment@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
|
||||||
comment-id: ${{ github.event.comment.id }}
|
|
||||||
reactions: "+1"
|
|
||||||
- name: Get PR SHA
|
|
||||||
id: sha
|
|
||||||
uses: actions/github-script@v4
|
|
||||||
with:
|
|
||||||
result-encoding: string
|
|
||||||
script: |
|
|
||||||
const { owner, repo, number } = context.issue;
|
|
||||||
const pr = await github.pulls.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: number,
|
|
||||||
});
|
|
||||||
return pr.data.head.sha
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
ref: ${{ steps.sha.outputs.result }}
|
|
||||||
fetch-depth: 2
|
|
||||||
- name: Setup Node.js 18.x
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: 18.x
|
|
||||||
- name: Set up publish access
|
|
||||||
run: |
|
|
||||||
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
|
|
||||||
env:
|
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
- name: Auto release preview
|
|
||||||
id: "autorelease"
|
|
||||||
run: |
|
|
||||||
yarn add @actions/core -W
|
|
||||||
yarn autorelease preview ${{ github.event.issue.number }}
|
|
||||||
- name: Post comment post release
|
|
||||||
if: always()
|
|
||||||
uses: peter-evans/create-or-update-comment@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
|
||||||
issue-number: ${{ github.event.issue.number }}
|
|
||||||
body: "@${{ github.event.comment.user.login }} ${{ steps.autorelease.outputs.result }}"
|
|
||||||
@@ -28,32 +28,12 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
|
|||||||
|
|
||||||
## Releasing
|
## Releasing
|
||||||
|
|
||||||
### Create a test release
|
|
||||||
|
|
||||||
You can create a test release by posting the below comment in your pull request:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
@excalibot trigger release
|
|
||||||
```
|
|
||||||
|
|
||||||
Once the version is released `@excalibot` will post a comment with the release version.
|
|
||||||
|
|
||||||
### Creating a production release
|
### Creating a production release
|
||||||
|
|
||||||
To release the next stable version follow the below steps:
|
To release the next stable version follow the below steps:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn prerelease:excalidraw
|
yarn release --tag=latest --version=0.19.0
|
||||||
```
|
```
|
||||||
|
|
||||||
You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
|
You will need to pass the `latest` tag with `version` for which you want to create the release. This will make the changes needed before publishing the packages into NPM, like updating dependencies of all `@excalidraw/*` packages, generating new entries in `CHANGELOG.md` and more.
|
||||||
|
|
||||||
The next step is to run the `release` script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
yarn release:excalidraw
|
|
||||||
```
|
|
||||||
|
|
||||||
This will publish the package.
|
|
||||||
|
|
||||||
Right now there are two steps to create a production release but once this works fine these scripts will be combined and more automation will be done.
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
|
"build:packages": "yarn --cwd ../../ build:packages",
|
||||||
|
"build:workspace": "yarn build:packages && yarn copy:assets",
|
||||||
"copy:assets": "cp -r ../../packages/excalidraw/dist/prod/fonts ./public",
|
"copy:assets": "cp -r ../../packages/excalidraw/dist/prod/fonts ./public",
|
||||||
"dev": "yarn build:workspace && next dev -p 3005",
|
"dev": "yarn build:workspace && next dev -p 3005",
|
||||||
"build": "yarn build:workspace && next build",
|
"build": "yarn build:workspace && next build",
|
||||||
|
|||||||
@@ -17,6 +17,6 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview --port 5002",
|
"preview": "vite preview --port 5002",
|
||||||
"build:preview": "yarn build && yarn preview",
|
"build:preview": "yarn build && yarn preview",
|
||||||
"build:package": "yarn workspace @excalidraw/excalidraw run build:esm"
|
"build:packages": "yarn --cwd ../../ build:packages"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"outputDirectory": "dist",
|
"outputDirectory": "dist",
|
||||||
"installCommand": "yarn install",
|
"installCommand": "yarn install",
|
||||||
"buildCommand": "yarn build:package && yarn build"
|
"buildCommand": "yarn build:packages && yarn build"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ import {
|
|||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
import { isCurve } from "@excalidraw/math/curve";
|
import { isCurve } from "@excalidraw/math/curve";
|
||||||
|
|
||||||
import type { DebugElement } from "@excalidraw/excalidraw/visualdebug";
|
|
||||||
|
|
||||||
import type { Curve } from "@excalidraw/math";
|
import type { Curve } from "@excalidraw/math";
|
||||||
|
|
||||||
|
import type { DebugElement } from "@excalidraw/utils/visualdebug";
|
||||||
|
|
||||||
import { STORAGE_KEYS } from "../app_constants";
|
import { STORAGE_KEYS } from "../app_constants";
|
||||||
|
|
||||||
const renderLine = (
|
const renderLine = (
|
||||||
|
|||||||
+10
-5
@@ -52,13 +52,17 @@
|
|||||||
"build-node": "node ./scripts/build-node.js",
|
"build-node": "node ./scripts/build-node.js",
|
||||||
"build:app:docker": "yarn --cwd ./excalidraw-app build:app:docker",
|
"build:app:docker": "yarn --cwd ./excalidraw-app build:app:docker",
|
||||||
"build:app": "yarn --cwd ./excalidraw-app build:app",
|
"build:app": "yarn --cwd ./excalidraw-app build:app",
|
||||||
"build:package": "yarn --cwd ./packages/excalidraw build:esm",
|
"build:common": "yarn --cwd ./packages/common build:esm",
|
||||||
|
"build:element": "yarn --cwd ./packages/element build:esm",
|
||||||
|
"build:excalidraw": "yarn --cwd ./packages/excalidraw build:esm",
|
||||||
|
"build:math": "yarn --cwd ./packages/math build:esm",
|
||||||
|
"build:packages": "yarn build:common && yarn build:math && yarn build:element && yarn build:excalidraw",
|
||||||
"build:version": "yarn --cwd ./excalidraw-app build:version",
|
"build:version": "yarn --cwd ./excalidraw-app build:version",
|
||||||
"build": "yarn --cwd ./excalidraw-app build",
|
"build": "yarn --cwd ./excalidraw-app build",
|
||||||
"build:preview": "yarn --cwd ./excalidraw-app build:preview",
|
"build:preview": "yarn --cwd ./excalidraw-app build:preview",
|
||||||
"start": "yarn --cwd ./excalidraw-app start",
|
"start": "yarn --cwd ./excalidraw-app start",
|
||||||
"start:production": "yarn --cwd ./excalidraw-app start:production",
|
"start:production": "yarn --cwd ./excalidraw-app start:production",
|
||||||
"start:example": "yarn build:package && yarn --cwd ./examples/with-script-in-browser start",
|
"start:example": "yarn build:packages && 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:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watch=false",
|
||||||
"test:app": "vitest",
|
"test:app": "vitest",
|
||||||
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
"test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
|
||||||
@@ -76,9 +80,10 @@
|
|||||||
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
||||||
"autorelease": "node scripts/autorelease.js",
|
"release": "node scripts/release.js",
|
||||||
"prerelease:excalidraw": "node scripts/prerelease.js",
|
"release:test": "node scripts/release.js --tag=test",
|
||||||
"release:excalidraw": "node scripts/release.js",
|
"release:next": "node scripts/release.js --tag=next",
|
||||||
|
"release:latest": "node scripts/release.js --tag=latest",
|
||||||
"rm:build": "rimraf --glob excalidraw-app/build excalidraw-app/dist excalidraw-app/dev-dist packages/*/dist packages/*/build examples/*/build examples/*/dist",
|
"rm:build": "rimraf --glob excalidraw-app/build excalidraw-app/dist excalidraw-app/dev-dist packages/*/dist packages/*/build examples/*/build examples/*/dist",
|
||||||
"rm:node_modules": "rimraf --glob node_modules excalidraw-app/node_modules packages/*/node_modules",
|
"rm:node_modules": "rimraf --glob node_modules excalidraw-app/node_modules packages/*/node_modules",
|
||||||
"clean-install": "yarn rm:node_modules && yarn install"
|
"clean-install": "yarn rm:node_modules && yarn install"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@excalidraw/common",
|
"name": "@excalidraw/common",
|
||||||
"version": "0.1.0",
|
"version": "0.18.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./dist/types/common/src/index.d.ts",
|
"types": "./dist/types/common/src/index.d.ts",
|
||||||
"main": "./dist/prod/index.js",
|
"main": "./dist/prod/index.js",
|
||||||
@@ -13,7 +13,10 @@
|
|||||||
"default": "./dist/prod/index.js"
|
"default": "./dist/prod/index.js"
|
||||||
},
|
},
|
||||||
"./*": {
|
"./*": {
|
||||||
"types": "./dist/types/common/src/*.d.ts"
|
"types": "./dist/types/common/src/*.d.ts",
|
||||||
|
"development": "./dist/dev/index.js",
|
||||||
|
"production": "./dist/prod/index.js",
|
||||||
|
"default": "./dist/prod/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@excalidraw/element",
|
"name": "@excalidraw/element",
|
||||||
"version": "0.1.0",
|
"version": "0.18.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./dist/types/element/src/index.d.ts",
|
"types": "./dist/types/element/src/index.d.ts",
|
||||||
"main": "./dist/prod/index.js",
|
"main": "./dist/prod/index.js",
|
||||||
@@ -13,7 +13,10 @@
|
|||||||
"default": "./dist/prod/index.js"
|
"default": "./dist/prod/index.js"
|
||||||
},
|
},
|
||||||
"./*": {
|
"./*": {
|
||||||
"types": "./dist/types/element/src/*.d.ts"
|
"types": "./dist/types/element/src/*.d.ts",
|
||||||
|
"development": "./dist/dev/index.js",
|
||||||
|
"production": "./dist/prod/index.js",
|
||||||
|
"default": "./dist/prod/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@@ -52,5 +55,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"gen:types": "rimraf types && tsc",
|
"gen:types": "rimraf types && tsc",
|
||||||
"build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types"
|
"build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@excalidraw/common": "0.18.0",
|
||||||
|
"@excalidraw/math": "0.18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,11 +124,6 @@ export const getDefaultAppState = (): Omit<
|
|||||||
searchMatches: null,
|
searchMatches: null,
|
||||||
lockedMultiSelections: {},
|
lockedMultiSelections: {},
|
||||||
activeLockedId: null,
|
activeLockedId: null,
|
||||||
cropPositionMovement: {
|
|
||||||
enabled: false,
|
|
||||||
croppingElementId: undefined,
|
|
||||||
directionLock: null,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -254,7 +249,6 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
searchMatches: { browser: false, export: false, server: false },
|
searchMatches: { browser: false, export: false, server: false },
|
||||||
lockedMultiSelections: { browser: true, export: true, server: true },
|
lockedMultiSelections: { browser: true, export: true, server: true },
|
||||||
activeLockedId: { browser: false, export: false, server: false },
|
activeLockedId: { browser: false, export: false, server: false },
|
||||||
cropPositionMovement: { browser: false, export: false, server: false },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const _clearAppStateForStorage = <
|
const _clearAppStateForStorage = <
|
||||||
|
|||||||
@@ -191,7 +191,6 @@ import {
|
|||||||
FlowChartNavigator,
|
FlowChartNavigator,
|
||||||
getLinkDirectionFromKey,
|
getLinkDirectionFromKey,
|
||||||
cropElement,
|
cropElement,
|
||||||
getUncroppedImageElement,
|
|
||||||
wrapText,
|
wrapText,
|
||||||
isElementLink,
|
isElementLink,
|
||||||
parseElementLinkFromURL,
|
parseElementLinkFromURL,
|
||||||
@@ -461,7 +460,6 @@ import type {
|
|||||||
} from "../types";
|
} from "../types";
|
||||||
import type { RoughCanvas } from "roughjs/bin/canvas";
|
import type { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
import type { Action, ActionResult } from "../actions/types";
|
import type { Action, ActionResult } from "../actions/types";
|
||||||
import type { GlobalPoint } from "@excalidraw/math";
|
|
||||||
|
|
||||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||||
@@ -595,6 +593,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
* insert to DOM before user initially scrolls to them) */
|
* insert to DOM before user initially scrolls to them) */
|
||||||
private initializedEmbeds = new Set<ExcalidrawIframeLikeElement["id"]>();
|
private initializedEmbeds = new Set<ExcalidrawIframeLikeElement["id"]>();
|
||||||
|
|
||||||
|
private handleToastClose = () => {
|
||||||
|
this.setToast(null);
|
||||||
|
};
|
||||||
|
|
||||||
private elementsPendingErasure: ElementsPendingErasure = new Set();
|
private elementsPendingErasure: ElementsPendingErasure = new Set();
|
||||||
|
|
||||||
public flowChartCreator: FlowChartCreator = new FlowChartCreator();
|
public flowChartCreator: FlowChartCreator = new FlowChartCreator();
|
||||||
@@ -1709,14 +1711,16 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
/>
|
/>
|
||||||
</ElementCanvasButtons>
|
</ElementCanvasButtons>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.toast !== null && (
|
{this.state.toast !== null && (
|
||||||
<Toast
|
<Toast
|
||||||
message={this.state.toast.message}
|
message={this.state.toast.message}
|
||||||
onClose={() => this.setToast(null)}
|
onClose={this.handleToastClose}
|
||||||
duration={this.state.toast.duration}
|
duration={this.state.toast.duration}
|
||||||
closable={this.state.toast.closable}
|
closable={this.state.toast.closable}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.state.contextMenu && (
|
{this.state.contextMenu && (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
items={this.state.contextMenu.items}
|
items={this.state.contextMenu.items}
|
||||||
@@ -6543,44 +6547,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.clearSelectionIfNotUsingSelection();
|
this.clearSelectionIfNotUsingSelection();
|
||||||
this.updateBindingEnabledOnPointerMove(event);
|
this.updateBindingEnabledOnPointerMove(event);
|
||||||
|
|
||||||
// Check if we're in crop mode and hitting uncropped area - if so, skip selection handling
|
|
||||||
if (this.state.croppingElementId) {
|
|
||||||
const croppingElement = this.scene
|
|
||||||
.getNonDeletedElementsMap()
|
|
||||||
.get(this.state.croppingElementId);
|
|
||||||
|
|
||||||
if (
|
|
||||||
croppingElement &&
|
|
||||||
isImageElement(croppingElement) &&
|
|
||||||
croppingElement.crop
|
|
||||||
) {
|
|
||||||
const uncroppedElement = getUncroppedImageElement(
|
|
||||||
croppingElement,
|
|
||||||
this.scene.getNonDeletedElementsMap(),
|
|
||||||
);
|
|
||||||
const hitUncroppedArea = hitElementItself({
|
|
||||||
point: pointFrom<GlobalPoint>(
|
|
||||||
pointerDownState.origin.x,
|
|
||||||
pointerDownState.origin.y,
|
|
||||||
),
|
|
||||||
element: uncroppedElement,
|
|
||||||
threshold: this.getElementHitThreshold(uncroppedElement),
|
|
||||||
elementsMap: this.scene.getNonDeletedElementsMap(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hitUncroppedArea) {
|
|
||||||
// Set a dedicated flag for crop position movement
|
|
||||||
pointerDownState.cropPositionMovement.enabled = true;
|
|
||||||
pointerDownState.cropPositionMovement.croppingElementId =
|
|
||||||
croppingElement.id;
|
|
||||||
// Set isCropping state to true so crop mode UI stays active
|
|
||||||
this.setState({
|
|
||||||
isCropping: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.handleSelectionOnPointerDown(event, pointerDownState)) {
|
if (this.handleSelectionOnPointerDown(event, pointerDownState)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -6992,11 +6958,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
boxSelection: {
|
boxSelection: {
|
||||||
hasOccurred: false,
|
hasOccurred: false,
|
||||||
},
|
},
|
||||||
cropPositionMovement: {
|
|
||||||
enabled: false,
|
|
||||||
croppingElementId: undefined,
|
|
||||||
directionLock: null,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7221,39 +7182,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.croppingElementId) {
|
if (
|
||||||
const croppingElement = this.scene
|
this.state.croppingElementId &&
|
||||||
.getNonDeletedElementsMap()
|
pointerDownState.hit.element?.id !== this.state.croppingElementId
|
||||||
.get(this.state.croppingElementId);
|
) {
|
||||||
if (croppingElement) {
|
this.finishImageCropping();
|
||||||
const uncroppedElement = getUncroppedImageElement(
|
|
||||||
croppingElement as any,
|
|
||||||
this.scene.getNonDeletedElementsMap(),
|
|
||||||
);
|
|
||||||
const hitUncroppedArea = hitElementItself({
|
|
||||||
point: pointFrom<GlobalPoint>(
|
|
||||||
pointerDownState.origin.x,
|
|
||||||
pointerDownState.origin.y,
|
|
||||||
),
|
|
||||||
element: uncroppedElement,
|
|
||||||
threshold: this.getElementHitThreshold(uncroppedElement),
|
|
||||||
elementsMap: this.scene.getNonDeletedElementsMap(),
|
|
||||||
});
|
|
||||||
if (!hitUncroppedArea) {
|
|
||||||
this.finishImageCropping();
|
|
||||||
} else {
|
|
||||||
// ensure the image remains selected so crop handles are rendered
|
|
||||||
if (
|
|
||||||
(!this.state.selectedElementIds ||
|
|
||||||
Object.keys(this.state.selectedElementIds).length === 0) &&
|
|
||||||
this.state.croppingElementId
|
|
||||||
) {
|
|
||||||
this.setState({
|
|
||||||
selectedElementIds: { [this.state.croppingElementId]: true },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pointerDownState.hit.element) {
|
if (pointerDownState.hit.element) {
|
||||||
@@ -7279,7 +7212,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerDownState.hit.allHitElements.some((element) =>
|
pointerDownState.hit.allHitElements.some((element) =>
|
||||||
this.isASelectedElement(element),
|
this.isASelectedElement(element),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(hitElement === null || !someHitElementIsSelected) &&
|
(hitElement === null || !someHitElementIsSelected) &&
|
||||||
!event.shiftKey &&
|
!event.shiftKey &&
|
||||||
@@ -8103,165 +8035,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
|
const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
|
||||||
|
|
||||||
// #region dedicated crop position movement
|
|
||||||
if (
|
|
||||||
pointerDownState.cropPositionMovement.enabled &&
|
|
||||||
pointerDownState.cropPositionMovement.croppingElementId
|
|
||||||
) {
|
|
||||||
const croppingElement = pointerDownState.cropPositionMovement
|
|
||||||
.croppingElementId
|
|
||||||
? this.scene
|
|
||||||
.getNonDeletedElementsMap()
|
|
||||||
.get(pointerDownState.cropPositionMovement.croppingElementId)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (
|
|
||||||
croppingElement &&
|
|
||||||
isImageElement(croppingElement) &&
|
|
||||||
croppingElement.crop
|
|
||||||
) {
|
|
||||||
const transformHandleType = pointerDownState.resize.handleType;
|
|
||||||
|
|
||||||
if (!transformHandleType) {
|
|
||||||
const crop = croppingElement.crop;
|
|
||||||
const image =
|
|
||||||
isInitializedImageElement(croppingElement) &&
|
|
||||||
this.imageCache.get(croppingElement.fileId)?.image;
|
|
||||||
|
|
||||||
if (image && !(image instanceof Promise)) {
|
|
||||||
// calculate total drag offset from the original pointer down position
|
|
||||||
const totalDragOffset = {
|
|
||||||
x: pointerCoords.x - pointerDownState.origin.x,
|
|
||||||
y: pointerCoords.y - pointerDownState.origin.y,
|
|
||||||
};
|
|
||||||
|
|
||||||
// apply shift key constraint for directional movement
|
|
||||||
const threshold = 20;
|
|
||||||
let snappingToOrigin = false;
|
|
||||||
if (event.shiftKey) {
|
|
||||||
if (!pointerDownState.cropPositionMovement.directionLock) {
|
|
||||||
if (
|
|
||||||
Math.abs(totalDragOffset.x) > threshold ||
|
|
||||||
Math.abs(totalDragOffset.y) > threshold
|
|
||||||
) {
|
|
||||||
pointerDownState.cropPositionMovement.directionLock =
|
|
||||||
Math.abs(totalDragOffset.x) > Math.abs(totalDragOffset.y)
|
|
||||||
? "x"
|
|
||||||
: "y";
|
|
||||||
} else {
|
|
||||||
// if within threshold and not locked, always snap to origin
|
|
||||||
snappingToOrigin = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if user moves back within threshold, unlock and snap back
|
|
||||||
if (
|
|
||||||
Math.abs(totalDragOffset.x) < threshold &&
|
|
||||||
Math.abs(totalDragOffset.y) < threshold
|
|
||||||
) {
|
|
||||||
pointerDownState.cropPositionMovement.directionLock = null;
|
|
||||||
snappingToOrigin = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pointerDownState.cropPositionMovement.directionLock = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (snappingToOrigin) {
|
|
||||||
totalDragOffset.x = 0;
|
|
||||||
totalDragOffset.y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pointerDownState.cropPositionMovement.directionLock === "x") {
|
|
||||||
totalDragOffset.y = 0;
|
|
||||||
} else if (
|
|
||||||
pointerDownState.cropPositionMovement.directionLock === "y"
|
|
||||||
) {
|
|
||||||
totalDragOffset.x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// scale the drag offset
|
|
||||||
const scaledDragOffset = vectorScale(
|
|
||||||
vector(totalDragOffset.x, totalDragOffset.y),
|
|
||||||
Math.max(this.state.zoom.value, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
||||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
|
||||||
croppingElement,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
const topLeft = vectorFromPoint(
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom(x1, y1),
|
|
||||||
pointFrom(cx, cy),
|
|
||||||
croppingElement.angle,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const topRight = vectorFromPoint(
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom(x2, y1),
|
|
||||||
pointFrom(cx, cy),
|
|
||||||
croppingElement.angle,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const bottomLeft = vectorFromPoint(
|
|
||||||
pointRotateRads(
|
|
||||||
pointFrom(x1, y2),
|
|
||||||
pointFrom(cx, cy),
|
|
||||||
croppingElement.angle,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const topEdge = vectorNormalize(
|
|
||||||
vectorSubtract(topRight, topLeft),
|
|
||||||
);
|
|
||||||
const leftEdge = vectorNormalize(
|
|
||||||
vectorSubtract(bottomLeft, topLeft),
|
|
||||||
);
|
|
||||||
|
|
||||||
// project scaledDragOffset onto leftEdge and topEdge to decompose
|
|
||||||
const offsetVector = vector(
|
|
||||||
vectorDot(scaledDragOffset, topEdge),
|
|
||||||
vectorDot(scaledDragOffset, leftEdge),
|
|
||||||
);
|
|
||||||
|
|
||||||
// get the original crop from when the drag started
|
|
||||||
const originalCroppingElement =
|
|
||||||
pointerDownState.originalElements.get(croppingElement.id) as
|
|
||||||
| ExcalidrawImageElement
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
const originalCrop = originalCroppingElement?.crop || crop;
|
|
||||||
|
|
||||||
const nextCrop = {
|
|
||||||
...crop,
|
|
||||||
x: clamp(
|
|
||||||
originalCrop.x -
|
|
||||||
offsetVector[0] * Math.sign(croppingElement.scale[0]),
|
|
||||||
0,
|
|
||||||
image.naturalWidth - crop.width,
|
|
||||||
),
|
|
||||||
y: clamp(
|
|
||||||
originalCrop.y -
|
|
||||||
offsetVector[1] * Math.sign(croppingElement.scale[1]),
|
|
||||||
0,
|
|
||||||
image.naturalHeight - crop.height,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.scene.mutateElement(croppingElement, {
|
|
||||||
crop: nextCrop,
|
|
||||||
});
|
|
||||||
|
|
||||||
// set drag occurred flag for consistency
|
|
||||||
pointerDownState.drag.hasOccurred = true;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.activeLockedId) {
|
if (this.state.activeLockedId) {
|
||||||
this.setState({
|
this.setState({
|
||||||
activeLockedId: null,
|
activeLockedId: null,
|
||||||
@@ -8546,6 +8319,96 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #region move crop region
|
||||||
|
if (this.state.croppingElementId) {
|
||||||
|
const croppingElement = this.scene
|
||||||
|
.getNonDeletedElementsMap()
|
||||||
|
.get(this.state.croppingElementId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
croppingElement &&
|
||||||
|
isImageElement(croppingElement) &&
|
||||||
|
croppingElement.crop !== null &&
|
||||||
|
pointerDownState.hit.element === croppingElement
|
||||||
|
) {
|
||||||
|
const crop = croppingElement.crop;
|
||||||
|
const image =
|
||||||
|
isInitializedImageElement(croppingElement) &&
|
||||||
|
this.imageCache.get(croppingElement.fileId)?.image;
|
||||||
|
|
||||||
|
if (image && !(image instanceof Promise)) {
|
||||||
|
const instantDragOffset = vectorScale(
|
||||||
|
vector(
|
||||||
|
pointerCoords.x - lastPointerCoords.x,
|
||||||
|
pointerCoords.y - lastPointerCoords.y,
|
||||||
|
),
|
||||||
|
Math.max(this.state.zoom.value, 2),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
croppingElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const topLeft = vectorFromPoint(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(x1, y1),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
croppingElement.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const topRight = vectorFromPoint(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(x2, y1),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
croppingElement.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const bottomLeft = vectorFromPoint(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(x1, y2),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
croppingElement.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const topEdge = vectorNormalize(
|
||||||
|
vectorSubtract(topRight, topLeft),
|
||||||
|
);
|
||||||
|
const leftEdge = vectorNormalize(
|
||||||
|
vectorSubtract(bottomLeft, topLeft),
|
||||||
|
);
|
||||||
|
|
||||||
|
// project instantDrafOffset onto leftEdge and topEdge to decompose
|
||||||
|
const offsetVector = vector(
|
||||||
|
vectorDot(instantDragOffset, topEdge),
|
||||||
|
vectorDot(instantDragOffset, leftEdge),
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextCrop = {
|
||||||
|
...crop,
|
||||||
|
x: clamp(
|
||||||
|
crop.x -
|
||||||
|
offsetVector[0] * Math.sign(croppingElement.scale[0]),
|
||||||
|
0,
|
||||||
|
image.naturalWidth - crop.width,
|
||||||
|
),
|
||||||
|
y: clamp(
|
||||||
|
crop.y -
|
||||||
|
offsetVector[1] * Math.sign(croppingElement.scale[1]),
|
||||||
|
0,
|
||||||
|
image.naturalHeight - crop.height,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.scene.mutateElement(croppingElement, {
|
||||||
|
crop: nextCrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Snap cache *must* be synchronously popuplated before initial drag,
|
// Snap cache *must* be synchronously popuplated before initial drag,
|
||||||
// otherwise the first drag even will not snap, causing a jump before
|
// otherwise the first drag even will not snap, causing a jump before
|
||||||
// it snaps to its position if previously snapped already.
|
// it snaps to its position if previously snapped already.
|
||||||
@@ -9029,10 +8892,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
isCropping,
|
isCropping,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
// Clean up crop position movement flag
|
|
||||||
const wasCropPositionMovement =
|
|
||||||
pointerDownState.cropPositionMovement.enabled;
|
|
||||||
|
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
isRotating: false,
|
isRotating: false,
|
||||||
@@ -9572,46 +9431,17 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// click outside the cropping region to exit
|
// click outside the cropping region to exit
|
||||||
if (croppingElementId) {
|
if (
|
||||||
const croppingElement = this.scene
|
// not in the cropping mode at all
|
||||||
.getNonDeletedElementsMap()
|
!croppingElementId ||
|
||||||
.get(croppingElementId);
|
// in the cropping mode
|
||||||
|
(croppingElementId &&
|
||||||
if (
|
// not cropping and no hit element
|
||||||
croppingElement &&
|
((!hitElement && !isCropping) ||
|
||||||
isImageElement(croppingElement) &&
|
// hitting something else
|
||||||
croppingElement.crop
|
(hitElement && hitElement.id !== croppingElementId)))
|
||||||
) {
|
) {
|
||||||
const uncroppedElement = getUncroppedImageElement(
|
this.finishImageCropping();
|
||||||
croppingElement,
|
|
||||||
this.scene.getNonDeletedElementsMap(),
|
|
||||||
);
|
|
||||||
const pointer = pointFrom<GlobalPoint>(sceneCoords.x, sceneCoords.y);
|
|
||||||
const hitUncroppedArea = hitElementItself({
|
|
||||||
point: pointer,
|
|
||||||
element: uncroppedElement,
|
|
||||||
threshold: this.getElementHitThreshold(uncroppedElement),
|
|
||||||
elementsMap: this.scene.getNonDeletedElementsMap(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hitUncroppedArea) {
|
|
||||||
this.finishImageCropping();
|
|
||||||
} else {
|
|
||||||
// ensure the image remains selected so crop handles are rendered
|
|
||||||
if (
|
|
||||||
(!this.state.selectedElementIds ||
|
|
||||||
Object.keys(this.state.selectedElementIds).length === 0) &&
|
|
||||||
this.state.croppingElementId
|
|
||||||
) {
|
|
||||||
this.setState({
|
|
||||||
selectedElementIds: { [this.state.croppingElementId]: true },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// fallback: if not in cropping mode or no cropping element, finish cropping
|
|
||||||
this.finishImageCropping();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pointerStart = this.lastPointerDownEvent;
|
const pointerStart = this.lastPointerDownEvent;
|
||||||
@@ -9822,7 +9652,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
((hitElement &&
|
((hitElement &&
|
||||||
hitElementBoundingBoxOnly(
|
hitElementBoundingBoxOnly(
|
||||||
{
|
{
|
||||||
point: pointFrom<GlobalPoint>(
|
point: pointFrom(
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
),
|
),
|
||||||
@@ -10774,8 +10604,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
|
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("hi");
|
|
||||||
|
|
||||||
const croppingElement = this.scene
|
const croppingElement = this.scene
|
||||||
.getNonDeletedElementsMap()
|
.getNonDeletedElementsMap()
|
||||||
.get(this.state.croppingElementId);
|
.get(this.state.croppingElementId);
|
||||||
|
|||||||
@@ -66,12 +66,22 @@
|
|||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"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": "rimraf types && tsc",
|
||||||
|
"build:esm": "rimraf dist && node ../../scripts/buildPackage.js && yarn gen:types"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^17.0.2 || ^18.2.0 || ^19.0.0",
|
"react": "^17.0.2 || ^18.2.0 || ^19.0.0",
|
||||||
"react-dom": "^17.0.2 || ^18.2.0 || ^19.0.0"
|
"react-dom": "^17.0.2 || ^18.2.0 || ^19.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "6.0.2",
|
"@braintree/sanitize-url": "6.0.2",
|
||||||
|
"@excalidraw/common": "0.18.0",
|
||||||
|
"@excalidraw/element": "0.18.0",
|
||||||
|
"@excalidraw/math": "0.18.0",
|
||||||
"@excalidraw/laser-pointer": "1.3.1",
|
"@excalidraw/laser-pointer": "1.3.1",
|
||||||
"@excalidraw/mermaid-to-excalidraw": "1.1.2",
|
"@excalidraw/mermaid-to-excalidraw": "1.1.2",
|
||||||
"@excalidraw/random-username": "1.1.0",
|
"@excalidraw/random-username": "1.1.0",
|
||||||
@@ -124,12 +134,5 @@
|
|||||||
"harfbuzzjs": "0.3.6",
|
"harfbuzzjs": "0.3.6",
|
||||||
"jest-diff": "29.7.0",
|
"jest-diff": "29.7.0",
|
||||||
"typescript": "4.9.4"
|
"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": "rimraf types && tsc",
|
|
||||||
"build:esm": "rimraf dist && node ../../scripts/buildPackage.js && yarn gen:types"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -444,11 +444,6 @@ export interface AppState {
|
|||||||
// as elements are unlocked, we remove the groupId from the elements
|
// as elements are unlocked, we remove the groupId from the elements
|
||||||
// and also remove groupId from this map
|
// and also remove groupId from this map
|
||||||
lockedMultiSelections: { [groupId: string]: true };
|
lockedMultiSelections: { [groupId: string]: true };
|
||||||
cropPositionMovement: {
|
|
||||||
croppingElementId?: string;
|
|
||||||
enabled: boolean;
|
|
||||||
directionLock: "x" | "y" | null;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SearchMatch = {
|
export type SearchMatch = {
|
||||||
@@ -802,11 +797,6 @@ export type PointerDownState = Readonly<{
|
|||||||
boxSelection: {
|
boxSelection: {
|
||||||
hasOccurred: boolean;
|
hasOccurred: boolean;
|
||||||
};
|
};
|
||||||
cropPositionMovement: {
|
|
||||||
croppingElementId?: string;
|
|
||||||
enabled: boolean;
|
|
||||||
directionLock: "x" | "y" | null;
|
|
||||||
};
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type UnsubscribeCallback = () => void;
|
export type UnsubscribeCallback = () => void;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@excalidraw/math",
|
"name": "@excalidraw/math",
|
||||||
"version": "0.1.0",
|
"version": "0.18.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./dist/types/math/src/index.d.ts",
|
"types": "./dist/types/math/src/index.d.ts",
|
||||||
"main": "./dist/prod/index.js",
|
"main": "./dist/prod/index.js",
|
||||||
@@ -13,7 +13,10 @@
|
|||||||
"default": "./dist/prod/index.js"
|
"default": "./dist/prod/index.js"
|
||||||
},
|
},
|
||||||
"./*": {
|
"./*": {
|
||||||
"types": "./dist/types/math/src/*.d.ts"
|
"types": "./dist/types/math/src/*.d.ts",
|
||||||
|
"development": "./dist/dev/index.js",
|
||||||
|
"production": "./dist/prod/index.js",
|
||||||
|
"default": "./dist/prod/index.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@@ -56,5 +59,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"gen:types": "rimraf types && tsc",
|
"gen:types": "rimraf types && tsc",
|
||||||
"build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types"
|
"build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@excalidraw/common": "0.18.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
const { exec, execSync } = require("child_process");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
const core = require("@actions/core");
|
|
||||||
|
|
||||||
const excalidrawDir = `${__dirname}/../packages/excalidraw`;
|
|
||||||
const excalidrawPackage = `${excalidrawDir}/package.json`;
|
|
||||||
const pkg = require(excalidrawPackage);
|
|
||||||
const isPreview = process.argv.slice(2)[0] === "preview";
|
|
||||||
|
|
||||||
const getShortCommitHash = () => {
|
|
||||||
return execSync("git rev-parse --short HEAD").toString().trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
const publish = () => {
|
|
||||||
const tag = isPreview ? "preview" : "next";
|
|
||||||
|
|
||||||
try {
|
|
||||||
execSync(`yarn --frozen-lockfile`);
|
|
||||||
execSync(`yarn run build:esm`, { cwd: excalidrawDir });
|
|
||||||
execSync(`yarn --cwd ${excalidrawDir} publish --tag ${tag}`);
|
|
||||||
console.info(`Published ${pkg.name}@${tag}🎉`);
|
|
||||||
core.setOutput(
|
|
||||||
"result",
|
|
||||||
`**Preview version has been shipped** :rocket:
|
|
||||||
You can use [@excalidraw/excalidraw@${pkg.version}](https://www.npmjs.com/package/@excalidraw/excalidraw/v/${pkg.version}) for testing!`,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
core.setOutput("result", "package couldn't be published :warning:!");
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// get files changed between prev and head commit
|
|
||||||
exec(`git diff --name-only HEAD^ HEAD`, async (error, stdout, stderr) => {
|
|
||||||
if (error || stderr) {
|
|
||||||
console.error(error);
|
|
||||||
core.setOutput("result", ":warning: Package couldn't be published!");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
const changedFiles = stdout.trim().split("\n");
|
|
||||||
|
|
||||||
const excalidrawPackageFiles = changedFiles.filter((file) => {
|
|
||||||
return (
|
|
||||||
file.indexOf("packages/excalidraw") >= 0 ||
|
|
||||||
file.indexOf("buildPackage.js") > 0
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if (!excalidrawPackageFiles.length) {
|
|
||||||
console.info("Skipping release as no valid diff found");
|
|
||||||
core.setOutput("result", "Skipping release as no valid diff found");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update package.json
|
|
||||||
let version = `${pkg.version}-${getShortCommitHash()}`;
|
|
||||||
|
|
||||||
// update readme
|
|
||||||
|
|
||||||
if (isPreview) {
|
|
||||||
// use pullNumber-commithash as the version for preview
|
|
||||||
const pullRequestNumber = process.argv.slice(3)[0];
|
|
||||||
version = `${pkg.version}-${pullRequestNumber}-${getShortCommitHash()}`;
|
|
||||||
}
|
|
||||||
pkg.version = version;
|
|
||||||
|
|
||||||
fs.writeFileSync(excalidrawPackage, JSON.stringify(pkg, null, 2), "utf8");
|
|
||||||
|
|
||||||
console.info("Publish in progress...");
|
|
||||||
publish();
|
|
||||||
});
|
|
||||||
@@ -11,12 +11,9 @@ const getConfig = (outdir) => ({
|
|||||||
entryNames: "[name]",
|
entryNames: "[name]",
|
||||||
assetNames: "[dir]/[name]",
|
assetNames: "[dir]/[name]",
|
||||||
alias: {
|
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"),
|
"@excalidraw/utils": path.resolve(__dirname, "../packages/utils/src"),
|
||||||
},
|
},
|
||||||
|
external: ["@excalidraw/common", "@excalidraw/element", "@excalidraw/math"],
|
||||||
});
|
});
|
||||||
|
|
||||||
function buildDev(config) {
|
function buildDev(config) {
|
||||||
|
|||||||
@@ -28,12 +28,9 @@ const getConfig = (outdir) => ({
|
|||||||
assetNames: "[dir]/[name]",
|
assetNames: "[dir]/[name]",
|
||||||
chunkNames: "[dir]/[name]-[hash]",
|
chunkNames: "[dir]/[name]-[hash]",
|
||||||
alias: {
|
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"),
|
"@excalidraw/utils": path.resolve(__dirname, "../packages/utils/src"),
|
||||||
},
|
},
|
||||||
|
external: ["@excalidraw/common", "@excalidraw/element", "@excalidraw/math"],
|
||||||
loader: {
|
loader: {
|
||||||
".woff2": "file",
|
".woff2": "file",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
const fs = require("fs");
|
|
||||||
const util = require("util");
|
|
||||||
|
|
||||||
const exec = util.promisify(require("child_process").exec);
|
|
||||||
const updateChangelog = require("./updateChangelog");
|
|
||||||
|
|
||||||
const excalidrawDir = `${__dirname}/../packages/excalidraw/`;
|
|
||||||
const excalidrawPackage = `${excalidrawDir}/package.json`;
|
|
||||||
|
|
||||||
const updatePackageVersion = (nextVersion) => {
|
|
||||||
const pkg = require(excalidrawPackage);
|
|
||||||
pkg.version = nextVersion;
|
|
||||||
const content = `${JSON.stringify(pkg, null, 2)}\n`;
|
|
||||||
fs.writeFileSync(excalidrawPackage, content, "utf-8");
|
|
||||||
};
|
|
||||||
|
|
||||||
const prerelease = async (nextVersion) => {
|
|
||||||
try {
|
|
||||||
await updateChangelog(nextVersion);
|
|
||||||
updatePackageVersion(nextVersion);
|
|
||||||
await exec(`git add -u`);
|
|
||||||
await exec(
|
|
||||||
`git commit -m "docs: release @excalidraw/excalidraw@${nextVersion} 🎉"`,
|
|
||||||
);
|
|
||||||
|
|
||||||
console.info("Done!");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const nextVersion = process.argv.slice(2)[0];
|
|
||||||
if (!nextVersion) {
|
|
||||||
console.error("Pass the next version to release!");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
prerelease(nextVersion);
|
|
||||||
+230
-19
@@ -1,28 +1,239 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
const { execSync } = require("child_process");
|
const { execSync } = require("child_process");
|
||||||
|
|
||||||
const excalidrawDir = `${__dirname}/../packages/excalidraw`;
|
const updateChangelog = require("./updateChangelog");
|
||||||
const excalidrawPackage = `${excalidrawDir}/package.json`;
|
|
||||||
const pkg = require(excalidrawPackage);
|
|
||||||
|
|
||||||
const publish = () => {
|
// skipping utils for now, as it has independent release process
|
||||||
try {
|
const PACKAGES = ["common", "math", "element", "excalidraw"];
|
||||||
console.info("Installing the dependencies in root folder...");
|
const PACKAGES_DIR = path.resolve(__dirname, "../packages");
|
||||||
execSync(`yarn --frozen-lockfile`);
|
|
||||||
console.info("Installing the dependencies in excalidraw directory...");
|
/**
|
||||||
execSync(`yarn --frozen-lockfile`, { cwd: excalidrawDir });
|
* Returns the arguments for the release script.
|
||||||
console.info("Building ESM Package...");
|
*
|
||||||
execSync(`yarn run build:esm`, { cwd: excalidrawDir });
|
* Usage examples:
|
||||||
console.info("Publishing the package...");
|
* - yarn release --help -> prints this help message
|
||||||
execSync(`yarn --cwd ${excalidrawDir} publish`);
|
* - yarn release -> publishes `@excalidraw` packages with "test" tag and "-[hash]" version suffix
|
||||||
} catch (error) {
|
* - yarn release --tag=test -> same as above
|
||||||
console.error(error);
|
* - yarn release --tag=next -> publishes `@excalidraw` packages with "next" tag and version "-[hash]" suffix
|
||||||
|
* - yarn release --tag=next --non-interactive -> skips interactive prompts (runs on CI/CD), otherwise same as above
|
||||||
|
* - yarn release --tag=latest --version=0.19.0 -> publishes `@excalidraw` packages with "latest" tag and version "0.19.0" & prepares changelog for the release
|
||||||
|
*
|
||||||
|
* @returns [tag, version, nonInteractive]
|
||||||
|
*/
|
||||||
|
const getArguments = () => {
|
||||||
|
let tag = "test";
|
||||||
|
let version = "";
|
||||||
|
let nonInteractive = false;
|
||||||
|
|
||||||
|
for (const argument of process.argv.slice(2)) {
|
||||||
|
if (/--help/.test(argument)) {
|
||||||
|
console.info(`Available arguments:
|
||||||
|
--tag=<tag> -> (optional) "test" (default), "next" for auto release, "latest" for stable release
|
||||||
|
--version=<version> -> (optional) for "next" and "test", (required) for "latest" i.e. "0.19.0"
|
||||||
|
--non-interactive -> (optional) disables interactive prompts`);
|
||||||
|
|
||||||
|
console.info(`\nUsage examples:
|
||||||
|
- yarn release -> publishes \`@excalidraw\` packages with "test" tag and "-[hash]" version suffix
|
||||||
|
- yarn release --tag=test -> same as above
|
||||||
|
- yarn release --tag=next -> publishes \`@excalidraw\` packages with "next" tag and version "-[hash]" suffix
|
||||||
|
- yarn release --tag=next --non-interactive -> skips interactive prompts (runs on CI/CD), otherwise same as above
|
||||||
|
- yarn release --tag=latest --version=0.19.0 -> publishes \`@excalidraw\` packages with "latest" tag and version "0.19.0" & prepares changelog for the release`);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/--tag=/.test(argument)) {
|
||||||
|
tag = argument.split("=")[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/--version=/.test(argument)) {
|
||||||
|
version = argument.split("=")[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/--non-interactive/.test(argument)) {
|
||||||
|
nonInteractive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag !== "latest" && tag !== "next" && tag !== "test") {
|
||||||
|
console.error(`Unsupported tag "${tag}", use "latest", "next" or "test".`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag === "latest" && !version) {
|
||||||
|
console.error("Pass the version to make the latest stable release!");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!version) {
|
||||||
|
// set the next version based on the excalidraw package version + commit hash
|
||||||
|
const excalidrawPackageVersion = require(getPackageJsonPath(
|
||||||
|
"excalidraw",
|
||||||
|
)).version;
|
||||||
|
|
||||||
|
const hash = getShortCommitHash();
|
||||||
|
|
||||||
|
if (!excalidrawPackageVersion.includes(hash)) {
|
||||||
|
version = `${excalidrawPackageVersion}-${hash}`;
|
||||||
|
} else {
|
||||||
|
// ensuring idempotency
|
||||||
|
version = excalidrawPackageVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(`Running with tag "${tag}" and version "${version}"...`);
|
||||||
|
|
||||||
|
return [tag, version, nonInteractive];
|
||||||
|
};
|
||||||
|
|
||||||
|
const validatePackageName = (packageName) => {
|
||||||
|
if (!PACKAGES.includes(packageName)) {
|
||||||
|
console.error(`Package "${packageName}" not found!`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const release = () => {
|
const getPackageJsonPath = (packageName) => {
|
||||||
publish();
|
validatePackageName(packageName);
|
||||||
console.info(`Published ${pkg.version}!`);
|
return path.resolve(PACKAGES_DIR, packageName, "package.json");
|
||||||
};
|
};
|
||||||
|
|
||||||
release();
|
const updatePackageJsons = (nextVersion) => {
|
||||||
|
const packageJsons = new Map();
|
||||||
|
|
||||||
|
for (const packageName of PACKAGES) {
|
||||||
|
const pkg = require(getPackageJsonPath(packageName));
|
||||||
|
|
||||||
|
pkg.version = nextVersion;
|
||||||
|
|
||||||
|
if (pkg.dependencies) {
|
||||||
|
for (const dependencyName of PACKAGES) {
|
||||||
|
if (!pkg.dependencies[`@excalidraw/${dependencyName}`]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.dependencies[`@excalidraw/${dependencyName}`] = nextVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packageJsons.set(packageName, `${JSON.stringify(pkg, null, 2)}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// modify once, to avoid inconsistent state
|
||||||
|
for (const packageName of PACKAGES) {
|
||||||
|
const content = packageJsons.get(packageName);
|
||||||
|
fs.writeFileSync(getPackageJsonPath(packageName), content, "utf-8");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getShortCommitHash = () => {
|
||||||
|
return execSync("git rev-parse --short HEAD").toString().trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
const askToCommit = (tag, nextVersion) => {
|
||||||
|
if (tag !== "latest") {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const rl = require("readline").createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
rl.question(
|
||||||
|
"Do you want to commit these changes to git? (Y/n): ",
|
||||||
|
(answer) => {
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
if (answer.toLowerCase() === "y") {
|
||||||
|
execSync(`git add -u`);
|
||||||
|
execSync(
|
||||||
|
`git commit -m "chore: release @excalidraw/excalidraw@${nextVersion} 🎉"`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
"Skipping commit. Don't forget to commit manually later!",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildPackages = () => {
|
||||||
|
console.info("Running yarn install...");
|
||||||
|
execSync(`yarn --frozen-lockfile`, { stdio: "inherit" });
|
||||||
|
|
||||||
|
console.info("Removing existing build artifacts...");
|
||||||
|
execSync(`yarn rm:build`, { stdio: "inherit" });
|
||||||
|
|
||||||
|
for (const packageName of PACKAGES) {
|
||||||
|
console.info(`Building "@excalidraw/${packageName}"...`);
|
||||||
|
execSync(`yarn run build:esm`, {
|
||||||
|
cwd: path.resolve(PACKAGES_DIR, packageName),
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const askToPublish = (tag, version) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const rl = require("readline").createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
rl.question(
|
||||||
|
"Do you want to publish these changes to npm? (Y/n): ",
|
||||||
|
(answer) => {
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
if (answer.toLowerCase() === "y") {
|
||||||
|
publishPackages(tag, version);
|
||||||
|
} else {
|
||||||
|
console.info("Skipping publish.");
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const publishPackages = (tag, version) => {
|
||||||
|
for (const packageName of PACKAGES) {
|
||||||
|
execSync(`yarn publish --tag ${tag}`, {
|
||||||
|
cwd: path.resolve(PACKAGES_DIR, packageName),
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.info(
|
||||||
|
`Published "@excalidraw/${packageName}@${tag}" with version "${version}"! 🎉`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** main */
|
||||||
|
(async () => {
|
||||||
|
const [tag, version, nonInteractive] = getArguments();
|
||||||
|
|
||||||
|
buildPackages();
|
||||||
|
|
||||||
|
if (tag === "latest") {
|
||||||
|
await updateChangelog(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePackageJsons(version);
|
||||||
|
|
||||||
|
if (nonInteractive) {
|
||||||
|
publishPackages(tag, version);
|
||||||
|
} else {
|
||||||
|
await askToCommit(tag, version);
|
||||||
|
await askToPublish(tag, version);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|||||||
@@ -20,14 +20,16 @@ const headerForType = {
|
|||||||
perf: "Performance",
|
perf: "Performance",
|
||||||
build: "Build",
|
build: "Build",
|
||||||
};
|
};
|
||||||
|
|
||||||
const badCommits = [];
|
const badCommits = [];
|
||||||
const getCommitHashForLastVersion = async () => {
|
const getCommitHashForLastVersion = async () => {
|
||||||
try {
|
try {
|
||||||
const commitMessage = `"release @excalidraw/excalidraw@${lastVersion}"`;
|
const commitMessage = `"release @excalidraw/excalidraw"`;
|
||||||
const { stdout } = await exec(
|
const { stdout } = await exec(
|
||||||
`git log --format=format:"%H" --grep=${commitMessage}`,
|
`git log --format=format:"%H" --grep=${commitMessage}`,
|
||||||
);
|
);
|
||||||
return stdout;
|
// take commit hash from latest release
|
||||||
|
return stdout.split(/\r?\n/)[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user