Skip to content

Commit

Permalink
Merge pull request #17 from AlexSciFier/category-links-sorting
Browse files Browse the repository at this point in the history
Category links sorting
  • Loading branch information
AlexSciFier authored Nov 27, 2022
2 parents 07f457e + dc0013d commit cf78241
Show file tree
Hide file tree
Showing 15 changed files with 470 additions and 158 deletions.
22 changes: 22 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@dnd-kit/core": "^6.0.5",
"@dnd-kit/modifiers": "^6.0.0",
"@dnd-kit/sortable": "^7.0.1",
"@heroicons/react": "^1.0.6",
"@testing-library/jest-dom": "^5.16.4",
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/components/LazyIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { useState } from "react";

export default function LazyIcon({ id, title }) {
const [isLoading, setIsLoading] = useState(true);
return (
<>
<img
onLoad={() => setIsLoading(false)}
className={`w-8 h-8 bg-contain bg-no-repeat bg-center transition-opacity ${isLoading ? "opacity-0" : "opacity-100"}`}
height={32}
width={32}
loading="lazy"
alt={`icon for ${title}`}
src={`${process.env.NODE_ENV !== "production" ? "http://localhost:3333" : ""}/api/bookmarks/${id}/icon`}
></img>
<div
className={`w-8 h-8 animate-pulse rounded bg-gray-200 absolute top-0 ${isLoading ? "" : "hidden"}`}
></div>
</>
);
}
31 changes: 31 additions & 0 deletions frontend/src/components/Sortable/SortableItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";

export default function SortableItem({ item, isActive = false, children }) {
const {
attributes,
listeners,
setNodeRef,
isDragging,
transform,
transition,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Translate.toString(transform),
transition,
};
return (
<div
{...listeners}
ref={setNodeRef}
style={style}
{...attributes}
className={`flex items-center gap-3 border dark:border-neutral-600 p-1 rounded bg-white dark:bg-gray-900 hover:bg-neutral-200 dark:hover:bg-gray-800 dark:text-white transition-shadow ${
isActive ? "shadow-xl z-50 cursor-grabbing" : "cursor-grab"
} ${isDragging ? "opacity-40" : ""}`}
>
{children}
</div>
);
}
98 changes: 98 additions & 0 deletions frontend/src/components/Sortable/SortableList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
DndContext,
DragOverlay,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import {
restrictToVerticalAxis,
restrictToParentElement,
} from "@dnd-kit/modifiers";
import React, { useMemo, useState } from "react";
import { useEffect } from "react";
import SortableItem from "./SortableItem";
import LazyIcon from "../LazyIcon";

export default function SortableList({ items, onDragEnd }) {
const [itemsState, setItemsState] = useState(items);
const [active, setActive] = useState(null);
const activeItem = useMemo(
() => itemsState.find((item) => item.id === active?.id),
[active, itemsState]
);

useEffect(() => {
setItemsState(items);
}, [items]);

function handleDragEnd(e) {
const { active, over } = e;

if (over && active.id !== over?.id) {
const activeIndex = itemsState.findIndex(({ id }) => id === active.id);
const overIndex = itemsState.findIndex(({ id }) => id === over.id);
let movedArray = arrayMove(itemsState, activeIndex, overIndex);
setItemsState(movedArray);
let sortedArray = movedArray.map((item, idx) => {
return { ...item, position: ++idx };
});

let idPositionPairArray = sortedArray.map((item) => {
return { id: item.id, position: item.position };
});
onDragEnd(idPositionPairArray);
}
}
const sensors = useSensors(
useSensor(MouseSensor),
useSensor(TouchSensor, {
activationConstraint: {
delay: 250,
tolerance: 5,
},
})
);
return (
<DndContext
sensors={sensors}
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
onDragEnd={handleDragEnd}
onDragStart={({ active }) => {
setActive(active);
}}
>
<SortableContext
items={itemsState}
strategy={verticalListSortingStrategy}
>
<div className="flex flex-col gap-3">
{itemsState.map((item) => (
<SortableItem key={item.id} item={item}>
<div className="relative flex-none">
<LazyIcon id={item.id} />
</div>
<div className="truncate flex-1">{item.title}</div>
</SortableItem>
))}
</div>
</SortableContext>
<DragOverlay>
{activeItem ? (
<SortableItem item={activeItem} isActive={true}>
<div className="relative flex-none">
<LazyIcon id={activeItem.id} />
</div>
<div className="truncate flex-1">{activeItem.title}</div>
</SortableItem>
) : null}
</DragOverlay>
</DndContext>
);
}
12 changes: 11 additions & 1 deletion frontend/src/context/bookmarkList.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { createContext, useContext, useRef, useState } from "react";
import { deleteJSON, getJSON } from "../helpers/fetch";
import { deleteJSON, getJSON, putJSON } from "../helpers/fetch";

const BookMarkList = createContext();

Expand Down Expand Up @@ -65,6 +65,15 @@ export function BookMarkListProvider({ children }) {
}
}

async function changePositions(idPositionPairArray) {
abortController.current = new AbortController();
await putJSON(
`api/bookmarks/changePositions`,
idPositionPairArray,
abortController.current.signal
);
}

return (
<BookMarkList.Provider
value={{
Expand All @@ -75,6 +84,7 @@ export function BookMarkListProvider({ children }) {
isBookmarksLoading,
fetchBookmarks,
deleteBookmark,
changePositions,
abort,
}}
>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/helpers/constants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const APP_NAME = "NeonLink";
export const VERSION = "1.2.3";
export const VERSION = "1.2.4";
export const DEF_MAX_ITEMS = 20;
export const DEF_COLUMNS = 3;
export const CARD_HEADER_STYLE = [
Expand Down
11 changes: 4 additions & 7 deletions frontend/src/pages/dashboard/components/group.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useState } from "react";
import { useInterfaceSettings } from "../../../context/interfaceSettingsContext";
import { pickColorBasedOnBgColor } from "../../../helpers/color";
import { getJSON } from "../../../helpers/fetch";
import LazyIcon from "../../../components/LazyIcon";

export default function Group({ category }) {
const { useImageAsBg, cardHeaderStyle, useNeonShadow } =
Expand Down Expand Up @@ -78,13 +79,9 @@ export default function Group({ category }) {
target="_blank"
rel="noopener noreferrer"
>
<div
className="w-6 h-6 flex-none"
style={{
backgroundImage: `url(${bookmark.icon})`,
backgroundSize: "cover",
}}
></div>
<div className="flex-none relative">
<LazyIcon id={bookmark.id} title={bookmark.title} />
</div>
<div className="truncate w-full dark:text-white">
{bookmark.title}
</div>
Expand Down
27 changes: 1 addition & 26 deletions frontend/src/pages/link/components/LinkTemplate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getDomain } from "../../../helpers/url";
import { prettyfyDate } from "../../../helpers/date";
import { Link } from "react-router-dom";
import { useInterfaceSettings } from "../../../context/interfaceSettingsContext";
import LazyIcon from "../../../components/LazyIcon";

function Options({ className, bookmarkId, showOptions, setShowOptions }) {
const [isDeleting, setIsDeleting] = useState(false);
Expand Down Expand Up @@ -144,29 +145,3 @@ export default function LinkTemplate({ bookmark }) {
</div>
);
}

function LazyIcon({ id, title }) {
const [isLoading, setIsLoading] = useState(true);
return (
<>
<img
onLoad={() => setIsLoading(false)}
className={`w-8 h-8 bg-contain bg-no-repeat bg-center transition-opacity ${
isLoading ? "opacity-0" : "opacity-100"
}`}
height={32}
width={32}
loading="lazy"
alt={`icon for ${title}`}
src={`${
process.env.NODE_ENV !== "production" ? "http://localhost:3333" : ""
}/api/bookmarks/${id}/icon`}
></img>
<div
className={`w-8 h-8 animate-pulse rounded bg-gray-200 absolute top-0 ${
isLoading ? "" : "hidden"
}`}
></div>
</>
);
}
Loading

0 comments on commit cf78241

Please sign in to comment.