Skip to content

Commit 0624198

Browse files
authored
Add up down arrows to section and children (#560)
* Add up down arrows to section and children * show up down arrows on hover
1 parent 434df94 commit 0624198

2 files changed

Lines changed: 234 additions & 47 deletions

File tree

apps/roam/src/components/settings/LeftSidebarGlobalSettings.tsx

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useCallback, useEffect, useMemo, useState, memo } from "react";
2-
import { Button, Collapse } from "@blueprintjs/core";
2+
import { Button, ButtonGroup, Collapse } from "@blueprintjs/core";
33
import FlagPanel from "roamjs-components/components/ConfigPanels/FlagPanel";
44
import AutocompleteInput from "roamjs-components/components/AutocompleteInput";
55
import getAllPageNames from "roamjs-components/queries/getAllPageNames";
@@ -19,22 +19,47 @@ import { refreshAndNotify } from "~/components/LeftSidebarView";
1919
const PageItem = memo(
2020
({
2121
page,
22+
index,
23+
isFirst,
24+
isLast,
25+
onMove,
2226
onRemove,
2327
}: {
2428
page: RoamBasicNode;
29+
index: number;
30+
isFirst: boolean;
31+
isLast: boolean;
32+
onMove: (index: number, direction: "up" | "down") => void;
2533
onRemove: (page: RoamBasicNode) => void;
2634
}) => {
2735
return (
28-
<div className="flex items-center justify-between rounded bg-gray-50 p-2 hover:bg-gray-100">
29-
<span className="flex-grow truncate">{page.text}</span>
30-
<Button
31-
icon="trash"
32-
minimal
33-
small
34-
intent="danger"
35-
onClick={() => onRemove(page)}
36-
title="Remove page"
37-
/>
36+
<div className="group flex items-center justify-between rounded bg-gray-50 p-2 hover:bg-gray-100">
37+
<div className="mr-2 flex-grow truncate">{page.text}</div>
38+
<ButtonGroup minimal>
39+
<Button
40+
icon="arrow-up"
41+
small
42+
disabled={isFirst}
43+
onClick={() => onMove(index, "up")}
44+
title="Move up"
45+
className="opacity-0 transition-opacity group-hover:opacity-100"
46+
/>
47+
<Button
48+
icon="arrow-down"
49+
small
50+
disabled={isLast}
51+
onClick={() => onMove(index, "down")}
52+
title="Move down"
53+
className="opacity-0 transition-opacity group-hover:opacity-100"
54+
/>
55+
<Button
56+
icon="trash"
57+
small
58+
intent="danger"
59+
onClick={() => onRemove(page)}
60+
title="Remove page"
61+
/>
62+
</ButtonGroup>
3863
</div>
3964
);
4065
},
@@ -116,6 +141,35 @@ const LeftSidebarGlobalSectionsContent = ({
116141
void initialize();
117142
}, [leftSidebar]);
118143

144+
const movePage = useCallback(
145+
(index: number, direction: "up" | "down") => {
146+
if (direction === "up" && index === 0) return;
147+
if (direction === "down" && index === pages.length - 1) return;
148+
149+
const newPages = [...pages];
150+
const [removed] = newPages.splice(index, 1);
151+
const newIndex = direction === "up" ? index - 1 : index + 1;
152+
newPages.splice(newIndex, 0, removed);
153+
154+
setPages(newPages);
155+
156+
if (childrenUid) {
157+
const order = direction === "down" ? newIndex + 1 : newIndex;
158+
159+
void window.roamAlphaAPI
160+
/* eslint-disable @typescript-eslint/naming-convention */
161+
.moveBlock({
162+
location: { "parent-uid": childrenUid, order },
163+
block: { uid: removed.uid },
164+
})
165+
.then(() => {
166+
refreshAndNotify();
167+
});
168+
}
169+
},
170+
[pages, childrenUid],
171+
);
172+
119173
const addPage = useCallback(
120174
async (pageName: string) => {
121175
if (!pageName || !childrenUid) return;
@@ -274,10 +328,14 @@ const LeftSidebarGlobalSectionsContent = ({
274328
</div>
275329
{pages.length > 0 ? (
276330
<div className="space-y-1">
277-
{pages.map((page) => (
331+
{pages.map((page, index) => (
278332
<PageItem
279333
key={page.uid}
280334
page={page}
335+
index={index}
336+
isFirst={index === 0}
337+
isLast={index === pages.length - 1}
338+
onMove={movePage}
281339
onRemove={() => void removePage(page)}
282340
/>
283341
))}

apps/roam/src/components/settings/LeftSidebarPersonalSettings.tsx

Lines changed: 164 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import discourseConfigRef from "~/utils/discourseConfigRef";
22
import React, { useCallback, useEffect, useMemo, useState } from "react";
33
import AutocompleteInput from "roamjs-components/components/AutocompleteInput";
44
import getAllPageNames from "roamjs-components/queries/getAllPageNames";
5-
import { Button, Dialog, Collapse, InputGroup, Icon } from "@blueprintjs/core";
5+
import {
6+
Button,
7+
ButtonGroup,
8+
Collapse,
9+
Dialog,
10+
InputGroup,
11+
} from "@blueprintjs/core";
612
import createBlock from "roamjs-components/writes/createBlock";
713
import deleteBlock from "roamjs-components/writes/deleteBlock";
814
import type { RoamBasicNode } from "roamjs-components/types";
@@ -26,11 +32,19 @@ const SectionItem = memo(
2632
setSettingsDialogSectionUid,
2733
pageNames,
2834
setSections,
35+
index,
36+
isFirst,
37+
isLast,
38+
onMoveSection,
2939
}: {
3040
section: LeftSidebarPersonalSectionConfig;
3141
setSections: Dispatch<SetStateAction<LeftSidebarPersonalSectionConfig[]>>;
3242
setSettingsDialogSectionUid: (uid: string | null) => void;
3343
pageNames: string[];
44+
index: number;
45+
isFirst: boolean;
46+
isLast: boolean;
47+
onMoveSection: (index: number, direction: "up" | "down") => void;
3448
}) => {
3549
const ref = extractRef(section.text);
3650
const blockText = getTextByBlockUid(ref);
@@ -203,6 +217,51 @@ const SectionItem = memo(
203217
[setSections],
204218
);
205219

220+
const moveChild = useCallback(
221+
(
222+
section: LeftSidebarPersonalSectionConfig,
223+
index: number,
224+
direction: "up" | "down",
225+
) => {
226+
if (!section.children) return;
227+
if (direction === "up" && index === 0) return;
228+
if (direction === "down" && index === section.children.length - 1)
229+
return;
230+
231+
const newChildren = [...section.children];
232+
const [removed] = newChildren.splice(index, 1);
233+
const newIndex = direction === "up" ? index - 1 : index + 1;
234+
newChildren.splice(newIndex, 0, removed);
235+
236+
setSections((prev) =>
237+
prev.map((s) => {
238+
if (s.uid === section.uid) {
239+
return {
240+
...s,
241+
children: newChildren,
242+
};
243+
}
244+
return s;
245+
}),
246+
);
247+
248+
if (section.childrenUid) {
249+
const order = direction === "down" ? newIndex + 1 : newIndex;
250+
251+
void window.roamAlphaAPI
252+
/* eslint-disable @typescript-eslint/naming-convention */
253+
.moveBlock({
254+
location: { "parent-uid": section.childrenUid, order },
255+
block: { uid: removed.uid },
256+
})
257+
.then(() => {
258+
refreshAndNotify();
259+
});
260+
}
261+
},
262+
[setSections],
263+
);
264+
206265
const handleAddChild = useCallback(async () => {
207266
if (childInput && section.childrenUid) {
208267
await addChildToSection(section, section.childrenUid, childInput);
@@ -224,7 +283,7 @@ const SectionItem = memo(
224283
border: "1px solid rgba(51, 51, 51, 0.2)",
225284
}}
226285
>
227-
<div className="flex items-center">
286+
<div className="group flex items-center">
228287
{!sectionWithoutSettingsAndChildren && (
229288
<Button
230289
icon={isExpanded ? "chevron-down" : "chevron-right"}
@@ -245,27 +304,43 @@ const SectionItem = memo(
245304
>
246305
<span className="font-medium">{originalName}</span>
247306
</div>
248-
<Button
249-
icon={sectionWithoutSettingsAndChildren ? "plus" : "settings"}
250-
minimal
251-
title={
252-
sectionWithoutSettingsAndChildren
253-
? "Add children"
254-
: "Edit section settings"
255-
}
256-
onClick={() =>
257-
sectionWithoutSettingsAndChildren
258-
? void convertToComplexSection(section)
259-
: void setSettingsDialogSectionUid(section.uid)
260-
}
261-
/>
262-
<Button
263-
icon="trash"
264-
minimal
265-
intent="danger"
266-
onClick={() => void removeSection(section)}
267-
title="Remove section"
268-
/>
307+
<ButtonGroup minimal>
308+
<Button
309+
icon="arrow-up"
310+
small
311+
disabled={isFirst}
312+
onClick={() => onMoveSection(index, "up")}
313+
title="Move section up"
314+
className="opacity-0 transition-opacity group-hover:opacity-100"
315+
/>
316+
<Button
317+
icon="arrow-down"
318+
small
319+
disabled={isLast}
320+
onClick={() => onMoveSection(index, "down")}
321+
title="Move section down"
322+
className="opacity-0 transition-opacity group-hover:opacity-100"
323+
/>
324+
<Button
325+
icon={sectionWithoutSettingsAndChildren ? "plus" : "settings"}
326+
title={
327+
sectionWithoutSettingsAndChildren
328+
? "Add children"
329+
: "Edit section settings"
330+
}
331+
onClick={() =>
332+
sectionWithoutSettingsAndChildren
333+
? void convertToComplexSection(section)
334+
: void setSettingsDialogSectionUid(section.uid)
335+
}
336+
/>
337+
<Button
338+
icon="trash"
339+
intent="danger"
340+
onClick={() => void removeSection(section)}
341+
title="Remove section"
342+
/>
343+
</ButtonGroup>
269344
</div>
270345

271346
{!sectionWithoutSettingsAndChildren && (
@@ -301,20 +376,41 @@ const SectionItem = memo(
301376

302377
{(section.children || []).length > 0 && (
303378
<div className="space-y-1">
304-
{(section.children || []).map((child) => (
379+
{(section.children || []).map((child, index) => (
305380
<div
306381
key={child.uid}
307-
className="flex items-center justify-between rounded bg-gray-50 p-2 hover:bg-gray-100"
382+
className="group flex items-center justify-between rounded bg-gray-50 p-2 hover:bg-gray-100"
308383
>
309-
<span className="flex-grow">{child.text}</span>
310-
<Button
311-
icon="trash"
312-
minimal
313-
small
314-
intent="danger"
315-
onClick={() => void removeChild(section, child)}
316-
title="Remove child"
317-
/>
384+
<div className="mr-2 min-w-0 flex-1 truncate">
385+
{child.text}
386+
</div>
387+
<ButtonGroup minimal className="flex-shrink-0">
388+
<Button
389+
icon="arrow-up"
390+
small
391+
disabled={index === 0}
392+
onClick={() => moveChild(section, index, "up")}
393+
title="Move child up"
394+
className="opacity-0 transition-opacity group-hover:opacity-100"
395+
/>
396+
<Button
397+
icon="arrow-down"
398+
small
399+
disabled={
400+
index === (section.children || []).length - 1
401+
}
402+
onClick={() => moveChild(section, index, "down")}
403+
title="Move child down"
404+
className="opacity-0 transition-opacity group-hover:opacity-100"
405+
/>
406+
<Button
407+
icon="trash"
408+
small
409+
intent="danger"
410+
onClick={() => void removeChild(section, child)}
411+
title="Remove child"
412+
/>
413+
</ButtonGroup>
318414
</div>
319415
))}
320416
</div>
@@ -424,6 +520,35 @@ const LeftSidebarPersonalSectionsContent = ({
424520
setNewSectionInput(value);
425521
}, []);
426522

523+
const moveSection = useCallback(
524+
(index: number, direction: "up" | "down") => {
525+
if (direction === "up" && index === 0) return;
526+
if (direction === "down" && index === sections.length - 1) return;
527+
528+
const newSections = [...sections];
529+
const [removed] = newSections.splice(index, 1);
530+
const newIndex = direction === "up" ? index - 1 : index + 1;
531+
newSections.splice(newIndex, 0, removed);
532+
533+
setSections(newSections);
534+
535+
if (personalSectionUid) {
536+
const order = direction === "down" ? newIndex + 1 : newIndex;
537+
538+
void window.roamAlphaAPI
539+
/* eslint-disable @typescript-eslint/naming-convention */
540+
.moveBlock({
541+
location: { "parent-uid": personalSectionUid, order },
542+
block: { uid: removed.uid },
543+
})
544+
.then(() => {
545+
refreshAndNotify();
546+
});
547+
}
548+
},
549+
[sections, personalSectionUid],
550+
);
551+
427552
const activeDialogSection = useMemo(() => {
428553
return sections.find((s) => s.uid === settingsDialogSectionUid) || null;
429554
}, [sections, settingsDialogSectionUid]);
@@ -470,13 +595,17 @@ const LeftSidebarPersonalSectionsContent = ({
470595
</div>
471596

472597
<div className="mt-2 space-y-2">
473-
{sections.map((section) => (
598+
{sections.map((section, index) => (
474599
<div key={section.uid}>
475600
<SectionItem
476601
section={section}
477602
setSettingsDialogSectionUid={setSettingsDialogSectionUid}
478603
pageNames={pageNames}
479604
setSections={setSections}
605+
index={index}
606+
isFirst={index === 0}
607+
isLast={index === sections.length - 1}
608+
onMoveSection={moveSection}
480609
/>
481610
</div>
482611
))}

0 commit comments

Comments
 (0)