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",
|
||||
"fake-indexeddb": "3.1.7",
|
||||
"firebase": "8.3.3",
|
||||
"fractional-indexing": "3.2.0",
|
||||
"i18next-browser-languagedetector": "6.1.4",
|
||||
"idb-keyval": "6.0.3",
|
||||
"image-blob-reduce": "3.0.1",
|
||||
|
@ -120,7 +120,7 @@ const restoreElementWithProperties = <
|
||||
version: element.version || 1,
|
||||
versionNonce: element.versionNonce ?? 0,
|
||||
// TODO: think about this more
|
||||
fractionalIndex: element.fractionalIndex ?? Infinity,
|
||||
fractionalIndex: element.fractionalIndex ?? null,
|
||||
isDeleted: element.isDeleted ?? false,
|
||||
id: element.id || randomId(),
|
||||
fillStyle: element.fillStyle || DEFAULT_ELEMENT_PROPS.fillStyle,
|
||||
|
@ -90,7 +90,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
||||
groupIds = [],
|
||||
frameId = null,
|
||||
// TODO: think about this more
|
||||
fractionalIndex = Infinity,
|
||||
fractionalIndex = null,
|
||||
roundness = null,
|
||||
boundElements = null,
|
||||
link = null,
|
||||
|
@ -50,7 +50,7 @@ type _ExcalidrawElementBase = Readonly<{
|
||||
Used for deterministic reconciliation of updates during collaboration,
|
||||
in case the versions (see above) are identical. */
|
||||
versionNonce: number;
|
||||
fractionalIndex: number;
|
||||
fractionalIndex: string | null;
|
||||
isDeleted: boolean;
|
||||
/** List of groups the element belongs to.
|
||||
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: [],
|
||||
frameId: null,
|
||||
roundness: null,
|
||||
fractionalIndex: Infinity,
|
||||
fractionalIndex: "",
|
||||
seed: 1041657908,
|
||||
version: 120,
|
||||
versionNonce: 1188004276,
|
||||
|
@ -168,7 +168,7 @@ export class API {
|
||||
x,
|
||||
y,
|
||||
frameId: rest.frameId ?? null,
|
||||
fractionalIndex: rest.fractionalIndex ?? Infinity,
|
||||
fractionalIndex: rest.fractionalIndex ?? null,
|
||||
angle: rest.angle ?? 0,
|
||||
strokeColor: rest.strokeColor ?? appState.currentItemStrokeColor,
|
||||
backgroundColor:
|
||||
|
163
src/zindex.ts
163
src/zindex.ts
@ -6,6 +6,7 @@ import { getSelectedElements } from "./scene";
|
||||
import Scene from "./scene/Scene";
|
||||
import { AppState } from "./types";
|
||||
import { arrayToMap, findIndex, findLastIndex } from "./utils";
|
||||
import { generateKeyBetween } from "fractional-indexing";
|
||||
|
||||
const isOfTargetFrame = (element: ExcalidrawElement, frameId: string) => {
|
||||
return element.frameId === frameId || element.id === frameId;
|
||||
@ -487,65 +488,78 @@ function shiftElementsAccountingForFrames(
|
||||
|
||||
// fractional indexing
|
||||
// -----------------------------------------------------------------------------
|
||||
const FRACTIONAL_INDEX_FLOOR = 0;
|
||||
const FRACTIONAL_INDEX_CEILING = 1;
|
||||
type FractionalIndex = ExcalidrawElement["fractionalIndex"];
|
||||
|
||||
const isFractionalIndexInValidRange = (index: number) => {
|
||||
return index > FRACTIONAL_INDEX_FLOOR && index < FRACTIONAL_INDEX_CEILING;
|
||||
};
|
||||
const fractionalIndexCompare = {
|
||||
isSmallerThan(indexA: string, indexB: string) {
|
||||
return indexA < indexB;
|
||||
},
|
||||
|
||||
const getFractionalIndex = (
|
||||
element: ExcalidrawElement | undefined,
|
||||
fallbackValue: number,
|
||||
) => {
|
||||
return element && isFractionalIndexInValidRange(element.fractionalIndex)
|
||||
? element.fractionalIndex
|
||||
: fallbackValue;
|
||||
isGreaterThan(indexA: string, indexB: string) {
|
||||
return indexA > indexB;
|
||||
},
|
||||
};
|
||||
|
||||
const isValidFractionalIndex = (
|
||||
index: number,
|
||||
predecessorElement: ExcalidrawElement | undefined,
|
||||
successorElement: ExcalidrawElement | undefined,
|
||||
index: FractionalIndex,
|
||||
predecessorFractionalIndex: FractionalIndex,
|
||||
successorFractionalIndex: FractionalIndex,
|
||||
) => {
|
||||
return (
|
||||
isFractionalIndexInValidRange(index) &&
|
||||
index > getFractionalIndex(predecessorElement, FRACTIONAL_INDEX_FLOOR) &&
|
||||
index < getFractionalIndex(successorElement, FRACTIONAL_INDEX_CEILING)
|
||||
if (index) {
|
||||
if (predecessorFractionalIndex) {
|
||||
if (successorFractionalIndex) {
|
||||
return (
|
||||
fractionalIndexCompare.isGreaterThan(
|
||||
index,
|
||||
predecessorFractionalIndex,
|
||||
) &&
|
||||
fractionalIndexCompare.isSmallerThan(index, successorFractionalIndex)
|
||||
);
|
||||
}
|
||||
return fractionalIndexCompare.isGreaterThan(
|
||||
index,
|
||||
predecessorFractionalIndex,
|
||||
);
|
||||
}
|
||||
|
||||
if (successorFractionalIndex) {
|
||||
return fractionalIndexCompare.isSmallerThan(
|
||||
index,
|
||||
successorFractionalIndex,
|
||||
);
|
||||
}
|
||||
|
||||
return index.length > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const generateFractionalIndex = (
|
||||
predecessorFractionalIndex: string | null,
|
||||
successorFractionalIndex: string | null,
|
||||
) => {
|
||||
if (predecessorFractionalIndex && successorFractionalIndex) {
|
||||
if (predecessorFractionalIndex < successorFractionalIndex) {
|
||||
return generateKeyBetween(
|
||||
predecessorFractionalIndex,
|
||||
successorFractionalIndex,
|
||||
);
|
||||
} else if (predecessorFractionalIndex > successorFractionalIndex) {
|
||||
return generateKeyBetween(
|
||||
successorFractionalIndex,
|
||||
predecessorFractionalIndex,
|
||||
);
|
||||
}
|
||||
return generateKeyBetween(predecessorFractionalIndex, null);
|
||||
}
|
||||
|
||||
return generateKeyBetween(
|
||||
predecessorFractionalIndex,
|
||||
successorFractionalIndex,
|
||||
);
|
||||
};
|
||||
|
||||
const randomNumInBetween = (start: number, end: number) => {
|
||||
return Math.random() * (end - start) + start;
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const getNextFractionalIndexAt = (
|
||||
index: number,
|
||||
allElements: ExcalidrawElement[],
|
||||
) => {
|
||||
const predecessor = allElements[index - 1];
|
||||
const successor = allElements[index + 1];
|
||||
|
||||
return generateFractionalIndex(predecessor, successor);
|
||||
};
|
||||
|
||||
/**
|
||||
* normalize the fractional indicies of the elements in the given array such that
|
||||
* a. all elements have a fraction index between floor and ceiling as defined above
|
||||
@ -557,37 +571,44 @@ export const normalizeFractionalIndexing = (
|
||||
let predecessor = -1;
|
||||
let successor = 1;
|
||||
|
||||
const normalizedElements: ExcalidrawElement[] = [];
|
||||
const normalizedElementsMap = arrayToMap(allElements);
|
||||
|
||||
for (const element of allElements) {
|
||||
const predecessorElement = allElements[predecessor];
|
||||
const successorElement = allElements[successor];
|
||||
const predecessorFractionalIndex =
|
||||
normalizedElementsMap.get(allElements[predecessor]?.id)
|
||||
?.fractionalIndex || null;
|
||||
|
||||
if (
|
||||
!isValidFractionalIndex(
|
||||
element.fractionalIndex,
|
||||
predecessorElement,
|
||||
successorElement,
|
||||
)
|
||||
) {
|
||||
const nextFractionalIndex = generateFractionalIndex(
|
||||
predecessorElement,
|
||||
successorElement,
|
||||
);
|
||||
const successorFractionalIndex =
|
||||
normalizedElementsMap.get(allElements[successor]?.id)?.fractionalIndex ||
|
||||
null;
|
||||
|
||||
normalizedElements.push({
|
||||
...element,
|
||||
fractionalIndex: nextFractionalIndex,
|
||||
});
|
||||
} else {
|
||||
normalizedElements.push(element);
|
||||
try {
|
||||
if (
|
||||
!isValidFractionalIndex(
|
||||
element.fractionalIndex,
|
||||
predecessorFractionalIndex,
|
||||
successorFractionalIndex,
|
||||
)
|
||||
) {
|
||||
const nextFractionalIndex = generateFractionalIndex(
|
||||
predecessorFractionalIndex,
|
||||
successorFractionalIndex,
|
||||
);
|
||||
|
||||
normalizedElementsMap.set(element.id, {
|
||||
...element,
|
||||
fractionalIndex: nextFractionalIndex,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
predecessor++;
|
||||
successor++;
|
||||
}
|
||||
|
||||
return normalizedElements;
|
||||
return [...normalizedElementsMap.values()];
|
||||
};
|
||||
|
||||
// public API
|
||||
|
@ -4891,6 +4891,11 @@ form-data@^4.0.0:
|
||||
combined-stream "^1.0.8"
|
||||
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:
|
||||
version "11.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
|
||||
|
Loading…
x
Reference in New Issue
Block a user