Enforce readonly mode (#714)

* Enforce readonly mode

* Fix test for React 18

* Move to dev
This commit is contained in:
Steve Ruiz 2022-06-09 16:00:47 +01:00 committed by GitHub
parent d919bd273e
commit d52d91b367
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 179 additions and 22 deletions

View file

@ -47,6 +47,7 @@
"@radix-ui/react-icons": "^1.1.1",
"@radix-ui/react-tooltip": "^0.1.7",
"@stitches/react": "^1.2.8",
"@testing-library/react": "^13.3.0",
"@tldraw/core": "^1.14.0-next.0",
"@tldraw/intersect": "^1.7.1",
"@tldraw/vec": "^1.7.0",
@ -61,7 +62,7 @@
},
"devDependencies": {
"@swc-node/jest": "^1.4.3",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.2",
"@tldraw/core": "*",
"@tldraw/intersect": "*",
@ -105,4 +106,4 @@
}
},
"gitHead": "4b1137849ad07da36fc8f0f19cb64e7535a79296"
}
}

View file

@ -223,6 +223,11 @@ export function Tldraw({
// Toggle the app's readOnly mode when the `readOnly` prop changes.
React.useEffect(() => {
app.readOnly = readOnly
if (!readOnly) {
app.selectNone()
app.cancelSession()
app.setEditingId()
}
}, [app, readOnly])
// Toggle the app's darkMode when the `darkMode` prop changes.

View file

@ -1758,9 +1758,11 @@ export class TldrawApp extends StateManager<TDSnapshot> {
e?.preventDefault()
this.copy(ids, e)
if (!this.readOnly) {
this.delete(ids)
}
return this
}
@ -1769,6 +1771,8 @@ export class TldrawApp extends StateManager<TDSnapshot> {
* @param ids The ids of the shapes to copy.
*/
copy = (ids = this.selectedIds, e?: ClipboardEvent): this => {
// Allow when in readOnly mode
e?.preventDefault()
this.clipboard = this.getClipboard(ids)

View file

@ -36,8 +36,9 @@ export class DrawTool extends BaseTool {
/* ----------------- Event Handlers ----------------- */
onPointerDown: TLPointerEventHandler = (info) => {
onPointerDown: TLPointerEventHandler = info => {
if (this.status !== Status.Idle) return
if (this.app.readOnly) return
const {
currentPoint,
appState: { currentPageId, currentStyle },
@ -66,6 +67,8 @@ export class DrawTool extends BaseTool {
}
onPointerMove: TLPointerEventHandler = () => {
if (this.app.readOnly) return
switch (this.status) {
case Status.Extending:
case Status.Creating: {

View file

@ -10,6 +10,7 @@ export class EllipseTool extends BaseTool {
/* ----------------- Event Handlers ----------------- */
onPointerDown: TLPointerEventHandler = () => {
if (this.app.readOnly) return
if (this.status !== Status.Idle) return
const {

View file

@ -18,12 +18,14 @@ export class EraseTool extends BaseTool {
/* ----------------- Event Handlers ----------------- */
onPointerDown: TLPointerEventHandler = () => {
if (this.app.readOnly) return
if (this.status !== Status.Idle) return
this.setStatus(Status.Pointing)
}
onPointerMove: TLPointerEventHandler = (info) => {
if (this.app.readOnly) return
switch (this.status) {
case Status.Pointing: {
if (Vec.dist(info.origin, info.point) > DEAD_ZONE) {
@ -40,6 +42,7 @@ export class EraseTool extends BaseTool {
}
onPointerUp: TLPointerEventHandler = () => {
if (this.app.readOnly) return
switch (this.status) {
case Status.Pointing: {
const shapeIdsAtPoint = this.app.shapes

View file

@ -10,6 +10,7 @@ export class LineTool extends BaseTool {
/* ----------------- Event Handlers ----------------- */
onPointerDown: TLPointerEventHandler = () => {
if (this.app.readOnly) return
if (this.status !== Status.Idle) return
const {

View file

@ -10,6 +10,7 @@ export class RectangleTool extends BaseTool {
/* ----------------- Event Handlers ----------------- */
onPointerDown: TLPointerEventHandler = () => {
if (this.app.readOnly) return
if (this.status !== Status.Idle) return
const {

View file

@ -50,7 +50,7 @@ export class SelectTool extends BaseTool<Status> {
/* --------------------- Methods -------------------- */
private deselect(id: string) {
this.app.select(...this.app.selectedIds.filter((oid) => oid !== id))
this.app.select(...this.app.selectedIds.filter(oid => oid !== id))
}
private select(id: string) {
@ -59,7 +59,7 @@ export class SelectTool extends BaseTool<Status> {
private pushSelect(id: string) {
const shape = this.app.getShape(id)
this.app.select(...this.app.selectedIds.filter((oid) => oid !== shape.parentId), id)
this.app.select(...this.app.selectedIds.filter(oid => oid !== shape.parentId), id)
}
private selectNone() {
@ -77,7 +77,7 @@ export class SelectTool extends BaseTool<Status> {
clonePaint = (point: number[]) => {
if (this.app.selectedIds.length === 0) return
const shapes = this.app.selectedIds.map((id) => this.app.getShape(id))
const shapes = this.app.selectedIds.map(id => this.app.getShape(id))
const bounds = Utils.expandBounds(Utils.getCommonBounds(shapes.map(TLDR.getBounds)), 16)
@ -92,7 +92,7 @@ export class SelectTool extends BaseTool<Status> {
const centeredBounds = Utils.centerBounds(bounds, gridPoint)
const hit = this.app.shapes.some((shape) =>
const hit = this.app.shapes.some(shape =>
TLDR.getShapeUtil(shape).hitTestBounds(shape, centeredBounds)
)
@ -187,6 +187,8 @@ export class SelectTool extends BaseTool<Status> {
break
}
case 'Tab': {
if (this.app.readOnly) return
if (
!this.app.pageState.editingId &&
this.status === Status.Idle &&
@ -213,6 +215,8 @@ export class SelectTool extends BaseTool<Status> {
break
}
case 'Enter': {
if (this.app.readOnly) return
const { pageState } = this.app
if (pageState.selectedIds.length === 1 && !pageState.editingId) {
this.app.setEditingId(pageState.selectedIds[0])
@ -239,7 +243,8 @@ export class SelectTool extends BaseTool<Status> {
// Pointer Events (generic)
onPointerMove: TLPointerEventHandler = (info, e) => {
onPointerMove: TLPointerEventHandler = () => {
if (this.app.readOnly) return
const { originPoint, currentPoint } = this.app
switch (this.status) {
@ -260,7 +265,7 @@ export class SelectTool extends BaseTool<Status> {
} else {
// Stat a transform session
this.setStatus(Status.Transforming)
const idsToTransform = this.app.selectedIds.flatMap((id) =>
const idsToTransform = this.app.selectedIds.flatMap(id =>
TLDR.getDocumentBranch(this.app.state, id, this.app.currentPageId)
)
if (idsToTransform.length === 1) {
@ -367,7 +372,7 @@ export class SelectTool extends BaseTool<Status> {
}
}
onPointerUp: TLPointerEventHandler = (info) => {
onPointerUp: TLPointerEventHandler = info => {
if (this.status === Status.TranslatingClone || this.status === Status.PointingClone) {
if (this.pointedId) {
this.app.completeSession()
@ -420,6 +425,7 @@ export class SelectTool extends BaseTool<Status> {
// Canvas
onDoubleClickCanvas: TLCanvasEventHandler = () => {
if (this.app.readOnly) return
// Needs debugging
// const { currentPoint } = this.app
// this.app.selectTool(TDShapeType.Text)
@ -523,7 +529,9 @@ export class SelectTool extends BaseTool<Status> {
}
}
onDoubleClickShape: TLPointerEventHandler = (info) => {
onDoubleClickShape: TLPointerEventHandler = info => {
if (this.app.readOnly) return
const shape = this.app.getShape(info.target)
if (shape.isLocked) {
@ -548,17 +556,17 @@ export class SelectTool extends BaseTool<Status> {
this.app.select(info.target)
}
onRightPointShape: TLPointerEventHandler = (info) => {
onRightPointShape: TLPointerEventHandler = info => {
if (!this.app.isSelected(info.target)) {
this.app.select(info.target)
}
}
onHoverShape: TLPointerEventHandler = (info) => {
onHoverShape: TLPointerEventHandler = info => {
this.app.setHoveredId(info.target)
}
onUnhoverShape: TLPointerEventHandler = (info) => {
onUnhoverShape: TLPointerEventHandler = info => {
const { currentPageId: oldCurrentPageId } = this.app
// Wait a frame; and if we haven't changed the hovered id,
@ -575,7 +583,7 @@ export class SelectTool extends BaseTool<Status> {
/* --------------------- Bounds --------------------- */
onPointBounds: TLBoundsEventHandler = (info) => {
onPointBounds: TLBoundsEventHandler = info => {
if (info.metaKey) {
if (!info.shiftKey) {
this.selectNone()
@ -604,12 +612,12 @@ export class SelectTool extends BaseTool<Status> {
/* ----------------- Bounds Handles ----------------- */
onPointBoundsHandle: TLBoundsHandleEventHandler = (info) => {
onPointBoundsHandle: TLBoundsHandleEventHandler = info => {
this.pointedBoundsHandle = info.target
this.setStatus(Status.PointingBoundsHandle)
}
onDoubleClickBoundsHandle: TLBoundsHandleEventHandler = (info) => {
onDoubleClickBoundsHandle: TLBoundsHandleEventHandler = info => {
switch (info.target) {
case 'center':
case 'left':
@ -642,12 +650,12 @@ export class SelectTool extends BaseTool<Status> {
/* --------------------- Handles -------------------- */
onPointHandle: TLPointerEventHandler = (info) => {
onPointHandle: TLPointerEventHandler = info => {
this.pointedHandleId = info.target as 'start' | 'end'
this.setStatus(Status.PointingHandle)
}
onDoubleClickHandle: TLPointerEventHandler = (info) => {
onDoubleClickHandle: TLPointerEventHandler = info => {
if (info.target === 'bend') {
const { selectedIds } = this.app
if (selectedIds.length !== 1) return
@ -670,7 +678,7 @@ export class SelectTool extends BaseTool<Status> {
/* ---------------------- Misc ---------------------- */
onShapeClone: TLShapeCloneHandler = (info) => {
onShapeClone: TLShapeCloneHandler = info => {
const selectedShapeId = this.app.selectedIds[0]
const clonedShape = this.getShapeClone(selectedShapeId, info.target)

View file

@ -13,6 +13,7 @@ export class StickyTool extends BaseTool {
/* ----------------- Event Handlers ----------------- */
onPointerDown: TLPointerEventHandler = () => {
if (this.app.readOnly) return
if (this.status === Status.Creating) {
this.setStatus(Status.Idle)
@ -58,6 +59,7 @@ export class StickyTool extends BaseTool {
}
onPointerUp: TLPointerEventHandler = () => {
if (this.app.readOnly) return
if (this.status === Status.Creating) {
this.setStatus(Status.Idle)
this.app.completeSession()

View file

@ -51,6 +51,7 @@ export class TextTool extends BaseTool {
}
onPointShape: TLPointerEventHandler = (info) => {
if (this.app.readOnly) return
const shape = this.app.getShape(info.target)
if (shape.type === TDShapeType.Text) {
this.setStatus(Status.Idle)
@ -59,6 +60,7 @@ export class TextTool extends BaseTool {
}
onShapeBlur = () => {
if (this.app.readOnly) return
this.stopEditingShape()
}
}

View file

@ -10,6 +10,7 @@ export class TriangleTool extends BaseTool {
/* ----------------- Event Handlers ----------------- */
onPointerDown: TLPointerEventHandler = () => {
if (this.app.readOnly) return
if (this.status !== Status.Idle) return
const {

129
yarn.lock
View file

@ -2193,6 +2193,22 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-checkbox@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-0.1.5.tgz#3a6bd54ba1720c8e5c03852acf460e35dfbe9da3"
integrity sha512-M8Y4dSXsKSbF+FryG5VvZKr/1MukMVG7swq9p5s7wYb8Rvn0UM0rQ5w8BWmSWSV4BL/gbJdhwVCznwXXlgZRZg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-label" "0.1.5"
"@radix-ui/react-presence" "0.1.2"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-use-size" "0.1.1"
"@radix-ui/react-collection@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-0.1.4.tgz#734061ffd5bb93e88889d49b87391a73a63824c9"
@ -2308,6 +2324,17 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-label@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.1.5.tgz#12cd965bfc983e0148121d4c99fb8e27a917c45c"
integrity sha512-Au9+n4/DhvjR0IHhvZ1LPdx/OW+3CGDie30ZyCkbSHIuLp4/CV4oPPGBwJ1vY99Jog3zyQhsGww9MXj8O9Aj/A==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-id" "0.1.5"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-menu@0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-0.1.6.tgz#7f9521a10f6a9cd819b33b33d5ed9538d79b2e75"
@ -2373,6 +2400,23 @@
"@babel/runtime" "^7.13.10"
"@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-radio-group@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-0.1.5.tgz#ca8a676123a18b44804aff10af46129e2c2b37c3"
integrity sha512-ybgHsmh/V2crKvK6xZ56dpPul7b+vyxcq7obWqHbr5W6Ca11wdm0E7lS0i/Y6pgfIKYOWIARmZYDpRMEeRCPOw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0"
"@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-context" "0.1.1"
"@radix-ui/react-label" "0.1.5"
"@radix-ui/react-presence" "0.1.2"
"@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-roving-focus" "0.1.5"
"@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-use-previous" "0.1.1"
"@radix-ui/react-use-size" "0.1.1"
"@radix-ui/react-roving-focus@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.1.5.tgz#cc48d17a36b56f253d54905b0fd60ee134cb97ee"
@ -2851,6 +2895,20 @@
lz-string "^1.4.4"
pretty-format "^27.0.2"
"@testing-library/dom@^8.5.0":
version "8.13.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5"
integrity sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
"@types/aria-query" "^4.2.0"
aria-query "^5.0.0"
chalk "^4.1.0"
dom-accessibility-api "^0.5.9"
lz-string "^1.4.4"
pretty-format "^27.0.2"
"@testing-library/jest-dom@^5.16.2":
version "5.16.2"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.2.tgz#f329b36b44aa6149cd6ced9adf567f8b6aa1c959"
@ -2866,6 +2924,21 @@
lodash "^4.17.15"
redent "^3.0.0"
"@testing-library/jest-dom@^5.16.4":
version "5.16.4"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd"
integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA==
dependencies:
"@babel/runtime" "^7.9.2"
"@types/testing-library__jest-dom" "^5.9.1"
aria-query "^5.0.0"
chalk "^3.0.0"
css "^3.0.0"
css.escape "^1.5.1"
dom-accessibility-api "^0.5.6"
lodash "^4.17.15"
redent "^3.0.0"
"@testing-library/react@^12.1.2":
version "12.1.2"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.2.tgz#f1bc9a45943461fa2a598bb4597df1ae044cfc76"
@ -2874,6 +2947,53 @@
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^8.0.0"
"@testing-library/react@^13.3.0":
version "13.3.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.3.0.tgz#bf298bfbc5589326bbcc8052b211f3bb097a97c5"
integrity sha512-DB79aA426+deFgGSjnf5grczDPiL4taK3hFaa+M5q7q20Kcve9eQottOG5kZ74KEr55v0tU2CQormSSDK87zYQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^8.5.0"
"@types/react-dom" "^18.0.0"
"@tldraw/core@*", "@tldraw/core@^1.13.1":
version "1.13.1"
resolved "https://registry.yarnpkg.com/@tldraw/core/-/core-1.13.1.tgz#b56f919b23f941dfe1842238131ccee7da19e2cd"
integrity sha512-tvQGi5Up2WriLVTtJbWNanchd03hUX5e3g/iRPptOacDlygQTX6XtfBa+4VA0F3vDwzoxeCTe2smpNMTbhKM1g==
dependencies:
"@tldraw/intersect" "^1.7.1"
"@tldraw/vec" "^1.7.0"
"@use-gesture/react" "^10.2.14"
mobx-react-lite "^3.2.3"
perfect-freehand "^1.1.0"
resize-observer-polyfill "^1.5.1"
"@tldraw/tldraw@*":
version "1.15.1"
resolved "https://registry.yarnpkg.com/@tldraw/tldraw/-/tldraw-1.15.1.tgz#f27cd992e96dd68f1ad3243e04c5964a19db6b2b"
integrity sha512-76RBv5urQAi/PBKfnENxfS/pZkwkJD5/LfCioreWAo8+ZYit0wRGzF8e5uii1INEibPpz/hgQz6W/4SU64m8vA==
dependencies:
"@radix-ui/react-alert-dialog" "^0.1.7"
"@radix-ui/react-checkbox" "^0.1.5"
"@radix-ui/react-context-menu" "^0.1.6"
"@radix-ui/react-dropdown-menu" "^0.1.6"
"@radix-ui/react-icons" "^1.1.1"
"@radix-ui/react-radio-group" "^0.1.5"
"@radix-ui/react-tooltip" "^0.1.7"
"@stitches/react" "^1.2.8"
"@tldraw/core" "^1.13.1"
"@tldraw/intersect" "^1.7.1"
"@tldraw/vec" "^1.7.0"
"@types/lz-string" "^1.3.34"
idb-keyval "^6.1.0"
lz-string "^1.4.4"
perfect-freehand "^1.1.0"
react-error-boundary "^3.1.4"
react-hotkey-hook "^1.0.2"
react-hotkeys-hook "^3.4.4"
tslib "^2.3.1"
zustand "^3.6.9"
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
@ -3116,7 +3236,7 @@
dependencies:
"@types/react" "*"
"@types/react-dom@^18.0.5":
"@types/react-dom@^18.0.0", "@types/react-dom@^18.0.5":
version "18.0.5"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a"
integrity sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==
@ -9509,6 +9629,11 @@ react-feather@^2.0.9:
dependencies:
prop-types "^15.7.2"
react-hotkey-hook@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-hotkey-hook/-/react-hotkey-hook-1.0.2.tgz#ca17a3f806092027eaaf41fd2f111afd9926e3ab"
integrity sha512-95GiOW8ORMqbBQ23+VHMF0giRmpiI8sFHPjbOR/e64zWI0QT+QO3Q/022c0HNBS/LrQsbGdjm64BNMah0WvlnA==
react-hotkeys-hook@^3.4.4:
version "3.4.4"
resolved "https://registry.yarnpkg.com/react-hotkeys-hook/-/react-hotkeys-hook-3.4.4.tgz#52ba5d8ef5e47cc2e776c70a9036d518e0993d51"
@ -10861,7 +10986,7 @@ tsconfig-replace-paths@^0.0.11:
globby "^10.0.1"
json5 "^2.2.0"
tslib@2.4.0, tslib@^2.4.0:
tslib@2.4.0, tslib@^2.3.1, tslib@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==