Skip to content
Open
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"autoprefixer": "^10.4.16",
"postcss": "^8.4.31",
"tailwindcss": "^3.4.1",
"unocss": "^0.58.5"
"unocss": "^0.58.5",
"vitest": "^3.2.2"
},
"dependencies": {
"@formkit/auto-animate": "^0.8.1",
Expand Down
37 changes: 18 additions & 19 deletions src/components/Board.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Action, useSubmissions } from "@solidjs/router";
import { For, batch, createEffect, createMemo, untrack } from "solid-js";
import { useSubmissions } from "@solidjs/router";
import { For, batch, createEffect, mapArray, untrack } from "solid-js";
import { createStore, produce, reconcile } from "solid-js/store";
import {
AddColumn,
Expand All @@ -19,6 +19,7 @@ import {
editNote,
moveNote,
} from "./Note";
import { sortIntoArray } from "~/lib/utils";

export enum DragTypes {
Note = "application/note",
Expand Down Expand Up @@ -211,11 +212,6 @@ export function Board(props: { board: BoardData }) {
const { notes, columns } = props.board;
applyMutations(mutations, notes, columns);

console.log(
`got server data, reset the board with mutations`,
...mutations
);

batch(() => {
setBoardStore("notes", reconcile(notes));
setBoardStore("columns", reconcile(columns));
Expand All @@ -230,12 +226,7 @@ export function Board(props: { board: BoardData }) {
(m) => m.timestamp > prevTimestamp
);

console.log(
`found submission, apply optimistic update with mutations`,
...latestMutations
);

if (!optimisticUpdates) return console.log(`Skipping optimistic update`);
if (!optimisticUpdates) return;

setBoardStore(
produce((b) => {
Expand All @@ -245,10 +236,18 @@ export function Board(props: { board: BoardData }) {
);
});

const sortedColumns = createMemo(() =>
boardStore.columns.slice().sort((a, b) => a.order - b.order)
const [sortedColumns, setSortedColumns] = createStore<Column[]>([]);
const mapped = mapArray(
() => boardStore.columns,
(column) => {
createEffect(() => {
setSortedColumns(produce((f) => sortIntoArray(f, column)));
});
}
);

createEffect(() => mapped());

let scrollContainerRef: HTMLDivElement | undefined;

return (
Expand All @@ -258,8 +257,8 @@ export function Board(props: { board: BoardData }) {
}}
class="pb-8 h-[calc(100vh-160px)] min-w-full overflow-x-auto overflow-y-hidden flex flex-start items-start flex-nowrap"
>
<ColumnGap right={sortedColumns()[0]} />
<For each={sortedColumns()}>
<ColumnGap right={sortedColumns[0]} />
<For each={sortedColumns}>
{(column, i) => (
<>
<Column
Expand All @@ -268,8 +267,8 @@ export function Board(props: { board: BoardData }) {
notes={boardStore.notes}
/>
<ColumnGap
left={sortedColumns()[i()]}
right={sortedColumns()[i() + 1]}
left={sortedColumns[i()]}
right={sortedColumns[i() + 1]}
/>
</>
)}
Expand Down
42 changes: 31 additions & 11 deletions src/components/Column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import {
For,
Match,
Switch,
createMemo,
createEffect,
createSignal,
mapArray,
onMount,
} from "solid-js";
import { type Board, type BoardId, DragTypes } from "./Board";
import { getIndexBetween } from "~/lib/utils";
import { getIndexBetween, sortIntoArray } from "~/lib/utils";
import { AddNote, Note, NoteId, moveNote } from "./Note";
import { getAuthUser } from "~/lib/auth";
import { db } from "~/lib/db";
import { fetchBoard } from "~/lib";
import { createStore, produce } from "solid-js/store";

export const renameColumn = action(
async (id: ColumnId, name: string, timestamp: number) => {
Expand Down Expand Up @@ -97,12 +99,30 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) {

const [acceptDrop, setAcceptDrop] = createSignal<boolean>(false);

const filteredNotes = createMemo(() =>
props.notes
.filter((n) => n.column === props.column.id)
.sort((a, b) => a.order - b.order)
const [filteredNotes, setFilteredNotes] = createStore<Note[]>([]);

const mapped = mapArray(
() => props.notes,
(note) => {
createEffect(() => {
setFilteredNotes(
produce((f) => {
if (note.column === props.column.id) {
sortIntoArray(f, note);
} else {
const index = f.findIndex((n) => n.id === note.id);
if (index !== -1) {
f.splice(index, 1);
}
}
})
);
});
}
);

createEffect(() => mapped());

return (
<div
draggable="true"
Expand Down Expand Up @@ -130,12 +150,12 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) {
const noteId = e.dataTransfer?.getData(DragTypes.Note) as
| NoteId
| undefined;
if (noteId && !filteredNotes().find((n) => n.id === noteId)) {
if (noteId && !filteredNotes.find((n) => n.id === noteId)) {
moveNoteAction(
noteId,
props.column.id,
getIndexBetween(
filteredNotes()[filteredNotes().length - 1]?.order,
filteredNotes[filteredNotes.length - 1]?.order,
undefined
),
new Date().getTime()
Expand Down Expand Up @@ -180,12 +200,12 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) {
class="flex h-full flex-col space-y-2 overflow-y-auto px-1"
ref={parent}
>
<For each={filteredNotes()}>
<For each={filteredNotes}>
{(n, i) => (
<Note
note={n}
previous={filteredNotes()[i() - 1]}
next={filteredNotes()[i() + 1]}
previous={filteredNotes[i() - 1]}
next={filteredNotes[i() + 1]}
/>
)}
</For>
Expand Down
102 changes: 102 additions & 0 deletions src/lib/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { describe, it, expect } from "vitest";
import { sortIntoArray } from "./utils";

describe("sortIntoArray", () => {
type TestItem = {
id: string;
order: number;
data?: string;
};

it("should add item to empty array", () => {
const array: TestItem[] = [];
const item: TestItem = { id: "1", order: 1 };

sortIntoArray(array, item);
expect(array).toEqual([{ id: "1", order: 1 }]);
});

it("should insert item in correct order position", () => {
const array: TestItem[] = [
{ id: "1", order: 1 },
{ id: "3", order: 3 },
];
const item: TestItem = { id: "2", order: 2 };

sortIntoArray(array, item);
expect(array).toEqual([
{ id: "1", order: 1 },
{ id: "2", order: 2 },
{ id: "3", order: 3 },
]);
});

it("should update existing item and maintain order", () => {
const array: TestItem[] = [
{ id: "1", order: 1 },
{ id: "2", order: 2 },
{ id: "3", order: 3 },
];
const item: TestItem = { id: "2", order: 4, data: "updated" };

sortIntoArray(array, item);
expect(array).toEqual([
{ id: "1", order: 1 },
{ id: "3", order: 3 },
{ id: "2", order: 4, data: "updated" },
]);
});

it("should append item with highest order", () => {
const array: TestItem[] = [
{ id: "1", order: 1 },
{ id: "2", order: 2 },
];
const item: TestItem = { id: "3", order: 3 };

sortIntoArray(array, item);
expect(array).toEqual([
{ id: "1", order: 1 },
{ id: "2", order: 2 },
{ id: "3", order: 3 },
]);
});

it("should insert item with lowest order", () => {
const array: TestItem[] = [
{ id: "2", order: 2 },
{ id: "3", order: 3 },
];
const item: TestItem = { id: "1", order: 1 };

sortIntoArray(array, item);
expect(array).toEqual([
{ id: "1", order: 1 },
{ id: "2", order: 2 },
{ id: "3", order: 3 },
]);
});

it("should handle items with same order", () => {
const array: TestItem[] = [
{ id: "1", order: 1 },
{ id: "2", order: 1 },
];
const item: TestItem = { id: "3", order: 1 };

sortIntoArray(array, item);
expect(array).toEqual([
{ id: "1", order: 1 },
{ id: "2", order: 1 },
{ id: "3", order: 1 },
]);
});

it("should preserve additional properties when updating", () => {
const array: TestItem[] = [{ id: "1", order: 1, data: "original" }];
const item: TestItem = { id: "1", order: 2, data: "updated" };

sortIntoArray(array, item);
expect(array).toEqual([{ id: "1", order: 2, data: "updated" }]);
});
});
28 changes: 28 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,31 @@ export const getIndexBetween = (
below: number | undefined,
above: number | undefined
) => getIndicesBetween(below, above, 1)[0];

export function sortIntoArray<T extends { id: string; order: number }>(
array: T[],
item: T
) {
if (array.length === 0) {
array.push(item);
} else {
const index = array.findIndex((n) => n.id === item.id);

if (index !== -1) {
array.splice(index, 1);
}

let inserted = false;
for (let i = 0; i < array.length; i++) {
if (array[i].order > item.order) {
array.splice(i, 0, item);
inserted = true;
break;
}
}

if (!inserted) {
array.push(item);
}
}
}