diff --git a/e2e/package.json b/e2e/package.json index 430656a69..dbd86d01e 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -34,6 +34,7 @@ "devDependencies": { "@sitespeed.io/edgedriver": "^112.0.1722-34", "@tldraw/editor": "workspace:*", + "@tldraw/indices": "workspace:*", "@tldraw/primitives": "workspace:*", "@types/mocha": "^10.0.1", "@types/sharp": "^0.31.1", diff --git a/e2e/test/specs/reorder.ts b/e2e/test/specs/reorder.ts index 88d7e40e9..7e0dfe9ea 100644 --- a/e2e/test/specs/reorder.ts +++ b/e2e/test/specs/reorder.ts @@ -1,16 +1,8 @@ +import { sortByIndex } from '@tldraw/indices' import { runtime, ui } from '../helpers' import { app } from '../helpers/ui' import { describe, it } from '../mocha-ext' -const sortByIndex = (a, b) => { - if (a.index < b.index) { - return -1 - } else if (a.index > b.index) { - return 1 - } - return 0 -} - describe('reorder', () => { const createShapes = async () => { await ui.tools.click('rectangle') diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 9e864b6cf..2134f18ca 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -29,6 +29,7 @@ }, "references": [ { "path": "../packages/editor" }, + { "path": "../packages/indices" }, { "path": "../packages/tlschema" }, { "path": "../packages/tlstore" }, { "path": "../packages/primitives" } diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md index 5f8ff5897..11c01cce1 100644 --- a/packages/editor/api-report.md +++ b/packages/editor/api-report.md @@ -17,6 +17,13 @@ import { EASINGS } from '@tldraw/primitives'; import { EmbedDefinition } from '@tldraw/tlschema'; import { EventEmitter } from 'eventemitter3'; import { getHashForString } from '@tldraw/utils'; +import { getIndexAbove } from '@tldraw/indices'; +import { getIndexBelow } from '@tldraw/indices'; +import { getIndexBetween } from '@tldraw/indices'; +import { getIndices } from '@tldraw/indices'; +import { getIndicesAbove } from '@tldraw/indices'; +import { getIndicesBelow } from '@tldraw/indices'; +import { getIndicesBetween } from '@tldraw/indices'; import { HistoryEntry } from '@tldraw/tlstore'; import { ID } from '@tldraw/tlstore'; import { MatLike } from '@tldraw/primitives'; @@ -33,6 +40,7 @@ import { SelectionEdge } from '@tldraw/primitives'; import { SelectionHandle } from '@tldraw/primitives'; import { SerializedSchema } from '@tldraw/tlstore'; import { Signal } from 'signia'; +import { sortByIndex } from '@tldraw/indices'; import { StoreSchema } from '@tldraw/tlstore'; import { StoreSnapshot } from '@tldraw/tlstore'; import { StoreValidator } from '@tldraw/tlstore'; @@ -754,32 +762,19 @@ export function getImageSizeFromSrc(dataURL: string): Promise<{ // @public export function getIncrementedName(name: string, others: string[]): string; -// @public (undocumented) -export function getIndexAbove(below: string): string; +export { getIndexAbove } -// @public (undocumented) -export function getIndexBelow(above: string): string; +export { getIndexBelow } -// @public (undocumented) -export function getIndexBetween(below: string, above?: string): string; +export { getIndexBetween } -// @public (undocumented) -export function getIndexGenerator(): () => string; +export { getIndices } -// @public (undocumented) -export function getIndices(n: number): string[]; +export { getIndicesAbove } -// @public (undocumented) -export function getIndicesAbove(below: string, n: number): string[]; +export { getIndicesBelow } -// @public (undocumented) -export function getIndicesBelow(above: string, n: number): string[]; - -// @public (undocumented) -export function getIndicesBetween(below: string | undefined, above: string | undefined, n: number): string[]; - -// @public (undocumented) -export function getMaxIndex(...indices: (string | undefined)[]): string; +export { getIndicesBetween } // @public export function getMediaAssetFromFile(file: File): Promise; @@ -883,9 +878,6 @@ export const ICON_SIZES: Record; // @public (undocumented) export const INDENT = " "; -// @public (undocumented) -export function indexGenerator(n?: number): Generator; - // @public (undocumented) export interface InitializingSyncedStore { // (undocumented) @@ -1470,15 +1462,7 @@ export function setRuntimeOverrides(input: Partial): void; // @public (undocumented) export function snapToGrid(n: number, gridSize: number): number; -// @public (undocumented) -export function sortById(a: T, b: T): -1 | 0 | 1; - -// @public (undocumented) -export function sortByIndex(a: T, b: T): -1 | 0 | 1; +export { sortByIndex } // @public (undocumented) export abstract class StateNode implements Partial { diff --git a/packages/editor/package.json b/packages/editor/package.json index fb1d1c614..44ac3ade7 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -45,6 +45,7 @@ "lint": "yarn run -T tsx ../../scripts/lint.ts" }, "dependencies": { + "@tldraw/indices": "workspace:*", "@tldraw/primitives": "workspace:*", "@tldraw/tlschema": "workspace:*", "@tldraw/tlstore": "workspace:*", diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 57bf1936d..69ffd9a3f 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -1,6 +1,16 @@ // Important! don't move this tlschema re-export to lib/index.ts, doing so causes esbuild to produce // incorrect output. https://github.com/evanw/esbuild/issues/1737 +export { + getIndexAbove, + getIndexBelow, + getIndexBetween, + getIndices, + getIndicesAbove, + getIndicesBelow, + getIndicesBetween, + sortByIndex, +} from '@tldraw/indices' // eslint-disable-next-line local/no-export-star export * from '@tldraw/tlschema' export { getHashForString } from '@tldraw/utils' @@ -243,20 +253,6 @@ export { hardResetApp } from './lib/utils/hard-reset' export { isAnimated, isGIF } from './lib/utils/is-gif-animated' export { setPropsForNextShape } from './lib/utils/props-for-next-shape' export { refreshPage } from './lib/utils/refresh-page' -export { - getIndexAbove, - getIndexBelow, - getIndexBetween, - getIndexGenerator, - getIndices, - getIndicesAbove, - getIndicesBelow, - getIndicesBetween, - getMaxIndex, - indexGenerator, - sortById, - sortByIndex, -} from './lib/utils/reordering/reordering' export { applyRotationToSnapshotShapes, getRotationSnapshot, diff --git a/packages/editor/src/lib/app/App.ts b/packages/editor/src/lib/app/App.ts index ab4968633..70ac619d4 100644 --- a/packages/editor/src/lib/app/App.ts +++ b/packages/editor/src/lib/app/App.ts @@ -1,24 +1,28 @@ import { - approximately, - areAnglesCompatible, + getIndexAbove, + getIndexBetween, + getIndices, + getIndicesAbove, + getIndicesBetween, + sortByIndex, +} from '@tldraw/indices' +import { Box2d, - clamp, EASINGS, - intersectPolygonPolygon, MatLike, Matrix2d, Matrix2dModel, PI2, - pointInPolygon, Vec2d, VecLike, + approximately, + areAnglesCompatible, + clamp, + intersectPolygonPolygon, + pointInPolygon, } from '@tldraw/primitives' import { Box2dModel, - createCustomShapeId, - createShapeId, - isShape, - isShapeId, TLArrowShape, TLAsset, TLAssetId, @@ -54,14 +58,26 @@ import { TLUserId, TLVideoAsset, Vec2dModel, + createCustomShapeId, + createShapeId, + isShape, + isShapeId, } from '@tldraw/tlschema' import { BaseRecord, ComputedCache, HistoryEntry } from '@tldraw/tlstore' -import { annotateError, compact, dedupe, deepCopy, partition, structuredClone } from '@tldraw/utils' +import { + annotateError, + compact, + dedupe, + deepCopy, + partition, + sortById, + structuredClone, +} from '@tldraw/utils' import { EventEmitter } from 'eventemitter3' import { nanoid } from 'nanoid' -import { atom, computed, EMPTY_ARRAY, transact } from 'signia' -import { TldrawEditorConfig } from '../config/TldrawEditorConfig' +import { EMPTY_ARRAY, atom, computed, transact } from 'signia' import { TLShapeDef } from '../config/TLShapeDefinition' +import { TldrawEditorConfig } from '../config/TldrawEditorConfig' import { ANIMATION_MEDIUM_MS, BLACKLISTED_PROPS, @@ -79,27 +95,18 @@ import { MAX_PAGES, MAX_SHAPES_PER_PAGE, MAX_ZOOM, - MIN_ZOOM, MINOR_NUDGE_FACTOR, + MIN_ZOOM, STYLES, SVG_PADDING, ZOOMS, } from '../constants' import { exportPatternSvgDefs } from '../hooks/usePattern' +import { WeakMapCache } from '../utils/WeakMapCache' import { dataUrlToFile, getMediaAssetFromFile } from '../utils/assets' import { getIncrementedName, uniqueId } from '../utils/data' import { setPropsForNextShape } from '../utils/props-for-next-shape' -import { - getIndexAbove, - getIndexBetween, - getIndices, - getIndicesAbove, - getIndicesBetween, - sortById, - sortByIndex, -} from '../utils/reordering/reordering' import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation' -import { WeakMapCache } from '../utils/WeakMapCache' import { arrowBindingsIndex } from './derivations/arrowBindingsIndex' import { parentsToChildrenWithIndexes } from './derivations/parentsToChildrenWithIndexes' import { shapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage' @@ -111,18 +118,18 @@ import { HistoryManager } from './managers/HistoryManager' import { SnapManager } from './managers/SnapManager' import { TextManager } from './managers/TextManager' import { TickManager } from './managers/TickManager' -import { TLExportColors } from './shapeutils/shared/TLExportColors' +import { TLArrowShapeDef } from './shapeutils/TLArrowUtil/TLArrowUtil' import { getCurvedArrowInfo } from './shapeutils/TLArrowUtil/arrow/curved-arrow' import { getArrowTerminalsInArrowSpace, getIsArrowStraight, } from './shapeutils/TLArrowUtil/arrow/shared' import { getStraightArrowInfo } from './shapeutils/TLArrowUtil/arrow/straight-arrow' -import { TLArrowShapeDef } from './shapeutils/TLArrowUtil/TLArrowUtil' import { TLFrameShapeDef } from './shapeutils/TLFrameUtil/TLFrameUtil' import { TLGroupShapeDef } from './shapeutils/TLGroupUtil/TLGroupUtil' import { TLResizeMode, TLShapeUtil } from './shapeutils/TLShapeUtil' import { TLTextShapeDef } from './shapeutils/TLTextUtil/TLTextUtil' +import { TLExportColors } from './shapeutils/shared/TLExportColors' import { RootState } from './statechart/RootState' import { StateNode } from './statechart/StateNode' import { TLClipboardModel } from './types/clipboard-types' diff --git a/packages/editor/src/lib/app/derivations/parentsToChildrenWithIndexes.test.ts b/packages/editor/src/lib/app/derivations/parentsToChildrenWithIndexes.test.ts index 5524dfff9..fc647de7c 100644 --- a/packages/editor/src/lib/app/derivations/parentsToChildrenWithIndexes.test.ts +++ b/packages/editor/src/lib/app/derivations/parentsToChildrenWithIndexes.test.ts @@ -1,6 +1,6 @@ +import { getIndexAbove, getIndexBetween } from '@tldraw/indices' import { createCustomShapeId } from '@tldraw/tlschema' import { TestApp } from '../../test/TestApp' -import { getIndexAbove, getIndexBetween } from '../../utils/reordering/reordering' let app: TestApp diff --git a/packages/editor/src/lib/app/managers/SnapManager.ts b/packages/editor/src/lib/app/managers/SnapManager.ts index 4b18acd36..fc5cc6c04 100644 --- a/packages/editor/src/lib/app/managers/SnapManager.ts +++ b/packages/editor/src/lib/app/managers/SnapManager.ts @@ -1,3 +1,4 @@ +import { sortByIndex } from '@tldraw/indices' import { Box2d, flipSelectionHandleX, @@ -15,7 +16,6 @@ import { TLLineShape, TLParentId, TLShape, TLShapeId, Vec2dModel } from '@tldraw import { compact, dedupe, deepCopy } from '@tldraw/utils' import { atom, computed, EMPTY_ARRAY } from 'signia' import { uniqueId } from '../../utils/data' -import { sortByIndex } from '../../utils/reordering/reordering' import type { App } from '../App' import { getSplineForLineShape, TLLineShapeDef } from '../shapeutils/TLLineUtil/TLLineUtil' diff --git a/packages/editor/src/lib/app/shapeutils/TLLineUtil/TLLineUtil.tsx b/packages/editor/src/lib/app/shapeutils/TLLineUtil/TLLineUtil.tsx index 0effc4616..6f26751f1 100644 --- a/packages/editor/src/lib/app/shapeutils/TLLineUtil/TLLineUtil.tsx +++ b/packages/editor/src/lib/app/shapeutils/TLLineUtil/TLLineUtil.tsx @@ -1,29 +1,29 @@ /* eslint-disable react-hooks/rules-of-hooks */ +import { getIndexBetween, sortByIndex } from '@tldraw/indices' import { CubicSpline2d, - getDrawLinePathData, - intersectLineSegmentPolyline, - pointNearToPolyline, Polyline2d, Vec2d, VecLike, + getDrawLinePathData, + intersectLineSegmentPolyline, + pointNearToPolyline, } from '@tldraw/primitives' import { - lineShapeMigrations, - lineShapeTypeValidator, TLHandle, TLLineShape, + lineShapeMigrations, + lineShapeTypeValidator, } from '@tldraw/tlschema' import { deepCopy } from '@tldraw/utils' import { SVGContainer } from '../../../components/SVGContainer' import { defineShape } from '../../../config/TLShapeDefinition' -import { getIndexBetween, sortByIndex } from '../../../utils/reordering/reordering' import { WeakMapCache } from '../../../utils/WeakMapCache' -import { getPerfectDashProps } from '../shared/getPerfectDashProps' +import { OnHandleChangeHandler, OnResizeHandler, TLShapeUtil } from '../TLShapeUtil' import { ShapeFill } from '../shared/ShapeFill' import { TLExportColors } from '../shared/TLExportColors' +import { getPerfectDashProps } from '../shared/getPerfectDashProps' import { useForceSolid } from '../shared/useForceSolid' -import { OnHandleChangeHandler, OnResizeHandler, TLShapeUtil } from '../TLShapeUtil' import { getLineDrawPath, getLineIndicatorPath, getLinePoints } from './components/getLinePath' import { getLineSvg } from './components/getLineSvg' diff --git a/packages/editor/src/lib/app/statechart/TLLineTool/children/Pointing.ts b/packages/editor/src/lib/app/statechart/TLLineTool/children/Pointing.ts index f9c8adcc7..997036889 100644 --- a/packages/editor/src/lib/app/statechart/TLLineTool/children/Pointing.ts +++ b/packages/editor/src/lib/app/statechart/TLLineTool/children/Pointing.ts @@ -1,7 +1,7 @@ +import { getIndexAbove, sortByIndex } from '@tldraw/indices' import { Matrix2d, Vec2d } from '@tldraw/primitives' import { TLHandle, TLLineShape, TLShapeId, TLShapeType, createShapeId } from '@tldraw/tlschema' import { last, structuredClone } from '@tldraw/utils' -import { getIndexAbove, sortByIndex } from '../../../../utils/reordering/reordering' import { TLEventHandlers, TLInterruptEvent } from '../../../types/event-types' import { StateNode } from '../../StateNode' import { TLLineTool } from '../TLLineTool' diff --git a/packages/editor/src/lib/app/statechart/TLSelectTool/children/DraggingHandle.ts b/packages/editor/src/lib/app/statechart/TLSelectTool/children/DraggingHandle.ts index cb2b6a861..78f14ce67 100644 --- a/packages/editor/src/lib/app/statechart/TLSelectTool/children/DraggingHandle.ts +++ b/packages/editor/src/lib/app/statechart/TLSelectTool/children/DraggingHandle.ts @@ -1,3 +1,4 @@ +import { sortByIndex } from '@tldraw/indices' import { Matrix2d, snapAngle, Vec2d } from '@tldraw/primitives' import { TLArrowShape, @@ -7,7 +8,6 @@ import { TLShapePartial, } from '@tldraw/tlschema' import { deepCopy } from '@tldraw/utils' -import { sortByIndex } from '../../../../utils/reordering/reordering' import { TLCancelEvent, TLEventHandlers, diff --git a/packages/editor/src/lib/test/tools/groups.test.ts b/packages/editor/src/lib/test/tools/groups.test.ts index 90696bd48..9fe89d938 100644 --- a/packages/editor/src/lib/test/tools/groups.test.ts +++ b/packages/editor/src/lib/test/tools/groups.test.ts @@ -1,3 +1,4 @@ +import { sortByIndex } from '@tldraw/indices' import { approximately, Box2d, VecLike } from '@tldraw/primitives' import { createCustomShapeId, @@ -16,7 +17,6 @@ import { TLDrawTool } from '../../app/statechart/TLDrawTool/TLDrawTool' import { TLEraserTool } from '../../app/statechart/TLEraserTool/TLEraserTool' import { TLLineTool } from '../../app/statechart/TLLineTool/TLLineTool' import { TLNoteTool } from '../../app/statechart/TLNoteTool/TLNoteTool' -import { sortByIndex } from '../../utils/reordering/reordering' import { TestApp } from '../TestApp' let i = 0 diff --git a/packages/editor/src/lib/utils/reordering/reordering.ts b/packages/editor/src/lib/utils/reordering/reordering.ts deleted file mode 100644 index 1157a36fc..000000000 --- a/packages/editor/src/lib/utils/reordering/reordering.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { generateKeyBetween, generateNKeysBetween } from './dgreensp' - -/** @public */ -export function getIndicesBetween(below: string | undefined, above: string | undefined, n: number) { - return generateNKeysBetween(below, above, n) -} - -/** @public */ -export function getIndicesAbove(below: string, n: number) { - return generateNKeysBetween(below, undefined, n) -} - -/** @public */ -export function getIndicesBelow(above: string, n: number) { - return generateNKeysBetween(undefined, above, n) -} - -/** @public */ -export function getIndexBetween(below: string, above?: string) { - return generateNKeysBetween(below, above, 1)[0] -} - -/** @public */ -export function getIndexAbove(below: string) { - return generateNKeysBetween(below, undefined, 1)[0] -} - -/** @public */ -export function getIndexBelow(above: string) { - return generateNKeysBetween(undefined, above, 1)[0] -} - -/** @public */ -export function getIndices(n: number) { - return ['a1', ...generateNKeysBetween('a1', undefined, n)] -} - -/** @public */ -export function getIndexGenerator() { - let order = 'a1' - return () => { - order = generateKeyBetween(order, undefined) - return order - } -} - -/** @public */ -export function* indexGenerator(n = 1) { - let order = 'a1' - let i = 0 - while (i < n) { - i++ - order = generateKeyBetween(order, undefined) - yield order - } -} - -/** @public */ -export function sortByIndex(a: T, b: T) { - if (a.index < b.index) { - return -1 - } else if (a.index > b.index) { - return 1 - } - return 0 -} - -/** @public */ -export function sortById(a: T, b: T) { - if (a.id < b.id) { - return -1 - } else if (a.id > b.id) { - return 1 - } - return 0 -} - -/** @public */ -export function getMaxIndex(...indices: (string | undefined)[]): string { - return indices.reduce((acc, curr) => (!curr ? acc : acc! < curr ? curr : acc), 'a1')! -} diff --git a/packages/editor/tsconfig.json b/packages/editor/tsconfig.json index 93e3fdb01..e07879d98 100644 --- a/packages/editor/tsconfig.json +++ b/packages/editor/tsconfig.json @@ -15,6 +15,7 @@ { "path": "../tlschema" }, { "path": "../tlstore" }, { "path": "../tlvalidate" }, - { "path": "../utils" } + { "path": "../utils" }, + { "path": "../indices" } ] } diff --git a/packages/indices/CHANGELOG.md b/packages/indices/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/indices/LICENSE b/packages/indices/LICENSE new file mode 100644 index 000000000..4f227c380 --- /dev/null +++ b/packages/indices/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +Copyright 2023 tldraw GB Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/indices/README.md b/packages/indices/README.md new file mode 100644 index 000000000..1fd048535 --- /dev/null +++ b/packages/indices/README.md @@ -0,0 +1,77 @@ +# @tldraw/indices + +Welcome to **indices** by [tldraw](https://tldraw.dev). This library provides functions for features that rely on fractional indexing. It is based on the observable [Implementing Fractional Indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) by [@DavidG](https://twitter.com/DavidG). + +Fractional indexing is a method for creating indices between other indices without running into precision errors. This is useful when items are modeled as object maps rather than as arrays. + +For example, imagine a stack of 5 cards: + +```ts +const cards = { + A: 0, + B: 1, + C: 2, + D: 3, + E: 4, +} +``` + +In a naive implementation of indexing, moving card E to the "bottom" of the stack would mean changing every item in the map. + +```ts +const cards = { + A: 1, + B: 2, + C: 3, + D: 4, + E: 0, // <-- moved to back +} +``` + +each with an index between `0` and `51`. In a naive implementation of indices, moving the top card (index `51`) to the bottom of the deck would mean changing its index to `0`, changing the previous bottom card's index to `1`, and so forth incrementing every other card's index by one. + +## Installation + +```bash +npm i @tldraw/indices +# or +yarn add @tldraw/indices +``` + +## Usage + +### getIndexBetween + +Get an index between two other indices. + +### getIndexAbove + +Get an index above a given index. + +### getIndexBelow + +Get an index below a given index. + +### getIndicesBetween + +Get a number of indices between two indices. + +### getIndicesAbove + +Get a number of indices above a given index. + +### getIndicesBelow + +Get a number of indices below a given index. + +### getIndices + +Get an array of indices with a given length. + +### `sortByIndex` + +Sort an array of objects by their `index` property. + +## License + +The source code in this repository (as well as our 2.0+ distributions and releases) are currently licensed under Apache-2.0. These licenses are subject to change in our upcoming 2.0 release. If you are planning to use tldraw in a commercial product, please reach out at [hello@tldraw.com](mailto://hello@tldraw.com). diff --git a/packages/indices/api-extractor.json b/packages/indices/api-extractor.json new file mode 100644 index 000000000..f1ed80e93 --- /dev/null +++ b/packages/indices/api-extractor.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "extends": "../../config/api-extractor.json" +} diff --git a/packages/indices/api-report.md b/packages/indices/api-report.md new file mode 100644 index 000000000..85076cc70 --- /dev/null +++ b/packages/indices/api-report.md @@ -0,0 +1,35 @@ +## API Report File for "@tldraw/indices" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +// @public +export function getIndexAbove(below: string): string; + +// @public +export function getIndexBelow(above: string): string; + +// @public +export function getIndexBetween(below: string, above?: string): string; + +// @public +export function getIndices(n: number, start?: string): string[]; + +// @public +export function getIndicesAbove(below: string, n: number): string[]; + +// @public +export function getIndicesBelow(above: string, n: number): string[]; + +// @public +export function getIndicesBetween(below: string | undefined, above: string | undefined, n: number): string[]; + +// @public +export function sortByIndex(a: T, b: T): -1 | 0 | 1; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/indices/package.json b/packages/indices/package.json new file mode 100644 index 000000000..5abae1108 --- /dev/null +++ b/packages/indices/package.json @@ -0,0 +1,58 @@ +{ + "name": "@tldraw/indices", + "description": "A tiny little drawing app (fractional indices).", + "version": "2.0.0-alpha.12", + "packageManager": "yarn@3.5.0", + "author": { + "name": "tldraw GB Ltd.", + "email": "hello@tldraw.com" + }, + "homepage": "https://tldraw.dev", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/tldraw/tldraw" + }, + "bugs": { + "url": "https://github.com/tldraw/tldraw/issues" + }, + "keywords": [ + "tldraw", + "drawing", + "app", + "development", + "whiteboard", + "canvas", + "infinite", + "fractional", + "index" + ], + "/* NOTE */": "These `main` and `types` fields are rewritten by the build script. They are not the actual values we publish", + "main": "./src/index.ts", + "types": "./.tsbuild/index.d.ts", + "/* GOTCHA */": "files will include ./dist and index.d.ts by default, add any others you want to include in here", + "files": [], + "scripts": { + "test": "lazy inherit", + "test-coverage": "lazy inherit", + "build": "yarn run -T tsx ../../scripts/build-package.ts", + "build-api": "yarn run -T tsx ../../scripts/build-api.ts", + "prepack": "yarn run -T tsx ../../scripts/prepack.ts", + "postpack": "../../scripts/postpack.sh", + "pack-tarball": "yarn pack", + "lint": "yarn run -T tsx ../../scripts/lint.ts" + }, + "jest": { + "preset": "config/jest/node", + "moduleNameMapper": { + "^~(.*)": "/src/$1" + }, + "transformIgnorePatterns": [ + "node_modules/(?!(nanoid|escape-string-regexp)/)" + ] + }, + "devDependencies": { + "benchmark": "^2.1.4", + "lazyrepo": "0.0.0-alpha.26" + } +} diff --git a/packages/indices/src/index.ts b/packages/indices/src/index.ts new file mode 100644 index 000000000..020568622 --- /dev/null +++ b/packages/indices/src/index.ts @@ -0,0 +1,10 @@ +export { + getIndexAbove, + getIndexBelow, + getIndexBetween, + getIndices, + getIndicesAbove, + getIndicesBelow, + getIndicesBetween, + sortByIndex, +} from './lib/reordering' diff --git a/packages/editor/src/lib/utils/reordering/dgreensp/dgreensp.test.ts b/packages/indices/src/lib/dgreensp/dgreensp.test.ts similarity index 100% rename from packages/editor/src/lib/utils/reordering/dgreensp/dgreensp.test.ts rename to packages/indices/src/lib/dgreensp/dgreensp.test.ts diff --git a/packages/editor/src/lib/utils/reordering/dgreensp/dgreensp.ts b/packages/indices/src/lib/dgreensp/dgreensp.ts similarity index 100% rename from packages/editor/src/lib/utils/reordering/dgreensp/dgreensp.ts rename to packages/indices/src/lib/dgreensp/dgreensp.ts diff --git a/packages/editor/src/lib/utils/reordering/dgreensp/index.ts b/packages/indices/src/lib/dgreensp/index.ts similarity index 100% rename from packages/editor/src/lib/utils/reordering/dgreensp/index.ts rename to packages/indices/src/lib/dgreensp/index.ts diff --git a/packages/editor/src/lib/utils/reordering/reordering.test.ts b/packages/indices/src/lib/reordering.test.ts similarity index 100% rename from packages/editor/src/lib/utils/reordering/reordering.test.ts rename to packages/indices/src/lib/reordering.test.ts diff --git a/packages/indices/src/lib/reordering.ts b/packages/indices/src/lib/reordering.ts new file mode 100644 index 000000000..ea3c5011d --- /dev/null +++ b/packages/indices/src/lib/reordering.ts @@ -0,0 +1,84 @@ +import { generateNKeysBetween } from './dgreensp' + +/** + * Get a number of indices between two indices. + * @param below - (optional) The index below. + * @param above - (optional) The index above. + * @param n - The number of indices to get. + * @public + */ +export function getIndicesBetween(below: string | undefined, above: string | undefined, n: number) { + return generateNKeysBetween(below, above, n) +} + +/** + * Get a number of indices above an index. + * @param below - The index below. + * @param n - The number of indices to get. + * @public + */ +export function getIndicesAbove(below: string, n: number) { + return generateNKeysBetween(below, undefined, n) +} + +/** + * Get a number of indices below an index. + * @param above - The index above. + * @param n - The number of indices to get. + * @public + */ +export function getIndicesBelow(above: string, n: number) { + return generateNKeysBetween(undefined, above, n) +} + +/** + * Get the index between two indices. + * @param below - The index below. + * @param above - The index above. + * @public + */ +export function getIndexBetween(below: string, above?: string) { + return generateNKeysBetween(below, above, 1)[0] +} + +/** + * Get the index above a given index. + * @param below - The index below. + * @public + */ +export function getIndexAbove(below: string) { + return generateNKeysBetween(below, undefined, 1)[0] +} + +/** + * Get the index below a given index. + * @param above - The index above. + * @public + */ +export function getIndexBelow(above: string) { + return generateNKeysBetween(undefined, above, 1)[0] +} + +/** + * Get n number of indices, starting at an index. + * @param n - The number of indices to get. + * @param start - (optional) The index to start at. + * @public + */ +export function getIndices(n: number, start = 'a1') { + return [start, ...generateNKeysBetween(start, undefined, n)] +} + +/** + * Sort by index. + * @param a - An object with an index property. + * @param b - An object with an index property. + * @public */ +export function sortByIndex(a: T, b: T) { + if (a.index < b.index) { + return -1 + } else if (a.index > b.index) { + return 1 + } + return 0 +} diff --git a/packages/indices/tsconfig.json b/packages/indices/tsconfig.json new file mode 100644 index 000000000..5ca07842c --- /dev/null +++ b/packages/indices/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/tsconfig.base.json", + "include": ["src"], + "exclude": ["node_modules", "dist", ".tsbuild*"], + "compilerOptions": { + "outDir": "./.tsbuild", + "rootDir": "src" + }, + "references": [{ "path": "../tlschema" }] +} diff --git a/packages/utils/api-report.md b/packages/utils/api-report.md index 8f6c1fc8c..a49c5b11f 100644 --- a/packages/utils/api-report.md +++ b/packages/utils/api-report.md @@ -140,6 +140,11 @@ export function rng(seed?: string): () => number; // @public export function rotateArray(arr: T[], offset: number): T[]; +// @public (undocumented) +export function sortById(a: T, b: T): -1 | 1; + // @public (undocumented) const structuredClone_2: (i: T) => T; export { structuredClone_2 as structuredClone } diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 35eb270da..ff7e582c5 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -24,4 +24,5 @@ export { objectMapValues, } from './lib/object' export { rafThrottle, throttledRaf } from './lib/raf' +export { sortById } from './lib/sort' export { isDefined, isNonNull, isNonNullish, structuredClone } from './lib/value' diff --git a/packages/utils/src/lib/sort.ts b/packages/utils/src/lib/sort.ts new file mode 100644 index 000000000..b94aa3aac --- /dev/null +++ b/packages/utils/src/lib/sort.ts @@ -0,0 +1,4 @@ +/** @public */ +export function sortById(a: T, b: T) { + return a.id > b.id ? 1 : -1 +} diff --git a/public-yarn.lock b/public-yarn.lock index d22a63954..387d81ff4 100644 --- a/public-yarn.lock +++ b/public-yarn.lock @@ -4577,6 +4577,7 @@ __metadata: dependencies: "@sitespeed.io/edgedriver": ^112.0.1722-34 "@tldraw/editor": "workspace:*" + "@tldraw/indices": "workspace:*" "@tldraw/primitives": "workspace:*" "@types/mocha": ^10.0.1 "@types/sharp": ^0.31.1 @@ -4610,6 +4611,7 @@ __metadata: "@peculiar/webcrypto": ^1.4.0 "@testing-library/jest-dom": ^5.16.5 "@testing-library/react": ^14.0.0 + "@tldraw/indices": "workspace:*" "@tldraw/primitives": "workspace:*" "@tldraw/tlschema": "workspace:*" "@tldraw/tlstore": "workspace:*" @@ -4659,6 +4661,15 @@ __metadata: languageName: unknown linkType: soft +"@tldraw/indices@workspace:*, @tldraw/indices@workspace:packages/indices": + version: 0.0.0-use.local + resolution: "@tldraw/indices@workspace:packages/indices" + dependencies: + benchmark: ^2.1.4 + lazyrepo: 0.0.0-alpha.26 + languageName: unknown + linkType: soft + "@tldraw/monorepo@workspace:.": version: 0.0.0-use.local resolution: "@tldraw/monorepo@workspace:."