fix: editor native layout, UI polish, meme templates auth, pipeline export, install queuing

- Remove editor from AppLayout so it owns the full viewport with no sidebar/padding
- Add back-arrow in menu bar for navigation, restyle menu bar to match native app feel
- Slim left toolbar from 48px to 36px with smaller icons and subtler dividers
- Fix options bar overlapping canvas via shrink-0 and overflow-hidden
- Fix opacity/blend controls clipping in right panel with min-w-0 on flex children
- Add global range slider CSS for consistent thin-track + round-thumb styling
- Make meme template routes public so img tags can load without Bearer auth
- Show pipeline export buttons in default compact view, not just expanded hover
- Queue individual AI feature installs instead of erroring when one is active
This commit is contained in:
SnapOtter
2026-05-08 23:30:27 +08:00
parent 23f24c10d1
commit e22ffa4ec9
23 changed files with 181 additions and 94 deletions
@@ -261,7 +261,7 @@ export function LayersPanel() {
function BlendModeSelect({ value, onChange }: { value: string; onChange: (mode: string) => void }) {
return (
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 min-w-0">
<label htmlFor="blend-mode-select" className="text-[10px] text-muted-foreground shrink-0">
Blend
</label>
@@ -269,7 +269,7 @@ function BlendModeSelect({ value, onChange }: { value: string; onChange: (mode:
id="blend-mode-select"
value={value}
onChange={(e) => onChange(e.target.value)}
className="flex-1 h-7 text-xs bg-muted border border-border rounded px-1.5 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
className="flex-1 min-w-0 h-7 text-xs bg-muted border border-border rounded px-1.5 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
data-testid="blend-mode-select"
>
{BLEND_MODES.map((mode) => (
@@ -289,7 +289,7 @@ function BlendModeSelect({ value, onChange }: { value: string; onChange: (mode:
function OpacitySlider({ value, onChange }: { value: number; onChange: (v: number) => void }) {
const percent = Math.round(value * 100);
return (
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 min-w-0">
<label htmlFor="layer-opacity-slider" className="text-[10px] text-muted-foreground shrink-0">
Opacity
</label>
@@ -300,10 +300,10 @@ function OpacitySlider({ value, onChange }: { value: number; onChange: (v: numbe
max={100}
value={percent}
onChange={(e) => onChange(Number(e.target.value) / 100)}
className="flex-1"
className="flex-1 min-w-0"
data-testid="layer-opacity-slider"
/>
<span className="text-xs font-mono text-foreground tabular-nums w-8 text-right">
<span className="text-[10px] font-mono text-foreground tabular-nums shrink-0">
{percent}%
</span>
</div>
@@ -990,7 +990,7 @@ function EffectSlider({
onChange: (v: number) => void;
}) {
return (
<div className="flex items-center gap-1.5">
<div className="flex items-center gap-1.5 min-w-0">
<span className="text-[10px] text-muted-foreground shrink-0 w-12">{label}</span>
<input
type="range"
@@ -998,9 +998,9 @@ function EffectSlider({
max={max}
value={value}
onChange={(e) => onChange(Number(e.target.value))}
className="flex-1"
className="flex-1 min-w-0"
/>
<span className="text-[10px] font-mono text-foreground tabular-nums w-10 text-right">
<span className="text-[10px] font-mono text-foreground tabular-nums w-11 text-right shrink-0">
{value}
{suffix}
</span>
@@ -2,7 +2,6 @@
import { Minus, Plus } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { useEditorStore } from "@/stores/editor-store";
const THUMBNAIL_MAX_HEIGHT = 80;
@@ -257,11 +256,7 @@ export function NavigatorPanel() {
step={0.001}
value={Math.log(zoom / MIN_ZOOM) / Math.log(MAX_ZOOM / MIN_ZOOM)}
onChange={handleZoomSlider}
className={cn(
"flex-1 h-1 appearance-none rounded-full bg-muted",
"[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-2.5 [&::-webkit-slider-thumb]:h-2.5",
"[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-foreground [&::-webkit-slider-thumb]:cursor-pointer",
)}
className="flex-1"
/>
<button
type="button"