0.8.1: dirty-state + Cmd/Ctrl+S, fullscreen plays to OUT, UX fixes
- Unsaved-changes indicator: a small accent dot appears on the save
button whenever the scene has changed since the last save/load.
Save/load/new all mark the project clean on next tick.
- Cmd/Ctrl+S saves the project (reuses the same flow as the button;
prompts for a name on first save). Tooltip now mentions the
shortcut.
- Fullscreen preview plays to the OUT marker (or scene.duration when
no OUT is set) instead of looping at the last keyframe. Dropped
the runtime's effectiveEnd helper; RUNTIME_VERSION 13 → 14.
- "Still frame" keyframes no longer disable easing. A held kf's
easing still drives the segment INTO it from the previous kf, so
you can ease into a pause. Only the first kf disables easing
(nothing precedes it). Dropped `timeline.holdDisabledHint`.
- Settings dialog: hint text 11px → 13px (and dropped italic) so
the inline help is readable; section headings 10px → 13px.
- Canvas stage shadow: `shadowCfg` always emits explicit values plus
a `shadowEnabled` flag instead of returning `{}`. Konva's default
shadowOpacity is 1, so bailing with `{}` could leave stale shadow
attrs on the shape. Preview and editor now gate on the same
condition deterministically.
- Sidebar empty-state copy: "No clips" → "No clips yet" to match
"No buttons yet".
Regression tests: runtime rangeOut fallback is scene.duration (not
last kf); sidebar empty-states both contain "yet"; the
holdDisabledHint key is gone; saveProject tooltips mention the
shortcut.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1e89ea86e0
commit
82ee5c98d0
10 files changed
+133
-48
No files matched your search
@@ -840,11 +840,14 @@ const stackItems = computed<StackItem[]>(() => {
|
||||
})
|
||||
|
||||
function shadowCfg(props: AnimatableProps): Record<string, unknown> {
|
||||
// Konva's default shadowOpacity is 1, so returning {} for the "no
|
||||
// shadow" case isn't enough — the shape would reuse whatever values
|
||||
// were set on a previous render. Always emit explicit values and let
|
||||
// `shadowEnabled` be the single source of truth. The gate matches
|
||||
// the canvas/preview path: opacity > 0 AND some blur or distance.
|
||||
const opacity = props.shadowOpacity ?? 0
|
||||
if (opacity <= 0) return {}
|
||||
const blur = props.shadowBlur ?? 0
|
||||
const distance = props.shadowDistance ?? 0
|
||||
if (blur <= 0 && distance <= 0) return {}
|
||||
const angle = props.shadowAngle ?? 90
|
||||
const rad = (angle * Math.PI) / 180
|
||||
return {
|
||||
@@ -853,6 +856,7 @@ function shadowCfg(props: AnimatableProps): Record<string, unknown> {
|
||||
shadowOpacity: opacity,
|
||||
shadowOffsetX: Math.cos(rad) * distance,
|
||||
shadowOffsetY: Math.sin(rad) * distance,
|
||||
shadowEnabled: opacity > 0 && (blur > 0 || distance > 0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -454,11 +454,11 @@ section {
|
||||
|
||||
section h3 {
|
||||
margin: 0;
|
||||
font-size: 10px;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-dim);
|
||||
color: var(--text-dim-strong);
|
||||
letter-spacing: 0.08em;
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.row {
|
||||
@@ -558,9 +558,9 @@ input[type='number'] {
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
color: var(--text-dim-strong);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -2825,13 +2825,13 @@ onBeforeUnmount(() => {
|
||||
v-for="opt in EASING_OPTIONS"
|
||||
:key="opt.value"
|
||||
class="kf-menu-item"
|
||||
:class="{ active: menu.currentEasing === opt.value && !menu.isHeld }"
|
||||
:disabled="menu.isFirst || menu.isHeld"
|
||||
:class="{ active: menu.currentEasing === opt.value }"
|
||||
:disabled="menu.isFirst"
|
||||
@click="applyEasing(opt.value)"
|
||||
>
|
||||
<span class="kf-menu-icon">
|
||||
<PhCheck
|
||||
v-if="menu.currentEasing === opt.value && !menu.isHeld"
|
||||
v-if="menu.currentEasing === opt.value"
|
||||
:size="12"
|
||||
weight="bold"
|
||||
/>
|
||||
@@ -2839,7 +2839,6 @@ onBeforeUnmount(() => {
|
||||
{{ opt.label }}
|
||||
</button>
|
||||
<div v-if="menu.isFirst" class="kf-menu-note">{{ t('timeline.firstKfHint') }}</div>
|
||||
<div v-else-if="menu.isHeld" class="kf-menu-note">{{ t('timeline.holdDisabledHint') }}</div>
|
||||
</div>
|
||||
<div class="kf-menu-divider"></div>
|
||||
<button
|
||||
|
||||
Reference in new issue
Block a user