Skip to content

Commit

Permalink
added sortable drag and drop for saved timers
Browse files Browse the repository at this point in the history
  • Loading branch information
MacielG1 committed Sep 18, 2023
1 parent 83d5243 commit 93708ed
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 96 deletions.
56 changes: 56 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"preview": "vite preview"
},
"dependencies": {
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/sortable": "^7.0.2",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
1 change: 1 addition & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function App() {
>
<TimerApp />
<Sidebar />

<SettingsMenu />
</main>
);
Expand Down
3 changes: 2 additions & 1 deletion src/components/SaveTimer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ export default function SaveTimer() {

function handleSaveTimer() {
let timerStorage = JSON.parse(localStorage.getItem("savedTimer")) || [];

let data = {
Title: timerName,
Title: `Timer ${timerStorage.length + 1}`,
id: uuidv4(),
Rounds: Rounds,
WorkMinutes: workMin,
Expand Down
129 changes: 42 additions & 87 deletions src/components/Sidebar.jsx
Original file line number Diff line number Diff line change
@@ -1,98 +1,53 @@
import useStore from "../store/useStore";
import CloseIcon from "../assets/icons/CloseIcon.svg";
import { inputSettings as lang } from "../utils/lang";

export default function Sidebar() {
const removeBorders = useStore((state) => state.removeUIBorders);
const enableBackgroundColors = useStore((state) => state.enableBackgroundColors);
import { DndContext, PointerSensor, TouchSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
import { SortableContext, arrayMove } from "@dnd-kit/sortable";
import SidebarItem from "./SidebarItem";

export default function Sidebar() {
const savedWorkouts = useStore((state) => state.savedWorkouts);
const setSavedWorkouts = useStore((state) => state.setSavedWorkouts);
const SetIsLoadingSavedTimer = useStore((state) => state.SetIsLoadingSavedTimer);
const preferredLanguage = useStore((state) => state.preferredLanguage);

function handleDelete(id) {
let timers = savedWorkouts.filter((i) => i.id !== id);
setSavedWorkouts(timers);
localStorage.setItem("savedTimer", JSON.stringify(timers));
}

function handleLoad(id) {
let timer = savedWorkouts.find((i) => i.id === id);

useStore.setState({
Rounds: timer.Rounds,
WorkMinutes: timer.WorkMinutes,
WorkSeconds: timer.WorkSeconds,
WorkColor: timer.WorkColor,
RestMinutes: timer.RestMinutes,
RestSeconds: timer.RestSeconds,
RestColor: timer.RestColor,
PrepareMinutes: timer.PrepareMinutes,
PrepareSeconds: timer.PrepareSeconds,
PrepColor: timer.PrepColor,
});

// adds an effect to the inputs when the btn is clicked
SetIsLoadingSavedTimer(true);
setTimeout(() => {
SetIsLoadingSavedTimer(false);
}, 300);
const pointerSensor = useSensor(PointerSensor, {
activationConstraint: {
distance: 8,
},
});
const touchSensor = useSensor(TouchSensor, {
activationConstraint: {
delay: 250,
tolerance: 5,
},
});

const sensors = useSensors(pointerSensor, touchSensor);

function handleDragEnd(e) {
const { active, over } = e;
if (active.id !== over.id) {
const currentItems = [...savedWorkouts];

const oldIndex = currentItems.findIndex((i) => i.id === active.id);
const newIndex = currentItems.findIndex((i) => i.id === over.id);

const newArray = arrayMove(currentItems, oldIndex, newIndex);
localStorage.setItem("savedTimer", JSON.stringify(newArray));

setSavedWorkouts(newArray);
}
}

return (
<aside className="flex justify-center min-[1360px]:absolute mt-5 xl:mt-4 2xl:mt-9 2xl:pl-1 max-h-[95%] ">
<section className="2xl:px-2 py-1 2xl-px-4 flex flex-col gap-6 overflow-x-hidden overflow-y-auto">
{savedWorkouts.map((i, index) => (
<div
className="relative bg-neutral-900 px-16 sm:px-6 md:px-6 2xl:px-10 py-2 2xl:py-4 rounded-lg border border-gray-500 text-base xl:text-sm 2xl:text-base"
key={i.id}
style={{ borderColor: enableBackgroundColors && removeBorders ? "transparent" : "" }}
>
<div className="p-2 px-3 absolute top-0 right-0">
<button onClick={() => handleDelete(i.id)}>
<img src={CloseIcon} alt="close" className="w-6" width="24" height="24" />
</button>
</div>
<div className="flex text-2xl justify-center">
<span>{i.Title || `Timer ${index + 1}`}</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-3 sm:gap-y-2 lg:gap-x-6 py-2 sm:py-4 px-0 justify-center text-left text-md sm:text-lg min-[1360px]:text-sm min-[1620px]:text-base">
<span className="sm:pl-5 text-center sm:text-left order-1 sm:order-none ">
{lang.rounds[preferredLanguage]} {i.Rounds}
</span>

<div className="order-2 sm:order-none flex gap-2 items-center">
<span className={`rounded-full w-3 h-3 inline-block `} style={{ backgroundColor: i.WorkColor }}></span>
<span>
{lang.work[preferredLanguage]} {`${i.WorkMinutes}:${i.WorkSeconds}`}
</span>
</div>
<div className="order-4 sm:order-none flex gap-2 items-center">
<span className={` rounded-full w-3 h-3 inline-block `} style={{ backgroundColor: i.PrepColor }}></span>
<span>
{lang.prepare[preferredLanguage]} {`${i.PrepareMinutes}:${i.PrepareSeconds}`}
</span>
</div>

<div className="order-3 sm:order-none flex gap-2 items-center">
<span className={`rounded-full w-3 h-3 inline-block`} style={{ backgroundColor: i.RestColor }}></span>
<span>
{lang.rest[preferredLanguage]} {`${i.RestMinutes}:${i.RestSeconds}`}
</span>
</div>
</div>
<div className="flex justify-center">
<button
onClick={() => handleLoad(i.id)}
className="bg-blue-500 text-black font-medium rounded-lg px-2 py-1 hover:bg-blue-600 transition duration-200"
>
Load
</button>
</div>
</div>
))}
</section>
</aside>
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<aside className="flex justify-center min-[1360px]:fixed mt-5 xl:mt-4 2xl:mt-9 2xl:pl-1 max-h-[95%] xl:overflow-y-auto pr-2 ">
<section className="2xl:px-2 py-1 2xl-px-4 flex flex-col gap-6 ">
<SortableContext items={savedWorkouts}>
{savedWorkouts.map((i) => (
<SidebarItem item={i} key={i.id} />
))}
</SortableContext>
</section>
</aside>
</DndContext>
);
}
99 changes: 99 additions & 0 deletions src/components/SidebarItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import useStore from "../store/useStore";
import CloseIcon from "../assets/icons/CloseIcon.svg";
import { inputSettings as lang } from "../utils/lang";

export default function SidebarItem({ item }) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging, isSorting } = useSortable({ id: item.id });

const style = {
transform: CSS.Transform.toString(transform),
transition,
};
const savedWorkouts = useStore((state) => state.savedWorkouts);
const setSavedWorkouts = useStore((state) => state.setSavedWorkouts);
const SetIsLoadingSavedTimer = useStore((state) => state.SetIsLoadingSavedTimer);
const preferredLanguage = useStore((state) => state.preferredLanguage);
const removeBorders = useStore((state) => state.removeUIBorders);
const enableBackgroundColors = useStore((state) => state.enableBackgroundColors);

function handleDelete(id) {
let timers = savedWorkouts.filter((i) => i.id !== id);
setSavedWorkouts(timers);
localStorage.setItem("savedTimer", JSON.stringify(timers));
}

function handleLoad(id) {
let timer = savedWorkouts.find((i) => i.id === id);

console.log(timer);
useStore.setState({
Rounds: timer.Rounds,
WorkMinutes: timer.WorkMinutes,
WorkSeconds: timer.WorkSeconds,
WorkColor: timer.WorkColor,
RestMinutes: timer.RestMinutes,
RestSeconds: timer.RestSeconds,
RestColor: timer.RestColor,
PrepareMinutes: timer.PrepareMinutes,
PrepareSeconds: timer.PrepareSeconds,
PrepColor: timer.PrepColor,
});

// adds an effect to the inputs when the btn is clicked
SetIsLoadingSavedTimer(true);
setTimeout(() => {
SetIsLoadingSavedTimer(false);
}, 300);
}

return (
<div
ref={setNodeRef}
{...attributes}
{...listeners}
className="relative bg-neutral-900 px-16 sm:px-6 md:px-6 2xl:px-10 py-2 2xl:py-4 rounded-lg border border-gray-500 text-base xl:text-sm 2xl:text-base"
style={{ ...style, borderColor: enableBackgroundColors && removeBorders ? "transparent" : "" }}
>
<div className="p-2 px-3 absolute top-0 right-0">
<button onClick={() => handleDelete(item.id)}>
<img src={CloseIcon} alt="close" className="w-6" width="24" height="24" />
</button>
</div>
<div className="flex text-2xl justify-center">
<span>{item.Title}</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-3 sm:gap-y-2 lg:gap-x-6 py-2 sm:py-4 px-0 justify-center text-left text-md sm:text-lg min-[1360px]:text-sm min-[1620px]:text-base">
<span className="sm:pl-5 text-center sm:text-left order-1 sm:order-none ">
{lang.rounds[preferredLanguage]} {item.Rounds}
</span>

<div className="order-2 sm:order-none flex gap-2 items-center">
<span className={`rounded-full w-3 h-3 inline-block `} style={{ backgroundColor: item.WorkColor }}></span>
<span>
{lang.work[preferredLanguage]} {`${item.WorkMinutes}:${item.WorkSeconds}`}
</span>
</div>
<div className="order-4 sm:order-none flex gap-2 items-center">
<span className={` rounded-full w-3 h-3 inline-block `} style={{ backgroundColor: item.PrepColor }}></span>
<span>
{lang.prepare[preferredLanguage]} {`${item.PrepareMinutes}:${item.PrepareSeconds}`}
</span>
</div>

<div className="order-3 sm:order-none flex gap-2 items-center">
<span className={`rounded-full w-3 h-3 inline-block`} style={{ backgroundColor: item.RestColor }}></span>
<span>
{lang.rest[preferredLanguage]} {`${item.RestMinutes}:${item.RestSeconds}`}
</span>
</div>
</div>
<div className="flex justify-center">
<button onClick={() => handleLoad(item.id)} className="bg-blue-500 text-black font-medium rounded-lg px-2 py-1 hover:bg-blue-600 transition duration-200">
Load
</button>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion src/components/TimerApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function TimerApp() {

return (
<section
className="relative flex flex-col mx-auto px-0 xs:px-2 sm:px-4 py-4 2xl:py-12 bg-neutral-950 border border-gray-500 mt-3 sm:mt-5 text-center rounded-xl
className="relative flex flex-col mx-auto px-0 xs:px-2 sm:px-4 py-2 2xl:py-12 bg-neutral-950 border border-gray-500 mt-3 sm:mt-5 text-center rounded-xl
max-w-sm xs:max-w-lg sm:max-w-xl lg:max-w-[41rem] 2xl:max-w-3xl w-full 2xl:mt-10 "
style={{
borderColor: enableBackgroundColors && removeBorders ? "transparent" : mainTimerBorder,
Expand Down
9 changes: 2 additions & 7 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,12 @@ input[type=number] {-moz-appearance: textfield;} /* Firefox */

}



body {
margin: 0;
min-width: 320px;


}



input[type="color"]::-webkit-color-swatch
{
border: 1px solid rgb(0, 0, 0);
Expand All @@ -56,12 +51,12 @@ input[type="color"]::-moz-color-swatch {
}

::-webkit-scrollbar-thumb {
background-color: #424242;
background-color: #747474;
border-radius: 10px;
}

::-webkit-scrollbar-track {
background-color: #747474;
background-color: #424242;
border-radius: 10px;
}

Expand Down

0 comments on commit 93708ed

Please sign in to comment.