Skip to content

Commit

Permalink
Clean up saving stuff.
Browse files Browse the repository at this point in the history
  • Loading branch information
eswan18 committed Aug 31, 2023
1 parent 9d9ff1d commit 4ace255
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SolidPlusButton from "@/components/buttons/SolidPlusButton"

export default function CreateNewExerciseWidget({ addNewExercise }: { addNewExercise: () => void}) {
return (
<div className="rounded-lg p-2 lg:p-4 shadow-lg m-1 lg:m-2 flex flex-col min-h-[7rem] items-center justify-center w-24">
<div className="rounded-lg p-2 lg:p-4 shadow-lg m-1 lg:m-2 flex flex-col items-center justify-center w-24">
<SolidPlusButton type="button" onClick={addNewExercise}/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ function ExerciseTypeSelector({ setType, exTypeOptions }: ExerciseTypeSelectorPr
return (
<select className="w-full rounded-lg p-2 lg:p-4 shadow-lg m-1 lg:m-2" onChange={(e) => setType(exTypeOptions.find((type) => type.name === e.target.value))}>
<option value="">Choose an exercise type</option>
{exTypeOptions.map((type) => <option value={type.name}>{type.name}</option>)}
{exTypeOptions.map((type) => <option value={type.name} key={type.id}>{type.name}</option>)}
</select>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
import { useState, useEffect, useRef } from "react";
import { Exercise } from "@/lib/resources/apiTypes";
import { createExercise, overwriteExercise } from "@/lib/resources/exercises";
import SaveStatusIndicator, { SaveStatus } from "@/components/indicators/SaveStatusIndicator";


type SaveState = "saved" | "saving" | "unsaved"


export async function saveExercise({ exercise, setExerciseSaveState, setExerciseId }: { exercise: Exercise, setExerciseSaveState: (saveState: SaveState) => void, setExerciseId: (id?: string) => void }) {
export async function saveExercise({ exercise, setExerciseSaveState, setExerciseId }: { exercise: Exercise, setExerciseSaveState: (saveState: SaveStatus) => void, setExerciseId: (id?: string) => void }) {
setExerciseSaveState("saving");
// If the exercise has an ID, we update it. Otherwise, we create a new one.
if (exercise.id) {
Expand All @@ -24,74 +22,81 @@ export async function saveExercise({ exercise, setExerciseSaveState, setExercise
}

export default function ExerciseWidget({ exercise }: { exercise: Exercise }) {
const [saveState, setSaveState] = useState<SaveState>(exercise.id ? "saved" : "unsaved");
const [saveState, setSaveState] = useState<SaveStatus>(exercise.id ? "saved" : "unsaved");
const [ex, setEx] = useState<Exercise>(exercise);
const [id, setId] = useState<string | undefined>(exercise.id);
const justLoadedFromServer = useRef<boolean>(!!ex.id);

useEffect(() => {
saveExercise({ exercise: ex, setExerciseSaveState: setSaveState, setExerciseId: setId })
// This keeps us from saving data we already recieved from the server.
if (justLoadedFromServer.current) {
justLoadedFromServer.current = false;
} else {
saveExercise({ exercise: ex, setExerciseSaveState: setSaveState, setExerciseId: setId })
}
}, [ex]);

return (
<div className="rounded-lg p-1 lg:px-4 shadow-lg m-1 lg:mx-2 flex flex-col items-center w-24 bg-white">
<div className="flex flex-col items-center justify-start">
<SaveStatusContainer saveState={saveState} />
<div className="rounded-lg p-1 shadow-lg m-1 lg:mx-2 flex flex-col items-center w-20 bg-white shrink-0">
<div className="flex flex-col items-center justify-start relative">
<SaveStatusOverlayContainer saveState={saveState} />
<div className="text-2xl font-bold mt-1">
{ex.weight}
</div>
<div className="text-xl">
<i className="fa-solid fa-xmark text-gray-400" /> {ex.reps}
</div>
{ /* a little save status indicator */}
<StatusAndInteractContainer saveState={saveState} />
<EditButtonContainer saveState={saveState} />
</div>
</div>
)
}

function StatusAndInteractContainer({saveState}: {saveState: SaveState}) {
function EditButtonContainer({saveState}: {saveState: SaveStatus}) {
const disabled = !(saveState === "saved");
// This is the bottom of the widget, that shows a save indicator *or* buttons to edit & delete.
const color = disabled ? "text-gray-400" : "text-fuchsia-900";
return (
<div className={`mt-2 ${ color }`}>
<div className={`mt-1 ${ color }`}>
<button disabled={disabled}><i className="text-lg fa-solid fa-pen-to-square" /></button>
</div>
)
}

function SaveStatusContainer({saveState}: {saveState: SaveState}) {
function SaveStatusOverlayContainer({saveState}: {saveState: SaveStatus}) {
const [isSaveStateIndicatorOpaque, setIsSaveStateIndicatorOpaque] = useState(false);
const prevState = useRef<SaveState | undefined>(undefined);
const prevState = useRef<SaveStatus | undefined>(saveState);
const [isFullyHidden, setIsFullyHidden] = useState(true);

useEffect(() => {
// Temporarily make the save indicator opaque when the save state changes.
if (prevState.current != null) {
if (prevState.current != saveState) {
setIsFullyHidden(false);
setIsSaveStateIndicatorOpaque(true);
// If we've saved the data, let the indicator fade out after a second.
if (saveState === "saved") {
setTimeout(() => {
setIsSaveStateIndicatorOpaque(false)
}, 1000);
}
// As the idicator fades out, set the fully hidden state to true so that the
// previously-covered objects can respond to clicks.
setTimeout(() => {
setIsFullyHidden(true);
}, 1500);
}
// Record the previous state for comparison next time.
prevState.current = saveState;
}, [saveState]);

const saveStateIndicatorOpacity = isSaveStateIndicatorOpaque ? "opacity-100" : "opacity-0";
const fadeOutClasses = "transition-opacity ease-out duration-500 opacity-0";
const saveStateIndicatorOpacity = isSaveStateIndicatorOpaque ? "opacity-90" : fadeOutClasses;
return (
<div className="h-6">
<div className='text-lg font-bold'>
<div className={`transition-opacity ease-out duration-1000 ${saveStateIndicatorOpacity}`}>
{saveState === "saved" ?
<i className='fa-regular fa-circle-check' />
: saveState === "unsaved" ?
<i className="fa-regular fa-circle opacity-25" />
:
<i className="fa-solid fa-spinner animate-spin" />
}
</div>
<div
className={`absolute w-full h-full top-0 left-0
bg-white text-4xl ${saveStateIndicatorOpacity} ${isFullyHidden ? "hidden" : null}`}
>
<div className="flex flex-row justify-center items-center h-full">
<SaveStatusIndicator saveStatus={saveState} />
</div>
</div>
)
Expand Down
4 changes: 2 additions & 2 deletions components/ClientModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export default function ClientModal({ handleClose, children }: ClientModalProps)
useEscapeKey(handleClose);
return (
<div className="fixed z-10 inset-0 min-h-screen min-w-full p-12 lg:p-24 lg:px-32 bg-gray-800 bg-opacity-60">
<div className="w-full max-w-2xl mx-auto h-full p-8 lg:p-18 border border-gray-900 bg-white dark:bg-gray-900 rounded-md lg:rounded-lg">
<div className="w-full h-8 lg:h-12">
<div className="max-w-2xl mx-auto p-8 lg:p-18 border border-gray-900 bg-white dark:bg-gray-900 rounded-md lg:rounded-lg w-auto">
<div className="h-8 lg:h-12">
<button className="float-left border border-gray-300 rounded-md w-8 h-8 lg:h-11 lg:w-11" onClick={handleClose}>
<i className="fa-solid fa-xmark text-gray-900 text-base lg:text-2xl p-2" />
</button>
Expand Down
12 changes: 12 additions & 0 deletions components/indicators/SaveStatusIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type SaveStatus = 'unsaved' | 'saving' | 'saved';

export default function SaveStatusIndicator({saveStatus}: {saveStatus: SaveStatus}) {
switch (saveStatus) {
case "unsaved":
return <i className="fa-regular fa-circle" />
case "saving":
return <i className="fa-solid fa-spinner animate-spin" />
case "saved":
return <i className='fa-regular fa-circle-check' />
}
}
26 changes: 26 additions & 0 deletions stories/Components/Indicators/SaveStatusIndicator.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Meta, StoryObj } from '@storybook/react';
import SaveStatusIndicator from '@/components/indicators/SaveStatusIndicator';

const meta = {
title: 'Components/Indicators/SaveStatusIndicator',
component: SaveStatusIndicator,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
} satisfies Meta<typeof SaveStatusIndicator>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Saving: Story = {
args: {saveStatus: "saving"},
};

export const Unsaved: Story = {
args: {saveStatus: "unsaved"},
};

export const Saved: Story = {
args: {saveStatus: "saved"},
};

0 comments on commit 4ace255

Please sign in to comment.