ObsiViewer/src/app/editor/components/block/blocks/button-block.component.ts
Bruno Charest 6b47ec39ff ```
feat: add bookmark view modes (card/tile/cover) and enhance button block with visual config modal

- Added three view modes for bookmark blocks: card (default with side image), tile (compact horizontal), and cover (large top image)
- Implemented view mode submenu in block context menu with visual indicators for active mode
- Enhanced bookmark favicon handling with fallback logic and error state tracking
- Refactored button block with inline visual editor replacing text inputs
- Created Button
2025-11-20 13:51:51 -05:00

129 lines
3.9 KiB
TypeScript

import { Component, Input, Output, EventEmitter, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Block, ButtonProps } from '../../../core/models/block.model';
import { ButtonConfigModalComponent } from './button-config-modal.component';
@Component({
selector: 'app-button-block',
standalone: true,
imports: [CommonModule, ButtonConfigModalComponent],
template: `
<div class="relative group inline-block">
<!-- Rendered Button -->
<a
[href]="props.url"
[target]="props.openInNewTab ? '_blank' : '_self'"
class="inline-flex items-center justify-center font-medium transition-all select-none no-underline cursor-pointer"
[ngClass]="getButtonClasses()"
[style.background-color]="getBgColor()"
[style.color]="getTextColor()"
(click)="onClick($event)"
>
{{ props.label || 'Button' }}
</a>
<!-- Edit Trigger (visible on hover/focus if not configuring) -->
<button
*ngIf="!showConfig()"
class="absolute -top-2 -right-2 bg-surface2 border border-app rounded-full p-1 opacity-0 group-hover:opacity-100 transition-opacity shadow-sm z-10"
title="Configure button"
(click)="openConfig($event)"
>
<svg class="w-3 h-3 text-neutral-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
</button>
<!-- Config Modal -->
@if (showConfig()) {
<app-button-config-modal
[props]="props"
[blockId]="block.id"
(saveProps)="onSaveConfig($event)"
(cancel)="closeConfig()"
/>
}
</div>
`
})
export class ButtonBlockComponent {
@Input({ required: true }) block!: Block<ButtonProps>;
@Output() update = new EventEmitter<ButtonProps>();
showConfig = signal(false);
ngOnInit() {
// If new button (empty label), open config immediately
if (!this.props.label) {
this.showConfig.set(true);
}
}
get props(): ButtonProps {
return this.block.props;
}
getButtonClasses(): string {
const classes: string[] = [];
// Size
switch (this.props.size) {
case 'small': classes.push('px-3 py-1.5 text-xs'); break;
case 'large': classes.push('px-6 py-3 text-base'); break;
default: classes.push('px-4 py-2 text-sm'); break; // medium
}
// Shape
if (this.props.shape === 'pill') {
classes.push('rounded-full');
} else if (this.props.shape === 'rounded') {
classes.push('rounded-lg');
} else {
classes.push('rounded-md'); // default/square
}
// Variant / Style
if (this.props.variant === '3d') {
classes.push('border-b-4 border-black/20 active:border-b-0 active:translate-y-1');
} else if (this.props.variant === 'shadow') {
classes.push('shadow-lg hover:shadow-xl hover:-translate-y-0.5');
} else if (this.props.variant === 'outline') {
classes.push('border-2');
}
return classes.join(' ');
}
getBgColor(): string {
if (this.props.variant === 'outline') return 'transparent';
return this.props.backgroundColor || '#3b82f6';
}
getTextColor(): string {
if (this.props.variant === 'outline') {
return this.props.backgroundColor || '#3b82f6';
}
// Simple contrast check could be added here, assuming white for now for colored buttons
return '#ffffff';
}
onClick(event: MouseEvent) {
// Prevent navigation in editor
event.preventDefault();
}
openConfig(event: MouseEvent) {
event.stopPropagation();
this.showConfig.set(true);
}
closeConfig() {
this.showConfig.set(false);
}
onSaveConfig(newProps: ButtonProps) {
this.update.emit(newProps);
this.showConfig.set(false);
}
}