Skip to content

Instantly share code, notes, and snippets.

@jjhiggz
Created September 9, 2024 16:05
Show Gist options
  • Save jjhiggz/3a26314f89ac1a3a94a8adcc955ac56e to your computer and use it in GitHub Desktop.
Save jjhiggz/3a26314f89ac1a3a94a8adcc955ac56e to your computer and use it in GitHub Desktop.
Multi Item Alignment Stuff
import { buildMatrix } from "./build-matrix";
describe("Build Matrix", () => {
it("Should turn my shit into matrix of rows", () => {
expect(
buildMatrix({
// prettier-ignore
elements: [
1/1,
1/3, 2/3,
1/4, 3/4,
4/5, 1/5,
1/4,1/4
],
defaultWidth: 1 / 4,
getWidthAsDecimalFromElement: (el) => el,
}),
).toEqual(
// prettier-ignore
[
[1],
[1/3, 2/3],
[1/4, 3/4],
[4/5, 1/5,],
[1/4, 1/4]
],
);
});
});
/**
Builds a Matrix of widths, makes it much easier to get the last row from an array of elements
*/
export const buildMatrix = <T>({
elements,
defaultWidth,
getWidthAsDecimalFromElement,
}: {
elements: T[];
defaultWidth: number;
getWidthAsDecimalFromElement: (input: T) => number | undefined;
}) => {
let matrix: number[][] = [];
let currRow: number[] = [];
let currRowWidth = 0;
for (let element of elements) {
const elWidth = getWidthAsDecimalFromElement(element) ?? defaultWidth;
const newCurrRowWidth = elWidth + currRowWidth;
if (newCurrRowWidth > 1) {
matrix.push(currRow);
currRowWidth = elWidth;
currRow = [elWidth];
} else if (newCurrRowWidth === 1) {
currRow.push(elWidth);
matrix.push(currRow);
currRow = [];
currRowWidth = 0;
} else {
currRow.push(elWidth);
currRowWidth = newCurrRowWidth;
}
}
if (currRow.length > 0) {
matrix.push(currRow);
}
return matrix;
};
import type { MultiItemAlignment } from "@prisma/client";
import { match } from "ts-pattern";
import { Fractions } from "./fraction.utils";
import { buildMatrix } from "~/components/Render/build-matrix";
import {
getAllIntersectionsRightAligned,
getCenterAlignedIntersections,
} from "./get-intersecttions";
/**
* Because we're only using right border to apply styles,
* We will need to create "ghost elements" next to things
* that need a left border
* This should only happen ever in the last row, hence this
* function
*/
export const correctLastRow = <T, DefaultValue>({
items,
defaultItemWidthAsDecimal,
getItemWidth,
generateValue,
alignment,
}: {
items: T[];
defaultItemWidthAsDecimal: number;
getItemWidth: (item: T) => number | undefined;
generateValue: (fraction: number) => DefaultValue;
alignment: Exclude<MultiItemAlignment, "DEFAULT">;
}): (T | DefaultValue)[] => {
return match(alignment)
.with("CENTER", () => {
const matrix = buildMatrix({
defaultWidth: defaultItemWidthAsDecimal,
elements: items,
getWidthAsDecimalFromElement: getItemWidth,
});
const secondLastRow = (matrix.at(-2) ?? []).map(
Fractions.getClosestFraction,
);
const lastRow = (matrix.at(-1) ?? []).map(Fractions.getClosestFraction);
if (!lastRow) return items;
const lastRowWidth = Fractions.sumFrac(lastRow);
const ghostWidth = Fractions.fraq((1 - lastRowWidth.toNumber()) / 2);
if (ghostWidth.toNumber() === 0) return items;
const lastRowIntersections = getCenterAlignedIntersections([...lastRow]);
const secondLastRowIntersections = getCenterAlignedIntersections(
secondLastRow ?? [],
);
const firstIntersection = lastRowIntersections.at(0)?.reduce().toString();
const isFirstValid = secondLastRowIntersections.some(
(fraction) => {
const cleaned = fraction.reduce().toString();
return cleaned === firstIntersection;
},
// .eq(lastRowIntersections.at(0)!),
);
const isLastValid = secondLastRowIntersections.some((fraction) =>
fraction.eq(lastRowIntersections.at(-1)!),
);
const shouldDrawGhosts = isFirstValid && isLastValid;
const lastRowStuff = items.slice(items.length - lastRow.length);
const notLastRowStuff = items.slice(0, items.length - lastRow.length);
const ghostPlaceholder = shouldDrawGhosts
? [
generateValue(
Fractions.getClosestFractionAsDecimal(ghostWidth.toNumber()),
),
]
: [];
return [
...notLastRowStuff,
...ghostPlaceholder,
...lastRowStuff,
...ghostPlaceholder,
];
})
.with("LEFT", () => {
return items;
})
.with("RIGHT", () => {
const matrix = buildMatrix({
defaultWidth: defaultItemWidthAsDecimal,
elements: items,
getWidthAsDecimalFromElement: getItemWidth,
});
const secondLastRow = (matrix.at(-2) ?? []).map(
Fractions.getClosestFraction,
);
const lastRow = (matrix.at(-1) ?? []).map(Fractions.getClosestFraction);
if (!lastRow) return items;
const lastRowWidth = Fractions.sumFrac(lastRow);
const ghostWidth = Fractions.fraq(1 - lastRowWidth.toNumber());
if (ghostWidth.toNumber() === 0) return items;
const lastRowIntersections = getAllIntersectionsRightAligned([
ghostWidth,
...lastRow,
]);
const secondLastRowIntersections = getAllIntersectionsRightAligned(
secondLastRow ?? [],
);
const shouldDrawGhosts = secondLastRowIntersections.some((fraction) =>
fraction.eq(lastRowIntersections.at(0)!),
);
const lastRowStuff = items.slice(items.length - lastRow.length);
const notLastRowStuff = items.slice(0, items.length - lastRow.length);
const ghostPlaceholder = shouldDrawGhosts
? [
generateValue(
Fractions.getClosestFractionAsDecimal(ghostWidth.toNumber()),
),
]
: [];
return [...notLastRowStuff, ...ghostPlaceholder, ...lastRowStuff];
})
.exhaustive();
};
import { range } from "remeda";
import { getCalloutBorderIndeces } from "./get-callout-border-indeces";
describe("getCalloutBorderIndeces", () => {
describe(`center`, () => {
it(`center 3X3
<-----1/1------>
1/3 <-----2/3-->
1/4 <-----3/4-->
<----4/5---> 1/5
1/4 1/4/ 1/4 1/4
`, () => {
expect(
getCalloutBorderIndeces({
// prettier-ignore
elements: [
1,
1/3, 2/3,
1/4, 3/4,
4/5, 1/5,
1/4, 1/4
],
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: (w) => w,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([1, 3, 5, 7]);
});
it(`center 3X3
1/5 1/5 1/5 1/5 1/5
1/5
`, () => {
expect(
getCalloutBorderIndeces({
// prettier-ignore
elements: [
.2, .2, .2, .2, .2 ,
.4, .2, .4
],
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: (e) => e,
defaultWidth: 0.2,
}).rightBorderIndices,
).toEqual([0, 1, 2, 3, 5, 6]);
});
it(`center 3X3
o|o|o
o|o|o
o|o
`, () => {
expect(
getCalloutBorderIndeces({
elements: range(0, 8),
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: () => undefined,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 1, 3, 4, 6]);
});
it(`center 3X3
o|o|o
o|o|o
o|o|o
`, () => {
expect(
getCalloutBorderIndeces({
elements: range(0, 9),
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: () => undefined,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 1, 3, 4, 6, 7]);
});
it(`3X3 with space on top
<->|o
o|o|o
o|o|o
`, () => {
expect(
getCalloutBorderIndeces({
elements: [2 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3],
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 2, 3, 5, 6]);
});
it(` 3X3 with space on top and in middle
<->|o
o|<->
o|o|o
`, () => {
expect(
getCalloutBorderIndeces({
elements: [2 / 3, undefined, undefined, 2 / 3, 2 / 3, 1 / 3],
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 2, 4]);
});
it(`3X3 Staggered With One on End
<->|o
o|<->
x o x
`, () => {
expect(
getCalloutBorderIndeces({
elements: [2 / 3, undefined, undefined, 2 / 3, undefined],
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 2]);
});
it(`3X3 Staggered With One on End
o|o|o|o
o|o|o|o
x|o|o|x
`, () => {
expect(
getCalloutBorderIndeces({
elements: [2 / 3, undefined, undefined, 2 / 3, undefined],
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 2]);
});
});
it(`Would I be able to flag dummy elements to not do
o|o|o|o|o
o|o|o|o|o
<->|o|<->
`, () => {
expect(
getCalloutBorderIndeces({
// prettier-ignore
elements: [
1/5, 1/5, 1/5, 1/5, 1/5,
1/5, 1/5, 1/5, 1/5, 1/5,
2/5, 1/5, 2/5
],
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 1, 2, 3, 5, 6, 7, 8, 10, 11]);
});
it(`Would I be able to flag dummy elements to not do
o|o|o|o|o
o|o|o|o|o
<->|o|<->
`, () => {
expect(
getCalloutBorderIndeces({
// prettier-ignore
elements: [
1/5, 1/5, 1/5, 1/5, 1/5,
1/5, 1/5, 1/5, 1/5, 1/5,
2/5, 1/5, 2/5
],
elementAlignment: "CENTER",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 1, 2, 3, 5, 6, 7, 8, 10, 11]);
});
describe("left", () => {
it(`3X3 staggered with one on end
<->|o
o|<->
o
`, () => {
expect(
getCalloutBorderIndeces({
elements: [2 / 3, undefined, undefined, 2 / 3, undefined],
elementAlignment: "LEFT",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 2]);
});
it(`Matching Uneven Rows
o|<->
o|<->
o|
`, () => {
expect(
getCalloutBorderIndeces({
elements: [1 / 3, 2 / 3, 1 / 3, 2 / 3, 1 / 3],
elementAlignment: "LEFT",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 2, 4]);
});
it(`one on end
o|o|o
o|o|o
o|
`, () => {
expect(
getCalloutBorderIndeces({
elements: range(0, 7),
elementAlignment: "LEFT",
getWidthAsDecimalFromElement: () => undefined,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 1, 3, 4, 6]);
});
it(`4 -> 3
o|o|o|o
o|o|o|
`, () => {
expect(
getCalloutBorderIndeces({
elements: range(0, 7),
elementAlignment: "LEFT",
getWidthAsDecimalFromElement: () => undefined,
defaultWidth: 1 / 4,
}).rightBorderIndices,
).toEqual([0, 1, 2, 4, 5, 6]);
});
it(`4X 1/4 -> 2 -> 3/4
o|o|o|o
o|o|
<---->
<--->
`, () => {
expect(
getCalloutBorderIndeces({
elements: [1 / 4, 1 / 4, 1 / 4, 1 / 4, 1 / 4, 1 / 4, 4 / 4, 3 / 4],
elementAlignment: "LEFT",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 4,
}).rightBorderIndices,
).toEqual([0, 1, 2, 4, 5]);
});
it(`
o|o|o
o|o|o
o|o|o
`, () => {
expect(
getCalloutBorderIndeces({
elements: range(0, 9),
elementAlignment: "LEFT",
getWidthAsDecimalFromElement: () => undefined,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 1, 3, 4, 6, 7]);
});
it(`
<->|<->|<->|<->|<->
<--->|<->|<->|<->
`, () => {
expect(
getCalloutBorderIndeces({
// prettier-ignore
elements: [
1/5, 1/5, 1/5, 1/5, 1/5,
1/4 ,1/5, 1/5, 1/5
],
elementAlignment: "LEFT",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 1, 2, 3, 5, 6, 7]);
});
it(`
<->|<->|<->|<->|<->
<->|<->|<->|
`, () => {
expect(
getCalloutBorderIndeces({
// prettier-ignore
elements: [
1/5, 1/5, 1/5, 1/5, 1/5,
1/5, 1/5, 1/5,
],
elementAlignment: "LEFT",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 3,
}).rightBorderIndices,
).toEqual([0, 1, 2, 3, 5, 6, 7]);
});
});
describe("RIGHT", () => {
it(`
o|o|o
o|o|o
o|o|o
`, () => {
const result = getCalloutBorderIndeces({
elements: range(0, 9),
elementAlignment: "RIGHT",
getWidthAsDecimalFromElement: () => undefined,
defaultWidth: 1 / 3,
});
expect(result.rightBorderIndices).toEqual([0, 1, 3, 4, 6, 7]);
expect(result.leftBorderIndices).toEqual([]);
});
it(`
o|o|o|o
<->|<->
o|o|o|o
`, () => {
const result = getCalloutBorderIndeces({
elements: [
1 / 4,
1 / 4,
1 / 4,
1 / 4,
1 / 2,
1 / 2,
1 / 4,
1 / 4,
1 / 4,
1 / 4,
],
elementAlignment: "RIGHT",
getWidthAsDecimalFromElement: (i) => i,
defaultWidth: 1 / 4,
});
expect(result.rightBorderIndices).toEqual([0, 1, 2, 4, 6, 7, 8]);
});
it(`
o|o|o
o|o|o
o|o
`, () => {
const result = getCalloutBorderIndeces({
elements: range(0, 8),
elementAlignment: "RIGHT",
getWidthAsDecimalFromElement: () => undefined,
defaultWidth: 1 / 3,
});
expect(result.rightBorderIndices).toEqual([0, 1, 3, 4, 6]);
expect(result.leftBorderIndices).toEqual([6]);
});
});
it(`
o|o|o|o|o
o|o|o
`, () => {
const result = getCalloutBorderIndeces({
elements: range(0, 8),
elementAlignment: "RIGHT",
getWidthAsDecimalFromElement: () => undefined,
defaultWidth: 1 / 5,
});
expect(result.rightBorderIndices).toEqual([0, 1, 2, 3, 5, 6]);
});
});
import type { MultiItemAlignment } from "@prisma/client";
import { sum } from "remeda";
import { match } from "ts-pattern";
import { buildMatrix } from "./build-matrix";
import {
getCenterAlignedIntersections,
getLeftAlignedIntersections,
} from "~/utils/get-intersecttions";
import { Fractions } from "~/utils/fraction.utils";
const areTwoRowsConsistent = (a: number[], b: number[]) => {
for (let i = 0; i < Math.max(a.length, b.length); i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
};
type CReturnType = {
rightBorderIndices: number[];
leftBorderIndices: number[];
};
/**
Determine the indexes of border-right separators
assuming that ghost elements are already added to the elements
*/
export const getCalloutBorderIndeces = <T>({
elements,
defaultWidth,
elementAlignment,
getWidthAsDecimalFromElement,
}: {
elements: T[];
elementAlignment: Exclude<MultiItemAlignment, "DEFAULT">;
defaultWidth: number;
getWidthAsDecimalFromElement: (input: T) => number | undefined;
}): CReturnType => {
return match(elementAlignment)
.with("CENTER", () => {
const matrix = buildMatrix({
elements,
getWidthAsDecimalFromElement,
defaultWidth,
});
let rightBorderIndices: number[] = [];
let indexCount = 0;
let isStaggered = false;
for (let i = 0; i < matrix.length; i++) {
const row = matrix[i]!;
const prevRowToCompare = matrix[i - 1] ?? row;
// const isLastRow = i === matrix.length - 1;
const isRowFull = sum(row) === 1;
for (let j = 0; j < row.length; j++) {
const isLastInRow = j === row.length - 1;
const isOnlyInRow = row.length === 1;
if (isOnlyInRow) {
// Do nothing
} else if (!isLastInRow) {
rightBorderIndices.push(indexCount);
} else if (!isRowFull) {
// if (isLastInRow && !isStaggered) {
if (isLastInRow && !isStaggered) {
const lastRowIntersections = getCenterAlignedIntersections(
row.map(Fractions.fraq),
);
const secondLastRowIntersections = getCenterAlignedIntersections(
(matrix.at(i - 1) ?? []).map(Fractions.fraq),
);
const doEndsMatch = [
lastRowIntersections.at(0),
lastRowIntersections.at(1),
].every((position) => {
return secondLastRowIntersections.some((intersection) =>
intersection.eq(position ?? -1),
);
});
console.log({
doEndsMatch,
row,
lastRowIntersections,
secondLastRowIntersections,
});
if (doEndsMatch) rightBorderIndices.push(indexCount);
}
} else if (isRowFull) {
if (j < row.length - 1) {
rightBorderIndices.push(indexCount);
}
}
indexCount++;
}
if (!areTwoRowsConsistent(row, prevRowToCompare)) {
isStaggered = true;
}
}
return {
rightBorderIndices: rightBorderIndices,
leftBorderIndices: [],
} satisfies CReturnType;
})
.with("LEFT", () => {
const matrix = buildMatrix({
elements,
getWidthAsDecimalFromElement,
defaultWidth,
});
let rightIndices: number[] = [];
let indexCount = 0;
let isStaggered = false;
for (let i = 0; i < matrix.length; i++) {
const row = matrix[i]!;
const prevRowToCompare = matrix[i - 1] ?? row;
const isRowFull = sum(row) === 1;
for (let j = 0; j < row.length; j++) {
const isLastInRow = j === row.length - 1;
if (!isLastInRow) {
rightIndices.push(indexCount);
} else if (!isRowFull) {
console.log({
row,
});
const secondLastRowIntersections = getLeftAlignedIntersections(
(matrix.at(i - 1) ?? []).map(Fractions.fraq),
);
const lastRowIntersections = getLeftAlignedIntersections(
row.map(Fractions.fraq),
);
const shouldHaveRightBorderOnLast = secondLastRowIntersections.some(
(intersection) => lastRowIntersections.at(-1)?.eq(intersection),
);
if (isLastInRow && !isStaggered && shouldHaveRightBorderOnLast) {
rightIndices.push(indexCount);
}
} else if (isRowFull) {
if (j < row.length - 1) {
rightIndices.push(indexCount);
}
}
indexCount++;
}
if (!areTwoRowsConsistent(row, prevRowToCompare)) {
isStaggered = true;
}
}
return {
leftBorderIndices: [],
rightBorderIndices: rightIndices,
} satisfies CReturnType;
})
.with("RIGHT", () => {
const matrix = buildMatrix({
elements,
getWidthAsDecimalFromElement,
defaultWidth,
});
let rightBorderIndices: number[] = [];
let leftBorderIndices: number[] = [];
let indexCount = 0;
for (let i = 0; i < matrix.length; i++) {
const row = matrix[i]!;
const isLastRow = i === matrix.length - 1;
const isRowFull = sum(row) === 1;
for (let j = 0; j < row.length; j++) {
const isLastInRow = j === row.length - 1;
if (j === 0 && isLastRow && !isRowFull) {
leftBorderIndices.push(indexCount);
}
if (!isLastInRow) {
rightBorderIndices.push(indexCount);
} else if (isRowFull) {
if (j < row.length - 1) {
rightBorderIndices.push(indexCount);
}
}
indexCount++;
}
}
return {
rightBorderIndices,
leftBorderIndices: leftBorderIndices,
} satisfies CReturnType;
})
.exhaustive();
};
import type { Fraction } from "fractions-math";
import { reverse } from "remeda";
import { Fractions } from "./fraction.utils";
const { fraq } = Fractions;
/**
* Get's the intersections of a center aligned row of fractions
```ts
getCenterAlignedIntersections([1/4, 1/4, 1/4])
// => [1/8, 3/8, 5/8, 7/8]
```
Why?
```txt
|---------------------------
| key |
| |
| <----> = 1/4 |
| || = wall |
| |-*.| = distance |
|__________________________|
```
====EXAMPLE====
```txt
Input: [1/4, 1/4, 1/4]
|| <----><----><----> ||
|--|
--------|
--------------|
--------------------|
1/8, 3/8 , 5/8 7/8
```
*/
export const getCenterAlignedIntersections = (input: Fraction[]) => {
let totalWidthOfParts = Fractions.sumFrac(input);
if (totalWidthOfParts.eq(1)) {
let totalWidth = fraq(0);
let intersections: Fraction[] = [];
for (let el of input.slice(0, input.length - 1)) {
totalWidth = totalWidth.add(el);
intersections.push(totalWidth);
}
return intersections;
} else {
const edgeSize = fraq(1).sub(totalWidthOfParts).div(2);
let totalWidth = edgeSize;
let intersections: Fraction[] = [totalWidth];
for (let el of input) {
totalWidth = totalWidth.add(el);
intersections.push(totalWidth);
}
return intersections;
}
};
/**
* Get's the intersections of a right aligned aligned row of fractions
```ts
getRightAlignedIntersections([1/4, 1/4, 1/4])
// => [1/4, 2/4, 3/4]
```
Why?
```txt
|---------------------------
| key |
| |
| <----> = 1/4 |
| || = wall |
| |-*.| = distance |
|__________________________|
```
====EXAMPLE====
```txt
Input: [1/4, 1/4, 1/4]
|| <----><----><---->||
|---|
----------|
----------------|
1/4, 2/4, 3/4
```
*/
export const getAllIntersectionsRightAligned = (input: Fraction[]) => {
let totalWidth = Fractions.fraq(0);
let intersections: Fraction[] = [];
let one = Fractions.fraq(1);
for (let block of reverse(input)) {
totalWidth = totalWidth.add(block);
if (totalWidth.eq(1)) continue;
intersections.unshift(one.sub(totalWidth));
}
return intersections;
};
/**
* Get's the intersections of a left aligned aligned row of fractions
```ts
getLeftAlignedIntersections([1/4, 1/4, 1/4])
// => [1/4, 2/4, 3/4]
```
Why?
```txt
|---------------------------
| key |
| |
| <----> = 1/4 |
| || = wall |
| |-*.| = distance |
|__________________________|
```
====EXAMPLE====
```txt
Input: [1/4, 1/4, 1/4]
||<----><----><----> ||
|-----|
------------|
------------------|
1/4, 2/4, 3/4
```
*/
export const getLeftAlignedIntersections = (input: Fraction[]) => {
let totalWidth = Fractions.fraq(0);
let intersections: Fraction[] = [];
for (let block of input) {
totalWidth = totalWidth.add(block);
if (totalWidth.eq(1)) continue;
intersections.push(totalWidth);
}
return intersections;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment