From 25f187b8b534df4f6827ef614fec1eec98ef9b7b Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Sun, 13 Oct 2024 22:54:59 -0500 Subject: [PATCH 1/7] delete baord using solid-event --- prisma/dev.db | Bin 73728 -> 73728 bytes src/lib/db.ts | 10 +--------- src/routes/index.tsx | 29 +++++++++++------------------ 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/prisma/dev.db b/prisma/dev.db index 123a99fee86b022853a21f92f53af6e9700428fc..467295801d2ca39c3a53c0aa1dd37c6e291d463c 100644 GIT binary patch delta 771 zcmbV~J!@1!6o%*Ku2DiZv#VwN0+no#k(s$OcV@83l0qyrTHG$6^K}-%B*lWLg_~F` zSQ)d0Wg7ccZyAt)jrj*k{0o9V!3i4FMi#3%#hiz8p7U<*H#YYh+w)oH)!F5&^X~qe zm6;r`BK!&O;MKZ)@ix9L^1NCoHB$i~5Nb?8gck)d06@H=Hh6BBkTk?cOT9Jw;NdmD zu_mTc*XZl+<AaTIN1-f~Ix1a6V3C7#F=%15QyvDMF;;+c(MV%8RVGT!1PE%Zj8X8&*imH^ zr6u(~$}+NqkfC6lDhM*sj0P>z2W=zkB|BC+#>3xAC-IS#zN}_* z+(r0fI!G6zy0!g%Zh#Q#o|`{W^`H8FfPT1Sd58en&WkfEA10Y`d3G9IIRXR-(Cb0y z1^5GUU G-TeX9?A46` delta 214 zcmZoTz|wGlWr8%L(nJ|&Mx~7jOZ+*Q_$M*&pWvUgSx}*kpVfqkIaGb}o%t%0cgK5f zW>HwrKY4S0AO{nl90NZ;pWJ3a0U^H4a&^xbp>q9!JWTv;4E%@r+xQyz8a4||_`oMB zD$N>Nl3HA%oSc+mnrg_{r~1_{31p}g0|UcxaiDB*N?KwuLi$tu1dud8P`XncDD9Y+ rUy_l^u(@r1r2r$>rVT6sKxf`&;D5`1d$Ztz^Ze6q^D}C~T;~e_;LAkA diff --git a/src/lib/db.ts b/src/lib/db.ts index 0c2c97c..6031213 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,14 +1,6 @@ import { PrismaClient } from "@prisma/client"; -import { PrismaLibSQL } from "@prisma/adapter-libsql"; -import { createClient } from "@libsql/client"; -const libsql = createClient({ - url: `${process.env.TURSO_DATABASE_URL}`, - authToken: `${process.env.TURSO_AUTH_TOKEN}`, -}); - -const adapter = new PrismaLibSQL(libsql); -const db = new PrismaClient({ adapter }); +const db = new PrismaClient(); process.on("beforeExit", () => { db.$disconnect(); diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 8aca058..08c4d13 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -4,8 +4,8 @@ import { cache, createAsync, redirect, + useAction, useSubmission, - useSubmissions, type RouteDefinition, } from "@solidjs/router"; import { BsTrash } from "solid-icons/bs"; @@ -13,6 +13,7 @@ import { For, Show, onMount } from "solid-js"; import { getUser } from "~/lib"; import { getAuthUser } from "~/lib/auth"; import { db } from "~/lib/db"; +import { createEvent, createAsyncSubject } from "solid-events"; const addBoard = action(async (formData: FormData) => { "use server"; @@ -66,25 +67,17 @@ export const route = { export default function Home() { const user = createAsync(() => getUser()); - const serverBoards = createAsync(() => getBoards()); const addBoardSubmission = useSubmission(addBoard); - const deleteBoardSubmissions = useSubmissions(deleteBoard); - const boards = () => { - if (deleteBoardSubmissions.pending) { - const deletedBoards: number[] = []; + const [onDeleteBoard, emitDeleteBoard] = createEvent(); + onDeleteBoard(useAction(deleteBoard)); - for (const sub of deleteBoardSubmissions) { - deletedBoards.push(sub.input[0]); - } - - return serverBoards()?.filter( - (board) => !deletedBoards.includes(board.id) - ); - } - - return serverBoards(); - }; + const boards = createAsyncSubject( + () => getBoards(), + onDeleteBoard( + (boardId) => (boards) => boards.filter((board) => board.id !== boardId) + ) + ); let inputRef: HTMLInputElement | undefined; @@ -162,7 +155,7 @@ export default function Home() {
emitDeleteBoard(board.id)} method="post" > -
- From ae0ed02e9fa623be7e1e6ca9893c0763e1ad140c Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Sun, 13 Oct 2024 23:44:43 -0500 Subject: [PATCH 3/7] column move using events --- prisma/dev.db | Bin 73728 -> 73728 bytes src/components/Column.tsx | 180 ++++++++++++++++++++++---------------- 2 files changed, 104 insertions(+), 76 deletions(-) diff --git a/prisma/dev.db b/prisma/dev.db index ed3048972f719a115da1057a47fa35ffec2e6c73..78a46840c8f582c5e484b8a524a9a720dab1588c 100644 GIT binary patch delta 269 zcmZoTz|wGlWr8$g)I=F)#;A=63)c(sXECty9cAFp?3nUh&0FfaZj|mfNs31GzWWBp80mg>*4;a9J$AXnLRFa)BDKR1{PgLprMS6lkeVB;W9F0 zG#X>xH&T1v4Y3lC7e9zR5ps1Yj<7m&}&Z^FRe#=o9Fmj4F7$!0+X TC4Nl}=1`)X!MZv2{Nexr7~n$+ delta 119 zcmZoTz|wGlWr8%L*F+g-Mz4(t3)k~AFfg$2dou7}=3mNR&hNQdP{ER)O^1awl%H{O z!g*B=Qx+Z_W*~3!seS4k%se_wAel+~)i{}Wv>AbHPR7Z1@2LnG@o<4nie=z$<6qAo Q%YOrCk}v<}*z=160QpuOqyPW_ diff --git a/src/components/Column.tsx b/src/components/Column.tsx index cca6d73..5734ae2 100644 --- a/src/components/Column.tsx +++ b/src/components/Column.tsx @@ -15,6 +15,7 @@ import { AddNote, Note, NoteId, moveNote } from "./Note"; import { getAuthUser } from "~/lib/auth"; import { db } from "~/lib/db"; import { fetchBoard } from "~/lib"; +import { createEvent, createSubject, halt } from "solid-events"; export const renameColumn = action( async (id: ColumnId, name: string, timestamp: number) => { @@ -88,6 +89,9 @@ export type Column = { order: number; }; +type BlurInput = FocusEvent & { + target: HTMLInputElement; +}; export function Column(props: { column: Column; board: Board; notes: Note[] }) { let parent: HTMLDivElement | undefined; @@ -95,7 +99,56 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) { const deleteAction = useAction(deleteColumn); const moveNoteAction = useAction(moveNote); - const [acceptDrop, setAcceptDrop] = createSignal(false); + const [onDragStart, emitDragStart] = createEvent(); + const [onDragOver, emitDragOver] = createEvent< + DragEvent & { + currentTarget: HTMLDivElement; + } + >(); + const [onDragExit, emitDragExit] = createEvent(); + const [onDragLeave, emitDragLeave] = createEvent(); + const [onDrop, emitDrop] = createEvent(); + const [onBlur, emitBlur] = createEvent(); + + onDragStart((e) => + e.dataTransfer?.setData(DragTypes.Column, props.column.id) + ); + + onDrop((e) => { + if (e.dataTransfer?.types.includes(DragTypes.Note)) { + const noteId = e.dataTransfer?.getData(DragTypes.Note) as + | NoteId + | undefined; + + if (noteId && !filteredNotes().find((n) => n.id === noteId)) { + moveNoteAction( + noteId, + props.column.id, + getIndexBetween( + filteredNotes()[filteredNotes().length - 1]?.order, + undefined + ), + new Date().getTime() + ); + } + } + }); + + onBlur((e) => { + if (e.target.reportValidity()) { + renameAction(props.column.id, e.target.value, new Date().getTime()); + } + }); + + const acceptDrop = createSubject( + false, + onDragOver((e) => + e.dataTransfer?.types.includes(DragTypes.Note) ? true : halt() + ), + onDragLeave(() => false), + onDragExit(() => false), + onDrop(() => false) + ); const filteredNotes = createMemo(() => props.notes @@ -111,39 +164,12 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) { border: acceptDrop() === true ? "2px solid red" : "2px solid transparent", }} - onDragStart={(e) => { - e.dataTransfer?.setData(DragTypes.Column, props.column.id); - }} + onDragStart={emitDragStart} onDragEnter={(e) => e.preventDefault()} - onDragOver={(e) => { - e.preventDefault(); - if (e.dataTransfer?.types.includes(DragTypes.Note)) { - setAcceptDrop(true); - return; - } - }} - onDragLeave={(e) => setAcceptDrop(false)} - onDragExit={(e) => setAcceptDrop(false)} - onDrop={(e) => { - e.preventDefault(); - if (e.dataTransfer?.types.includes(DragTypes.Note)) { - const noteId = e.dataTransfer?.getData(DragTypes.Note) as - | NoteId - | undefined; - if (noteId && !filteredNotes().find((n) => n.id === noteId)) { - moveNoteAction( - noteId, - props.column.id, - getIndexBetween( - filteredNotes()[filteredNotes().length - 1]?.order, - undefined - ), - new Date().getTime() - ); - } - } - setAcceptDrop(false); - }} + onDragOver={(e) => (e.preventDefault(), emitDragOver(e))} + onDragLeave={emitDragLeave} + onDragExit={emitDragExit} + onDrop={(e) => (e.preventDefault(), emitDrop(e))} >
@@ -153,15 +179,7 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) { class="input input-ghost text-2xl font-bold w-full" value={props.column.title} required - onBlur={(e) => { - if (e.target.reportValidity()) { - renameAction( - props.column.id, - e.target.value, - new Date().getTime() - ); - } - }} + onBlur={emitBlur} onKeyDown={(e) => { if (e.keyCode === 13) { // @ts-expect-error maybe use currentTarget? @@ -203,8 +221,45 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) { } export function ColumnGap(props: { left?: Column; right?: Column }) { - const [active, setActive] = createSignal(false); const moveColumnAction = useAction(moveColumn); + + const [onDragOver, emitDragOver] = createEvent< + DragEvent & { + currentTarget: HTMLDivElement; + } + >(); + const [onDragExit, emitDragExit] = createEvent(); + const [onDragLeave, emitDragLeave] = createEvent(); + const [onDrop, emitDrop] = createEvent(); + + onDrop((e) => { + if ( + e.dataTransfer?.types.includes(DragTypes.Column) && + e.dataTransfer?.types.length === 1 + ) { + const columnId = e.dataTransfer?.getData(DragTypes.Column) as + | ColumnId + | undefined; + if (columnId) { + if (columnId === props.left?.id || columnId === props.right?.id) return; + const newOrder = getIndexBetween(props.left?.order, props.right?.order); + moveColumnAction(columnId, newOrder, new Date().getTime()); + } + } + }); + + const active = createSubject( + false, + onDragOver((e) => + e.dataTransfer?.types.includes(DragTypes.Column) && + e.dataTransfer?.types.length === 1 + ? true + : halt() + ), + onDrop(() => false), + onDragLeave(() => false), + onDragExit(() => false) + ); return (
e.preventDefault()} - onDragOver={(e) => { - e.preventDefault(); - e.stopPropagation(); - if ( - e.dataTransfer?.types.includes(DragTypes.Column) && - e.dataTransfer?.types.length === 1 - ) { - setActive(true); - } - }} - onDragLeave={(e) => setActive(false)} - onDragExit={(e) => setActive(false)} - onDrop={(e) => { - e.preventDefault(); - setActive(false); - if ( - e.dataTransfer?.types.includes(DragTypes.Column) && - e.dataTransfer?.types.length === 1 - ) { - const columnId = e.dataTransfer?.getData(DragTypes.Column) as - | ColumnId - | undefined; - if (columnId) { - if (columnId === props.left?.id || columnId === props.right?.id) - return; - const newOrder = getIndexBetween( - props.left?.order, - props.right?.order - ); - moveColumnAction(columnId, newOrder, new Date().getTime()); - } - } - }} + onDragOver={(e) => ( + e.preventDefault(), e.stopPropagation(), emitDragOver(e) + )} + onDragLeave={emitDragLeave} + onDragExit={emitDragExit} + onDrop={(e) => (e.preventDefault(), emitDrop(e))} /> ); } From 61d5c2ab931c6d1563666a12770ad0466ca4ecca Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Mon, 14 Oct 2024 00:12:53 -0500 Subject: [PATCH 4/7] board optimistic ui using subjectstore --- prisma/dev.db | Bin 73728 -> 73728 bytes src/components/Board.tsx | 361 +++++++------------------------------ src/components/Column.tsx | 30 +-- src/components/Note.tsx | 41 +++-- src/components/actions.tsx | 153 ++++++++++++++++ src/routes/board/[id].tsx | 5 +- 6 files changed, 263 insertions(+), 327 deletions(-) create mode 100644 src/components/actions.tsx diff --git a/prisma/dev.db b/prisma/dev.db index 78a46840c8f582c5e484b8a524a9a720dab1588c..7c59823f3e833881f3a81d74f444c2374471e33a 100644 GIT binary patch delta 1226 zcma)6O=wj|6uvikulFVI{^S{B3~334mttB5=YQskrG;*~Dm3YBN~_?^y)%SlQ9?k4 z6to*RE~<=Ow77CtDTvL=E)t=OmSmA`NzGaiYIjycM~PRnYq2 z&tUJk|5nAJ?CT&Ni$bkR%TcpgjD=2YPG$sSN>MZRz;`+Wp?k1B5z(+&bfVht4r26S z{pOFmzDC3Tm6vOMLttPch+FH8tS^2)?Mr3`#|M^L>w|v{e$#%`zT7_U?Q(1VwfVZP zx8TfhRCI!{2)U7*Glc-v%mPYE0TjXnSg5Q(ni+|h;KNRCc40j5d+=FSv#3V0qLc!o zIRn&qYF=p$FozgXA#*_qKeSJp?nK2Ni5Q}oz&WT4t2{~jR03*XndH!xNK(RBXP*@R zaFEGbXf(zMV5143!h5-d7(ic{E{%^7p+hw(jNSM>MLr*iiqX183Av$EYM^r@0L2gj z#VQSQ3W+paC`rxvv**jRsZ*O>5yxK~j*9V`$sn&3&jrBJcuf$@K@O<~P#IMUrE^Tt zIbzN&s-?Cc%OssW8Wm$TGe@Q}1WTYLSAarDfPzBT*_G15VrEgbWS1(N)JbJ*RXjU- zKvtqu$d(E)=DVbn=Rg?_0au18W=m!(KEE(Ow=lQYFNrE~KO{$?&*>$;O$r=#R%@at$0x!Vuka~oTr;p4B|^Q~WwPiF`7qDmT3 zLIF6DUV15Wpt;dNB1p`rV_uX!D svX>n@>hA1L)gPF0k9R+5WVgJdw!7}s)0u|!#Pcd_JiMzIqNFD) z+|p;{o!oU^g^MSOhdorDfiaYEvgA7zuGHd`qLOlx)FK{`axR{5ZuU?~u&mQN751{^ zin59_Zm1L&Oez~F1@dcRF&9W`vdx`XmRNT7(8+V}M6*P40vUhqY+@E?V4S?*jy5Cv zWXAm}lNs+tG5Sr8x%Ur9M&Dni2=ps2?;8gG8~i5x+ { - const mutations = untrack(() => getMutations()); - - 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)); - setBoardStore("timestamp", Date.now()); - }); - }); - - createEffect(() => { - const mutations = getMutations(); - const prevTimestamp = untrack(() => boardStore.timestamp); - const latestMutations = mutations.filter( - (m) => m.timestamp > prevTimestamp - ); - - console.log( - `found submission, apply optimistic update with mutations`, - ...latestMutations - ); - - if (!optimisticUpdates) return console.log(`Skipping optimistic update`); - - setBoardStore( - produce((b) => { - applyMutations(latestMutations, b.notes, b.columns); - b.timestamp = Date.now(); - }) - ); - }); + const { + onMoveColumn, + onMoveNote, + onCreateNote, + onCreateColumn, + onDeleteColumn, + onRenameColumn, + onDeleteNote, + onEditNote, + boardData, + } = useBoardActions(); + + const boardStore = createSubjectStore( + boardData, + onCreateNote(([note]) => (board) => { + if (!optimisticUpdates) return; + const index = board.notes.findIndex((n) => n.id === note.id); + if (index === -1) board.notes.push(note); + }), + onMoveNote(([note, column, order]) => (board) => { + if (!optimisticUpdates) return; + const index = board.notes.findIndex((n) => n.id === note); + if (index !== -1) { + board.notes[index].column = column; + board.notes[index].order = order; + } + }), + onEditNote(([id, content]) => (board) => { + if (!optimisticUpdates) return; + const index = board.notes.findIndex((n) => n.id === id); + if (index !== -1) board.notes[index].body = content; + }), + onDeleteNote(([id]) => (board) => { + if (!optimisticUpdates) return; + const index = board.notes.findIndex((n) => n.id === id); + if (index !== -1) board.notes.splice(index, 1); + }), + onCreateColumn(([id, boardId, name]) => (board) => { + if (!optimisticUpdates) return; + const index = board.columns.findIndex((c) => c.id === id); + if (index === -1) + board.columns.push({ + id: id, + board: boardId, + title: name, + order: board.columns.length + 1, + }); + }), + onRenameColumn(([id, name]) => (board) => { + if (!optimisticUpdates) return; + const index = board.columns.findIndex((c) => c.id === id); + if (index !== -1) board.columns[index].title = name; + }), + onMoveColumn(([id, order]) => (board) => { + if (!optimisticUpdates) return; + const index = board.columns.findIndex((c) => c.id === id); + if (index !== -1) board.columns[index].order = order; + }), + onDeleteColumn(([id]) => (board) => { + if (!optimisticUpdates) return; + const index = board.columns.findIndex((c) => c.id === id); + if (index !== -1) board.columns.splice(index, 1); + }) + ); const sortedColumns = createMemo(() => boardStore.columns.slice().sort((a, b) => a.order - b.order) @@ -285,73 +129,6 @@ export function Board(props: { board: BoardData }) { ); } -function applyMutations( - mutations: Mutation[], - notes: Note[], - columns: Column[] -) { - for (const mut of mutations.sort((a, b) => a.timestamp - b.timestamp)) { - switch (mut.type) { - case "createNote": { - const index = notes.findIndex((n) => n.id === mut.id); - if (index === -1) - notes.push({ - id: mut.id, - column: mut.column, - body: mut.body, - order: mut.order, - board: mut.board, - }); - break; - } - case "moveNote": { - const index = notes.findIndex((n) => n.id === mut.id); - if (index !== -1) { - notes[index].column = mut.column; - notes[index].order = mut.order; - } - break; - } - case "editNote": { - const index = notes.findIndex((n) => n.id === mut.id); - if (index !== -1) notes[index].body = mut.content; - break; - } - case "deleteNote": { - const index = notes.findIndex((n) => n.id === mut.id); - if (index !== -1) notes.splice(index, 1); - break; - } - case "createColumn": { - const index = columns.findIndex((c) => c.id === mut.id); - if (index === -1) - columns.push({ - id: mut.id, - board: mut.board, - title: mut.title, - order: columns.length + 1, - }); - break; - } - case "renameColumn": { - const index = columns.findIndex((c) => c.id === mut.id); - if (index !== -1) columns[index].title = mut.title; - break; - } - case "moveColumn": { - const index = columns.findIndex((c) => c.id === mut.id); - if (index !== -1) columns[index].order = mut.order; - break; - } - case "deleteColumn": { - const index = columns.findIndex((c) => c.id === mut.id); - if (index !== -1) columns.splice(index, 1); - break; - } - } - } -} - let optimisticUpdates = true; if (typeof window !== "undefined") { // disable optimistic updates in production for testing/demonstration purposes diff --git a/src/components/Column.tsx b/src/components/Column.tsx index 5734ae2..8a10bea 100644 --- a/src/components/Column.tsx +++ b/src/components/Column.tsx @@ -16,6 +16,7 @@ import { getAuthUser } from "~/lib/auth"; import { db } from "~/lib/db"; import { fetchBoard } from "~/lib"; import { createEvent, createSubject, halt } from "solid-events"; +import { useBoardActions } from "./actions"; export const renameColumn = action( async (id: ColumnId, name: string, timestamp: number) => { @@ -95,9 +96,8 @@ type BlurInput = FocusEvent & { export function Column(props: { column: Column; board: Board; notes: Note[] }) { let parent: HTMLDivElement | undefined; - const renameAction = useAction(renameColumn); - const deleteAction = useAction(deleteColumn); - const moveNoteAction = useAction(moveNote); + const { emitRenameColumn, emitDeleteColumn, emitMoveNote } = + useBoardActions(); const [onDragStart, emitDragStart] = createEvent(); const [onDragOver, emitDragOver] = createEvent< @@ -121,22 +121,22 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) { | undefined; if (noteId && !filteredNotes().find((n) => n.id === noteId)) { - moveNoteAction( + emitMoveNote([ noteId, props.column.id, getIndexBetween( filteredNotes()[filteredNotes().length - 1]?.order, undefined ), - new Date().getTime() - ); + new Date().getTime(), + ]); } } }); onBlur((e) => { if (e.target.reportValidity()) { - renameAction(props.column.id, e.target.value, new Date().getTime()); + emitRenameColumn([props.column.id, e.target.value, new Date().getTime()]); } }); @@ -189,7 +189,9 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) { /> @@ -221,7 +223,7 @@ export function Column(props: { column: Column; board: Board; notes: Note[] }) { } export function ColumnGap(props: { left?: Column; right?: Column }) { - const moveColumnAction = useAction(moveColumn); + const { emitMoveColumn } = useBoardActions(); const [onDragOver, emitDragOver] = createEvent< DragEvent & { @@ -243,7 +245,7 @@ export function ColumnGap(props: { left?: Column; right?: Column }) { if (columnId) { if (columnId === props.left?.id || columnId === props.right?.id) return; const newOrder = getIndexBetween(props.left?.order, props.right?.order); - moveColumnAction(columnId, newOrder, new Date().getTime()); + emitMoveColumn([columnId, newOrder, new Date().getTime()]); } } }); @@ -281,7 +283,7 @@ export function ColumnGap(props: { left?: Column; right?: Column }) { export function AddColumn(props: { board: BoardId; onAdd: () => void }) { const [active, setActive] = createSignal(false); - const addColumn = useAction(createColumn); + const { emitCreateColumn } = useBoardActions(); let inputRef: HTMLInputElement | undefined; let plusRef: HTMLButtonElement | undefined; @@ -296,12 +298,12 @@ export function AddColumn(props: { board: BoardId; onAdd: () => void }) {
( e.preventDefault(), - addColumn( + emitCreateColumn([ crypto.randomUUID() as ColumnId, props.board, inputRef?.value ?? "Column", - new Date().getTime() - ), + new Date().getTime(), + ]), inputRef && (inputRef.value = ""), props.onAdd() )} diff --git a/src/components/Note.tsx b/src/components/Note.tsx index 893fa38..b567f2b 100644 --- a/src/components/Note.tsx +++ b/src/components/Note.tsx @@ -9,6 +9,7 @@ import { getAuthUser } from "~/lib/auth"; import { db } from "~/lib/db"; import { fetchBoard } from "~/lib"; import { createEvent, createSubject, halt } from "solid-events"; +import { useBoardActions } from "./actions"; export const createNote = action( async ({ @@ -125,9 +126,7 @@ type BlurTextArea = FocusEvent & { }; export function Note(props: { note: Note; previous?: Note; next?: Note }) { - const updateAction = useAction(editNote); - const deleteAction = useAction(deleteNote); - const moveNoteAction = useAction(moveNote); + const { emitMoveNote, emitDeleteNote, emitEditNote } = useBoardActions(); let input: HTMLTextAreaElement | undefined; @@ -165,26 +164,26 @@ export function Note(props: { note: Note; previous?: Note; next?: Note }) { if (!noteId || noteId === props.note.id) return; if (acceptDrop() === "top" && props.previous?.id !== noteId) { - return moveNoteAction( + return emitMoveNote([ noteId, props.note.column, getIndexBetween(props.previous?.order, props.note.order), - new Date().getTime() - ); + new Date().getTime(), + ]); } if (acceptDrop() === "bottom" && props.next?.id !== noteId) { - return moveNoteAction( + return emitMoveNote([ noteId, props.note.column, getIndexBetween(props.note.order, props.next?.order), - new Date().getTime() - ); + new Date().getTime(), + ]); } }); onBlur((e) => - updateAction(props.note.id, e.target.value, new Date().getTime()) + emitEditNote([props.note.id, e.target.value, new Date().getTime()]) ); const acceptDrop = createSubject<"top" | "bottom" | false>( @@ -244,7 +243,7 @@ export function Note(props: { note: Note; previous?: Note; next?: Note }) { @@ -262,7 +261,7 @@ export function AddNote(props: { onAdd: () => void; board: BoardId; }) { - const addNote = useAction(createNote); + const { emitCreateNote } = useBoardActions(); const [onSubmit, emitSubmit] = createEvent(); const [onCancel, emitCancel] = createEvent(); @@ -285,14 +284,16 @@ export function AddNote(props: { inputRef?.reportValidity(); return; } - addNote({ - id: crypto.randomUUID() as NoteId, - board: props.board, - column: props.column, - body, - order: props.length + 1, - timestamp: new Date().getTime(), - }); + emitCreateNote([ + { + id: crypto.randomUUID() as NoteId, + board: props.board, + column: props.column, + body, + order: props.length + 1, + timestamp: new Date().getTime(), + }, + ]); inputRef && (inputRef.value = ""); props.onAdd(); }); diff --git a/src/components/actions.tsx b/src/components/actions.tsx new file mode 100644 index 0000000..3b52426 --- /dev/null +++ b/src/components/actions.tsx @@ -0,0 +1,153 @@ +import { useAction, useSubmission } from "@solidjs/router"; +import { Accessor, createContext, ParentProps, useContext } from "solid-js"; + +import { createColumn, deleteColumn, moveColumn, renameColumn } from "./Column"; +import { createNote, deleteNote, editNote, moveNote } from "./Note"; +import { BoardData } from "./Board"; +import { + createEvent, + createSubject, + createTopic, + Emitter, + halt, + Handler, +} from "solid-events"; + +type CreateColumnProps = Parameters; +type MoveColumnProps = Parameters; +type RenameColumnProps = Parameters; +type DeleteColumnProps = Parameters; +type CreateNoteProps = Parameters; +type MoveNoteProps = Parameters; +type EditNoteProps = Parameters; +type DeleteNoteProps = Parameters; + +const ctx = createContext<{ + onCreateNote: Handler; + emitCreateNote: Emitter; + onMoveNote: Handler; + emitMoveNote: Emitter; + onEditNote: Handler; + emitEditNote: Emitter; + onDeleteNote: Handler; + emitDeleteNote: Emitter; + onCreateColumn: Handler; + emitCreateColumn: Emitter; + onMoveColumn: Handler; + emitMoveColumn: Emitter; + onRenameColumn: Handler; + emitRenameColumn: Emitter; + onDeleteColumn: Handler; + emitDeleteColumn: Emitter; + boardData: Accessor; +}>(); + +export function useBoardActions() { + const value = useContext(ctx); + if (!value) throw new Error("BoardActionsProvider not found"); + return value; +} + +export function BoardActionsProvider(props: ParentProps<{ board: BoardData }>) { + const [onCreateNote, emitCreateNote] = createEvent(); + const createNoteAction = useAction(createNote); + const createNoteSubmission = useSubmission(createNote); + const onCreateNoteComplete = onCreateNote((p) => createNoteAction(...p)); + + const [onMoveNote, emitMoveNote] = createEvent(); + const moveNoteAction = useAction(moveNote); + const moveNoteSubmission = useSubmission(moveNote); + const onMoveNoteComplete = onMoveNote((p) => moveNoteAction(...p)); + + const [onEditNote, emitEditNote] = createEvent(); + const updateNoteAction = useAction(editNote); + const updateNoteSubmission = useSubmission(editNote); + const onEditNoteComplete = onEditNote((p) => updateNoteAction(...p)); + + const [onDeleteNote, emitDeleteNote] = createEvent(); + const deleteNoteAction = useAction(deleteNote); + const deleteNoteSubmission = useSubmission(deleteNote); + const onDeleteNoteComplete = onDeleteNote((p) => deleteNoteAction(...p)); + + const [onCreateColumn, emitCreateColumn] = createEvent(); + const createColumnAction = useAction(createColumn); + const createColumnSubmission = useSubmission(createColumn); + const onCreateColumnComplete = onCreateColumn((p) => + createColumnAction(...p) + ); + + const [onMoveColumn, emitMoveColumn] = createEvent(); + const moveColumnAction = useAction(moveColumn); + const moveColumnSubmission = useSubmission(moveColumn); + const onMoveColumnComplete = onMoveColumn((p) => moveColumnAction(...p)); + + const [onRenameColumn, emitRenameColumn] = createEvent(); + const renameColumnAction = useAction(renameColumn); + const renameColumnSubmission = useSubmission(renameColumn); + const onRenameColumnComplete = onRenameColumn((p) => + renameColumnAction(...p) + ); + + const [onDeleteColumn, emitDeleteColumn] = createEvent(); + const deleteColumnAction = useAction(deleteColumn); + const deleteColumnSubmission = useSubmission(deleteColumn); + const onDeleteColumnComplete = onDeleteColumn((p) => + deleteColumnAction(...p) + ); + + const onActionComplete = createTopic( + onCreateNoteComplete, + onMoveNoteComplete, + onEditNoteComplete, + onDeleteNoteComplete, + onCreateColumnComplete, + onMoveColumnComplete, + onRenameColumnComplete, + onDeleteColumnComplete + ); + + const boardData = createSubject( + { + board: props.board.board, + columns: props.board.columns, + notes: props.board.notes, + }, + onActionComplete(() => { + if ( + createNoteSubmission.pending || + moveNoteSubmission.pending || + updateNoteSubmission.pending || + deleteNoteSubmission.pending || + createColumnSubmission.pending || + moveColumnSubmission.pending || + renameColumnSubmission.pending || + deleteColumnSubmission.pending + ) + halt(); + + return props.board; + }) + ); + + const value = { + onCreateNote, + emitCreateNote, + onMoveNote, + emitMoveNote, + onEditNote, + emitEditNote, + onDeleteNote, + emitDeleteNote, + onCreateColumn, + emitCreateColumn, + onMoveColumn, + emitMoveColumn, + onRenameColumn, + emitRenameColumn, + onDeleteColumn, + emitDeleteColumn, + boardData, + }; + + return {props.children}; +} diff --git a/src/routes/board/[id].tsx b/src/routes/board/[id].tsx index f5e188f..68116cf 100644 --- a/src/routes/board/[id].tsx +++ b/src/routes/board/[id].tsx @@ -8,6 +8,7 @@ import { useSubmission, } from "@solidjs/router"; import { Show } from "solid-js"; +import { BoardActionsProvider } from "~/components/actions"; import { Board } from "~/components/Board"; import EditableText from "~/components/EditableText"; import { fetchBoard } from "~/lib"; @@ -56,7 +57,9 @@ export default function Page(props: RouteSectionProps) {
- + + +
)} From dca02ce99f6b7dcfa3d37d2db23dee3172cb66f2 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Mon, 14 Oct 2024 02:01:33 -0500 Subject: [PATCH 5/7] using partition --- src/components/Note.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/Note.tsx b/src/components/Note.tsx index b567f2b..90187fa 100644 --- a/src/components/Note.tsx +++ b/src/components/Note.tsx @@ -8,7 +8,12 @@ import { getIndexBetween } from "~/lib/utils"; import { getAuthUser } from "~/lib/auth"; import { db } from "~/lib/db"; import { fetchBoard } from "~/lib"; -import { createEvent, createSubject, halt } from "solid-events"; +import { + createEvent, + createPartition, + createSubject, + halt, +} from "solid-events"; import { useBoardActions } from "./actions"; export const createNote = action( @@ -186,16 +191,18 @@ export function Note(props: { note: Note; previous?: Note; next?: Note }) { emitEditNote([props.note.id, e.target.value, new Date().getTime()]) ); + const [onDragOverValidEl, onDragOverInvalidEl] = createPartition( + onDragOver, + (e) => !!e.dataTransfer?.types.includes(DragTypes.Note) + ); + const acceptDrop = createSubject<"top" | "bottom" | false>( false, onDragExit(() => false), onDragLeave(() => false), onDrop(() => false), - onDragOver((e) => { - if (!e.dataTransfer?.types.includes(DragTypes.Note)) { - return false; - } - + onDragOverInvalidEl(() => false), + onDragOverValidEl((e) => { const rect = e.currentTarget.getBoundingClientRect(); const midpoint = (rect.top + rect.bottom) / 2; const isTop = e.clientY < midpoint; From 11515414158db50254367fcfc9445581032d8033 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Mon, 14 Oct 2024 02:20:21 -0500 Subject: [PATCH 6/7] declarative drop note --- src/components/Note.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/Note.tsx b/src/components/Note.tsx index 90187fa..29ae124 100644 --- a/src/components/Note.tsx +++ b/src/components/Note.tsx @@ -159,15 +159,19 @@ export function Note(props: { note: Note; previous?: Note; next?: Note }) { onDragEnd(() => false) ); - onDrop((e) => { - if (!e.dataTransfer?.types.includes(DragTypes.Note)) return; + const onDropNote = onDrop((e) => { + if (!e.dataTransfer?.types.includes(DragTypes.Note)) halt(); const noteId = e.dataTransfer?.getData(DragTypes.Note) as | NoteId | undefined; - if (!noteId || noteId === props.note.id) return; + if (!noteId || noteId === props.note.id) halt(); + return noteId; + }); + + onDropNote((noteId) => { if (acceptDrop() === "top" && props.previous?.id !== noteId) { return emitMoveNote([ noteId, From 5a5db21dcb67a50dc1eee6e819a45890abcebbbc Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Mon, 14 Oct 2024 03:54:38 -0500 Subject: [PATCH 7/7] using package from npm --- package.json | 1 + pnpm-lock.yaml | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/package.json b/package.json index 4c62b34..32f3a88 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "kysely": "^0.27.3", "lowdb": "^7.0.1", "prisma": "^5.7.0", + "solid-events": "^0.0.4", "solid-icons": "^1.1.0", "solid-js": "^1.8.22", "unimport": "^3.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6167e5b..e562ac4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: prisma: specifier: ^5.7.0 version: 5.18.0 + solid-events: + specifier: ^0.0.4 + version: 0.0.4 solid-icons: specifier: ^1.1.0 version: 1.1.0(solid-js@1.8.22) @@ -947,6 +950,11 @@ packages: peerDependencies: solid-js: ^1.8.6 + '@solidjs/router@0.14.8': + resolution: {integrity: sha512-S+rD5Twp0820cM03wEIYtb7/4KN7Cfr3BP+qPIqb7IXO/SZ72tWqHEMQsmcjDbr4yVfpA+5Sq0Y+xcq09y1gQA==} + peerDependencies: + solid-js: ^1.8.6 + '@solidjs/start@1.0.6': resolution: {integrity: sha512-O5knaeqDBx+nKLJRm5ZJurnXZtIYBOwOreQ10APaVtVjKIKKRC5HxJ1Kwqg7atOQNNDgsF0pzhW218KseaZ1UA==} @@ -2579,6 +2587,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -2671,6 +2682,9 @@ packages: smob@1.5.0: resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + solid-events@0.0.4: + resolution: {integrity: sha512-ZpHHOf0X0PnWyp3ECnIrB8+VMVrDPlnkbVel2GpLhceJh39LWk/aLyKbYeMoUEM7TzhkTc6rbBN40VO3A3M3lA==} + solid-icons@1.1.0: resolution: {integrity: sha512-IesTfr/F1ElVwH2E1110s2RPXH4pujKfSs+koT8rwuTAdleO5s26lNSpqJV7D1+QHooJj18mcOiz2PIKs0ic+A==} peerDependencies: @@ -2679,6 +2693,9 @@ packages: solid-js@1.8.22: resolution: {integrity: sha512-VBzN5j+9Y4rqIKEnK301aBk+S7fvFSTs9ljg+YEdFxjNjH0hkjXPiQRcws9tE5fUzMznSS6KToL5hwMfHDgpLA==} + solid-js@1.9.2: + resolution: {integrity: sha512-fe/K03nV+kMFJYhAOE8AIQHcGxB4rMIEoEyrulbtmf217NffbbwBqJnJI4ovt16e+kaIt0czE2WA7mP/pYN9yg==} + solid-refresh@0.6.3: resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} peerDependencies: @@ -3897,6 +3914,10 @@ snapshots: dependencies: solid-js: 1.8.22 + '@solidjs/router@0.14.8(solid-js@1.9.2)': + dependencies: + solid-js: 1.9.2 + '@solidjs/start@1.0.6(rollup@4.21.0)(solid-js@1.8.22)(vinxi@0.4.2(@libsql/client@0.6.2)(@types/node@20.16.1)(better-sqlite3@9.6.0)(ioredis@5.4.1)(terser@5.31.6))(vite@5.4.2(@types/node@20.16.1)(terser@5.31.6))': dependencies: '@vinxi/plugin-directives': 0.4.1(vinxi@0.4.2(@libsql/client@0.6.2)(@types/node@20.16.1)(better-sqlite3@9.6.0)(ioredis@5.4.1)(terser@5.31.6)) @@ -5758,6 +5779,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rxjs@7.8.1: + dependencies: + tslib: 2.6.3 + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -5849,6 +5874,12 @@ snapshots: smob@1.5.0: {} + solid-events@0.0.4: + dependencies: + '@solidjs/router': 0.14.8(solid-js@1.9.2) + rxjs: 7.8.1 + solid-js: 1.9.2 + solid-icons@1.1.0(solid-js@1.8.22): dependencies: solid-js: 1.8.22 @@ -5859,6 +5890,12 @@ snapshots: seroval: 1.1.1 seroval-plugins: 1.1.1(seroval@1.1.1) + solid-js@1.9.2: + dependencies: + csstype: 3.1.3 + seroval: 1.1.1 + seroval-plugins: 1.1.1(seroval@1.1.1) + solid-refresh@0.6.3(solid-js@1.8.22): dependencies: '@babel/generator': 7.25.0