1
0
mirror of https://github.com/XFox111/TabsAsideExtension.git synced 2026-04-22 07:58:01 +03:00

!feat: major 3.0 release candidate

This commit is contained in:
2025-05-03 23:59:43 +03:00
parent dbc8c7fd4d
commit 39793a38c3
143 changed files with 14277 additions and 0 deletions
@@ -0,0 +1,61 @@
import { CollectionItem, GroupItem, TabItem } from "@/models/CollectionModels";
import { DragEndEvent } from "@dnd-kit/core";
import { arrayMove } from "@dnd-kit/sortable";
import { DndItem } from "../../hooks/useDndItem";
export default function applyReorder(collections: CollectionItem[], { over, active }: DragEndEvent): null | CollectionItem[]
{
if (!over || active.id === over.id)
return null;
const activeItem: DndItem = active.data.current as DndItem;
const overItem: DndItem = over.data.current as DndItem;
console.log("DragEnd", `active: ${active.id} ${activeItem.item.type}`, `over: ${over.id} ${overItem.item.type}`);
let newList: CollectionItem[] = [
...collections.map(collection => ({
...collection,
items: collection.items.map<TabItem | GroupItem>(item =>
item.type === "group" ?
{ ...item, items: item.items.map(tab => ({ ...tab })) } :
{ ...item }
)
}))
];
if (activeItem.item.type === "collection")
{
newList = arrayMove(
newList,
activeItem.indices[0],
overItem.indices[0]
);
return newList;
}
const sourceItem: GroupItem | CollectionItem = activeItem.indices.length > 2 ?
(newList[activeItem.indices[0]].items[activeItem.indices[1]] as GroupItem) :
newList[activeItem.indices[0]];
if ((over.id as string).endsWith("_dropzone") || overItem.item.type === "collection")
{
const destItem: GroupItem | CollectionItem = overItem.indices.length > 1 ?
(newList[overItem.indices[0]].items[overItem.indices[1]] as GroupItem) :
newList[overItem.indices[0]];
destItem.items.push(activeItem.item as any);
sourceItem.items.splice(activeItem.indices[activeItem.indices.length - 1], 1);
}
else
{
sourceItem.items = arrayMove(
sourceItem.items,
activeItem.indices[activeItem.indices.length - 1],
overItem.indices[overItem.indices.length - 1]
);
}
return newList;
}
@@ -0,0 +1,121 @@
import { ClientRect, Collision, CollisionDescriptor, CollisionDetection } from "@dnd-kit/core";
import { DndItem } from "../../hooks/useDndItem";
import { centerOfRectangle, distanceBetween, getIntersectionRatio, getMaxIntersectionRatio, getRectSideCoordinates, sortCollisionsAsc } from "./dndUtils";
export function collisionDetector(vertical?: boolean): CollisionDetection
{
return (args): Collision[] =>
{
const { collisionRect, droppableContainers, droppableRects, active, pointerCoordinates } = args;
const activeItem = active.data.current as DndItem;
if (!pointerCoordinates)
return [];
const collisions: CollisionDescriptor[] = [];
const centerRect = centerOfRectangle(
collisionRect,
collisionRect.left,
collisionRect.top
);
for (const droppableContainer of droppableContainers)
{
const { id, data } = droppableContainer;
const rect = droppableRects.get(id);
const droppableItem: DndItem = data.current as DndItem;
if (!rect)
continue;
let value: number = 0;
if (activeItem.item.type === "collection")
{
if (droppableItem.item.type !== "collection")
continue;
value = distanceBetween(centerOfRectangle(rect), centerRect);
collisions.push({ id, data: { droppableContainer, value } });
continue;
}
const intersectionRatio: number = getIntersectionRatio(rect, collisionRect);
const intersectionCoefficient: number = intersectionRatio / getMaxIntersectionRatio(rect, collisionRect);
if (droppableItem.item.type === "collection")
{
if (activeItem.indices.length === 2 && activeItem.indices[0] === droppableItem.indices[0])
continue;
if (intersectionCoefficient < 0.7 && activeItem.item.type === "tab")
continue;
if (activeItem.indices.length === 3 && activeItem.indices[0] === droppableItem.indices[0])
{
const [collectionId, groupId] = activeItem.indices;
const groupRect: ClientRect | undefined = droppableRects.get(`${collectionId}/${groupId}`);
if (!groupRect)
continue;
value = 1 / (intersectionRatio - getIntersectionRatio(groupRect, collisionRect));
}
else
{
value = 1 / intersectionRatio;
}
}
else if (droppableItem.item.type === "group" && (id as string).endsWith("_dropzone"))
{
if (activeItem.item.type === "group")
continue;
if (
activeItem.indices.length === 3 &&
activeItem.indices[0] === droppableItem.indices[0] &&
activeItem.indices[1] === droppableItem.indices[1]
)
continue;
if (intersectionCoefficient < 0.5)
continue;
value = 1 / intersectionRatio;
}
else if (activeItem.indices.length === droppableItem.indices.length)
{
if (activeItem.indices[0] !== droppableItem.indices[0])
continue;
if (activeItem.indices.length === 3 && activeItem.indices[1] !== droppableItem.indices[1])
continue;
if (droppableItem.item.type === "group" && droppableItem.item.pinned === true)
continue;
if (activeItem.item.type === "tab" && droppableItem.item.type === "tab")
{
value = distanceBetween(centerOfRectangle(rect), centerRect);
}
else
{
const activeIndex: number = activeItem.indices[activeItem.indices.length - 1];
const droppableIndex: number = droppableItem.indices[droppableItem.indices.length - 1];
const before: boolean = activeIndex < droppableIndex;
value = distanceBetween(
getRectSideCoordinates(rect, before, vertical),
getRectSideCoordinates(collisionRect, before, vertical)
);
}
}
if ((value > 0 && value < Number.POSITIVE_INFINITY) || active.id === id)
collisions.push({ id, data: { droppableContainer, value } });
};
return collisions.sort(sortCollisionsAsc);
};
}
+128
View File
@@ -0,0 +1,128 @@
import { ClientRect, CollisionDescriptor } from "@dnd-kit/core";
import { Coordinates } from "@dnd-kit/utilities";
export function getRectSideCoordinates(rect: ClientRect, before: boolean, vertical?: boolean)
{
if (before)
return vertical ? bottomsideOfRect(rect) : rightsideOfRect(rect);
return vertical ? topsideOfRect(rect) : leftsideOfRect(rect);
}
export function getMaxIntersectionRatio(entry: ClientRect, target: ClientRect): number
{
const entrySize = entry.width * entry.height;
const targetSize = target.width * target.height;
return Math.min(targetSize / entrySize, entrySize / targetSize);
}
function topsideOfRect(rect: ClientRect): Coordinates
{
const { left, top } = rect;
return {
x: left + rect.width * 0.5,
y: top
};
}
function bottomsideOfRect(rect: ClientRect): Coordinates
{
const { left, bottom } = rect;
return {
x: left + rect.width * 0.5,
y: bottom
};
}
function rightsideOfRect(rect: ClientRect): Coordinates
{
const { right, top } = rect;
return {
x: right,
y: top + rect.height * 0.5
};
}
function leftsideOfRect(rect: ClientRect): Coordinates
{
const { left, top } = rect;
return {
x: left,
y: top + rect.height * 0.5
};
}
/*
* MIT License
*
* Copyright (c) 2021, Claudéric Demers
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
export function distanceBetween(p1: Coordinates, p2: Coordinates)
{
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
export function sortCollisionsAsc(
{ data: { value: a } }: CollisionDescriptor,
{ data: { value: b } }: CollisionDescriptor
)
{
return a - b;
}
export function getIntersectionRatio(entry: ClientRect, target: ClientRect): number
{
const top = Math.max(target.top, entry.top);
const left = Math.max(target.left, entry.left);
const right = Math.min(target.left + target.width, entry.left + entry.width);
const bottom = Math.min(target.top + target.height, entry.top + entry.height);
const width = right - left;
const height = bottom - top;
if (left < right && top < bottom)
{
const targetArea = target.width * target.height;
const entryArea = entry.width * entry.height;
const intersectionArea = width * height;
const intersectionRatio =
intersectionArea / (targetArea + entryArea - intersectionArea);
return Number(intersectionRatio.toFixed(4));
}
// Rectangles do not overlap, or overlap has an area of zero (edge/corner overlap)
return 0;
}
export function centerOfRectangle(
rect: ClientRect,
left = rect.left,
top = rect.top
): Coordinates
{
return {
x: left + rect.width * 0.5,
y: top + rect.height * 0.5
};
}