add space children reorder mechanism

This commit is contained in:
Ajay Bura 2024-05-11 22:01:51 +05:30
parent bda7adc033
commit cb5bb2fc73
3 changed files with 243 additions and 16 deletions

View file

@ -36,6 +36,7 @@ import { getSpaceRoomPath } from '../../pages/pathUtils';
import { HierarchyItemMenu } from './HierarchyItemMenu';
import { StateEvent } from '../../../types/matrix/room';
import { AfterItemDropTarget, CanDropCallback, useDnDMonitor } from './DnD';
import { ASCIILexicalTable, orderKeys } from '../../utils/ASCIILexicalTable';
export function Lobby() {
const navigate = useNavigate();
@ -45,6 +46,7 @@ export function Lobby() {
const allJoinedRooms = useMemo(() => new Set(allRooms), [allRooms]);
const space = useSpace();
const spacePowerLevels = usePowerLevels(space);
const lex = useMemo(() => new ASCIILexicalTable(' '.charCodeAt(0), '~'.charCodeAt(0), 6), []);
const scrollRef = useRef<HTMLDivElement>(null);
const heroSectionRef = useRef<HTMLDivElement>(null);
@ -144,13 +146,118 @@ export function Lobby() {
[getRoom, space.roomId, roomsPowerLevels, canEditSpaceChild]
);
const reorderSpace = useCallback(
(item: HierarchyItem, containerItem: HierarchyItem) => {
if (!item.parentId) return;
const childItems = flattenHierarchy
.filter((i) => i.parentId && i.space)
.filter((i) => i.roomId !== item.roomId);
const beforeIndex = childItems.findIndex((i) => i.roomId === containerItem.roomId);
const insertIndex = beforeIndex + 1;
childItems.splice(insertIndex, 0, {
...item,
content: { ...item.content, order: undefined },
});
const currentOrders = childItems.map((i) => {
if (typeof i.content.order === 'string' && lex.has(i.content.order)) {
return i.content.order;
}
return undefined;
});
const newOrders = orderKeys(lex, currentOrders);
newOrders?.forEach((orderKey, index) => {
const itm = childItems[index];
if (!itm || !itm.parentId) return;
const parentPL = roomsPowerLevels.get(itm.parentId);
const canEdit = parentPL && canEditSpaceChild(parentPL);
if (canEdit && orderKey !== currentOrders[index]) {
mx.sendStateEvent(
itm.parentId,
StateEvent.SpaceChild,
{ ...itm.content, order: orderKey },
itm.roomId
);
}
});
},
[mx, flattenHierarchy, lex, roomsPowerLevels, canEditSpaceChild]
);
const reorderRoom = useCallback(
(item: HierarchyItem, containerItem: HierarchyItem): void => {
if (!item.parentId) {
return;
}
const containerParentId: string = containerItem.space
? containerItem.roomId
: containerItem.parentId;
const itemContent = item.content;
if (item.parentId !== containerParentId) {
mx.sendStateEvent(item.parentId, StateEvent.SpaceChild, {}, item.roomId);
}
const childItems = flattenHierarchy
.filter((i) => i.parentId === containerParentId && !item.space)
.filter((i) => i.roomId !== item.roomId);
const beforeItem: HierarchyItem | undefined = containerItem.space ? undefined : containerItem;
const beforeIndex = childItems.findIndex((i) => i.roomId === beforeItem?.roomId);
const insertIndex = beforeIndex + 1;
childItems.splice(insertIndex, 0, {
...item,
parentId: containerParentId,
content: { ...itemContent, order: undefined },
});
const currentOrders = childItems.map((i) => {
if (typeof i.content.order === 'string' && lex.has(i.content.order)) {
return i.content.order;
}
return undefined;
});
const newOrders = orderKeys(lex, currentOrders);
newOrders?.forEach((orderKey, index) => {
const itm = childItems[index];
if (itm && orderKey !== currentOrders[index]) {
mx.sendStateEvent(
containerParentId,
StateEvent.SpaceChild,
{ ...itm.content, order: orderKey },
itm.roomId
);
}
});
},
[mx, flattenHierarchy, lex]
);
useDnDMonitor(
scrollRef,
setDraggingItem,
useCallback((item, container) => {
console.log(item, container);
useCallback(
(item, container) => {
if (!canDrop(item, container)) {
return;
}
if (item.space) {
reorderSpace(item, container.item);
} else {
reorderRoom(item, container.item);
// TODO: prompt when dragging restricted room of private space to public space and etc.
}, [])
}
},
[reorderRoom, reorderSpace, canDrop]
)
);
const addSpaceRoom = (roomId: string) => setSpaceRooms({ type: 'PUT', roomId });

View file

@ -6,6 +6,7 @@ import { roomToParentsAtom } from '../state/room/roomToParents';
import { MSpaceChildContent, StateEvent } from '../../types/matrix/room';
import { getAllParents, getStateEvents, isValidChild } from '../utils/room';
import { isRoomId } from '../utils/matrix';
import { SortFunc } from '../utils/sort';
export type HierarchyItem =
| {
@ -22,6 +23,26 @@ export type HierarchyItem =
space?: false;
parentId: string;
};
const hierarchyItemByOrder: SortFunc<HierarchyItem> = (a, b) => {
const aOrder = a.content.order;
const bOrder = b.content.order;
const aTs = a.ts;
const bTs = b.ts;
if (!aOrder && !bOrder) {
return aTs - bTs;
}
if (!bOrder) return -1;
if (!aOrder) return 1;
if (aOrder > bOrder) {
return 1;
}
return 0;
};
const getFlattenSpaceHierarchy = (
rootSpaceId: string,
spaceRooms: Set<string>,
@ -66,10 +87,9 @@ const getFlattenSpaceHierarchy = (
};
findAndCollectHierarchySpaces(rootSpaceItem);
// TODO: sort by order + added ts
spaceItems = [
rootSpaceItem,
...spaceItems.filter((item) => item.roomId !== rootSpaceId).sort((a, b) => a.ts - b.ts),
...spaceItems.filter((item) => item.roomId !== rootSpaceId).sort(hierarchyItemByOrder),
];
const hierarchy: HierarchyItem[] = spaceItems.flatMap((spaceItem) => {
@ -93,7 +113,7 @@ const getFlattenSpaceHierarchy = (
};
childItems.push(childItem);
});
return [spaceItem, ...childItems.sort((a, b) => a.ts - b.ts)];
return [spaceItem, ...childItems.sort(hierarchyItemByOrder)];
});
return hierarchy;

View file

@ -185,16 +185,53 @@ export class ASCIILexicalTable {
return undefined;
}
between(i: string, j: string): string | undefined {
if (!this.has(i) || !this.has(j)) {
between(a: string, b: string): string | undefined {
if (!this.has(a) || !this.has(b)) {
return undefined;
}
const centerIndex = Math.floor((this.index(i) + this.index(j)) / 2);
const centerIndex = Math.floor((this.index(a) + this.index(b)) / 2);
const b = this.get(centerIndex);
if (b === i || b === j) return undefined;
return b;
const str = this.get(centerIndex);
if (str === a || str === b) return undefined;
return str;
}
nBetween(n: number, a: string, b: string): string[] | undefined {
if (n <= 0 || !this.has(a) || !this.has(b)) {
return undefined;
}
const indexA = this.index(a);
const indexB = this.index(b);
const nBetween = Math.max(indexA, indexB) - Math.min(indexA, indexB);
if (nBetween < n) {
return undefined;
}
const segmentSize = Math.floor(nBetween / (n + 1));
if (segmentSize === 0) return undefined;
const items: string[] = [];
for (
let segmentIndex = indexA + segmentSize;
segmentIndex < indexB;
segmentIndex += segmentSize
) {
if (items.length === n) break;
const str = this.get(segmentIndex);
if (!str) break;
items.push(str);
}
if (items.length < n) {
return undefined;
}
return items;
}
}
@ -234,10 +271,12 @@ export class ASCIILexicalTable {
// console.log('\n');
// const lex = new ASCIILexicalTable('a'.charCodeAt(0), 'c'.charCodeAt(0), 5);
// const lex = new ASCIILexicalTable('a'.charCodeAt(0), 'c'.charCodeAt(0), 3);
// printLex(lex);
// console.log(lex.between('a', 'aacb'));
// console.log(lex.get(123) === 'baa');
// console.log(lex.size());
// console.log(lex.nBetween(8, ' ', '~~~~~'));
// console.log(lex.between('a', 'ccc'));
// console.log(lex.get(11));
// console.log(lex.get(11) === 'aaac');
// const lex4 = new ASCIILexicalTable(' '.charCodeAt(0), '~'.charCodeAt(0), 5);
@ -291,3 +330,64 @@ export class ASCIILexicalTable {
// };
// perf();
const findNextFilledKey = (
fromIndex: number,
keys: Array<string | undefined>
): [number, string] | [-1, undefined] => {
for (let j = fromIndex; j < keys.length; j += 1) {
const key = keys[j];
if (typeof key === 'string') {
return [j, key];
}
}
return [-1, undefined];
};
export const orderKeys = (
lex: ASCIILexicalTable,
keys: Array<string | undefined>
): Array<string> | undefined => {
const newKeys: string[] = [];
for (let i = 0; i < keys.length; ) {
const key = keys[i];
const collectedKeys: string[] = [];
const [nextKeyIndex, nextKey] = findNextFilledKey(i + 1, keys);
const isKey = typeof key === 'string';
if (isKey) {
collectedKeys.push(key);
}
const keyToGenerateCount =
(nextKeyIndex === -1 ? keys.length : nextKeyIndex) - (key ? i + 1 : i + 0);
if (keyToGenerateCount > 0) {
const generatedKeys = lex.nBetween(
keyToGenerateCount,
key ?? lex.first(),
nextKey ?? lex.last()
);
if (generatedKeys) {
collectedKeys.push(...generatedKeys);
} else {
return lex.nBetween(keys?.length, lex.first(), lex.last());
}
}
newKeys.push(...collectedKeys);
i += collectedKeys.length;
}
if (newKeys.length !== keys.length) {
return undefined;
}
return newKeys;
};
// const lex = new ASCIILexicalTable('a'.charCodeAt(0), 'b'.charCodeAt(0), 2);
// const keys = [undefined, undefined];
// console.log(orderKeys(lex, keys));