feat: annotation UX overhaul, animated packing, toolbar redesign
- PinOverlay: zero-allocation rendering with in-place Graphics/Text updates, fix WebGL texture crash (addressModeU null) from orphaned Text objects - Pins always visible on media, track during drag via updatePositions() fast path - Replace eraser with Review mode in toolbar tool group (orange gradient) - Add tool instruction hints bar below toolbar (context-sensitive per tool) - Feedback panel: glassmorphism, improved ThreadDetail/CommentItem/ThreadListItem - Timestamps: relative with "ago" suffix, full date on hover tooltip - De-emphasize pin numbers, show status text in thread list - Animated arrangement transitions (easeOutCubic, 320ms) for pack/grid/row/column - Disable snap guides (user requested) - Paste now selects newly created items - Remove eraser keyboard shortcuts, remap tool shortcuts - Orphaned thread cleanup: safe destroy check for deleted media pins
This commit is contained in:
@@ -99,6 +99,14 @@ async function _pasteInternal(ctx: ShortcutContext): Promise<void> {
|
||||
}
|
||||
ctx.clipboardRef.current = newItems;
|
||||
ctx.scene._applyZOrder();
|
||||
|
||||
// Select the newly pasted items
|
||||
ctx.selection.selectedIds.clear();
|
||||
for (const item of newItems) {
|
||||
ctx.selection.selectedIds.add(item.id);
|
||||
}
|
||||
ctx.selection.transformBox.update(newItems);
|
||||
|
||||
ctx.onChange();
|
||||
}
|
||||
|
||||
@@ -110,6 +118,15 @@ function _opUpdate(ctx: ShortcutContext, op: (items: SceneItem[]) => void): void
|
||||
ctx.onChange(items.map(i => i.id));
|
||||
}
|
||||
|
||||
/** Like _opUpdate but defers transformBox update until animation completes */
|
||||
function _opUpdateAnimated(ctx: ShortcutContext, op: (items: SceneItem[]) => void): void {
|
||||
const items = ctx.selection.getSelectedItems();
|
||||
const { onArrangeAnimationDone } = require('./operations');
|
||||
onArrangeAnimationDone(() => ctx.selection.transformBox.update(items));
|
||||
op(items);
|
||||
ctx.onChange(items.map(i => i.id));
|
||||
}
|
||||
|
||||
export const shortcuts: ShortcutDef[] = [
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
@@ -184,27 +201,27 @@ export const shortcuts: ShortcutDef[] = [
|
||||
{
|
||||
id: 'arrange-optimal', keys: { key: 'p', ctrl: true, shift: true },
|
||||
category: 'arrangement', description: 'Arrange optimal (pack)', needsSelection: true, minSelection: 2,
|
||||
handler: (ctx) => _opUpdate(ctx, ops.arrangeOptimal),
|
||||
handler: (ctx) => _opUpdateAnimated(ctx, ops.arrangeOptimal),
|
||||
},
|
||||
{
|
||||
id: 'arrange-by-name', keys: { key: 'n', ctrl: true, alt: true },
|
||||
category: 'arrangement', description: 'Arrange by name', needsSelection: true, minSelection: 2,
|
||||
handler: (ctx) => _opUpdate(ctx, ops.arrangeByName),
|
||||
handler: (ctx) => _opUpdateAnimated(ctx, ops.arrangeByName),
|
||||
},
|
||||
{
|
||||
id: 'arrange-by-order', keys: { key: 'o', ctrl: true, alt: true },
|
||||
category: 'arrangement', description: 'Arrange by z-order', needsSelection: true, minSelection: 2,
|
||||
handler: (ctx) => _opUpdate(ctx, ops.arrangeByZOrder),
|
||||
handler: (ctx) => _opUpdateAnimated(ctx, ops.arrangeByZOrder),
|
||||
},
|
||||
{
|
||||
id: 'arrange-random', keys: { key: 'r', ctrl: true, alt: true },
|
||||
category: 'arrangement', description: 'Arrange randomly', needsSelection: true, minSelection: 2,
|
||||
handler: (ctx) => _opUpdate(ctx, ops.arrangeRandomly),
|
||||
handler: (ctx) => _opUpdateAnimated(ctx, ops.arrangeRandomly),
|
||||
},
|
||||
{
|
||||
id: 'stack', keys: { key: 's', ctrl: true, alt: true },
|
||||
category: 'arrangement', description: 'Stack (pile on top)', needsSelection: true, minSelection: 2,
|
||||
handler: (ctx) => _opUpdate(ctx, ops.stackObjects),
|
||||
handler: (ctx) => _opUpdateAnimated(ctx, ops.stackObjects),
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
@@ -322,12 +339,12 @@ export const shortcuts: ShortcutDef[] = [
|
||||
{
|
||||
id: 'equal-spacing-h', keys: { key: 'h', ctrl: true, shift: true },
|
||||
category: 'arrangement', description: 'Equal horizontal spacing', needsSelection: true, minSelection: 2,
|
||||
handler: (ctx) => _opUpdate(ctx, ops.equalSpacingH),
|
||||
handler: (ctx) => _opUpdateAnimated(ctx, ops.equalSpacingH),
|
||||
},
|
||||
{
|
||||
id: 'equal-spacing-v', keys: { key: 'v', ctrl: true, shift: true },
|
||||
category: 'arrangement', description: 'Equal vertical spacing', needsSelection: true, minSelection: 2,
|
||||
handler: (ctx) => _opUpdate(ctx, ops.equalSpacingV),
|
||||
handler: (ctx) => _opUpdateAnimated(ctx, ops.equalSpacingV),
|
||||
},
|
||||
|
||||
// ═══════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user