Browse Source

Improve event movement within the event tree

It's still buggy, but it's better than before.
master
buttercat1791 1 year ago
parent
commit
b041d206c1
  1. 47
      src/lib/components/Preview.svelte
  2. 90
      src/lib/parser.ts

47
src/lib/components/Preview.svelte

@ -1,9 +1,11 @@ @@ -1,9 +1,11 @@
<script lang="ts">
import { parser } from "$lib/parser";
import { parser, SiblingSearchDirection } from "$lib/parser";
import { Button, ButtonGroup, CloseButton, Heading, Input, P, Textarea, Tooltip } from "flowbite-svelte";
import { CaretDownSolid, CaretUpSolid, EditOutline } from "flowbite-svelte-icons";
import { createEventDispatcher } from "svelte";
// TODO: Fix move between parents.
export let sectionClass: string = '';
export let isSectionStart: boolean = false;
export let rootId: string;
@ -42,15 +44,26 @@ @@ -42,15 +44,26 @@
if (subtreeNeedsUpdate) {
subtreeUpdateCount++;
subtreeNeedsUpdate = false;
const prevChildCount = orderedChildren.length;
orderedChildren = $parser.getOrderedChildIds(rootId);
const newChildCount = orderedChildren.length;
// If the number of children has changed, a child has been added or removed, and a child may
// have been moved into a different subtree. Due to the `needsUpdate` binding in the
// component's recursion, setting `needsUpdate` to true will force the parent to rerender its
// subtree.
if (newChildCount !== prevChildCount) {
needsUpdate = true;
}
}
}
$: {
if (parentId) {
if (parentId && allowEditing) {
// Check for previous/next siblings on load
const previousSibling = $parser.getPreviousSibling(rootId, parentId, depth);
const nextSibling = $parser.getNextSibling(rootId, parentId, depth);
const previousSibling = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Previous);
const nextSibling = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Next);
// Hide arrows if no siblings exist
hasPreviousSibling = !!previousSibling[0];
@ -108,24 +121,26 @@ @@ -108,24 +121,26 @@
const moveUp = (rootId: string, parentId: string) => {
// Get the previous sibling and its index
const [prevSiblingId, prevIndex] = $parser.getPreviousSibling(rootId, parentId, depth);
if (prevSiblingId && prevIndex != null) {
// Move the current event before the previous sibling
$parser.moveEvent(rootId, parentId, parentId, prevIndex);
needsUpdate = true;
const [prevSiblingId, prevIndex] = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Previous);
if (!prevSiblingId || prevIndex == null) {
return;
}
// Move the current event before the previous sibling.
$parser.moveEvent(rootId, prevSiblingId, false);
needsUpdate = true;
};
const moveDown = (rootId: string, parentId: string) => {
// Get the next sibling and its index
const [nextSiblingId, nextIndex] = $parser.getNextSibling(rootId, parentId, depth);
if (nextSiblingId && nextIndex != null) {
// Move the current event after the next sibling
$parser.moveEvent(rootId, parentId, parentId, nextIndex + 1);
needsUpdate = true;
const [nextSiblingId, nextIndex] = $parser.getNearestSibling(rootId, depth - 1, SiblingSearchDirection.Next);
if (!nextSiblingId || nextIndex == null) {
return;
}
// Move the current event after the next sibling
$parser.moveEvent(rootId, nextSiblingId, true);
needsUpdate = true;
};
</script>

90
src/lib/parser.ts

@ -277,7 +277,6 @@ export default class Pharos { @@ -277,7 +277,6 @@ export default class Pharos {
*/
getNearestSibling(
targetDTag: string,
parentDTag: string,
depth: number,
direction: SiblingSearchDirection
): [string | null, string | null] {
@ -292,6 +291,12 @@ export default class Pharos { @@ -292,6 +291,12 @@ export default class Pharos {
throw new Error(`The event indicated by #d:${targetDTag} does not exist at level ${depth} of the event tree.`);
}
const parentDTag = this.getParent(targetDTag);
if (!parentDTag) {
throw new Error(`The event indicated by #d:${targetDTag} does not have a parent.`);
}
const grandparentDTag = this.getParent(parentDTag);
// If the target is the first node at its level and we're searching for a previous sibling,
@ -302,7 +307,7 @@ export default class Pharos { @@ -302,7 +307,7 @@ export default class Pharos {
return [null, null];
}
return this.getNearestSibling(parentDTag, grandparentDTag!, depth - 1, direction);
return this.getNearestSibling(parentDTag, depth - 1, direction);
}
// If the target is the last node at its level and we're searching for a next sibling,
@ -313,7 +318,7 @@ export default class Pharos { @@ -313,7 +318,7 @@ export default class Pharos {
return [null, null];
}
return this.getNearestSibling(parentDTag, grandparentDTag!, depth - 1, direction);
return this.getNearestSibling(parentDTag, depth - 1, direction);
}
// * Base case: There is an adjacent sibling at the same depth as the target.
@ -353,62 +358,55 @@ export default class Pharos { @@ -353,62 +358,55 @@ export default class Pharos {
/**
* Moves an event within the event tree.
* @param dTag The d tag of the event to be moved.
* @param oldParentDTag The d tag of the moved event's current parent.
* @param newParentDTag The d tag of the moved event's new parent.
* @param index The index at which to insert the event to be moved among the children of the new
* parent.
* @param insertAfter If true, the event will be inserted after the specified index, otherwise,
* it will be inserted immediately before the specified index.
* @param targetDTag The d tag of the event to be moved.
* @param destinationDTag The d tag another event, next to which the target will be placed.
* @param insertAfter If true, the target will be placed after the destination event, otherwise,
* it will be placed before the destination event.
* @throws Throws an error if the parameters specify an invalid move.
* @remarks Both the old and new parent events must be kind 30040 index events. Moving the event
* within the tree changes the hash of several events, so the event tree will be regenerated when
* the consumer next invokes `getEvents()`.
*/
// TODO: Update this to work with two d tags and a direction.
moveEvent(
dTag: string,
oldParentDTag: string,
newParentDTag: string,
index?: number,
insertAfter: boolean = false
): void {
const event = this.events.get(dTag);
if (!event) {
throw new Error(`No event found for #d:${dTag}.`);
}
* @remarks Moving the target event within the tree changes the hash of several events, so the
* event tree will be regenerated when the consumer next invokes `getEvents()`.
*/
moveEvent(targetDTag: string, destinationDTag: string, insertAfter: boolean = false): void {
const targetEvent = this.events.get(targetDTag);
const destinationEvent = this.events.get(destinationDTag);
const targetParent = this.getParent(targetDTag);
const destinationParent = this.getParent(destinationDTag);
if (this.eventToKindMap.get(oldParentDTag) !== 30040) {
throw new Error(`Old parent event #d:${oldParentDTag} is not an index event.`);
if (!targetEvent) {
throw new Error(`No event found for #d:${targetDTag}.`);
}
if (this.eventToKindMap.get(newParentDTag) !== 30040) {
throw new Error(`New parent event #d:${newParentDTag} is not an index event.`);
if (!destinationEvent) {
throw new Error(`No event found for #d:${destinationDTag}.`);
}
const oldParentMap = this.indexToChildEventsMap.get(oldParentDTag);
const newParentMap = this.indexToChildEventsMap.get(newParentDTag);
if (!targetParent) {
throw new Error(`The event indicated by #d:${targetDTag} does not have a parent.`);
}
if (!oldParentMap?.has(dTag)) {
throw new Error(`Event #d:${dTag} is not a child of parent #d:${oldParentDTag}.`);
if (!destinationParent) {
throw new Error(`The event indicated by #d:${destinationDTag} does not have a parent.`);
}
// Remove the target event from the old parent.
oldParentMap?.delete(dTag);
// Remove the target from among the children of its current parent.
this.indexToChildEventsMap.get(targetParent)?.delete(targetDTag);
this.shouldUpdateEventTree = true;
// If necessary, remove the target event from among the children of its destination parent.
this.indexToChildEventsMap.get(destinationParent)?.delete(targetDTag);
if (index == null) {
newParentMap?.add(dTag);
return;
}
// Get the index of the destination event among the children of its parent.
const destinationIndex = Array.from(this.indexToChildEventsMap.get(destinationParent) ?? [])
.indexOf(destinationDTag);
// Add the target event to the new parent at the specified index.
const newParentChildren: string[] = Array.from(newParentMap || new Set());
newParentChildren.splice(index + Number(insertAfter), 0, dTag);
this.indexToChildEventsMap.set(newParentDTag, new Set(newParentChildren));
// Insert next to the index of the destination event, either before or after as specified by
// the insertAfter flag.
const destinationChildren = Array.from(this.indexToChildEventsMap.get(destinationParent) ?? []);
insertAfter
? destinationChildren.splice(destinationIndex + 1, 0, targetDTag)
: destinationChildren.splice(destinationIndex, 0, targetDTag);
this.indexToChildEventsMap.set(destinationParent, new Set(destinationChildren));
this.buildEventsByLevelMap(newParentDTag, 0);
this.shouldUpdateEventTree = true;
}
/**

Loading…
Cancel
Save