Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

All notable changes to Mellow Visual Timer are documented in this file.

## [0.4.0] - 2026-08-19

### Added

- Compact, expandable routine-step editor for creating and editing sequential timers.
- Context-aware timer creation: the Basic and Routine filters open their matching timer form.

### Changed

- Unified timer type, duration-unit, and list-filter controls with sliding segmented controls.
- Simplified timer cards to prioritize the timer information and management actions.
- Updated the About version and README to reflect the v0.4.0 release.

### Fixed

- Dragging a clock face no longer scrolls the surrounding view; scrolling remains available outside the dial.

## [0.3.0] - 2026-08-19

### Added
Expand All @@ -17,4 +34,3 @@ All notable changes to Mellow Visual Timer are documented in this file.
- Combined timer direction, alarm sound, and volume settings into one tab.
- Simplified the focus-statistics sharing UI and added distinct KPI emoji markers.
- Migrated the app build to Vite with Vite PWA support.

11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- **Drag** on the clock face or **click** to set your desired time quickly and easily.
2. **Create Your Own Timers**:
- Save your frequently used timers as **Basic Timers** in the **Timer List**.
- Combine multiple Basic Timers into a **Routine Timer** for sequential execution, perfect for workflows or workout routines.
- Combine multiple Basic Timers into a **Routine Timer** for sequential execution, then edit, reorder, or remove its individual steps.
3. **Start the Timer**:
- Press the **Play** button and watch the timer progress in real-time.
- The timer will continue running even if you switch tabs or minimize the app.
Expand All @@ -42,7 +42,8 @@
- Create single timers with predefined durations and custom colors.
- **Routine Timer**
- Group multiple Basic Timers to run sequentially.
- Customize **time intervals**, **start points**, and **repeat modes** (single or infinite loop).
- Add, edit, reorder, or remove individual timer steps, including each step's duration, color, and alarm interval.
- Start a routine once or enable repeat mode while it runs.
- **Use cases**:
- Pomodoro sessions (e.g., 50-minute focus + 10-minute break).
- Workout routines with different exercises and rest periods.
Expand Down Expand Up @@ -170,7 +171,9 @@ Contributions are welcome! To contribute:

Feel free to open [issues](https://github.com/do0ori/visual-timer/issues) for bug reports or feature suggestions.

## Release Roadmap
## Release History

- **v0.3.0:** Vite migration, focus stats, custom alarm audio, unified settings, updated themes, and version information.
- **v0.4.0 (planned):** A dedicated Routine Timer editing UX update, focused on clearer step creation, editing, reordering, and deletion.
- **v0.4.0:** A streamlined Routine Timer editing flow with clearer step creation, inline editing, reordering, deletion, context-aware timer creation, and compact timer cards.

See [CHANGELOG.md](CHANGELOG.md) for detailed release notes.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "visual-timer",
"version": "0.3.0",
"version": "0.4.0",
"private": false,
"type": "module",
"homepage": "https://do0ori.github.io/visual-timer",
Expand Down
29 changes: 25 additions & 4 deletions src/components/common/TimerFace.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useSettingsStore } from '../../store/settingsStore';
import { Theme } from '../../store/types/theme';
import { getAdjustedColor } from '../../utils/colorUtils';
Expand All @@ -24,6 +24,17 @@ const TimerFace: React.FC<TimerFaceProps> = ({
}) => {
const { isClockwise } = useSettingsStore();
const [isDragging, setIsDragging] = useState<boolean>(false);
const dialCircleRef = useRef<SVGCircleElement>(null);

useEffect(() => {
const dialCircle = dialCircleRef.current;
if (!dialCircle || !handleDragEvent) return;

const preventTouchScroll = (event: TouchEvent) => event.preventDefault();
dialCircle.addEventListener('touchmove', preventTouchScroll, { passive: false });

return () => dialCircle.removeEventListener('touchmove', preventTouchScroll);
}, [handleDragEvent]);

const fullProgress = 2 * Math.PI * progressRadius;

Expand All @@ -48,6 +59,7 @@ const TimerFace: React.FC<TimerFaceProps> = ({
<svg className="size-full" viewBox="-50 -50 100 100">
{/* Timer Background */}
<circle
ref={dialCircleRef}
cx={0}
cy={0}
r={baseRadius}
Expand All @@ -57,11 +69,20 @@ const TimerFace: React.FC<TimerFaceProps> = ({
onMouseDown={() => setIsDragging(true)}
onMouseUp={() => setIsDragging(false)}
onMouseMove={handleMouseMove}
onTouchStart={() => setIsDragging(true)}
onTouchStart={(e) => {
if (!handleDragEvent) return;
e.preventDefault();
setIsDragging(true);
}}
onTouchEnd={() => setIsDragging(false)}
onTouchMove={handleMouseMove}
onTouchCancel={() => setIsDragging(false)}
onTouchMove={(e) => {
if (!handleDragEvent) return;
e.preventDefault();
handleMouseMove(e);
}}
onClick={handleDragEvent}
style={{ pointerEvents: 'visiblePainted' }}
style={{ pointerEvents: 'visiblePainted', touchAction: handleDragEvent ? 'none' : 'auto' }}
className="relative cursor-pointer"
/>
{/* Clock Ticks */}
Expand Down
22 changes: 14 additions & 8 deletions src/components/timers/timer-management/TimerItemOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,28 @@ import { useThemeStore } from '../../../store/themeStore';
import { BaseTimerData, RoutineTimerData, TimerData } from '../../../store/types/timer';
import BaseTimerForm from './forms/BaseTimerForm';
import RoutineTimerForm from './forms/RoutineTimerForm';
import TimerTypeSelector from './fields/TimerTypeSelector';

type TimerItemOverlayProps = {
initialTimerData: TimerData | null;
initialTimerType?: TimerType;
mode: 'add' | 'edit';
onClose: () => void;
};

const TimerItemOverlay: React.FC<TimerItemOverlayProps> = ({ initialTimerData, mode, onClose }) => {
const TimerItemOverlay: React.FC<TimerItemOverlayProps> = ({ initialTimerData, initialTimerType, mode, onClose }) => {
const { selectedTheme, compColor } = useThemeStore();
const [timerType, setTimerType] = useState<TimerType>(initialTimerData?.type || TIMER_TYPE.BASE);
const [timerType, setTimerType] = useState<TimerType>(
initialTimerData?.type || initialTimerType || TIMER_TYPE.BASE
);

const { isOpen, close } = useOverlay('timer-item', onClose);

useEffect(() => {
if (isOpen) {
setTimerType(initialTimerData?.type || TIMER_TYPE.BASE);
setTimerType(initialTimerData?.type || initialTimerType || TIMER_TYPE.BASE);
}
}, [isOpen, initialTimerData]);
}, [isOpen, initialTimerData, initialTimerType]);

if (!isOpen) return null;

Expand Down Expand Up @@ -55,12 +59,16 @@ const TimerItemOverlay: React.FC<TimerItemOverlayProps> = ({ initialTimerData, m

{/* Content Body */}
<div className="flex-1 overflow-y-auto p-4 sm:p-6 no-scrollbar">
{mode === 'add' && (
<div className="max-w-md mx-auto mb-6 w-full">
<TimerTypeSelector selectedType={timerType} onTypeSelect={setTimerType} />
</div>
)}

{timerType === TIMER_TYPE.BASE && (
<BaseTimerForm
initialData={initialTimerData as BaseTimerData}
mode={mode}
timerType={timerType}
setTimerType={setTimerType}
close={close}
/>
)}
Expand All @@ -69,8 +77,6 @@ const TimerItemOverlay: React.FC<TimerItemOverlayProps> = ({ initialTimerData, m
<RoutineTimerForm
initialData={initialTimerData as RoutineTimerData}
mode={mode}
timerType={timerType}
setTimerType={setTimerType}
close={close}
/>
)}
Expand Down
Loading
Loading