-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: implement number component * feat: remove log
- Loading branch information
1 parent
679c4c8
commit be35afe
Showing
11 changed files
with
405 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import 'package:commander_ui/commander_ui.dart'; | ||
|
||
Future<void> main() async { | ||
final commander = Commander(level: Level.verbose); | ||
|
||
final value = await commander.number('What is your age ?', | ||
interval: 1, | ||
onDisplay: (value) => value?.toStringAsFixed(2), | ||
validate: (validator) => validator | ||
..lowerThan(18, message: 'You must be at least 18 years old') | ||
..greaterThan(99, message: 'You must be at most 99 years old')); | ||
|
||
print(value); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import 'dart:async'; | ||
import 'dart:io'; | ||
|
||
import 'package:commander_ui/src/application/terminals/terminal.dart'; | ||
import 'package:commander_ui/src/application/themes/default_number_theme.dart'; | ||
import 'package:commander_ui/src/application/utils/terminal_tools.dart'; | ||
import 'package:commander_ui/src/application/validators/chain_validator.dart'; | ||
import 'package:commander_ui/src/domains/models/chain_validator.dart'; | ||
import 'package:commander_ui/src/domains/models/component.dart'; | ||
import 'package:commander_ui/src/domains/themes/number_theme.dart'; | ||
import 'package:commander_ui/src/io.dart'; | ||
import 'package:mansion/mansion.dart'; | ||
|
||
/// A component that asks the user for input. | ||
final class Number<T extends num> | ||
with TerminalTools | ||
implements Component<Future<T>> { | ||
final _completer = Completer<T>(); | ||
|
||
final Terminal _terminal; | ||
final NumberTheme _theme; | ||
|
||
final String _message; | ||
double _value = 0.0; | ||
final double _interval; | ||
final Function(NumberChainValidator)? _validate; | ||
final String? Function(T?) _onDisplay; | ||
|
||
String? normalizeValue(double value) { | ||
return _onDisplay(switch (T) { | ||
int => value.toInt() as T, | ||
_ => value as T, | ||
}); | ||
} | ||
|
||
Number(this._terminal, | ||
{required String message, | ||
T? defaultValue, | ||
T? interval, | ||
Function(NumberChainValidator)? validate, | ||
String? Function(T?)? onDisplay, | ||
NumberTheme? theme}) | ||
: _message = message, | ||
_value = double.parse(defaultValue != null ? '$defaultValue' : '0.0'), | ||
_interval = double.parse(interval != null ? '$interval' : '1.0'), | ||
_validate = validate, | ||
_onDisplay = (onDisplay ?? (value) => value.toString()), | ||
_theme = theme ?? DefaultNumberTheme(); | ||
|
||
@override | ||
Future<T> handle() { | ||
createSpace(_terminal, 1); | ||
stdout.writeAnsiAll([CursorPosition.save, CursorVisibility.hide]); | ||
|
||
_render(); | ||
_waitResponse(); | ||
|
||
return _completer.future; | ||
} | ||
|
||
void _waitResponse() { | ||
final key = readKey(_terminal); | ||
|
||
if (key.controlChar == ControlCharacter.arrowUp || key.char == 'k') { | ||
_value += _interval; | ||
} else if (key.controlChar == ControlCharacter.arrowDown || | ||
key.char == 'j') { | ||
_value -= _interval; | ||
} else if ([ControlCharacter.ctrlJ, ControlCharacter.ctrlM] | ||
.contains(key.controlChar)) { | ||
if (_validate != null) { | ||
final validator = ValidatorChain(); | ||
_validate!(validator); | ||
|
||
final result = validator.execute(_value); | ||
if (result case String error) { | ||
_onError(error); | ||
|
||
return; | ||
} | ||
} | ||
|
||
return _onSuccess(); | ||
} | ||
|
||
_render(); | ||
_waitResponse(); | ||
} | ||
|
||
void _render() { | ||
final buffer = StringBuffer(); | ||
|
||
List<Sequence> askSequence = [ | ||
..._theme.askPrefixColor, | ||
Print('${_theme.askPrefix} '), | ||
SetStyles.reset, | ||
]; | ||
|
||
buffer.writeAnsiAll([ | ||
CursorPosition.restore, | ||
Clear.afterCursor, | ||
...askSequence, | ||
Print(_message), | ||
const CursorPosition.moveRight(1), | ||
..._theme.inputColor, | ||
Print('${normalizeValue(_value)}'), | ||
]); | ||
|
||
stdout.write(buffer.toString()); | ||
} | ||
|
||
void _onError(String error) { | ||
final buffer = StringBuffer(); | ||
|
||
List<Sequence> errorSequence = [ | ||
..._theme.errorPrefixColor, | ||
Print('${_theme.errorSuffix} '), | ||
SetStyles.reset, | ||
]; | ||
|
||
buffer.writeAnsiAll([ | ||
CursorPosition.restore, | ||
Clear.afterCursor, | ||
...errorSequence, | ||
Print(_message), | ||
const CursorPosition.moveRight(1), | ||
]); | ||
|
||
buffer.writeAnsiAll([ | ||
AsciiControl.lineFeed, | ||
..._theme.validatorColorMessage, | ||
Print(error), | ||
SetStyles.reset, | ||
]); | ||
|
||
stdout.write(buffer.toString()); | ||
|
||
stdout.writeAnsiAll([ | ||
const CursorPosition.moveUp(1), | ||
CursorPosition.moveToColumn(_message.length + 2), | ||
const CursorPosition.moveRight(2), | ||
..._theme.inputColor, | ||
Print('${normalizeValue(_value)}'), | ||
]); | ||
|
||
_waitResponse(); | ||
} | ||
|
||
void _onSuccess() { | ||
final buffer = StringBuffer(); | ||
|
||
List<Sequence> successSequence = [ | ||
..._theme.successPrefixColor, | ||
Print('${_theme.successSuffix} '), | ||
SetStyles.reset, | ||
]; | ||
|
||
buffer.writeAnsiAll([ | ||
CursorPosition.restore, | ||
Clear.untilEndOfLine, | ||
Clear.afterCursor, | ||
...successSequence, | ||
Print(_message), | ||
Print(' '), | ||
..._theme.inputColor, | ||
Print('${normalizeValue(_value)}'), | ||
SetStyles.reset, | ||
AsciiControl.lineFeed, | ||
CursorVisibility.show, | ||
]); | ||
|
||
stdout.write(buffer.toString()); | ||
|
||
return switch (T) { | ||
int => _completer.complete(_value.toInt() as T), | ||
_ => _completer.complete(_value as T), | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import 'package:commander_ui/src/domains/themes/ask_theme.dart'; | ||
import 'package:commander_ui/src/domains/themes/number_theme.dart'; | ||
import 'package:mansion/mansion.dart'; | ||
|
||
final class DefaultNumberTheme implements NumberTheme { | ||
@override | ||
String askPrefix = '?'; | ||
|
||
@override | ||
String errorSuffix = '✘'; | ||
|
||
@override | ||
String successSuffix = '✔'; | ||
|
||
@override | ||
String Function(String? value) defaultValueFormatter = | ||
(String? value) => switch (value) { | ||
String value => ' ($value)', | ||
_ => '', | ||
}; | ||
|
||
@override | ||
String? Function(String? value) inputFormatter = (String? value) => value; | ||
|
||
@override | ||
List<Sequence> successPrefixColor = [ | ||
SetStyles.reset, | ||
SetStyles(Style.foreground(Color.green)) | ||
]; | ||
|
||
@override | ||
List<Sequence> errorPrefixColor = [ | ||
SetStyles(Style.foreground(Color.brightRed)) | ||
]; | ||
|
||
@override | ||
List<Sequence> askPrefixColor = [SetStyles(Style.foreground(Color.yellow))]; | ||
|
||
@override | ||
List<Sequence> validatorColorMessage = [ | ||
SetStyles(Style.foreground(Color.brightRed)) | ||
]; | ||
|
||
@override | ||
List<Sequence> defaultValueColorMessage = [ | ||
SetStyles(Style.foreground(Color.brightBlack)) | ||
]; | ||
|
||
@override | ||
List<Sequence> inputColor = [SetStyles(Style.foreground(Color.brightBlack))]; | ||
|
||
DefaultNumberTheme(); | ||
|
||
/// Creates a new [AskTheme] with the provided values based on [DefaultAskTheme]. | ||
factory DefaultNumberTheme.copyWith( | ||
{String? askPrefix, | ||
String? errorSuffix, | ||
String? successSuffix, | ||
String Function(String? value)? defaultValueFormatter, | ||
String? Function(String? value)? inputFormatter, | ||
List<Sequence>? successPrefixColor, | ||
List<Sequence>? errorPrefixColor, | ||
List<Sequence>? validatorColorMessage, | ||
List<Sequence>? askPrefixColor, | ||
List<Sequence>? defaultValueColorMessage, | ||
List<Sequence>? inputColor}) { | ||
final theme = DefaultNumberTheme(); | ||
|
||
theme.askPrefix = askPrefix ?? theme.askPrefix; | ||
theme.errorSuffix = errorSuffix ?? theme.errorSuffix; | ||
theme.successSuffix = successSuffix ?? theme.successSuffix; | ||
theme.defaultValueFormatter = | ||
defaultValueFormatter ?? theme.defaultValueFormatter; | ||
theme.inputFormatter = inputFormatter ?? theme.inputFormatter; | ||
theme.successPrefixColor = successPrefixColor ?? theme.successPrefixColor; | ||
theme.errorPrefixColor = errorPrefixColor ?? theme.errorPrefixColor; | ||
theme.validatorColorMessage = | ||
validatorColorMessage ?? theme.validatorColorMessage; | ||
theme.askPrefixColor = askPrefixColor ?? theme.askPrefixColor; | ||
theme.defaultValueColorMessage = | ||
defaultValueColorMessage ?? theme.defaultValueColorMessage; | ||
theme.inputColor = inputColor ?? theme.inputColor; | ||
|
||
return theme; | ||
} | ||
} |
Oops, something went wrong.