mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-02-13 16:13:24 +01:00
add space children reorder mechanism
This commit is contained in:
parent
bda7adc033
commit
cb5bb2fc73
3 changed files with 243 additions and 16 deletions
|
@ -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);
|
||||
// TODO: prompt when dragging restricted room of private space to public space and etc.
|
||||
}, [])
|
||||
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 });
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue