blob: 73bfabf7d476cef2c4f49058724401964ed58b49 [file]
import 'dart:convert';
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import '../../../utils/styles.dart';
import '../../workspace/data/workspace_repository.dart';
class ImagePreview extends StatefulComponent {
const ImagePreview({required this.path, required this.workspaceRepository, super.key});
final String path;
final WorkspaceRepository workspaceRepository;
@override
State<ImagePreview> createState() => _ImagePreviewState();
@css
static List<StyleRule> get styles => _ImagePreviewState.styles;
}
class _ImagePreviewState extends State<ImagePreview> {
String? _dataUrl;
String? _error;
@override
void initState() {
super.initState();
_loadImage();
}
@override
void didUpdateComponent(ImagePreview oldComponent) {
super.didUpdateComponent(oldComponent);
if (oldComponent.path != component.path) {
_loadImage();
}
}
Future<void> _loadImage() async {
setState(() {
_dataUrl = null;
_error = null;
});
try {
final bytes = await component.workspaceRepository.workspaceResourceApi.readFileAsBytes(component.path);
final ext = component.path.split('.').last.toLowerCase();
final mime = switch (ext) {
'png' => 'image/png',
'jpg' || 'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
'svg' => 'image/svg+xml',
'bmp' => 'image/bmp',
'ico' => 'image/x-icon',
_ => 'application/octet-stream',
};
final base64String = base64Encode(bytes);
setState(() {
_dataUrl = 'data:$mime;base64,$base64String';
});
} catch (e) {
setState(() {
_error = e.toString();
});
}
}
@override
Component build(BuildContext context) {
if (_error != null) {
return div(classes: 'image-preview-error', [
const Component.text('Failed to load image: '),
Component.text(_error!),
]);
}
if (_dataUrl == null) {
return const div(classes: 'image-preview-loading', [
Component.text('Loading image...'),
]);
}
return div(classes: 'image-preview-container', [
img(src: _dataUrl!, classes: 'image-preview-el'),
]);
}
static List<StyleRule> get styles => [
css('.image-preview-container').styles(
display: .flex,
width: 100.percent,
height: 100.percent,
padding: .all(20.px),
boxSizing: .borderBox,
overflow: const Overflow.only(x: OverflowValue.auto, y: OverflowValue.auto),
justifyContent: .center,
alignItems: .center,
backgroundColor: colorContainer,
),
css('.image-preview-el').styles(
maxWidth: 100.percent,
maxHeight: 100.percent,
radius: .all(Radius.circular(4.px)),
shadow: BoxShadow(offsetX: 0.px, offsetY: 4.px, blur: 12.px, color: const Color.rgba(0, 0, 0, 0.15)),
raw: {'object-fit': 'contain'},
),
css('.image-preview-loading').styles(
display: .flex,
width: 100.percent,
height: 100.percent,
justifyContent: .center,
alignItems: .center,
color: colorOnSurfaceVariant,
fontSize: 14.px,
),
css('.image-preview-error').styles(
display: .flex,
width: 100.percent,
height: 100.percent,
justifyContent: .center,
alignItems: .center,
color: colorError,
fontSize: 14.px,
),
];
}