use string as fractional index value
This commit is contained in:
parent
5c1787bdf4
commit
00ffa08e28
@ -37,6 +37,7 @@
|
|||||||
"eslint-plugin-react": "7.32.2",
|
"eslint-plugin-react": "7.32.2",
|
||||||
"fake-indexeddb": "3.1.7",
|
"fake-indexeddb": "3.1.7",
|
||||||
"firebase": "8.3.3",
|
"firebase": "8.3.3",
|
||||||
|
"fractional-indexing": "3.2.0",
|
||||||
"i18next-browser-languagedetector": "6.1.4",
|
"i18next-browser-languagedetector": "6.1.4",
|
||||||
"idb-keyval": "6.0.3",
|
"idb-keyval": "6.0.3",
|
||||||
"image-blob-reduce": "3.0.1",
|
"image-blob-reduce": "3.0.1",
|
||||||
|
@ -120,7 +120,7 @@ const restoreElementWithProperties = <
|
|||||||
version: element.version || 1,
|
version: element.version || 1,
|
||||||
versionNonce: element.versionNonce ?? 0,
|
versionNonce: element.versionNonce ?? 0,
|
||||||
// TODO: think about this more
|
// TODO: think about this more
|
||||||
fractionalIndex: element.fractionalIndex ?? Infinity,
|
fractionalIndex: element.fractionalIndex ?? null,
|
||||||
isDeleted: element.isDeleted ?? false,
|
isDeleted: element.isDeleted ?? false,
|
||||||
id: element.id || randomId(),
|
id: element.id || randomId(),
|
||||||
fillStyle: element.fillStyle || DEFAULT_ELEMENT_PROPS.fillStyle,
|
fillStyle: element.fillStyle || DEFAULT_ELEMENT_PROPS.fillStyle,
|
||||||
|
@ -90,7 +90,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
|||||||
groupIds = [],
|
groupIds = [],
|
||||||
frameId = null,
|
frameId = null,
|
||||||
// TODO: think about this more
|
// TODO: think about this more
|
||||||
fractionalIndex = Infinity,
|
fractionalIndex = null,
|
||||||
roundness = null,
|
roundness = null,
|
||||||
boundElements = null,
|
boundElements = null,
|
||||||
link = null,
|
link = null,
|
||||||
|
@ -50,7 +50,7 @@ type _ExcalidrawElementBase = Readonly<{
|
|||||||
Used for deterministic reconciliation of updates during collaboration,
|
Used for deterministic reconciliation of updates during collaboration,
|
||||||
in case the versions (see above) are identical. */
|
in case the versions (see above) are identical. */
|
||||||
versionNonce: number;
|
versionNonce: number;
|
||||||
fractionalIndex: number;
|
fractionalIndex: string | null;
|
||||||
isDeleted: boolean;
|
isDeleted: boolean;
|
||||||
/** List of groups the element belongs to.
|
/** List of groups the element belongs to.
|
||||||
Ordered from deepest to shallowest. */
|
Ordered from deepest to shallowest. */
|
||||||
|
2
src/tests/fixtures/elementFixture.ts
vendored
2
src/tests/fixtures/elementFixture.ts
vendored
@ -17,7 +17,7 @@ const elementBase: Omit<ExcalidrawElement, "type"> = {
|
|||||||
groupIds: [],
|
groupIds: [],
|
||||||
frameId: null,
|
frameId: null,
|
||||||
roundness: null,
|
roundness: null,
|
||||||
fractionalIndex: Infinity,
|
fractionalIndex: "",
|
||||||
seed: 1041657908,
|
seed: 1041657908,
|
||||||
version: 120,
|
version: 120,
|
||||||
versionNonce: 1188004276,
|
versionNonce: 1188004276,
|
||||||
|
@ -168,7 +168,7 @@ export class API {
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
frameId: rest.frameId ?? null,
|
frameId: rest.frameId ?? null,
|
||||||
fractionalIndex: rest.fractionalIndex ?? Infinity,
|
fractionalIndex: rest.fractionalIndex ?? null,
|
||||||
angle: rest.angle ?? 0,
|
angle: rest.angle ?? 0,
|
||||||
strokeColor: rest.strokeColor ?? appState.currentItemStrokeColor,
|
strokeColor: rest.strokeColor ?? appState.currentItemStrokeColor,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
|
131
src/zindex.ts
131
src/zindex.ts
@ -6,6 +6,7 @@ import { getSelectedElements } from "./scene";
|
|||||||
import Scene from "./scene/Scene";
|
import Scene from "./scene/Scene";
|
||||||
import { AppState } from "./types";
|
import { AppState } from "./types";
|
||||||
import { arrayToMap, findIndex, findLastIndex } from "./utils";
|
import { arrayToMap, findIndex, findLastIndex } from "./utils";
|
||||||
|
import { generateKeyBetween } from "fractional-indexing";
|
||||||
|
|
||||||
const isOfTargetFrame = (element: ExcalidrawElement, frameId: string) => {
|
const isOfTargetFrame = (element: ExcalidrawElement, frameId: string) => {
|
||||||
return element.frameId === frameId || element.id === frameId;
|
return element.frameId === frameId || element.id === frameId;
|
||||||
@ -487,63 +488,76 @@ function shiftElementsAccountingForFrames(
|
|||||||
|
|
||||||
// fractional indexing
|
// fractional indexing
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
const FRACTIONAL_INDEX_FLOOR = 0;
|
type FractionalIndex = ExcalidrawElement["fractionalIndex"];
|
||||||
const FRACTIONAL_INDEX_CEILING = 1;
|
|
||||||
|
|
||||||
const isFractionalIndexInValidRange = (index: number) => {
|
const fractionalIndexCompare = {
|
||||||
return index > FRACTIONAL_INDEX_FLOOR && index < FRACTIONAL_INDEX_CEILING;
|
isSmallerThan(indexA: string, indexB: string) {
|
||||||
};
|
return indexA < indexB;
|
||||||
|
},
|
||||||
|
|
||||||
const getFractionalIndex = (
|
isGreaterThan(indexA: string, indexB: string) {
|
||||||
element: ExcalidrawElement | undefined,
|
return indexA > indexB;
|
||||||
fallbackValue: number,
|
},
|
||||||
) => {
|
|
||||||
return element && isFractionalIndexInValidRange(element.fractionalIndex)
|
|
||||||
? element.fractionalIndex
|
|
||||||
: fallbackValue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isValidFractionalIndex = (
|
const isValidFractionalIndex = (
|
||||||
index: number,
|
index: FractionalIndex,
|
||||||
predecessorElement: ExcalidrawElement | undefined,
|
predecessorFractionalIndex: FractionalIndex,
|
||||||
successorElement: ExcalidrawElement | undefined,
|
successorFractionalIndex: FractionalIndex,
|
||||||
) => {
|
) => {
|
||||||
|
if (index) {
|
||||||
|
if (predecessorFractionalIndex) {
|
||||||
|
if (successorFractionalIndex) {
|
||||||
return (
|
return (
|
||||||
isFractionalIndexInValidRange(index) &&
|
fractionalIndexCompare.isGreaterThan(
|
||||||
index > getFractionalIndex(predecessorElement, FRACTIONAL_INDEX_FLOOR) &&
|
index,
|
||||||
index < getFractionalIndex(successorElement, FRACTIONAL_INDEX_CEILING)
|
predecessorFractionalIndex,
|
||||||
|
) &&
|
||||||
|
fractionalIndexCompare.isSmallerThan(index, successorFractionalIndex)
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
return fractionalIndexCompare.isGreaterThan(
|
||||||
const randomNumInBetween = (start: number, end: number) => {
|
index,
|
||||||
return Math.random() * (end - start) + start;
|
predecessorFractionalIndex,
|
||||||
};
|
|
||||||
|
|
||||||
export const generateFractionalIndex = (
|
|
||||||
predecessorElement: ExcalidrawElement | undefined,
|
|
||||||
successorElement: ExcalidrawElement | undefined,
|
|
||||||
) => {
|
|
||||||
const start = getFractionalIndex(predecessorElement, FRACTIONAL_INDEX_FLOOR);
|
|
||||||
const end = getFractionalIndex(successorElement, FRACTIONAL_INDEX_CEILING);
|
|
||||||
|
|
||||||
const nextTemp = randomNumInBetween(start, end);
|
|
||||||
return (
|
|
||||||
(randomNumInBetween(nextTemp, end) + randomNumInBetween(start, nextTemp)) /
|
|
||||||
2
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successorFractionalIndex) {
|
||||||
|
return fractionalIndexCompare.isSmallerThan(
|
||||||
|
index,
|
||||||
|
successorFractionalIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return index.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const generateFractionalIndex = (
|
||||||
*
|
predecessorFractionalIndex: string | null,
|
||||||
*/
|
successorFractionalIndex: string | null,
|
||||||
export const getNextFractionalIndexAt = (
|
|
||||||
index: number,
|
|
||||||
allElements: ExcalidrawElement[],
|
|
||||||
) => {
|
) => {
|
||||||
const predecessor = allElements[index - 1];
|
if (predecessorFractionalIndex && successorFractionalIndex) {
|
||||||
const successor = allElements[index + 1];
|
if (predecessorFractionalIndex < successorFractionalIndex) {
|
||||||
|
return generateKeyBetween(
|
||||||
|
predecessorFractionalIndex,
|
||||||
|
successorFractionalIndex,
|
||||||
|
);
|
||||||
|
} else if (predecessorFractionalIndex > successorFractionalIndex) {
|
||||||
|
return generateKeyBetween(
|
||||||
|
successorFractionalIndex,
|
||||||
|
predecessorFractionalIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return generateKeyBetween(predecessorFractionalIndex, null);
|
||||||
|
}
|
||||||
|
|
||||||
return generateFractionalIndex(predecessor, successor);
|
return generateKeyBetween(
|
||||||
|
predecessorFractionalIndex,
|
||||||
|
successorFractionalIndex,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -557,37 +571,44 @@ export const normalizeFractionalIndexing = (
|
|||||||
let predecessor = -1;
|
let predecessor = -1;
|
||||||
let successor = 1;
|
let successor = 1;
|
||||||
|
|
||||||
const normalizedElements: ExcalidrawElement[] = [];
|
const normalizedElementsMap = arrayToMap(allElements);
|
||||||
|
|
||||||
for (const element of allElements) {
|
for (const element of allElements) {
|
||||||
const predecessorElement = allElements[predecessor];
|
const predecessorFractionalIndex =
|
||||||
const successorElement = allElements[successor];
|
normalizedElementsMap.get(allElements[predecessor]?.id)
|
||||||
|
?.fractionalIndex || null;
|
||||||
|
|
||||||
|
const successorFractionalIndex =
|
||||||
|
normalizedElementsMap.get(allElements[successor]?.id)?.fractionalIndex ||
|
||||||
|
null;
|
||||||
|
|
||||||
|
try {
|
||||||
if (
|
if (
|
||||||
!isValidFractionalIndex(
|
!isValidFractionalIndex(
|
||||||
element.fractionalIndex,
|
element.fractionalIndex,
|
||||||
predecessorElement,
|
predecessorFractionalIndex,
|
||||||
successorElement,
|
successorFractionalIndex,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const nextFractionalIndex = generateFractionalIndex(
|
const nextFractionalIndex = generateFractionalIndex(
|
||||||
predecessorElement,
|
predecessorFractionalIndex,
|
||||||
successorElement,
|
successorFractionalIndex,
|
||||||
);
|
);
|
||||||
|
|
||||||
normalizedElements.push({
|
normalizedElementsMap.set(element.id, {
|
||||||
...element,
|
...element,
|
||||||
fractionalIndex: nextFractionalIndex,
|
fractionalIndex: nextFractionalIndex,
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
normalizedElements.push(element);
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
predecessor++;
|
predecessor++;
|
||||||
successor++;
|
successor++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return normalizedElements;
|
return [...normalizedElementsMap.values()];
|
||||||
};
|
};
|
||||||
|
|
||||||
// public API
|
// public API
|
||||||
|
@ -4891,6 +4891,11 @@ form-data@^4.0.0:
|
|||||||
combined-stream "^1.0.8"
|
combined-stream "^1.0.8"
|
||||||
mime-types "^2.1.12"
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
|
fractional-indexing@3.2.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fractional-indexing/-/fractional-indexing-3.2.0.tgz#1193e63d54ff4e0cbe0c79a9ed6cfbab25d91628"
|
||||||
|
integrity sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==
|
||||||
|
|
||||||
fs-extra@^11.1.0:
|
fs-extra@^11.1.0:
|
||||||
version "11.1.1"
|
version "11.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user