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
6 changes: 4 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,11 @@ Interpretation:
| `staging` | Entwicklung, Standardziel fuer laufende Arbeit |
| `master` | Production, nur via PR |

### Commit- und Push-Regeln
### Test-, Commit- und Push-Regeln

Vor jedem Commit alle Checks ausfuehren:
Waehrend laufender Arbeit gezielt pruefen: passende Unit-/Smoke-Tests, Build oder Lint nach Risiko und Aenderungsumfang auswaehlen. Die volle Suite ist kein Pflichtschritt nach jedem kleinen Zwischenstand.

Vor jedem Commit bleiben alle Checks Pflicht:

```bash
npm run build
Expand Down
14 changes: 6 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,29 +87,27 @@ Interpretation:
| `staging` | Entwicklung - hierhin committen |
| `master` | Production - nur via PR |

### Commit-Regeln
### Test- und Commit-Regeln
```bash
# VOR jedem Commit: ALLE Checks durchführen!
npm run build # 1. Build muss fehlerfrei sein
npx eslint . # 2. Lint: 0 Errors (Warnings OK)
npm run lint # 2. Lint: 0 Errors (Warnings OK)
npm run test # 3. ALLE Tests müssen grün sein

# Nach JEDER Änderung sofort committen (NICHT pushen!):
git add . && git commit -m "description"
```

Während laufender Arbeit gezielt prüfen: passende Unit-/Smoke-Tests, Build oder Lint nach Risiko und Änderungsumfang auswählen. Die volle Suite ist kein Pflichtschritt nach jedem kleinen Zwischenstand.

**IMMER vor Commit:**
- `npm run build` ausführen — muss fehlerfrei sein
- `npx eslint .` ausführen — 0 Errors (Warnings sind OK)
- `npm run lint` ausführen — 0 Errors (Warnings sind OK)
- `npm run test` ausführen — ALLE Tests müssen grün sein
- Erst dann committen

**NIEMALS:**
- Direkt auf `master` committen
- Selbstständig zu `master` mergen
- Selbstständig pushen (nur wenn User es explizit verlangt)
- Mehrere Änderungen sammeln
- Committen ohne vorherigen Build-Check
- Committen, wenn Build, Lint oder Tests fehlschlagen

### Merge zu Master (nur wenn User es verlangt!)
```bash
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
</p>

<p>
<a href="https://github.com/Sportinger/MasterSelects/releases"><img src="https://img.shields.io/badge/version-1.7.6-blue.svg" alt="Version"></a>
<a href="https://github.com/Sportinger/MasterSelects/releases"><img src="https://img.shields.io/badge/version-1.7.7-blue.svg" alt="Version"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
<a href="https://app.fossa.com/projects/custom%2b61097%2fmasterselects"><img src="https://app.fossa.com/api/projects/custom%2b61097%2fmasterselects.svg?type=shield" alt="FOSSA Status"></a>
</p>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "masterselects",
"private": true,
"version": "1.7.6",
"version": "1.7.7",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
40 changes: 18 additions & 22 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -6443,6 +6443,24 @@ input[type="checkbox"] {
white-space: nowrap;
}

.timeline-ruler-cache-indicator {
position: absolute;
bottom: 1px;
height: 3px;
background: var(--amber-300);
opacity: 0.82;
cursor: pointer;
z-index: 2;
}

.timeline-ruler-cache-indicator.cache {
box-shadow: 0 0 5px rgba(251, 191, 36, 0.32);
}

.timeline-ruler-cache-indicator.proxy {
box-shadow: 0 0 5px rgba(251, 191, 36, 0.32);
}

/* Track lanes */
.track-lane {
position: relative;
Expand Down Expand Up @@ -8905,28 +8923,6 @@ input[type="checkbox"] {
z-index: 89;
}

/* Proxy frame cache indicator (yellow line for proxy frames loaded in memory) */
.proxy-cache-indicator {
position: absolute;
top: 3px;
height: 3px;
background: var(--amber-300);
opacity: 0.7;
pointer-events: none;
z-index: 88;
}

/* Non-proxy scrub cache indicator (yellow line for background-filled source frames) */
.scrub-cache-indicator {
position: absolute;
top: 6px;
height: 3px;
background: var(--amber-300);
opacity: 0.5;
pointer-events: none;
z-index: 87;
}

/* Timeline export progress overlay */
.timeline-export-overlay {
position: absolute;
Expand Down
8 changes: 8 additions & 0 deletions src/changelog-data.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
[
{
"date": "2026-05-09",
"type": "improve",
"title": "Timeline Cache Indicators and Slider Ranges",
"description": "Proxy and scrub cache range indicators now render directly in the timeline ruler, while persisted custom ranges update matching color, effect, and blendshape sliders immediately.",
"section": "Timeline / Controls",
"commits": ["103c205e"]
},
{
"date": "2026-05-08",
"type": "fix",
Expand Down
65 changes: 21 additions & 44 deletions src/components/common/EditableDraggableNumber.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { CSSProperties } from 'react';
import { createPortal } from 'react-dom';

const SETTINGS_STORAGE_PREFIX = 'editable-draggable-number-settings:';
const LEGACY_BOUNDS_STORAGE_PREFIX = 'editable-draggable-number-bounds:';

interface PersistedSettings {
min?: number;
max?: number;
defaultValue?: number;
}
import {
clearEditableDraggableNumberSettings,
dispatchEditableDraggableNumberSettingsUpdated,
getEditableDraggableNumberSettings,
saveEditableDraggableNumberSettings,
type EditableDraggableNumberSettings,
useEditableDraggableNumberSettingsRevision,
} from './EditableDraggableNumberSettings';

interface PopoverPlacement {
top: number;
Expand Down Expand Up @@ -82,32 +81,6 @@ function parseOptionalNumber(value: string): number | undefined {
return Number.isFinite(parsed) ? parsed : undefined;
}

function loadPersistedSettings(persistenceKey?: string): PersistedSettings | null {
if (!persistenceKey) return null;
try {
const raw = localStorage.getItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`);
const legacyRaw = localStorage.getItem(`${LEGACY_BOUNDS_STORAGE_PREFIX}${persistenceKey}`);
const source = raw ?? legacyRaw;
if (!source) return null;
const parsed = JSON.parse(source) as PersistedSettings;
return {
min: Number.isFinite(parsed.min) ? parsed.min : undefined,
max: Number.isFinite(parsed.max) ? parsed.max : undefined,
defaultValue: Number.isFinite(parsed.defaultValue) ? parsed.defaultValue : undefined,
};
} catch {
return null;
}
}

function savePersistedSettings(persistenceKey: string, settings: PersistedSettings): void {
localStorage.setItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`, JSON.stringify(settings));
}

function clearPersistedSettings(persistenceKey: string): void {
localStorage.removeItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`);
}

export function EditableDraggableNumber({
value,
onChange,
Expand Down Expand Up @@ -136,17 +109,17 @@ export function EditableDraggableNumber({
const [showBoundsPopover, setShowBoundsPopover] = useState(false);
const [draftMin, setDraftMin] = useState('');
const [draftMax, setDraftMax] = useState('');
const [persistedSettings, setPersistedSettings] = useState<PersistedSettings | null>(() => loadPersistedSettings(persistenceKey));
const [transientSettings, setTransientSettings] = useState<EditableDraggableNumberSettings | null>(null);
useEditableDraggableNumberSettingsRevision(persistenceKey);
const [popoverPlacement, setPopoverPlacement] = useState<PopoverPlacement>({
top: 0,
left: 0,
transformOrigin: 'center bottom',
visibility: 'hidden',
});

useEffect(() => {
setPersistedSettings(loadPersistedSettings(persistenceKey));
}, [persistenceKey]);
const storedSettings = getEditableDraggableNumberSettings(persistenceKey);
const persistedSettings = persistenceKey ? storedSettings : transientSettings;

const effectiveMin = persistedSettings?.min ?? min;
const effectiveMax = persistedSettings?.max ?? max;
Expand Down Expand Up @@ -370,15 +343,17 @@ export function EditableDraggableNumber({
? clampValue(nextDefaultValueInput, nextMin, nextMax)
: undefined;

const nextSettings: PersistedSettings = {
const nextSettings: EditableDraggableNumberSettings = {
min: nextMin,
max: nextMax,
defaultValue: nextDefaultValue,
};

setPersistedSettings(nextSettings);
if (persistenceKey) {
savePersistedSettings(persistenceKey, nextSettings);
saveEditableDraggableNumberSettings(persistenceKey, nextSettings);
dispatchEditableDraggableNumberSettingsUpdated(persistenceKey);
} else {
setTransientSettings(nextSettings);
}

const clampedCurrent = clampValue(value, nextSettings.min, nextSettings.max);
Expand All @@ -391,7 +366,6 @@ export function EditableDraggableNumber({
}, [decimals, draftDefaultValue, draftMax, draftMin, onChange, persistenceKey, value]);

const resetBounds = useCallback(() => {
setPersistedSettings(null);
setDraftDefaultValue(
defaultValue !== undefined
? formatEditableValue(defaultValue, decimals)
Expand All @@ -400,7 +374,10 @@ export function EditableDraggableNumber({
setDraftMin(min !== undefined ? String(min) : '');
setDraftMax(max !== undefined ? String(max) : '');
if (persistenceKey) {
clearPersistedSettings(persistenceKey);
clearEditableDraggableNumberSettings(persistenceKey);
dispatchEditableDraggableNumberSettingsUpdated(persistenceKey);
} else {
setTransientSettings(null);
}
}, [decimals, defaultValue, max, min, persistenceKey]);

Expand Down
116 changes: 116 additions & 0 deletions src/components/common/EditableDraggableNumberSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useEffect, useState } from 'react';

const SETTINGS_STORAGE_PREFIX = 'editable-draggable-number-settings:';
const LEGACY_BOUNDS_STORAGE_PREFIX = 'editable-draggable-number-bounds:';

export const EDITABLE_DRAGGABLE_NUMBER_SETTINGS_EVENT = 'editable-draggable-number-settings-updated';

export interface EditableDraggableNumberSettings {
min?: number;
max?: number;
defaultValue?: number;
}

export interface EditableDraggableNumberSettingsEventDetail {
persistenceKey?: string;
}

export function getEditableDraggableNumberSettings(
persistenceKey?: string,
): EditableDraggableNumberSettings | null {
if (!persistenceKey) return null;
try {
const raw = localStorage.getItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`);
const legacyRaw = localStorage.getItem(`${LEGACY_BOUNDS_STORAGE_PREFIX}${persistenceKey}`);
const source = raw ?? legacyRaw;
if (!source) return null;
const parsed = JSON.parse(source) as EditableDraggableNumberSettings;
return {
min: Number.isFinite(parsed.min) ? parsed.min : undefined,
max: Number.isFinite(parsed.max) ? parsed.max : undefined,
defaultValue: Number.isFinite(parsed.defaultValue) ? parsed.defaultValue : undefined,
};
} catch {
return null;
}
}

export function getEffectiveEditableDraggableNumberSettings({
persistenceKey,
min,
max,
defaultValue,
}: {
persistenceKey?: string;
min?: number;
max?: number;
defaultValue?: number;
}): EditableDraggableNumberSettings {
const persistedSettings = getEditableDraggableNumberSettings(persistenceKey);
return {
min: persistedSettings?.min ?? min,
max: persistedSettings?.max ?? max,
defaultValue: persistedSettings?.defaultValue ?? defaultValue,
};
}

export function saveEditableDraggableNumberSettings(
persistenceKey: string,
settings: EditableDraggableNumberSettings,
): void {
localStorage.setItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`, JSON.stringify(settings));
}

export function clearEditableDraggableNumberSettings(persistenceKey: string): void {
localStorage.removeItem(`${SETTINGS_STORAGE_PREFIX}${persistenceKey}`);
localStorage.removeItem(`${LEGACY_BOUNDS_STORAGE_PREFIX}${persistenceKey}`);
}

export function dispatchEditableDraggableNumberSettingsUpdated(persistenceKey: string): void {
window.dispatchEvent(
new CustomEvent<EditableDraggableNumberSettingsEventDetail>(
EDITABLE_DRAGGABLE_NUMBER_SETTINGS_EVENT,
{ detail: { persistenceKey } },
),
);
}

export function useEditableDraggableNumberSettingsRevision(persistenceKey?: string): number {
const [revision, setRevision] = useState(0);

useEffect(() => {
const shouldRefresh = (updatedKey?: string | null) =>
!persistenceKey || !updatedKey || updatedKey === persistenceKey;

const handleSettingsUpdated = (event: Event) => {
const detail = (event as CustomEvent<EditableDraggableNumberSettingsEventDetail>).detail;
if (shouldRefresh(detail?.persistenceKey)) {
setRevision((current) => current + 1);
}
};

const handleStorage = (event: StorageEvent) => {
if (!persistenceKey) {
setRevision((current) => current + 1);
return;
}

if (
event.key === `${SETTINGS_STORAGE_PREFIX}${persistenceKey}` ||
event.key === `${LEGACY_BOUNDS_STORAGE_PREFIX}${persistenceKey}`
) {
setRevision((current) => current + 1);
}
};

window.addEventListener(EDITABLE_DRAGGABLE_NUMBER_SETTINGS_EVENT, handleSettingsUpdated);
window.addEventListener('storage', handleStorage);

return () => {
window.removeEventListener(EDITABLE_DRAGGABLE_NUMBER_SETTINGS_EVENT, handleSettingsUpdated);
window.removeEventListener('storage', handleStorage);
};
}, [persistenceKey]);

return revision;
}
Loading
Loading