mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #512 from AppFlowy-IO/feat_grid_url
feat: add new field type - URL
This commit is contained in:
commit
7ff841c3d8
3
frontend/app_flowy/assets/images/grid/field/url.svg
Normal file
3
frontend/app_flowy/assets/images/grid/field/url.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 7.688L8.27223 12.1469C7.69304 12.6931 6.90749 13 6.0884 13C5.26931 13 4.48376 12.6931 3.90457 12.1469C3.32538 11.6006 3 10.8598 3 10.0873C3 9.31474 3.32538 8.57387 3.90457 8.02763L8.63234 3.56875C9.01847 3.20459 9.54216 3 10.0882 3C10.6343 3 11.158 3.20459 11.5441 3.56875C11.9302 3.93291 12.1472 4.42683 12.1472 4.94183C12.1472 5.45684 11.9302 5.95075 11.5441 6.31491L6.8112 10.7738C6.61814 10.9559 6.35629 11.0582 6.08326 11.0582C5.81022 11.0582 5.54838 10.9559 5.35531 10.7738C5.16225 10.5917 5.05379 10.3448 5.05379 10.0873C5.05379 9.82975 5.16225 9.58279 5.35531 9.40071L9.72297 5.28632" stroke="#333333" stroke-width="0.9989" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 797 B |
@ -160,6 +160,7 @@
|
||||
"numberFieldName": "Numbers",
|
||||
"singleSelectFieldName": "Select",
|
||||
"multiSelectFieldName": "Multiselect",
|
||||
"urlFieldName": "URL",
|
||||
"numberFormat": " Number format",
|
||||
"dateFormat": " Date format",
|
||||
"includeTime": " Include time",
|
||||
|
@ -10,9 +10,9 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/selection_type_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
||||
|
@ -3,6 +3,7 @@ part of 'cell_service.dart';
|
||||
typedef GridCellContext = _GridCellContext<String, String>;
|
||||
typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>;
|
||||
typedef GridDateCellContext = _GridCellContext<DateCellData, DateCalData>;
|
||||
typedef GridURLCellContext = _GridCellContext<URLCellData, String>;
|
||||
|
||||
class GridCellContextBuilder {
|
||||
final GridCellCache _cellCache;
|
||||
@ -75,12 +76,25 @@ class GridCellContextBuilder {
|
||||
cellDataLoader: cellDataLoader,
|
||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||
);
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
|
||||
case FieldType.URL:
|
||||
final cellDataLoader = GridCellDataLoader(
|
||||
gridCell: _gridCell,
|
||||
parser: URLCellDataParser(),
|
||||
);
|
||||
return GridURLCellContext(
|
||||
gridCell: _gridCell,
|
||||
cellCache: _cellCache,
|
||||
cellDataLoader: cellDataLoader,
|
||||
cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
|
||||
);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
|
||||
// T: the type of the CellData
|
||||
// D: the type of the data that will be save to disk
|
||||
// ignore: must_be_immutable
|
||||
class _GridCellContext<T, D> extends Equatable {
|
||||
final GridCell gridCell;
|
||||
@ -94,7 +108,8 @@ class _GridCellContext<T, D> extends Equatable {
|
||||
late final ValueNotifier<T?> _cellDataNotifier;
|
||||
bool isListening = false;
|
||||
VoidCallback? _onFieldChangedFn;
|
||||
Timer? _delayOperation;
|
||||
Timer? _loadDataOperation;
|
||||
Timer? _saveDataOperation;
|
||||
|
||||
_GridCellContext({
|
||||
required this.gridCell,
|
||||
@ -124,7 +139,7 @@ class _GridCellContext<T, D> extends Equatable {
|
||||
|
||||
FieldType get fieldType => gridCell.field.fieldType;
|
||||
|
||||
VoidCallback? startListening({required void Function(T) onCellChanged}) {
|
||||
VoidCallback? startListening({required void Function(T?) onCellChanged}) {
|
||||
if (isListening) {
|
||||
Log.error("Already started. It seems like you should call clone first");
|
||||
return null;
|
||||
@ -148,7 +163,7 @@ class _GridCellContext<T, D> extends Equatable {
|
||||
}
|
||||
|
||||
onCellChangedFn() {
|
||||
onCellChanged(_cellDataNotifier.value as T);
|
||||
onCellChanged(_cellDataNotifier.value);
|
||||
|
||||
if (cellDataLoader.config.reloadOnCellChanged) {
|
||||
_loadData();
|
||||
@ -175,13 +190,26 @@ class _GridCellContext<T, D> extends Equatable {
|
||||
return _fieldService.getFieldTypeOptionData(fieldType: fieldType);
|
||||
}
|
||||
|
||||
Future<Option<FlowyError>> saveCellData(D data) {
|
||||
return cellDataPersistence.save(data);
|
||||
void saveCellData(D data, {bool deduplicate = false, void Function(Option<FlowyError>)? resultCallback}) async {
|
||||
if (deduplicate) {
|
||||
_loadDataOperation?.cancel();
|
||||
_loadDataOperation = Timer(const Duration(milliseconds: 300), () async {
|
||||
final result = await cellDataPersistence.save(data);
|
||||
if (resultCallback != null) {
|
||||
resultCallback(result);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
final result = await cellDataPersistence.save(data);
|
||||
if (resultCallback != null) {
|
||||
resultCallback(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _loadData() {
|
||||
_delayOperation?.cancel();
|
||||
_delayOperation = Timer(const Duration(milliseconds: 10), () {
|
||||
_loadDataOperation?.cancel();
|
||||
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
||||
cellDataLoader.loadData().then((data) {
|
||||
_cellDataNotifier.value = data;
|
||||
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
||||
@ -190,7 +218,8 @@ class _GridCellContext<T, D> extends Equatable {
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_delayOperation?.cancel();
|
||||
_loadDataOperation?.cancel();
|
||||
_saveDataOperation?.cancel();
|
||||
|
||||
if (_onFieldChangedFn != null) {
|
||||
cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!);
|
||||
|
@ -58,7 +58,11 @@ class GridCellDataLoader<T> extends IGridCellDataLoader<T> {
|
||||
return fut.then(
|
||||
(result) => result.fold((Cell cell) {
|
||||
try {
|
||||
return parser.parserData(cell.data);
|
||||
if (cell.data.isEmpty) {
|
||||
return null;
|
||||
} else {
|
||||
return parser.parserData(cell.data);
|
||||
}
|
||||
} catch (e, s) {
|
||||
Log.error('$parser parser cellData failed, $e');
|
||||
Log.error('Stack trace \n $s');
|
||||
@ -105,9 +109,6 @@ class StringCellDataParser implements ICellDataParser<String> {
|
||||
class DateCellDataParser implements ICellDataParser<DateCellData> {
|
||||
@override
|
||||
DateCellData? parserData(List<int> data) {
|
||||
if (data.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return DateCellData.fromBuffer(data);
|
||||
}
|
||||
}
|
||||
@ -115,9 +116,13 @@ class DateCellDataParser implements ICellDataParser<DateCellData> {
|
||||
class SelectOptionCellDataParser implements ICellDataParser<SelectOptionCellData> {
|
||||
@override
|
||||
SelectOptionCellData? parserData(List<int> data) {
|
||||
if (data.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return SelectOptionCellData.fromBuffer(data);
|
||||
}
|
||||
}
|
||||
|
||||
class URLCellDataParser implements ICellDataParser<URLCellData> {
|
||||
@override
|
||||
URLCellData? parserData(List<int> data) {
|
||||
return URLCellData.fromBuffer(data);
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
|
||||
class CheckboxCellEvent with _$CheckboxCellEvent {
|
||||
const factory CheckboxCellEvent.initial() = _Initial;
|
||||
const factory CheckboxCellEvent.select() = _Selected;
|
||||
const factory CheckboxCellEvent.didReceiveCellUpdate(String cellData) = _DidReceiveCellUpdate;
|
||||
const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -37,7 +37,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
setFocusedDay: (focusedDay) {
|
||||
emit(state.copyWith(focusedDay: focusedDay));
|
||||
},
|
||||
didReceiveCellUpdate: (DateCellData cellData) {
|
||||
didReceiveCellUpdate: (DateCellData? cellData) {
|
||||
final dateData = dateDataFromCellData(cellData);
|
||||
final time = dateData.foldRight("", (dateData, previous) => dateData.time);
|
||||
emit(state.copyWith(dateData: dateData, time: time));
|
||||
@ -83,25 +83,26 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await cellContext.saveCellData(newDateData);
|
||||
result.fold(
|
||||
() => emit(state.copyWith(
|
||||
dateData: Some(newDateData),
|
||||
timeFormatError: none(),
|
||||
)),
|
||||
(err) {
|
||||
switch (ErrorCode.valueOf(err.code)!) {
|
||||
case ErrorCode.InvalidDateTimeFormat:
|
||||
emit(state.copyWith(
|
||||
dateData: Some(newDateData),
|
||||
timeFormatError: Some(timeFormatPrompt(err)),
|
||||
));
|
||||
break;
|
||||
default:
|
||||
Log.error(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
cellContext.saveCellData(newDateData, resultCallback: (result) {
|
||||
result.fold(
|
||||
() => emit(state.copyWith(
|
||||
dateData: Some(newDateData),
|
||||
timeFormatError: none(),
|
||||
)),
|
||||
(err) {
|
||||
switch (ErrorCode.valueOf(err.code)!) {
|
||||
case ErrorCode.InvalidDateTimeFormat:
|
||||
emit(state.copyWith(
|
||||
dateData: Some(newDateData),
|
||||
timeFormatError: Some(timeFormatPrompt(err)),
|
||||
));
|
||||
break;
|
||||
default:
|
||||
Log.error(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
String timeFormatPrompt(FlowyError error) {
|
||||
@ -183,7 +184,7 @@ class DateCalEvent with _$DateCalEvent {
|
||||
const factory DateCalEvent.setDateFormat(DateFormat dateFormat) = _DateFormat;
|
||||
const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime;
|
||||
const factory DateCalEvent.setTime(String time) = _Time;
|
||||
const factory DateCalEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate;
|
||||
const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -16,7 +16,13 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () => _startListening(),
|
||||
didReceiveCellUpdate: (DateCellData value) => emit(state.copyWith(data: Some(value))),
|
||||
didReceiveCellUpdate: (DateCellData? cellData) {
|
||||
if (cellData != null) {
|
||||
emit(state.copyWith(data: Some(cellData)));
|
||||
} else {
|
||||
emit(state.copyWith(data: none()));
|
||||
}
|
||||
},
|
||||
didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)),
|
||||
);
|
||||
},
|
||||
@ -47,7 +53,7 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||
@freezed
|
||||
class DateCellEvent with _$DateCellEvent {
|
||||
const factory DateCellEvent.initial() = _InitialCell;
|
||||
const factory DateCellEvent.didReceiveCellUpdate(DateCellData data) = _DidReceiveCellUpdate;
|
||||
const factory DateCellEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate;
|
||||
const factory DateCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
||||
emit(state.copyWith(content: value.cellContent));
|
||||
emit(state.copyWith(content: value.cellContent ?? ""));
|
||||
},
|
||||
updateCell: (_UpdateCell value) async {
|
||||
await _updateCellValue(value, emit);
|
||||
@ -58,7 +58,7 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
||||
class NumberCellEvent with _$NumberCellEvent {
|
||||
const factory NumberCellEvent.initial() = _Initial;
|
||||
const factory NumberCellEvent.updateCell(String text) = _UpdateCell;
|
||||
const factory NumberCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate;
|
||||
const factory NumberCellEvent.didReceiveCellUpdate(String? cellContent) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -44,7 +44,7 @@ class SelectOptionCellBloc extends Bloc<SelectOptionCellEvent, SelectOptionCellS
|
||||
onCellChanged: ((selectOptionContext) {
|
||||
if (!isClosed) {
|
||||
add(SelectOptionCellEvent.didReceiveOptions(
|
||||
selectOptionContext.selectOptions,
|
||||
selectOptionContext?.selectOptions ?? [],
|
||||
));
|
||||
}
|
||||
}),
|
||||
|
@ -43,7 +43,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
onCellChanged: ((cellContent) {
|
||||
if (!isClosed) {
|
||||
add(TextCellEvent.didReceiveCellUpdate(cellContent));
|
||||
add(TextCellEvent.didReceiveCellUpdate(cellContent ?? ""));
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
@ -0,0 +1,73 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service/cell_service.dart';
|
||||
|
||||
part 'url_cell_bloc.freezed.dart';
|
||||
|
||||
class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
||||
final GridURLCellContext cellContext;
|
||||
void Function()? _onCellChangedFn;
|
||||
URLCellBloc({
|
||||
required this.cellContext,
|
||||
}) : super(URLCellState.initial(cellContext)) {
|
||||
on<URLCellEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(
|
||||
content: cellData?.content ?? "",
|
||||
url: cellData?.url ?? "",
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(URLCellEvent.didReceiveCellUpdate(cellData));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class URLCellEvent with _$URLCellEvent {
|
||||
const factory URLCellEvent.initial() = _InitialCell;
|
||||
const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class URLCellState with _$URLCellState {
|
||||
const factory URLCellState({
|
||||
required String content,
|
||||
required String url,
|
||||
}) = _URLCellState;
|
||||
|
||||
factory URLCellState.initial(GridURLCellContext context) {
|
||||
final cellData = context.getCellData();
|
||||
return URLCellState(
|
||||
content: cellData?.content ?? "",
|
||||
url: cellData?.url ?? "",
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
import 'cell_service/cell_service.dart';
|
||||
|
||||
part 'url_cell_editor_bloc.freezed.dart';
|
||||
|
||||
class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
|
||||
final GridURLCellContext cellContext;
|
||||
void Function()? _onCellChangedFn;
|
||||
URLCellEditorBloc({
|
||||
required this.cellContext,
|
||||
}) : super(URLCellEditorState.initial(cellContext)) {
|
||||
on<URLCellEditorEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
updateText: (text) {
|
||||
cellContext.saveCellData(text, deduplicate: true);
|
||||
emit(state.copyWith(content: text));
|
||||
},
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(content: cellData?.content ?? ""));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
if (_onCellChangedFn != null) {
|
||||
cellContext.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellContext.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onCellChangedFn = cellContext.startListening(
|
||||
onCellChanged: ((cellData) {
|
||||
if (!isClosed) {
|
||||
add(URLCellEditorEvent.didReceiveCellUpdate(cellData));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class URLCellEditorEvent with _$URLCellEditorEvent {
|
||||
const factory URLCellEditorEvent.initial() = _InitialCell;
|
||||
const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate;
|
||||
const factory URLCellEditorEvent.updateText(String text) = _UpdateText;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class URLCellEditorState with _$URLCellEditorState {
|
||||
const factory URLCellEditorState({
|
||||
required String content,
|
||||
}) = _URLCellEditorState;
|
||||
|
||||
factory URLCellEditorState.initial(GridURLCellContext context) {
|
||||
final cellData = context.getCellData();
|
||||
return URLCellEditorState(
|
||||
content: cellData?.content ?? "",
|
||||
);
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import 'date_cell/date_cell.dart';
|
||||
import 'number_cell.dart';
|
||||
import 'select_option_cell/select_option_cell.dart';
|
||||
import 'text_cell.dart';
|
||||
import 'url_cell/url_cell.dart';
|
||||
|
||||
GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {GridCellStyle? style}) {
|
||||
final key = ValueKey(gridCell.cellId());
|
||||
@ -32,10 +33,10 @@ GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCache cellCache, {
|
||||
return NumberCell(cellContextBuilder: cellContextBuilder, key: key);
|
||||
case FieldType.RichText:
|
||||
return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
|
||||
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
case FieldType.URL:
|
||||
return GridURLCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
||||
class BlankCell extends StatelessWidget {
|
||||
@ -149,7 +150,7 @@ class CellContainer extends StatelessWidget {
|
||||
});
|
||||
|
||||
if (expander != null) {
|
||||
container = _CellEnterRegion(child: container, expander: expander!);
|
||||
container = CellEnterRegion(child: container, expander: expander!);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
@ -179,10 +180,10 @@ class CellContainer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _CellEnterRegion extends StatelessWidget {
|
||||
class CellEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final Widget expander;
|
||||
const _CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
|
||||
const CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -0,0 +1,96 @@
|
||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/url_cell_editor_bloc.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class URLCellEditor extends StatefulWidget {
|
||||
final GridURLCellContext cellContext;
|
||||
const URLCellEditor({required this.cellContext, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<URLCellEditor> createState() => _URLCellEditorState();
|
||||
|
||||
static void show(
|
||||
BuildContext context,
|
||||
GridURLCellContext cellContext,
|
||||
) {
|
||||
FlowyOverlay.of(context).remove(identifier());
|
||||
final editor = URLCellEditor(
|
||||
cellContext: cellContext,
|
||||
);
|
||||
|
||||
//
|
||||
FlowyOverlay.of(context).insertWithAnchor(
|
||||
widget: OverlayContainer(
|
||||
child: SizedBox(width: 200, child: editor),
|
||||
constraints: BoxConstraints.loose(const Size(300, 160)),
|
||||
),
|
||||
identifier: URLCellEditor.identifier(),
|
||||
anchorContext: context,
|
||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
||||
);
|
||||
}
|
||||
|
||||
static String identifier() {
|
||||
return (URLCellEditor).toString();
|
||||
}
|
||||
}
|
||||
|
||||
class _URLCellEditorState extends State<URLCellEditor> {
|
||||
late URLCellEditorBloc _cellBloc;
|
||||
late TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_cellBloc = URLCellEditorBloc(cellContext: widget.cellContext);
|
||||
_cellBloc.add(const URLCellEditorEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocListener<URLCellEditorBloc, URLCellEditorState>(
|
||||
listener: (context, state) {
|
||||
if (_controller.text != state.content) {
|
||||
_controller.text = state.content;
|
||||
}
|
||||
},
|
||||
child: TextField(
|
||||
autofocus: true,
|
||||
controller: _controller,
|
||||
onChanged: (value) => focusChanged(),
|
||||
maxLines: null,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
border: InputBorder.none,
|
||||
hintText: "",
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> focusChanged() async {
|
||||
if (mounted) {
|
||||
if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) {
|
||||
_cellBloc.add(URLCellEditorEvent.updateText(_controller.text));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
import 'dart:async';
|
||||
import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../cell_builder.dart';
|
||||
import 'cell_editor.dart';
|
||||
|
||||
class GridURLCellStyle extends GridCellStyle {
|
||||
String? placeholder;
|
||||
|
||||
GridURLCellStyle({
|
||||
this.placeholder,
|
||||
});
|
||||
}
|
||||
|
||||
class GridURLCell extends StatefulWidget with GridCellWidget {
|
||||
final GridCellContextBuilder cellContextBuilder;
|
||||
late final GridURLCellStyle? cellStyle;
|
||||
GridURLCell({
|
||||
required this.cellContextBuilder,
|
||||
GridCellStyle? style,
|
||||
Key? key,
|
||||
}) : super(key: key) {
|
||||
if (style != null) {
|
||||
cellStyle = (style as GridURLCellStyle);
|
||||
} else {
|
||||
cellStyle = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
State<GridURLCell> createState() => _GridURLCellState();
|
||||
}
|
||||
|
||||
class _GridURLCellState extends State<GridURLCell> {
|
||||
late URLCellBloc _cellBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
||||
_cellBloc = URLCellBloc(cellContext: cellContext);
|
||||
_cellBloc.add(const URLCellEvent.initial());
|
||||
_listenRequestFocus(context);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<URLCellBloc, URLCellState>(
|
||||
builder: (context, state) {
|
||||
final richText = RichText(
|
||||
textAlign: TextAlign.left,
|
||||
text: TextSpan(
|
||||
text: state.content,
|
||||
style: TextStyle(
|
||||
color: theme.main2,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
recognizer: _tapGesture(context),
|
||||
),
|
||||
);
|
||||
|
||||
return CellEnterRegion(
|
||||
child: Align(alignment: Alignment.centerLeft, child: richText),
|
||||
expander: _EditCellIndicator(onTap: () {}),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
widget.requestFocus.removeAllListener();
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
TapGestureRecognizer _tapGesture(BuildContext context) {
|
||||
final gesture = TapGestureRecognizer();
|
||||
gesture.onTap = () async {
|
||||
final url = context.read<URLCellBloc>().state.url;
|
||||
await _openUrlOrEdit(url);
|
||||
};
|
||||
return gesture;
|
||||
}
|
||||
|
||||
Future<void> _openUrlOrEdit(String url) async {
|
||||
final uri = Uri.parse(url);
|
||||
if (url.isNotEmpty && await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri);
|
||||
} else {
|
||||
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
||||
URLCellEditor.show(context, cellContext);
|
||||
}
|
||||
}
|
||||
|
||||
void _listenRequestFocus(BuildContext context) {
|
||||
widget.requestFocus.addListener(() {
|
||||
_openUrlOrEdit(_cellBloc.state.url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _EditCellIndicator extends StatelessWidget {
|
||||
final VoidCallback onTap;
|
||||
const _EditCellIndicator({required this.onTap, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return FlowyIconButton(
|
||||
width: 26,
|
||||
onPressed: onTap,
|
||||
hoverColor: theme.hover,
|
||||
radius: BorderRadius.circular(4),
|
||||
iconPadding: const EdgeInsets.all(5),
|
||||
icon: svgWidget("editor/edit", color: theme.iconColor),
|
||||
);
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import 'type_option/multi_select.dart';
|
||||
import 'type_option/number.dart';
|
||||
import 'type_option/rich_text.dart';
|
||||
import 'type_option/single_select.dart';
|
||||
import 'type_option/url.dart';
|
||||
|
||||
typedef UpdateFieldCallback = void Function(Field, Uint8List);
|
||||
typedef SwitchToFieldCallback = Future<Either<FieldTypeOptionData, FlowyError>> Function(
|
||||
@ -168,9 +169,12 @@ TypeOptionBuilder _makeTypeOptionBuild({
|
||||
typeOptionContext as RichTextTypeOptionContext,
|
||||
);
|
||||
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
case FieldType.URL:
|
||||
return URLTypeOptionBuilder(
|
||||
typeOptionContext as URLTypeOptionContext,
|
||||
);
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
||||
TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) {
|
||||
@ -205,9 +209,15 @@ TypeOptionContext _makeTypeOptionContext(GridFieldContext fieldContext) {
|
||||
fieldContext: fieldContext,
|
||||
dataBuilder: SingleSelectTypeOptionDataBuilder(),
|
||||
);
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
|
||||
case FieldType.URL:
|
||||
return URLTypeOptionContext(
|
||||
fieldContext: fieldContext,
|
||||
dataBuilder: URLTypeOptionDataBuilder(),
|
||||
);
|
||||
}
|
||||
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
abstract class TypeOptionWidget extends StatelessWidget {
|
||||
|
@ -17,9 +17,10 @@ extension FieldTypeListExtension on FieldType {
|
||||
return "grid/field/text";
|
||||
case FieldType.SingleSelect:
|
||||
return "grid/field/single_select";
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
case FieldType.URL:
|
||||
return "grid/field/url";
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
||||
String title() {
|
||||
@ -36,8 +37,9 @@ extension FieldTypeListExtension on FieldType {
|
||||
return LocaleKeys.grid_field_textFieldName.tr();
|
||||
case FieldType.SingleSelect:
|
||||
return LocaleKeys.grid_field_singleSelectFieldName.tr();
|
||||
default:
|
||||
throw UnimplementedError;
|
||||
case FieldType.URL:
|
||||
return LocaleKeys.grid_field_urlFieldName.tr();
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor_pannel.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef URLTypeOptionContext = TypeOptionContext<URLTypeOption>;
|
||||
|
||||
class URLTypeOptionDataBuilder extends TypeOptionDataBuilder<URLTypeOption> {
|
||||
@override
|
||||
URLTypeOption fromBuffer(List<int> buffer) {
|
||||
return URLTypeOption.fromBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class URLTypeOptionBuilder extends TypeOptionBuilder {
|
||||
URLTypeOptionBuilder(URLTypeOptionContext typeOptionContext);
|
||||
|
||||
@override
|
||||
Widget? get customWidget => null;
|
||||
}
|
@ -209,9 +209,10 @@ class _CellExpander extends StatelessWidget {
|
||||
return FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: FlowyIconButton(
|
||||
width: 26,
|
||||
onPressed: onExpand,
|
||||
iconPadding: const EdgeInsets.fromLTRB(6, 6, 6, 6),
|
||||
fillColor: theme.surface,
|
||||
iconPadding: const EdgeInsets.all(5),
|
||||
radius: BorderRadius.circular(4),
|
||||
icon: svgWidget("grid/expander", color: theme.main1),
|
||||
),
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
|
||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
|
||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@ -212,7 +213,11 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
|
||||
return SelectOptionCellStyle(
|
||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
|
||||
case FieldType.URL:
|
||||
return GridURLCellStyle(
|
||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ class FieldType extends $pb.ProtobufEnum {
|
||||
static const FieldType SingleSelect = FieldType._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SingleSelect');
|
||||
static const FieldType MultiSelect = FieldType._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MultiSelect');
|
||||
static const FieldType Checkbox = FieldType._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Checkbox');
|
||||
static const FieldType URL = FieldType._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'URL');
|
||||
|
||||
static const $core.List<FieldType> values = <FieldType> [
|
||||
RichText,
|
||||
@ -39,6 +40,7 @@ class FieldType extends $pb.ProtobufEnum {
|
||||
SingleSelect,
|
||||
MultiSelect,
|
||||
Checkbox,
|
||||
URL,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, FieldType> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
|
@ -29,11 +29,12 @@ const FieldType$json = const {
|
||||
const {'1': 'SingleSelect', '2': 3},
|
||||
const {'1': 'MultiSelect', '2': 4},
|
||||
const {'1': 'Checkbox', '2': 5},
|
||||
const {'1': 'URL', '2': 6},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `FieldType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List fieldTypeDescriptor = $convert.base64Decode('CglGaWVsZFR5cGUSDAoIUmljaFRleHQQABIKCgZOdW1iZXIQARIMCghEYXRlVGltZRACEhAKDFNpbmdsZVNlbGVjdBADEg8KC011bHRpU2VsZWN0EAQSDAoIQ2hlY2tib3gQBQ==');
|
||||
final $typed_data.Uint8List fieldTypeDescriptor = $convert.base64Decode('CglGaWVsZFR5cGUSDAoIUmljaFRleHQQABIKCgZOdW1iZXIQARIMCghEYXRlVGltZRACEhAKDFNpbmdsZVNlbGVjdBADEg8KC011bHRpU2VsZWN0EAQSDAoIQ2hlY2tib3gQBRIHCgNVUkwQBg==');
|
||||
@$core.Deprecated('Use gridDescriptor instead')
|
||||
const Grid$json = const {
|
||||
'1': 'Grid',
|
||||
|
@ -5,6 +5,7 @@ export './dart_notification.pb.dart';
|
||||
export './selection_type_option.pb.dart';
|
||||
export './row_entities.pb.dart';
|
||||
export './cell_entities.pb.dart';
|
||||
export './url_type_option.pb.dart';
|
||||
export './checkbox_type_option.pb.dart';
|
||||
export './event_map.pb.dart';
|
||||
export './text_type_option.pb.dart';
|
||||
|
@ -11,17 +11,17 @@ import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class RichTextTypeOption extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RichTextTypeOption', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format')
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
RichTextTypeOption._() : super();
|
||||
factory RichTextTypeOption({
|
||||
$core.String? format,
|
||||
$core.String? data,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (format != null) {
|
||||
_result.format = format;
|
||||
if (data != null) {
|
||||
_result.data = data;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
@ -47,12 +47,12 @@ class RichTextTypeOption extends $pb.GeneratedMessage {
|
||||
static RichTextTypeOption? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get format => $_getSZ(0);
|
||||
$core.String get data => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set format($core.String v) { $_setString(0, v); }
|
||||
set data($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasFormat() => $_has(0);
|
||||
$core.bool hasData() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearFormat() => clearField(1);
|
||||
void clearData() => clearField(1);
|
||||
}
|
||||
|
||||
|
@ -12,9 +12,9 @@ import 'dart:typed_data' as $typed_data;
|
||||
const RichTextTypeOption$json = const {
|
||||
'1': 'RichTextTypeOption',
|
||||
'2': const [
|
||||
const {'1': 'format', '3': 1, '4': 1, '5': 9, '10': 'format'},
|
||||
const {'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `RichTextTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List richTextTypeOptionDescriptor = $convert.base64Decode('ChJSaWNoVGV4dFR5cGVPcHRpb24SFgoGZm9ybWF0GAEgASgJUgZmb3JtYXQ=');
|
||||
final $typed_data.Uint8List richTextTypeOptionDescriptor = $convert.base64Decode('ChJSaWNoVGV4dFR5cGVPcHRpb24SEgoEZGF0YRgBIAEoCVIEZGF0YQ==');
|
||||
|
@ -0,0 +1,119 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: url_type_option.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class URLTypeOption extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'URLTypeOption', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'data')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
URLTypeOption._() : super();
|
||||
factory URLTypeOption({
|
||||
$core.String? data,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (data != null) {
|
||||
_result.data = data;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory URLTypeOption.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory URLTypeOption.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
URLTypeOption clone() => URLTypeOption()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
URLTypeOption copyWith(void Function(URLTypeOption) updates) => super.copyWith((message) => updates(message as URLTypeOption)) as URLTypeOption; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static URLTypeOption create() => URLTypeOption._();
|
||||
URLTypeOption createEmptyInstance() => create();
|
||||
static $pb.PbList<URLTypeOption> createRepeated() => $pb.PbList<URLTypeOption>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static URLTypeOption getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<URLTypeOption>(create);
|
||||
static URLTypeOption? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get data => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set data($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasData() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearData() => clearField(1);
|
||||
}
|
||||
|
||||
class URLCellData extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'URLCellData', createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'url')
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'content')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
URLCellData._() : super();
|
||||
factory URLCellData({
|
||||
$core.String? url,
|
||||
$core.String? content,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (url != null) {
|
||||
_result.url = url;
|
||||
}
|
||||
if (content != null) {
|
||||
_result.content = content;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory URLCellData.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory URLCellData.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
URLCellData clone() => URLCellData()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
URLCellData copyWith(void Function(URLCellData) updates) => super.copyWith((message) => updates(message as URLCellData)) as URLCellData; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static URLCellData create() => URLCellData._();
|
||||
URLCellData createEmptyInstance() => create();
|
||||
static $pb.PbList<URLCellData> createRepeated() => $pb.PbList<URLCellData>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static URLCellData getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<URLCellData>(create);
|
||||
static URLCellData? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get url => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set url($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasUrl() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearUrl() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get content => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set content($core.String v) { $_setString(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasContent() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearContent() => clearField(2);
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: url_type_option.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
||||
|
@ -0,0 +1,31 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: url_type_option.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
|
||||
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
@$core.Deprecated('Use uRLTypeOptionDescriptor instead')
|
||||
const URLTypeOption$json = const {
|
||||
'1': 'URLTypeOption',
|
||||
'2': const [
|
||||
const {'1': 'data', '3': 1, '4': 1, '5': 9, '10': 'data'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `URLTypeOption`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List uRLTypeOptionDescriptor = $convert.base64Decode('Cg1VUkxUeXBlT3B0aW9uEhIKBGRhdGEYASABKAlSBGRhdGE=');
|
||||
@$core.Deprecated('Use uRLCellDataDescriptor instead')
|
||||
const URLCellData$json = const {
|
||||
'1': 'URLCellData',
|
||||
'2': const [
|
||||
const {'1': 'url', '3': 1, '4': 1, '5': 9, '10': 'url'},
|
||||
const {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `URLCellData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List uRLCellDataDescriptor = $convert.base64Decode('CgtVUkxDZWxsRGF0YRIQCgN1cmwYASABKAlSA3VybBIYCgdjb250ZW50GAIgASgJUgdjb250ZW50');
|
@ -0,0 +1,9 @@
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: url_type_option.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
|
||||
|
||||
export 'url_type_option.pb.dart';
|
||||
|
2
frontend/rust-lib/Cargo.lock
generated
2
frontend/rust-lib/Cargo.lock
generated
@ -928,6 +928,7 @@ dependencies = [
|
||||
"dart-notify",
|
||||
"dashmap",
|
||||
"diesel",
|
||||
"fancy-regex",
|
||||
"flowy-database",
|
||||
"flowy-derive",
|
||||
"flowy-error",
|
||||
@ -953,6 +954,7 @@ dependencies = [
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -35,6 +35,8 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = {version = "1.0"}
|
||||
serde_repr = "0.1"
|
||||
indexmap = {version = "1.8.1", features = ["serde"]}
|
||||
fancy-regex = "0.10.0"
|
||||
url = { version = "2"}
|
||||
|
||||
[dev-dependencies]
|
||||
flowy-test = { path = "../flowy-test" }
|
||||
|
@ -329,7 +329,7 @@ pub(crate) async fn get_select_option_handler(
|
||||
let editor = manager.get_grid_editor(¶ms.grid_id)?;
|
||||
match editor.get_field_meta(¶ms.field_id).await {
|
||||
None => {
|
||||
tracing::error!("Can't find the corresponding field with id: {}", params.field_id);
|
||||
tracing::error!("Can't find the select option field with id: {}", params.field_id);
|
||||
data_result(SelectOptionCellData::default())
|
||||
}
|
||||
Some(field_meta) => {
|
||||
|
@ -19,6 +19,9 @@ pub use row_entities::*;
|
||||
mod cell_entities;
|
||||
pub use cell_entities::*;
|
||||
|
||||
mod url_type_option;
|
||||
pub use url_type_option::*;
|
||||
|
||||
mod checkbox_type_option;
|
||||
pub use checkbox_type_option::*;
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct RichTextTypeOption {
|
||||
// message fields
|
||||
pub format: ::std::string::String,
|
||||
pub data: ::std::string::String,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
@ -43,30 +43,30 @@ impl RichTextTypeOption {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// string format = 1;
|
||||
// string data = 1;
|
||||
|
||||
|
||||
pub fn get_format(&self) -> &str {
|
||||
&self.format
|
||||
pub fn get_data(&self) -> &str {
|
||||
&self.data
|
||||
}
|
||||
pub fn clear_format(&mut self) {
|
||||
self.format.clear();
|
||||
pub fn clear_data(&mut self) {
|
||||
self.data.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_format(&mut self, v: ::std::string::String) {
|
||||
self.format = v;
|
||||
pub fn set_data(&mut self, v: ::std::string::String) {
|
||||
self.data = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_format(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.format
|
||||
pub fn mut_data(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_format(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.format, ::std::string::String::new())
|
||||
pub fn take_data(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.data, ::std::string::String::new())
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ impl ::protobuf::Message for RichTextTypeOption {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.format)?;
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
@ -94,8 +94,8 @@ impl ::protobuf::Message for RichTextTypeOption {
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if !self.format.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(1, &self.format);
|
||||
if !self.data.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(1, &self.data);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
@ -103,8 +103,8 @@ impl ::protobuf::Message for RichTextTypeOption {
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if !self.format.is_empty() {
|
||||
os.write_string(1, &self.format)?;
|
||||
if !self.data.is_empty() {
|
||||
os.write_string(1, &self.data)?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
@ -145,9 +145,9 @@ impl ::protobuf::Message for RichTextTypeOption {
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"format",
|
||||
|m: &RichTextTypeOption| { &m.format },
|
||||
|m: &mut RichTextTypeOption| { &mut m.format },
|
||||
"data",
|
||||
|m: &RichTextTypeOption| { &m.data },
|
||||
|m: &mut RichTextTypeOption| { &mut m.data },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<RichTextTypeOption>(
|
||||
"RichTextTypeOption",
|
||||
@ -165,7 +165,7 @@ impl ::protobuf::Message for RichTextTypeOption {
|
||||
|
||||
impl ::protobuf::Clear for RichTextTypeOption {
|
||||
fn clear(&mut self) {
|
||||
self.format.clear();
|
||||
self.data.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
@ -183,8 +183,8 @@ impl ::protobuf::reflect::ProtobufValue for RichTextTypeOption {
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x16text_type_option.proto\",\n\x12RichTextTypeOption\x12\x16\n\x06for\
|
||||
mat\x18\x01\x20\x01(\tR\x06formatb\x06proto3\
|
||||
\n\x16text_type_option.proto\"(\n\x12RichTextTypeOption\x12\x12\n\x04dat\
|
||||
a\x18\x01\x20\x01(\tR\x04datab\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
@ -0,0 +1,403 @@
|
||||
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
#![allow(unused_attributes)]
|
||||
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||
|
||||
#![allow(box_pointers)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(trivial_casts)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
//! Generated file from `url_type_option.proto`
|
||||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct URLTypeOption {
|
||||
// message fields
|
||||
pub data: ::std::string::String,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a URLTypeOption {
|
||||
fn default() -> &'a URLTypeOption {
|
||||
<URLTypeOption as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl URLTypeOption {
|
||||
pub fn new() -> URLTypeOption {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// string data = 1;
|
||||
|
||||
|
||||
pub fn get_data(&self) -> &str {
|
||||
&self.data
|
||||
}
|
||||
pub fn clear_data(&mut self) {
|
||||
self.data.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_data(&mut self, v: ::std::string::String) {
|
||||
self.data = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_data(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_data(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.data, ::std::string::String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for URLTypeOption {
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
while !is.eof()? {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.data)?;
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if !self.data.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(1, &self.data);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if !self.data.is_empty() {
|
||||
os.write_string(1, &self.data)?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_size(&self) -> u32 {
|
||||
self.cached_size.get()
|
||||
}
|
||||
|
||||
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
|
||||
&self.unknown_fields
|
||||
}
|
||||
|
||||
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
|
||||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
Self::descriptor_static()
|
||||
}
|
||||
|
||||
fn new() -> URLTypeOption {
|
||||
URLTypeOption::new()
|
||||
}
|
||||
|
||||
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"data",
|
||||
|m: &URLTypeOption| { &m.data },
|
||||
|m: &mut URLTypeOption| { &mut m.data },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<URLTypeOption>(
|
||||
"URLTypeOption",
|
||||
fields,
|
||||
file_descriptor_proto()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static URLTypeOption {
|
||||
static instance: ::protobuf::rt::LazyV2<URLTypeOption> = ::protobuf::rt::LazyV2::INIT;
|
||||
instance.get(URLTypeOption::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Clear for URLTypeOption {
|
||||
fn clear(&mut self) {
|
||||
self.data.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for URLTypeOption {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for URLTypeOption {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||
::protobuf::reflect::ReflectValueRef::Message(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct URLCellData {
|
||||
// message fields
|
||||
pub url: ::std::string::String,
|
||||
pub content: ::std::string::String,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a URLCellData {
|
||||
fn default() -> &'a URLCellData {
|
||||
<URLCellData as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl URLCellData {
|
||||
pub fn new() -> URLCellData {
|
||||
::std::default::Default::default()
|
||||
}
|
||||
|
||||
// string url = 1;
|
||||
|
||||
|
||||
pub fn get_url(&self) -> &str {
|
||||
&self.url
|
||||
}
|
||||
pub fn clear_url(&mut self) {
|
||||
self.url.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_url(&mut self, v: ::std::string::String) {
|
||||
self.url = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_url(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.url
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_url(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.url, ::std::string::String::new())
|
||||
}
|
||||
|
||||
// string content = 2;
|
||||
|
||||
|
||||
pub fn get_content(&self) -> &str {
|
||||
&self.content
|
||||
}
|
||||
pub fn clear_content(&mut self) {
|
||||
self.content.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_content(&mut self, v: ::std::string::String) {
|
||||
self.content = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_content(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.content
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_content(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.content, ::std::string::String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for URLCellData {
|
||||
fn is_initialized(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
while !is.eof()? {
|
||||
let (field_number, wire_type) = is.read_tag_unpack()?;
|
||||
match field_number {
|
||||
1 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.url)?;
|
||||
},
|
||||
2 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.content)?;
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
};
|
||||
}
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
// Compute sizes of nested messages
|
||||
#[allow(unused_variables)]
|
||||
fn compute_size(&self) -> u32 {
|
||||
let mut my_size = 0;
|
||||
if !self.url.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(1, &self.url);
|
||||
}
|
||||
if !self.content.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(2, &self.content);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
}
|
||||
|
||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||
if !self.url.is_empty() {
|
||||
os.write_string(1, &self.url)?;
|
||||
}
|
||||
if !self.content.is_empty() {
|
||||
os.write_string(2, &self.content)?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
|
||||
fn get_cached_size(&self) -> u32 {
|
||||
self.cached_size.get()
|
||||
}
|
||||
|
||||
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
|
||||
&self.unknown_fields
|
||||
}
|
||||
|
||||
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
|
||||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
Self::descriptor_static()
|
||||
}
|
||||
|
||||
fn new() -> URLCellData {
|
||||
URLCellData::new()
|
||||
}
|
||||
|
||||
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
|
||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||
descriptor.get(|| {
|
||||
let mut fields = ::std::vec::Vec::new();
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"url",
|
||||
|m: &URLCellData| { &m.url },
|
||||
|m: &mut URLCellData| { &mut m.url },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"content",
|
||||
|m: &URLCellData| { &m.content },
|
||||
|m: &mut URLCellData| { &mut m.content },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<URLCellData>(
|
||||
"URLCellData",
|
||||
fields,
|
||||
file_descriptor_proto()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn default_instance() -> &'static URLCellData {
|
||||
static instance: ::protobuf::rt::LazyV2<URLCellData> = ::protobuf::rt::LazyV2::INIT;
|
||||
instance.get(URLCellData::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Clear for URLCellData {
|
||||
fn clear(&mut self) {
|
||||
self.url.clear();
|
||||
self.content.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Debug for URLCellData {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
::protobuf::text_format::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for URLCellData {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||
::protobuf::reflect::ReflectValueRef::Message(self)
|
||||
}
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x15url_type_option.proto\"#\n\rURLTypeOption\x12\x12\n\x04data\x18\
|
||||
\x01\x20\x01(\tR\x04data\"9\n\x0bURLCellData\x12\x10\n\x03url\x18\x01\
|
||||
\x20\x01(\tR\x03url\x12\x18\n\x07content\x18\x02\x20\x01(\tR\x07contentb\
|
||||
\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
||||
fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
|
||||
::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
|
||||
}
|
||||
|
||||
pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
|
||||
file_descriptor_proto_lazy.get(|| {
|
||||
parse_descriptor_proto()
|
||||
})
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message RichTextTypeOption {
|
||||
string format = 1;
|
||||
string data = 1;
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message URLTypeOption {
|
||||
string data = 1;
|
||||
}
|
||||
message URLCellData {
|
||||
string url = 1;
|
||||
string content = 2;
|
||||
}
|
@ -94,6 +94,7 @@ pub fn default_type_option_builder_from_type(field_type: &FieldType) -> Box<dyn
|
||||
FieldType::SingleSelect => SingleSelectTypeOption::default().into(),
|
||||
FieldType::MultiSelect => MultiSelectTypeOption::default().into(),
|
||||
FieldType::Checkbox => CheckboxTypeOption::default().into(),
|
||||
FieldType::URL => URLTypeOption::default().into(),
|
||||
};
|
||||
|
||||
type_option_builder_from_json_str(&s, field_type)
|
||||
@ -107,6 +108,7 @@ pub fn type_option_builder_from_json_str(s: &str, field_type: &FieldType) -> Box
|
||||
FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_json_str(s)),
|
||||
FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_json_str(s)),
|
||||
FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_json_str(s)),
|
||||
FieldType::URL => Box::new(URLTypeOptionBuilder::from_json_str(s)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,5 +121,6 @@ pub fn type_option_builder_from_bytes<T: Into<Bytes>>(bytes: T, field_type: &Fie
|
||||
FieldType::SingleSelect => Box::new(SingleSelectTypeOptionBuilder::from_protobuf_bytes(bytes)),
|
||||
FieldType::MultiSelect => Box::new(MultiSelectTypeOptionBuilder::from_protobuf_bytes(bytes)),
|
||||
FieldType::Checkbox => Box::new(CheckboxTypeOptionBuilder::from_protobuf_bytes(bytes)),
|
||||
FieldType::URL => Box::new(URLTypeOptionBuilder::from_protobuf_bytes(bytes)),
|
||||
}
|
||||
}
|
||||
|
@ -627,7 +627,7 @@ mod tests {
|
||||
field_meta: &FieldMeta,
|
||||
) -> String {
|
||||
type_option
|
||||
.decode_cell_data(encoded_data, &FieldType::DateTime, &field_meta)
|
||||
.decode_cell_data(encoded_data, &FieldType::DateTime, field_meta)
|
||||
.unwrap()
|
||||
.parse::<DateCellData>()
|
||||
.unwrap()
|
||||
|
@ -3,6 +3,7 @@ mod date_type_option;
|
||||
mod number_type_option;
|
||||
mod selection_type_option;
|
||||
mod text_type_option;
|
||||
mod url_type_option;
|
||||
mod util;
|
||||
|
||||
pub use checkbox_type_option::*;
|
||||
@ -10,3 +11,4 @@ pub use date_type_option::*;
|
||||
pub use number_type_option::*;
|
||||
pub use selection_type_option::*;
|
||||
pub use text_type_option::*;
|
||||
pub use url_type_option::*;
|
||||
|
@ -736,14 +736,10 @@ mod tests {
|
||||
) {
|
||||
assert_eq!(
|
||||
type_option
|
||||
.decode_cell_data(data(cell_data), field_type, field_meta)
|
||||
.decode_cell_data(cell_data, field_type, field_meta)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
expected_str.to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
fn data(s: &str) -> String {
|
||||
s.to_owned()
|
||||
}
|
||||
}
|
||||
|
@ -207,8 +207,6 @@ impl CellDataOperation<String, String> for MultiSelectTypeOption {
|
||||
return Ok(DecodedCellData::default());
|
||||
}
|
||||
|
||||
tracing::info!("😁{}", self.options.len());
|
||||
|
||||
let encoded_data = encoded_data.into();
|
||||
let select_options = select_option_ids(encoded_data)
|
||||
.into_iter()
|
||||
@ -537,7 +535,7 @@ mod tests {
|
||||
|
||||
let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
|
||||
let cell_data = type_option.apply_changeset(data, None).unwrap();
|
||||
assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]);
|
||||
assert_single_select_options(cell_data, &type_option, &field_meta, vec![google_option]);
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
@ -580,12 +578,12 @@ mod tests {
|
||||
cell_data,
|
||||
&type_option,
|
||||
&field_meta,
|
||||
vec![google_option.clone(), facebook_option.clone()],
|
||||
vec![google_option.clone(), facebook_option],
|
||||
);
|
||||
|
||||
let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
|
||||
let cell_data = type_option.apply_changeset(data, None).unwrap();
|
||||
assert_multi_select_options(cell_data, &type_option, &field_meta, vec![google_option.clone()]);
|
||||
assert_multi_select_options(cell_data, &type_option, &field_meta, vec![google_option]);
|
||||
|
||||
// Invalid option id
|
||||
let cell_data = type_option
|
||||
@ -612,7 +610,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
expected,
|
||||
type_option
|
||||
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
|
||||
.decode_cell_data(cell_data, &field_meta.field_type, field_meta)
|
||||
.unwrap()
|
||||
.parse::<SelectOptionCellData>()
|
||||
.unwrap()
|
||||
@ -629,7 +627,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
expected,
|
||||
type_option
|
||||
.decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
|
||||
.decode_cell_data(cell_data, &field_meta.field_type, field_meta)
|
||||
.unwrap()
|
||||
.parse::<SelectOptionCellData>()
|
||||
.unwrap()
|
||||
|
@ -27,7 +27,7 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder {
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
|
||||
pub struct RichTextTypeOption {
|
||||
#[pb(index = 1)]
|
||||
pub format: String,
|
||||
data: String, //It's not used.
|
||||
}
|
||||
impl_type_option!(RichTextTypeOption, FieldType::RichText);
|
||||
|
||||
|
@ -0,0 +1,186 @@
|
||||
use crate::impl_type_option;
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData};
|
||||
use bytes::Bytes;
|
||||
use fancy_regex::Regex;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::{internal_error, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::entities::{
|
||||
CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct URLTypeOptionBuilder(URLTypeOption);
|
||||
impl_into_box_type_option_builder!(URLTypeOptionBuilder);
|
||||
impl_builder_from_json_str_and_from_bytes!(URLTypeOptionBuilder, URLTypeOption);
|
||||
|
||||
impl TypeOptionBuilder for URLTypeOptionBuilder {
|
||||
fn field_type(&self) -> FieldType {
|
||||
self.0.field_type()
|
||||
}
|
||||
|
||||
fn entry(&self) -> &dyn TypeOptionDataEntry {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
|
||||
pub struct URLTypeOption {
|
||||
#[pb(index = 1)]
|
||||
data: String, //It's not used.
|
||||
}
|
||||
impl_type_option!(URLTypeOption, FieldType::URL);
|
||||
|
||||
impl CellDataOperation<EncodedCellData<URLCellData>, String> for URLTypeOption {
|
||||
fn decode_cell_data<T>(
|
||||
&self,
|
||||
encoded_data: T,
|
||||
decoded_field_type: &FieldType,
|
||||
_field_meta: &FieldMeta,
|
||||
) -> FlowyResult<DecodedCellData>
|
||||
where
|
||||
T: Into<EncodedCellData<URLCellData>>,
|
||||
{
|
||||
if !decoded_field_type.is_url() {
|
||||
return Ok(DecodedCellData::default());
|
||||
}
|
||||
let cell_data = encoded_data.into().try_into_inner()?;
|
||||
DecodedCellData::try_from_bytes(cell_data)
|
||||
}
|
||||
|
||||
fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
|
||||
where
|
||||
C: Into<CellContentChangeset>,
|
||||
{
|
||||
let changeset = changeset.into();
|
||||
let mut cell_data = URLCellData {
|
||||
url: "".to_string(),
|
||||
content: changeset.to_string(),
|
||||
};
|
||||
|
||||
if let Ok(Some(m)) = URL_REGEX.find(&changeset) {
|
||||
// Only support https scheme by now
|
||||
match url::Url::parse(m.as_str()) {
|
||||
Ok(url) => {
|
||||
if url.scheme() == "https" {
|
||||
cell_data.url = url.into();
|
||||
} else {
|
||||
cell_data.url = format!("https://{}", m.as_str());
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
cell_data.url = format!("https://{}", m.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cell_data.to_json()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
|
||||
pub struct URLCellData {
|
||||
#[pb(index = 1)]
|
||||
pub url: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl URLCellData {
|
||||
pub fn new(s: &str) -> Self {
|
||||
Self {
|
||||
url: "".to_string(),
|
||||
content: s.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_json(&self) -> FlowyResult<String> {
|
||||
serde_json::to_string(self).map_err(internal_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for URLCellData {
|
||||
type Err = FlowyError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
serde_json::from_str::<URLCellData>(s).map_err(internal_error)
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref URL_REGEX: Regex = Regex::new(
|
||||
"[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::services::field::FieldBuilder;
|
||||
use crate::services::field::{URLCellData, URLTypeOption};
|
||||
use crate::services::row::{CellDataOperation, EncodedCellData};
|
||||
use flowy_grid_data_model::entities::{FieldMeta, FieldType};
|
||||
|
||||
#[test]
|
||||
fn url_type_option_test_no_url() {
|
||||
let type_option = URLTypeOption::default();
|
||||
let field_type = FieldType::URL;
|
||||
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||
assert_changeset(&type_option, "123", &field_type, &field_meta, "123", "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn url_type_option_test_contains_url() {
|
||||
let type_option = URLTypeOption::default();
|
||||
let field_type = FieldType::URL;
|
||||
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||
assert_changeset(
|
||||
&type_option,
|
||||
"AppFlowy website - https://www.appflowy.io",
|
||||
&field_type,
|
||||
&field_meta,
|
||||
"AppFlowy website - https://www.appflowy.io",
|
||||
"https://www.appflowy.io/",
|
||||
);
|
||||
|
||||
assert_changeset(
|
||||
&type_option,
|
||||
"AppFlowy website appflowy.io",
|
||||
&field_type,
|
||||
&field_meta,
|
||||
"AppFlowy website appflowy.io",
|
||||
"https://appflowy.io",
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_changeset(
|
||||
type_option: &URLTypeOption,
|
||||
cell_data: &str,
|
||||
field_type: &FieldType,
|
||||
field_meta: &FieldMeta,
|
||||
expected: &str,
|
||||
expected_url: &str,
|
||||
) {
|
||||
let encoded_data = type_option.apply_changeset(cell_data, None).unwrap();
|
||||
let decode_cell_data = decode_cell_data(encoded_data, type_option, field_meta, field_type);
|
||||
assert_eq!(expected.to_owned(), decode_cell_data.content);
|
||||
assert_eq!(expected_url.to_owned(), decode_cell_data.url);
|
||||
}
|
||||
|
||||
fn decode_cell_data<T: Into<EncodedCellData<URLCellData>>>(
|
||||
encoded_data: T,
|
||||
type_option: &URLTypeOption,
|
||||
field_meta: &FieldMeta,
|
||||
field_type: &FieldType,
|
||||
) -> URLCellData {
|
||||
type_option
|
||||
.decode_cell_data(encoded_data, field_type, field_meta)
|
||||
.unwrap()
|
||||
.parse::<URLCellData>()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
@ -134,6 +134,7 @@ pub fn apply_cell_data_changeset<T: Into<CellContentChangeset>>(
|
||||
FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||
FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||
FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||
FieldType::URL => URLTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||
}?;
|
||||
|
||||
Ok(TypeOptionCellData::new(s, field_meta.field_type.clone()).json())
|
||||
@ -166,7 +167,6 @@ pub fn decode_cell_data<T: Into<String>>(
|
||||
field_meta: &FieldMeta,
|
||||
) -> FlowyResult<DecodedCellData> {
|
||||
let encoded_data = encoded_data.into();
|
||||
tracing::info!("😁{:?}", field_meta.type_options);
|
||||
let get_cell_data = || {
|
||||
let data = match t_field_type {
|
||||
FieldType::RichText => field_meta
|
||||
@ -187,6 +187,9 @@ pub fn decode_cell_data<T: Into<String>>(
|
||||
FieldType::Checkbox => field_meta
|
||||
.get_type_option_entry::<CheckboxTypeOption>(t_field_type)?
|
||||
.decode_cell_data(encoded_data, s_field_type, field_meta),
|
||||
FieldType::URL => field_meta
|
||||
.get_type_option_entry::<URLTypeOption>(t_field_type)?
|
||||
.decode_cell_data(encoded_data, s_field_type, field_meta),
|
||||
};
|
||||
Some(data)
|
||||
};
|
||||
|
@ -262,6 +262,9 @@ async fn grid_row_add_cells_test() {
|
||||
FieldType::Checkbox => {
|
||||
builder.add_cell(&field.id, "false".to_string()).unwrap();
|
||||
}
|
||||
FieldType::URL => {
|
||||
builder.add_cell(&field.id, "1".to_string()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
let context = builder.build();
|
||||
@ -328,6 +331,7 @@ async fn grid_cell_update() {
|
||||
SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
|
||||
}
|
||||
FieldType::Checkbox => "1".to_string(),
|
||||
FieldType::URL => "1".to_string(),
|
||||
};
|
||||
|
||||
scripts.push(UpdateCell {
|
||||
@ -349,6 +353,7 @@ async fn grid_cell_update() {
|
||||
FieldType::SingleSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false),
|
||||
FieldType::MultiSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false),
|
||||
FieldType::Checkbox => ("2".to_string(), false),
|
||||
FieldType::URL => ("2".to_string(), false),
|
||||
};
|
||||
|
||||
scripts.push(UpdateCell {
|
||||
|
@ -354,6 +354,10 @@ fn make_template_1_grid() -> BuildGridContext {
|
||||
let checkbox = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build();
|
||||
|
||||
// URL
|
||||
let url = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
|
||||
GridBuilder::default()
|
||||
.add_field(text_field)
|
||||
.add_field(single_select_field)
|
||||
@ -361,6 +365,7 @@ fn make_template_1_grid() -> BuildGridContext {
|
||||
.add_field(number_field)
|
||||
.add_field(date_field)
|
||||
.add_field(checkbox_field)
|
||||
.add_field(url_field)
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
|
@ -875,6 +875,7 @@ pub enum FieldType {
|
||||
SingleSelect = 3,
|
||||
MultiSelect = 4,
|
||||
Checkbox = 5,
|
||||
URL = 6,
|
||||
}
|
||||
|
||||
impl std::default::Default for FieldType {
|
||||
@ -932,6 +933,10 @@ impl FieldType {
|
||||
self == &FieldType::MultiSelect
|
||||
}
|
||||
|
||||
pub fn is_url(&self) -> bool {
|
||||
self == &FieldType::URL
|
||||
}
|
||||
|
||||
pub fn is_select_option(&self) -> bool {
|
||||
self == &FieldType::MultiSelect || self == &FieldType::SingleSelect
|
||||
}
|
||||
|
@ -8189,6 +8189,7 @@ pub enum FieldType {
|
||||
SingleSelect = 3,
|
||||
MultiSelect = 4,
|
||||
Checkbox = 5,
|
||||
URL = 6,
|
||||
}
|
||||
|
||||
impl ::protobuf::ProtobufEnum for FieldType {
|
||||
@ -8204,6 +8205,7 @@ impl ::protobuf::ProtobufEnum for FieldType {
|
||||
3 => ::std::option::Option::Some(FieldType::SingleSelect),
|
||||
4 => ::std::option::Option::Some(FieldType::MultiSelect),
|
||||
5 => ::std::option::Option::Some(FieldType::Checkbox),
|
||||
6 => ::std::option::Option::Some(FieldType::URL),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
@ -8216,6 +8218,7 @@ impl ::protobuf::ProtobufEnum for FieldType {
|
||||
FieldType::SingleSelect,
|
||||
FieldType::MultiSelect,
|
||||
FieldType::Checkbox,
|
||||
FieldType::URL,
|
||||
];
|
||||
values
|
||||
}
|
||||
@ -8337,10 +8340,10 @@ static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
wId\x12\x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x126\n\x16cell_\
|
||||
content_changeset\x18\x04\x20\x01(\tH\0R\x14cellContentChangesetB\x1f\n\
|
||||
\x1done_of_cell_content_changeset**\n\x0cMoveItemType\x12\r\n\tMoveField\
|
||||
\x10\0\x12\x0b\n\x07MoveRow\x10\x01*d\n\tFieldType\x12\x0c\n\x08RichText\
|
||||
\x10\0\x12\x0b\n\x07MoveRow\x10\x01*m\n\tFieldType\x12\x0c\n\x08RichText\
|
||||
\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\
|
||||
\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08C\
|
||||
heckbox\x10\x05b\x06proto3\
|
||||
heckbox\x10\x05\x12\x07\n\x03URL\x10\x06b\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
@ -167,4 +167,5 @@ enum FieldType {
|
||||
SingleSelect = 3;
|
||||
MultiSelect = 4;
|
||||
Checkbox = 5;
|
||||
URL = 6;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user