11// Design inspiration from Trello
2- import React from "react" ;
2+ import React , {
3+ useCallback ,
4+ useEffect ,
5+ useMemo ,
6+ useRef ,
7+ useState ,
8+ } from "react" ;
39import { Column , Result } from "../utils/types" ;
4- import { Button , Icon , InputGroup , Tooltip } from "@blueprintjs/core" ;
10+ import { Button , Icon , Popover , Tooltip } from "@blueprintjs/core" ;
511import Draggable from "react-draggable" ;
612import setInputSettings from "roamjs-components/util/setInputSettings" ;
713import openBlockInSidebar from "roamjs-components/writes/openBlockInSidebar" ;
@@ -11,8 +17,8 @@ import AutocompleteInput from "roamjs-components/components/AutocompleteInput";
1117import predefinedSelections from "../utils/predefinedSelections" ;
1218import toCellValue from "../utils/toCellValue" ;
1319import extractTag from "roamjs-components/util/extractTag" ;
14- import createBlock from "roamjs-components/writes/createBlock " ;
15- import updateBlock from "roamjs-components/writes/updateBlock " ;
20+ import deleteBlock from "roamjs-components/writes/deleteBlock " ;
21+ import getSubTree from "roamjs-components/util/getSubTree " ;
1622
1723const zPriority = z . record ( z . number ( ) . min ( 0 ) . max ( 1 ) ) ;
1824
@@ -25,7 +31,7 @@ const KanbanCard = (card: {
2531 $getColumnElement : ( x : number ) => HTMLDivElement | undefined ;
2632 result : Result ;
2733} ) => {
28- const [ isDragging , setIsDragging ] = React . useState ( false ) ;
34+ const [ isDragging , setIsDragging ] = useState ( false ) ;
2935
3036 return (
3137 < Draggable
@@ -99,11 +105,11 @@ const Kanban = ({
99105 onQuery : ( ) => void ;
100106 parentUid : string ;
101107} ) => {
102- const byUid = React . useMemo (
108+ const byUid = useMemo (
103109 ( ) => Object . fromEntries ( data . map ( ( d ) => [ d . uid , d ] as const ) ) ,
104110 [ data ]
105111 ) ;
106- const columnKey = React . useMemo ( ( ) => {
112+ const columnKey = useMemo ( ( ) => {
107113 const configuredKey = Array . isArray ( layout . key )
108114 ? layout . key [ 0 ]
109115 : typeof layout . key === "string"
@@ -130,7 +136,7 @@ const Kanban = ({
130136 return defaultColumnKey ;
131137 } , [ layout . key ] ) ;
132138 const DEFAULT_FORMAT = `No ${ columnKey } ` ;
133- const displayKey = React . useMemo ( ( ) => {
139+ const displayKey = useMemo ( ( ) => {
134140 const configuredDisplay = Array . isArray ( layout . display )
135141 ? layout . display [ 0 ]
136142 : typeof layout . display === "string"
@@ -145,7 +151,7 @@ const Kanban = ({
145151 } ) ;
146152 return defaultDisplayKey ;
147153 } , [ layout . key ] ) ;
148- const [ columns , setColumns ] = React . useState ( ( ) => {
154+ const [ columns , setColumns ] = useState ( ( ) => {
149155 const configuredCols = Array . isArray ( layout . columns )
150156 ? layout . columns
151157 : typeof layout . columns === "string"
@@ -172,7 +178,7 @@ const Kanban = ({
172178 . slice ( 0 , 3 ) ;
173179 } ) ;
174180
175- const [ prioritization , setPrioritization ] = React . useState ( ( ) => {
181+ const [ prioritization , setPrioritization ] = useState ( ( ) => {
176182 const base64 = Array . isArray ( layout . prioritization )
177183 ? layout . prioritization [ 0 ]
178184 : typeof layout . prioritization === "string"
@@ -189,16 +195,16 @@ const Kanban = ({
189195 } ) ;
190196 return stored ;
191197 } ) ;
192- const layoutUid = React . useMemo ( ( ) => {
198+ const layoutUid = useMemo ( ( ) => {
193199 return Array . isArray ( layout . uid )
194200 ? layout . uid [ 0 ]
195201 : typeof layout . uid === "string"
196202 ? layout . uid
197203 : "" ; // should we throw an error here? Should never happen in practice...
198204 } , [ layout . uid ] ) ;
199- const [ isAdding , setIsAdding ] = React . useState ( false ) ;
200- const [ newColumn , setNewColumn ] = React . useState ( "" ) ;
201- const cards = React . useMemo ( ( ) => {
205+ const [ isAdding , setIsAdding ] = useState ( false ) ;
206+ const [ newColumn , setNewColumn ] = useState ( "" ) ;
207+ const cards = useMemo ( ( ) => {
202208 const cards : Record < string , Result [ ] > = { } ;
203209 data . forEach ( ( d ) => {
204210 const column =
@@ -219,20 +225,20 @@ const Kanban = ({
219225 } ) ;
220226 return cards ;
221227 } , [ data , prioritization , columnKey ] ) ;
222- const potentialColumns = React . useMemo ( ( ) => {
228+ const potentialColumns = useMemo ( ( ) => {
223229 const columnSet = new Set ( columns ) ;
224230 return Object . keys ( cards ) . filter ( ( c ) => ! columnSet . has ( c ) ) ;
225231 } , [ cards , columns ] ) ;
226- React . useEffect ( ( ) => {
232+ useEffect ( ( ) => {
227233 const base64 = window . btoa ( JSON . stringify ( prioritization ) ) ;
228234 setInputSetting ( {
229235 blockUid : layoutUid ,
230236 key : "prioritization" ,
231237 value : base64 ,
232238 } ) ;
233239 } , [ prioritization ] ) ;
234- const containerRef = React . useRef < HTMLDivElement > ( null ) ;
235- const getColumnElement = React . useCallback (
240+ const containerRef = useRef < HTMLDivElement > ( null ) ;
241+ const getColumnElement = useCallback (
236242 ( x : number ) => {
237243 if ( ! containerRef . current ) return ;
238244 const columnEls = Array . from < HTMLDivElement > (
@@ -246,7 +252,7 @@ const Kanban = ({
246252 } ,
247253 [ containerRef ]
248254 ) ;
249- const reprioritizeAndUpdateBlock = React . useCallback < Reprioritize > (
255+ const reprioritizeAndUpdateBlock = useCallback < Reprioritize > (
250256 ( { uid, x, y } ) => {
251257 if ( ! containerRef . current ) return ;
252258 const newColumn = getColumnElement ( x ) ;
@@ -306,10 +312,41 @@ const Kanban = ({
306312 } ,
307313 [ setPrioritization , cards , containerRef , byUid , columnKey , parentUid ]
308314 ) ;
309- const showLegend = React . useMemo (
315+ const showLegend = useMemo (
310316 ( ) => ( Array . isArray ( layout . legend ) ? layout . legend [ 0 ] : layout . legend ) ,
311317 [ layout . legend ]
312318 ) ;
319+ const [ openedPopoverIndex , setOpenedPopoverIndex ] = useState < number | null > (
320+ null
321+ ) ;
322+
323+ const moveColumn = async (
324+ direction : "left" | "right" ,
325+ columnIndex : number
326+ ) => {
327+ const offset = direction === "left" ? - 1 : 1 ;
328+ const newColumns = [ ...columns ] ;
329+ // Swap elements
330+ [ newColumns [ columnIndex ] , newColumns [ columnIndex + offset ] ] = [
331+ newColumns [ columnIndex + offset ] ,
332+ newColumns [ columnIndex ] ,
333+ ] ;
334+
335+ const columnUid = getSubTree ( {
336+ key : "columns" ,
337+ parentUid : layoutUid ,
338+ } ) . uid ;
339+ await deleteBlock ( columnUid ) ;
340+
341+ setInputSettings ( {
342+ blockUid : layoutUid ,
343+ key : "columns" ,
344+ values : newColumns ,
345+ } ) ;
346+ setColumns ( newColumns ) ;
347+ setOpenedPopoverIndex ( null ) ;
348+ } ;
349+
313350 return (
314351 < >
315352 { showLegend === "Yes" && (
@@ -334,7 +371,7 @@ const Kanban = ({
334371 className = "gap-2 items-start relative roamjs-kanban-container overflow-x-scroll grid w-full"
335372 ref = { containerRef }
336373 >
337- { columns . map ( ( col ) => {
374+ { columns . map ( ( col , columnIndex ) => {
338375 return (
339376 < div
340377 key = { col }
@@ -347,19 +384,55 @@ const Kanban = ({
347384 style = { { display : "flex" } }
348385 >
349386 < span className = "font-bold" > { col } </ span >
350- < Button
351- icon = { "trash" }
352- minimal
353- onClick = { ( ) => {
354- const values = columns . filter ( ( c ) => c !== col ) ;
355- setInputSettings ( {
356- blockUid : layout . uid as string ,
357- key : "columns" ,
358- values,
359- } ) ;
360- setColumns ( values ) ;
361- } }
362- />
387+ < Popover
388+ autoFocus = { false }
389+ interactionKind = "hover"
390+ placement = "bottom"
391+ isOpen = { openedPopoverIndex === columnIndex }
392+ onInteraction = { ( next ) =>
393+ next
394+ ? setOpenedPopoverIndex ( columnIndex )
395+ : setOpenedPopoverIndex ( null )
396+ }
397+ captureDismiss = { true }
398+ content = {
399+ < >
400+ < Button
401+ className = "p-4"
402+ minimal
403+ icon = "arrow-left"
404+ disabled = { columnIndex === 0 }
405+ onClick = { ( ) => moveColumn ( "left" , columnIndex ) }
406+ />
407+ < Button
408+ className = "p-4"
409+ minimal
410+ icon = "arrow-right"
411+ disabled = { columnIndex === columns . length - 1 }
412+ onClick = { ( ) => moveColumn ( "right" , columnIndex ) }
413+ />
414+ < Button
415+ className = "p-4"
416+ intent = "danger"
417+ minimal
418+ icon = "trash"
419+ onClick = { ( ) => {
420+ const values = columns . filter ( ( c ) => c !== col ) ;
421+ setInputSettings ( {
422+ blockUid : layout . uid as string ,
423+ key : "columns" ,
424+ values,
425+ } ) ;
426+ setColumns ( values ) ;
427+ setOpenedPopoverIndex ( null ) ;
428+ } }
429+ />
430+ </ >
431+ }
432+ position = "bottom-left"
433+ >
434+ < Button icon = "more" minimal />
435+ </ Popover >
363436 </ div >
364437 { ( cards [ col ] || [ ] ) ?. map ( ( d ) => (
365438 < KanbanCard
@@ -397,7 +470,7 @@ const Kanban = ({
397470 onClick = { ( ) => {
398471 const values = [ ...columns , newColumn ] ;
399472 setInputSettings ( {
400- blockUid : layout . uid as string ,
473+ blockUid : layoutUid ,
401474 key : "columns" ,
402475 values,
403476 } ) ;
0 commit comments