diff --git a/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/CreateNewExerciseWidget.tsx b/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/CreateNewExerciseWidget.tsx index 909cc49..2663bd8 100644 --- a/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/CreateNewExerciseWidget.tsx +++ b/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/CreateNewExerciseWidget.tsx @@ -4,7 +4,7 @@ import SolidPlusButton from "@/components/buttons/SolidPlusButton" export default function CreateNewExerciseWidget({ addNewExercise }: { addNewExercise: () => void}) { return ( -
+
) diff --git a/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/ExerciseSetWidget.tsx b/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/ExerciseSetWidget.tsx index 641c6a1..372e568 100644 --- a/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/ExerciseSetWidget.tsx +++ b/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/ExerciseSetWidget.tsx @@ -136,7 +136,7 @@ function ExerciseTypeSelector({ setType, exTypeOptions }: ExerciseTypeSelectorPr return ( ) } \ No newline at end of file diff --git a/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/ExerciseWidget.tsx b/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/ExerciseWidget.tsx index c3930c0..34562e4 100644 --- a/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/ExerciseWidget.tsx +++ b/app/(protected)/live/workouts/[workoutId]/exerciseSetWidget/ExerciseWidget.tsx @@ -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) { @@ -24,49 +22,55 @@ export async function saveExercise({ exercise, setExerciseSaveState, setExercise } export default function ExerciseWidget({ exercise }: { exercise: Exercise }) { - const [saveState, setSaveState] = useState(exercise.id ? "saved" : "unsaved"); + const [saveState, setSaveState] = useState(exercise.id ? "saved" : "unsaved"); const [ex, setEx] = useState(exercise); const [id, setId] = useState(exercise.id); + const justLoadedFromServer = useRef(!!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 ( -
-
- +
+
+
{ex.weight}
{ex.reps}
- { /* a little save status indicator */} - +
) } -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 ( -
+
) } -function SaveStatusContainer({saveState}: {saveState: SaveState}) { +function SaveStatusOverlayContainer({saveState}: {saveState: SaveStatus}) { const [isSaveStateIndicatorOpaque, setIsSaveStateIndicatorOpaque] = useState(false); - const prevState = useRef(undefined); + const prevState = useRef(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") { @@ -74,24 +78,25 @@ function SaveStatusContainer({saveState}: {saveState: SaveState}) { 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 ( -
-
-
- {saveState === "saved" ? - - : saveState === "unsaved" ? - - : - - } -
+
+
+
) diff --git a/components/ClientModal.tsx b/components/ClientModal.tsx index ba82ef7..a4a440c 100644 --- a/components/ClientModal.tsx +++ b/components/ClientModal.tsx @@ -33,8 +33,8 @@ export default function ClientModal({ handleClose, children }: ClientModalProps) useEscapeKey(handleClose); return (
-
-
+
+
diff --git a/components/indicators/SaveStatusIndicator.tsx b/components/indicators/SaveStatusIndicator.tsx new file mode 100644 index 0000000..48d035d --- /dev/null +++ b/components/indicators/SaveStatusIndicator.tsx @@ -0,0 +1,12 @@ +export type SaveStatus = 'unsaved' | 'saving' | 'saved'; + +export default function SaveStatusIndicator({saveStatus}: {saveStatus: SaveStatus}) { + switch (saveStatus) { + case "unsaved": + return + case "saving": + return + case "saved": + return + } +} \ No newline at end of file diff --git a/stories/Components/Indicators/SaveStatusIndicator.stories.tsx b/stories/Components/Indicators/SaveStatusIndicator.stories.tsx new file mode 100644 index 0000000..14ea70a --- /dev/null +++ b/stories/Components/Indicators/SaveStatusIndicator.stories.tsx @@ -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; + +export default meta; +type Story = StoryObj; + +export const Saving: Story = { + args: {saveStatus: "saving"}, +}; + +export const Unsaved: Story = { + args: {saveStatus: "unsaved"}, +}; + +export const Saved: Story = { + args: {saveStatus: "saved"}, +}; \ No newline at end of file