Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix cell drag to reorder #251

Merged
merged 3 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@

const apiUrl = window.location.origin;

const currentVersion = 20240404;
const currentVersion = 20240406;
const tutorialHash = "fFjTsnFoSQMLwcvteVoNtL";

const termsVersion = 20240110;
Expand Down
105 changes: 51 additions & 54 deletions src/CellList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
import Cell from "./Cell.svelte";
import ButtonBar from "./ButtonBar.svelte";

let containers = [];
let cellElements: Cell[] = [];
let dragging = false;
let draggingSourceIndex;
let draggingContainer;
let draggingSkeleton;
let grabOffset;
let scrollingContainer;
let sheetBody;
let draggingSourceIndex: number;
let draggingSkeleton: HTMLDivElement;
let skeletonHeight: number;
let grabOffset: number;
let scrollingContainer: HTMLElement;
let sheetBody: HTMLUListElement;

export async function getMarkdown(): Promise<string> {
let markdown = "";
Expand All @@ -28,29 +27,33 @@
return markdown;
}

async function startDrag(event) {
function startDrag(event) {
if (!dragging) {
draggingSourceIndex = event.detail.index;

scrollingContainer = document.getElementById("main-content");
const draggingContainer = document.getElementById(`cell-container-${draggingSourceIndex}`);

draggingContainer = containers[event.detail.index];
if (!(scrollingContainer && draggingContainer)) {
return;
}

$activeCell = -1;
await tick();

const skeletonHeight = Math.min(scrollingContainer.getBoundingClientRect().height/2,
draggingContainer.getBoundingClientRect().height);
const draggingContainerRect = draggingContainer.getBoundingClientRect();

skeletonHeight = Math.min(scrollingContainer.getBoundingClientRect().height/2,
draggingContainerRect.height);

grabOffset = skeletonHeight / 2.0;

draggingSkeleton.style.top = `${event.detail.clientY-grabOffset}px`;
draggingSkeleton.style.left = `${draggingContainer.getBoundingClientRect().left}px`;
draggingSkeleton.style.left = `${draggingContainerRect.left}px`;
draggingSkeleton.style.height = `${skeletonHeight}px`;
draggingSkeleton.style.width = `${draggingContainer.getBoundingClientRect().width}px`;
draggingSkeleton.style.width = `${draggingContainerRect.width}px`;

dragging = true;

draggingSourceIndex = event.detail.index;

document.body.style.cursor = "grabbing";

window.addEventListener("mousemove", dragMove);
Expand All @@ -77,7 +80,7 @@
if (dragging) {
event.preventDefault(); // prevent scrolling on touch screens

let clientY;
let clientY: number;
if (event.type === "touchmove") {
clientY = event.changedTouches[0].clientY;
} else {
Expand All @@ -95,50 +98,43 @@
scrollingContainer.scroll(0, scrollingContainer.scrollTop + (skeletonRect.bottom - scrollRect.bottom));
}

let targetIndex = null;
for (const [i, container] of containers.entries()) {
if (container && container !== draggingContainer) {
const rect = container.getBoundingClientRect()
if (clientY > rect.top && clientY < rect.bottom) {
let targetIndex: number | null = null;
for (let i = 0; i < $cells.length; i++) {
const container = document.getElementById(`cell-container-${i}`);
if (container && i !== draggingSourceIndex) {
const rect = container.getBoundingClientRect();
if (i === 0 && clientY < rect.top) {
targetIndex = 0;
break;
} else if (i === $cells.length - 1 && clientY > rect.bottom) {
targetIndex = $cells.length - 1;
break;
} else if (draggingSourceIndex < i && clientY >= Math.max(rect.top, rect.bottom - skeletonHeight) && clientY <= rect.bottom) {
targetIndex = i;
break;
} else if (draggingSourceIndex > i && clientY >= rect.top && clientY <= Math.min(rect.bottom, rect.top + skeletonHeight)) {
targetIndex = i;

if (targetIndex !== draggingSourceIndex) {
if (clientY < 0.5*(rect.bottom + rect.top)) {
if (draggingSourceIndex === targetIndex - 1) {
// already moved above this element, need to prevent swapping
targetIndex = draggingSourceIndex;
}
} else {
targetIndex = i < $cells.length - 1 ? i + 1 : i
}
}

break;
}
}
}

if (targetIndex !== null) {
if (targetIndex !== draggingSourceIndex) {
// can't jump more than one cell at a time or order will be changed before drop
if (targetIndex - draggingSourceIndex > 1) {
targetIndex = draggingSourceIndex + 1;
} else if (draggingSourceIndex - targetIndex > 1) {
targetIndex = draggingSourceIndex - 1;
}

// Update cell location
// Need to make a shallow copy of $cells since destructuring assignment
// cannont be used on a reactive array.
let newCells = [...$cells];
[newCells[draggingSourceIndex], newCells[targetIndex]] = [newCells[targetIndex], newCells[draggingSourceIndex]];
$cells = newCells;
if (targetIndex !== null && targetIndex !== draggingSourceIndex) {
if (draggingSourceIndex > targetIndex) {
$cells = [...$cells.slice(0,targetIndex), $cells[draggingSourceIndex],
...$cells.slice(targetIndex, draggingSourceIndex),
...$cells.slice(draggingSourceIndex+1)];
} else {
$cells = [...$cells.slice(0,draggingSourceIndex),
...$cells.slice(draggingSourceIndex+1, targetIndex+1),
$cells[draggingSourceIndex],
...$cells.slice(targetIndex+1)];
}

draggingSourceIndex = targetIndex
draggingSourceIndex = targetIndex;

$results = [];
$mathCellChanged = true;
}
$results = [];
$mathCellChanged = true;
}

}
Expand Down Expand Up @@ -176,6 +172,7 @@
border: 2px solid lightgray;
border-radius: 10px;
position: fixed;
z-index: 100;
}
</style>

Expand All @@ -190,7 +187,7 @@
<li>
<ButtonBar on:insertSheet index={i} />
<div class="outer-container" class:first={i===0} class:last={i===$cells.length-1}
bind:this={containers[i]}
id={`cell-container-${i}`}
class:dragging={dragging && draggingSourceIndex === i}
>
<Cell
Expand Down
9 changes: 9 additions & 0 deletions src/Updates.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
}
</style>

<em>April 6, 2024</em>
<h4>Drag to Reorder Cells Improvements</h4>
<p>
Using the mouse or touch actions to drag and reorder cells has been improved
and no longer has issues after repeated drag events.
</p>

<br>

<em>April 4, 2024</em>
<h4>Differentiation and Integration Improvements</h4>
<p>
Expand Down
129 changes: 128 additions & 1 deletion tests/test_basic.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1509,4 +1509,131 @@ test('Test angular frequency conversions', async () => {
expect(parseLatexFloat(content)).toBeCloseTo(3/pi, precision);
content = await page.textContent('#result-units-4');
expect(content).toBe('s^-1');
});
});

test('Test cell drag to reorder', async () => {
await page.locator('#cell-0 >> math-field.editable').type("0=");

await page.locator('#add-math-cell').click();
await page.locator('#cell-1 >> math-field.editable').type("1=");

await page.locator('#add-math-cell').click();
await page.locator('#cell-2 >> math-field.editable').type("2=");

await page.locator('#add-math-cell').click();
await page.locator('#cell-3 >> math-field.editable').type("3=");

await page.locator('#add-math-cell').click();
await page.locator('#cell-4 >> math-field.editable').type("4=");

await page.locator('#add-math-cell').click();
await page.locator('#cell-5 >> math-field.editable').type("5=");

await page.locator('#add-math-cell').click();
await page.locator('#cell-6 >> math-field.editable').type("6=");

await page.locator('#cell-container-0 >> button[title="Drag to Move Cell"]')
.dragTo(page.locator('#cell-container-6 >> button[title="Drag to Move Cell"]'));

await page.waitForSelector('.status-footer', { state: 'detached'});

let content = await page.textContent('#result-value-0');
expect(parseLatexFloat(content)).toBeCloseTo(1, precision);

content = await page.textContent('#result-value-1');
expect(parseLatexFloat(content)).toBeCloseTo(2, precision);

content = await page.textContent('#result-value-2');
expect(parseLatexFloat(content)).toBeCloseTo(3, precision);

content = await page.textContent('#result-value-3');
expect(parseLatexFloat(content)).toBeCloseTo(4, precision);

content = await page.textContent('#result-value-4');
expect(parseLatexFloat(content)).toBeCloseTo(5, precision);

content = await page.textContent('#result-value-5');
expect(parseLatexFloat(content)).toBeCloseTo(6, precision);

content = await page.textContent('#result-value-6');
expect(parseLatexFloat(content)).toBeCloseTo(0, precision);

await page.locator('#cell-container-6 >> button[title="Drag to Move Cell"]')
.dragTo(page.locator('#cell-container-5 >> button[title="Drag to Move Cell"]'));

await page.waitForSelector('.status-footer', { state: 'detached'});

content = await page.textContent('#result-value-0');
expect(parseLatexFloat(content)).toBeCloseTo(1, precision);

content = await page.textContent('#result-value-1');
expect(parseLatexFloat(content)).toBeCloseTo(2, precision);

content = await page.textContent('#result-value-2');
expect(parseLatexFloat(content)).toBeCloseTo(3, precision);

content = await page.textContent('#result-value-3');
expect(parseLatexFloat(content)).toBeCloseTo(4, precision);

content = await page.textContent('#result-value-4');
expect(parseLatexFloat(content)).toBeCloseTo(5, precision);

content = await page.textContent('#result-value-5');
expect(parseLatexFloat(content)).toBeCloseTo(0, precision);

content = await page.textContent('#result-value-6');
expect(parseLatexFloat(content)).toBeCloseTo(6, precision);

await page.locator('#cell-container-5 >> button[title="Drag to Move Cell"]')
.dragTo(page.locator('#cell-container-0 >> button[title="Drag to Move Cell"]'));

await page.waitForSelector('.status-footer', { state: 'detached'});

content = await page.textContent('#result-value-0');
expect(parseLatexFloat(content)).toBeCloseTo(0, precision);

content = await page.textContent('#result-value-1');
expect(parseLatexFloat(content)).toBeCloseTo(1, precision);

content = await page.textContent('#result-value-2');
expect(parseLatexFloat(content)).toBeCloseTo(2, precision);

content = await page.textContent('#result-value-3');
expect(parseLatexFloat(content)).toBeCloseTo(3, precision);

content = await page.textContent('#result-value-4');
expect(parseLatexFloat(content)).toBeCloseTo(4, precision);

content = await page.textContent('#result-value-5');
expect(parseLatexFloat(content)).toBeCloseTo(5, precision);

content = await page.textContent('#result-value-6');
expect(parseLatexFloat(content)).toBeCloseTo(6, precision);

await page.locator('#cell-container-1 >> button[title="Drag to Move Cell"]')
.dragTo(page.locator('#cell-container-2 >> button[title="Drag to Move Cell"]'));

await page.waitForSelector('.status-footer', { state: 'detached'});

content = await page.textContent('#result-value-0');
expect(parseLatexFloat(content)).toBeCloseTo(0, precision);

content = await page.textContent('#result-value-1');
expect(parseLatexFloat(content)).toBeCloseTo(2, precision);

content = await page.textContent('#result-value-2');
expect(parseLatexFloat(content)).toBeCloseTo(1, precision);

content = await page.textContent('#result-value-3');
expect(parseLatexFloat(content)).toBeCloseTo(3, precision);

content = await page.textContent('#result-value-4');
expect(parseLatexFloat(content)).toBeCloseTo(4, precision);

content = await page.textContent('#result-value-5');
expect(parseLatexFloat(content)).toBeCloseTo(5, precision);

content = await page.textContent('#result-value-6');
expect(parseLatexFloat(content)).toBeCloseTo(6, precision);

});
Loading