implemented the Ticker module with a native ProseMirror document renderer and integrated API support for structured content, navigation trees, and proxied files
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
|
||||
|
||||
import '../../theming/app_theme.dart';
|
||||
import 'pm_document_view.dart';
|
||||
import 'pm_node.dart';
|
||||
|
||||
/// Renders a ProseMirror table. Flutter's built-in `Table` cannot span cells,
|
||||
/// so `flutter_layout_grid` places each cell explicitly, honouring
|
||||
/// colspan/rowspan via a simple HTML-style occupancy scan.
|
||||
class PmTableView extends StatelessWidget {
|
||||
final PmTable node;
|
||||
|
||||
static const double _minColumnWidth = 140;
|
||||
|
||||
const PmTableView({required this.node, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final rows = node.children.whereType<PmTableRow>().toList();
|
||||
if (rows.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
final occupied = <int, Set<int>>{};
|
||||
final placements = <Widget>[];
|
||||
var columnCount = 0;
|
||||
|
||||
for (var r = 0; r < rows.length; r++) {
|
||||
var col = 0;
|
||||
for (final cell in rows[r].children.whereType<PmTableCell>()) {
|
||||
while (occupied[r]?.contains(col) ?? false) {
|
||||
col++;
|
||||
}
|
||||
placements.add(
|
||||
GridPlacement(
|
||||
columnStart: col,
|
||||
columnSpan: cell.colspan,
|
||||
rowStart: r,
|
||||
rowSpan: cell.rowspan,
|
||||
child: _cell(context, cell),
|
||||
),
|
||||
);
|
||||
for (var dr = 0; dr < cell.rowspan; dr++) {
|
||||
final set = occupied[r + dr] ??= <int>{};
|
||||
for (var dc = 0; dc < cell.colspan; dc++) {
|
||||
set.add(col + dc);
|
||||
}
|
||||
}
|
||||
col += cell.colspan;
|
||||
columnCount = max(columnCount, col);
|
||||
}
|
||||
}
|
||||
if (columnCount == 0) return const SizedBox.shrink();
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final available = constraints.maxWidth.isFinite
|
||||
? constraints.maxWidth
|
||||
: _minColumnWidth * columnCount;
|
||||
final columnWidth = max(_minColumnWidth, available / columnCount);
|
||||
final totalWidth = columnWidth * columnCount;
|
||||
|
||||
final grid = SizedBox(
|
||||
width: totalWidth,
|
||||
child: LayoutGrid(
|
||||
columnSizes: List.filled(columnCount, fixed(columnWidth)),
|
||||
rowSizes: List.filled(rows.length, auto),
|
||||
children: placements,
|
||||
),
|
||||
);
|
||||
|
||||
if (totalWidth <= available) return grid;
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: grid,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _cell(BuildContext context, PmTableCell cell) {
|
||||
final theme = Theme.of(context);
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: cell.header ? theme.colorScheme.surfaceContainerHighest : null,
|
||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.sm),
|
||||
child: pmBlocks(cell.children, gap: AppSpacing.xs),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user