mirror of
https://github.com/chenasraf/mudblock.git
synced 2026-05-17 17:48:05 +00:00
refactor: alias/trigger refactors
- combined automation page - built in triggers - help - motd - triggers/aliases when disconnected
This commit is contained in:
@@ -10,6 +10,9 @@ enum MUDActionTarget {
|
||||
execute,
|
||||
script,
|
||||
input,
|
||||
output,
|
||||
immediate,
|
||||
none,
|
||||
}
|
||||
|
||||
class MUDAction {
|
||||
@@ -67,6 +70,17 @@ class MUDAction {
|
||||
store.input.text = content;
|
||||
store.setInput(content);
|
||||
break;
|
||||
case MUDActionTarget.output:
|
||||
debugPrint('ActionSendTo.output: $content');
|
||||
store.echoOwn(content);
|
||||
break;
|
||||
case MUDActionTarget.immediate:
|
||||
debugPrint('ActionSendTo.immediate: $content');
|
||||
store.send(content);
|
||||
break;
|
||||
case MUDActionTarget.none:
|
||||
debugPrint('ActionSendTo.none: $content');
|
||||
break;
|
||||
}
|
||||
debugPrint('MUDAction.invoke: done');
|
||||
}
|
||||
@@ -89,6 +103,9 @@ class MUDAction {
|
||||
|
||||
static String doVariableReplacements(GameStore store, String content) {
|
||||
debugPrint('MUDAction._doSpecialReplacements: $content');
|
||||
if (!store.connected) {
|
||||
return content;
|
||||
}
|
||||
content = content
|
||||
.replaceAll('%PASSWORD', store.currentProfile.password)
|
||||
.replaceAll('%USERNAME', store.currentProfile.username);
|
||||
@@ -106,6 +123,13 @@ class NativeMUDAction extends MUDAction {
|
||||
NativeMUDAction(this.customInvoke)
|
||||
: super('-- native code --', target: MUDActionTarget.script);
|
||||
|
||||
factory NativeMUDAction.echoSystem(String content) => NativeMUDAction((store, matches) {
|
||||
store.echoSystem(content);
|
||||
});
|
||||
factory NativeMUDAction.echo(String content) => NativeMUDAction((store, matches) {
|
||||
store.echo(content);
|
||||
});
|
||||
|
||||
final void Function(GameStore store, List<String> matches)
|
||||
customInvoke;
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ class AliasProcessResult {
|
||||
bool lineRemoved;
|
||||
bool processed;
|
||||
|
||||
AliasProcessResult({ this.lineRemoved = false, this.processed = false});
|
||||
AliasProcessResult({this.lineRemoved = false, this.processed = false});
|
||||
}
|
||||
|
||||
String _key(String str) => 'builtin-alias-$str';
|
||||
@@ -129,11 +129,12 @@ final builtInAliases = <Alias>[
|
||||
Alias(
|
||||
id: _key('help'),
|
||||
pattern: 'mudhelp',
|
||||
action: NativeMUDAction(
|
||||
(store, matches) {
|
||||
store.echo(BuiltinCommand.help());
|
||||
},
|
||||
),
|
||||
action: NativeMUDAction.echoSystem(BuiltinCommand.help()),
|
||||
),
|
||||
Alias(
|
||||
id: _key('motd'),
|
||||
pattern: 'mudmotd',
|
||||
action: NativeMUDAction.echoSystem(BuiltinCommand.motd()),
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
@@ -1,20 +1,61 @@
|
||||
// ignore_for_file: prefer_interpolation_to_compose_strings
|
||||
|
||||
import 'package:mudblock/core/string_utils.dart';
|
||||
|
||||
class BuiltinCommand {
|
||||
static const sep =
|
||||
'--------------------------------------------------------------------------------';
|
||||
static help() {
|
||||
const sep =
|
||||
'--------------------------------------------------------------------------------';
|
||||
final year = DateTime.now().year;
|
||||
final years = [2023];
|
||||
if (!years.contains(year)) {
|
||||
years.add(year);
|
||||
}
|
||||
return '''
|
||||
$sep
|
||||
Mudblock - Help
|
||||
$sep
|
||||
{_mudblockhelp}
|
||||
$sep
|
||||
Mudblock - Help
|
||||
$sep
|
||||
|
||||
Mudblock is a cross-platform MUD client.
|
||||
See more information at https://github.com/chenasraf/mudblock
|
||||
Mudblock is a cross-platform MUD client.
|
||||
See more information at https://github.com/chenasraf/mudblock
|
||||
|
||||
$sep
|
||||
Developed by Chen Asraf
|
||||
Copyright © $year - All Rights Reserved
|
||||
$sep
|
||||
''';
|
||||
$sep
|
||||
Developed by Chen Asraf
|
||||
Copyright © ${years.join('-')} - All Rights Reserved
|
||||
$sep
|
||||
|
||||
To get started, tap the hamburger menu at the top right corner and
|
||||
select a profile.
|
||||
|
||||
You can create, delete or update the profiles as you wish.
|
||||
|
||||
Each profile hosts its own settings, aliases, triggers, variables, buttons, and
|
||||
other similar features.
|
||||
|
||||
If you've never played an MUD, try one of the sample MUDs to get started!
|
||||
$sep
|
||||
{/_mudblockhelp}
|
||||
'''
|
||||
.trimMultiline() +
|
||||
'\n';
|
||||
}
|
||||
|
||||
static motd() {
|
||||
return '''
|
||||
{_mudblockmotd}
|
||||
$sep
|
||||
Welcome to MudBlock!
|
||||
$sep
|
||||
To get started, tap the hamburger menu at the top right corner and
|
||||
select a profile.
|
||||
|
||||
For more help, type "mudhelp"
|
||||
$sep
|
||||
{/_mudblockmotd}
|
||||
'''
|
||||
.trimMultiline() +
|
||||
'\n';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ class Trigger extends Automation {
|
||||
if (trigger.matches(str)) {
|
||||
trigger.invokeEffect(store, str);
|
||||
if (trigger.isRemovedFromBuffer) {
|
||||
debugPrint('TriggerProcessResult: lineRemoved = true');
|
||||
showLine = false;
|
||||
}
|
||||
if (trigger.autoDisable) {
|
||||
@@ -107,3 +108,32 @@ class TriggerProcessResult {
|
||||
TriggerProcessResult({this.lineRemoved = false});
|
||||
}
|
||||
|
||||
String _key(String str) => 'builtin-trigger-$str';
|
||||
|
||||
final builtInTriggers = <Trigger>[
|
||||
Trigger(
|
||||
id: _key('mudhelpflag-start'),
|
||||
pattern: '{_mudblockhelp}',
|
||||
action: MUDAction.empty(),
|
||||
isRemovedFromBuffer: true,
|
||||
),
|
||||
Trigger(
|
||||
id: _key('mudhelpflag-end'),
|
||||
pattern: '{/_mudblockhelp}',
|
||||
action: MUDAction.empty(),
|
||||
isRemovedFromBuffer: true,
|
||||
),
|
||||
Trigger(
|
||||
id: _key('mudmotdflag-start'),
|
||||
pattern: '{_mudblockmotd}',
|
||||
action: MUDAction.empty(),
|
||||
isRemovedFromBuffer: true,
|
||||
),
|
||||
Trigger(
|
||||
id: _key('mudmotdflag-end'),
|
||||
pattern: '{/_mudblockmotd}',
|
||||
action: MUDAction.empty(),
|
||||
isRemovedFromBuffer: true,
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
@@ -71,7 +71,14 @@ final routes = <String, Widget Function(BuildContext)>{
|
||||
),
|
||||
Paths.alias: (context) {
|
||||
final alias = ModalRoute.of(context)!.settings.arguments as Alias?;
|
||||
return AliasPage(alias: alias);
|
||||
return GameStore.consumer(
|
||||
builder: (context, store, child) => AliasPage(
|
||||
alias: alias,
|
||||
onSave: (alias) async {
|
||||
store.currentProfile.saveAlias(alias);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
// triggers
|
||||
@@ -88,7 +95,14 @@ final routes = <String, Widget Function(BuildContext)>{
|
||||
),
|
||||
Paths.trigger: (context) {
|
||||
final trigger = ModalRoute.of(context)!.settings.arguments as Trigger?;
|
||||
return TriggerPage(trigger: trigger);
|
||||
return GameStore.consumer(
|
||||
builder: (context, store, child) => TriggerPage(
|
||||
trigger: trigger,
|
||||
onSave: (trigger) async {
|
||||
store.currentProfile.saveTrigger(trigger);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
// variables
|
||||
|
||||
@@ -16,6 +16,7 @@ import '../core/string_utils.dart';
|
||||
import 'consts.dart';
|
||||
import 'features/action.dart';
|
||||
import 'features/alias.dart';
|
||||
import 'features/builtin_command.dart';
|
||||
import 'features/profile.dart';
|
||||
import 'features/trigger.dart';
|
||||
import 'routes.dart';
|
||||
@@ -45,11 +46,14 @@ class GameStore extends ChangeNotifier {
|
||||
|
||||
/// accepts csp but NOT double csp
|
||||
RegExp get outgoingMsgSplitPattern =>
|
||||
RegExp("(?<!$commandSeparator)$commandSeparator(?!$commandSeparator)");
|
||||
_outgoingMsgSplitPattern(commandSeparator);
|
||||
RegExp _outgoingMsgSplitPattern(String csp) =>
|
||||
RegExp("(?<!$csp)$csp(?!$csp)");
|
||||
|
||||
MUDProfile get currentProfile => _currentProfile!;
|
||||
List<Alias> get aliases => builtInAliases + (_currentProfile?.aliases ?? []);
|
||||
List<Trigger> get triggers => currentProfile.triggers;
|
||||
List<Trigger> get triggers =>
|
||||
builtInTriggers + (_currentProfile?.triggers ?? []);
|
||||
|
||||
get connected => _clientReady && _client.connected;
|
||||
|
||||
@@ -62,13 +66,7 @@ class GameStore extends ChangeNotifier {
|
||||
}
|
||||
|
||||
void appStart(BuildContext context) async {
|
||||
echoSystem('''
|
||||
Welcome to MudBlock!
|
||||
To get started, tap the hamburger menu at the top right corner and
|
||||
select a profile.
|
||||
'''
|
||||
.trimMultiline());
|
||||
// For more help, type "mudhelp"
|
||||
echoSystem(BuiltinCommand.motd());
|
||||
}
|
||||
|
||||
void selectProfileAndConnect(BuildContext context) async {
|
||||
@@ -162,9 +160,12 @@ class GameStore extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
void processLines(String text) {
|
||||
void processLines(String text, {int? color}) {
|
||||
final lines = text.split(incomingMsgSplitPattern);
|
||||
for (final (i, line) in lines.indexed) {
|
||||
for (var (i, line) in lines.indexed) {
|
||||
if (color != null && !line.startsWith('$esc[')) {
|
||||
line = '$esc[${color}m$line';
|
||||
}
|
||||
onLine(line, newLine: i != lines.length - 1);
|
||||
}
|
||||
}
|
||||
@@ -269,12 +270,12 @@ class GameStore extends ChangeNotifier {
|
||||
|
||||
/// echoOwn - same as echo, but with predefined color
|
||||
void echoOwn(String line) {
|
||||
echo('$esc[93m$line');
|
||||
processLines(line, color: 93);
|
||||
}
|
||||
|
||||
/// echoSystem - same as echo, but with predefined color
|
||||
void echoSystem(String line) {
|
||||
echo('$esc[96m$line');
|
||||
processLines(line, color: 96);
|
||||
}
|
||||
|
||||
/// echoError - same as echo, but with predefined color
|
||||
@@ -284,18 +285,27 @@ class GameStore extends ChangeNotifier {
|
||||
|
||||
/// sendBytes - raw send bytes - DOES NOT split by outgoingMsgSplitPattern, no processing
|
||||
void sendBytes(List<int> bytes) {
|
||||
if (!_clientReady || !_client.connected) {
|
||||
return;
|
||||
}
|
||||
debugPrint('Sending bytes: $bytes');
|
||||
_client.sendBytes(bytes);
|
||||
}
|
||||
|
||||
/// sendString - send string - DOES NOT split by outgoingMsgSplitPattern, no processing
|
||||
void sendString(String line) {
|
||||
if (!_clientReady || !_client.connected) {
|
||||
return;
|
||||
}
|
||||
debugPrint('Sending string: $line');
|
||||
_client.send(line + newline);
|
||||
}
|
||||
|
||||
/// send - raw send string - no processing, DOES split by outgoingMsgSplitPattern
|
||||
void send(String text) {
|
||||
if (!_clientReady || !_client.connected) {
|
||||
return;
|
||||
}
|
||||
for (var line in _splitCsp(text)) {
|
||||
if (isCompressed) {
|
||||
debugPrint('Sending compressed bytes: $line');
|
||||
@@ -313,7 +323,8 @@ class GameStore extends ChangeNotifier {
|
||||
line = MUDAction.doVariableReplacements(this, line);
|
||||
debugPrint('processing aliases for: $line');
|
||||
var result = Alias.processLine(this, aliases, line);
|
||||
if (!result.lineRemoved && currentProfile.settings.echoCommands) {
|
||||
if (!result.lineRemoved &&
|
||||
(_currentProfile == null || currentProfile.settings.echoCommands)) {
|
||||
echoOwn(line);
|
||||
}
|
||||
if (!result.processed) {
|
||||
@@ -323,22 +334,23 @@ class GameStore extends ChangeNotifier {
|
||||
}
|
||||
|
||||
List<String> _splitCsp(String line) {
|
||||
final pattern = _currentProfile != null
|
||||
? outgoingMsgSplitPattern
|
||||
: _outgoingMsgSplitPattern(';');
|
||||
final csp = _currentProfile != null
|
||||
? currentProfile.settings.commandSeparator
|
||||
: ';';
|
||||
return line
|
||||
.split(outgoingMsgSplitPattern)
|
||||
.map(
|
||||
(l) => l.replaceAll(
|
||||
'$commandSeparator$commandSeparator',
|
||||
commandSeparator,
|
||||
),
|
||||
)
|
||||
.split(pattern)
|
||||
.map((l) => l.replaceAll('$csp$csp', csp))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// submitInput - echo input, process aliases and triggers, then send, scroll to end, select input
|
||||
void submitAsInput(String text) {
|
||||
if (!_clientReady || !_client.connected) {
|
||||
return;
|
||||
}
|
||||
// if (!_clientReady || !_client.connected) {
|
||||
// return;
|
||||
// }
|
||||
execute(text);
|
||||
scrollToEnd();
|
||||
selectInput();
|
||||
|
||||
@@ -1,202 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/features/action.dart';
|
||||
import '../core/features/alias.dart';
|
||||
import '../core/platform_utils.dart';
|
||||
import 'automation_page.dart';
|
||||
|
||||
class AliasPage extends StatefulWidget {
|
||||
const AliasPage({super.key, required this.alias});
|
||||
class AliasPage extends StatelessWidget {
|
||||
const AliasPage({
|
||||
super.key,
|
||||
this.alias,
|
||||
required this.onSave,
|
||||
});
|
||||
|
||||
final Alias? alias;
|
||||
|
||||
@override
|
||||
State<AliasPage> createState() => _AliasPageState();
|
||||
}
|
||||
|
||||
class _AliasPageState extends State<AliasPage> {
|
||||
late final Alias alias;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
alias = widget.alias?.copyWith() ?? Alias.empty();
|
||||
super.initState();
|
||||
}
|
||||
final void Function(Alias) onSave;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Alias'),
|
||||
actions: [
|
||||
Switch.adaptive(
|
||||
value: alias.enabled,
|
||||
onChanged: (value) async {
|
||||
alias.enabled = value;
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SizedBox(
|
||||
width: 1200,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final patternTextField = TextField(
|
||||
controller: TextEditingController(text: alias.pattern),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Pattern',
|
||||
helperText: 'The pattern to match your input against',
|
||||
),
|
||||
onChanged: (value) {
|
||||
alias.pattern = value;
|
||||
},
|
||||
);
|
||||
final regexCheckbox = CheckboxListTile(
|
||||
title: const Text('Regular Expression'),
|
||||
// subtitle: const Text(
|
||||
// 'Whether the pattern is a regular expression',
|
||||
// ),
|
||||
value: alias.isRegex,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
alias.isRegex = value ?? false;
|
||||
});
|
||||
},
|
||||
);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (PlatformUtils.isDesktop)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: patternTextField,
|
||||
),
|
||||
SizedBox(
|
||||
width: 270,
|
||||
child: regexCheckbox,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!PlatformUtils.isDesktop) ...[
|
||||
patternTextField,
|
||||
regexCheckbox,
|
||||
],
|
||||
TextField(
|
||||
controller: TextEditingController(text: alias.label),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Label',
|
||||
helperText: 'Can be used to identify the alias in scripts',
|
||||
),
|
||||
onChanged: (value) {
|
||||
alias.label = value;
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
controller:
|
||||
TextEditingController(text: alias.action.content),
|
||||
minLines: PlatformUtils.isDesktop ? 20 : 5,
|
||||
maxLines: PlatformUtils.isDesktop ? 50 : 20,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Action',
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
onChanged: (value) {
|
||||
alias.action.content = value;
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
|
||||
child: Text(
|
||||
'Send To',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
DropdownMenu(
|
||||
initialSelection: alias.action.target,
|
||||
onSelected: (value) {
|
||||
if (value is MUDActionTarget) {
|
||||
alias.action.target = value;
|
||||
}
|
||||
},
|
||||
dropdownMenuEntries: const [
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.world,
|
||||
label: 'World',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.execute,
|
||||
label: 'Execute',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.script,
|
||||
label: 'Script',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.input,
|
||||
label: 'Command Input',
|
||||
),
|
||||
],
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Case Sensitive'),
|
||||
subtitle: const Text(
|
||||
'If checked, the pattern will only match if the case matches.',
|
||||
),
|
||||
value: alias.isCaseSensitive,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
alias.isCaseSensitive = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Remove From Output'),
|
||||
subtitle: const Text(
|
||||
'If checked, your input line will be removed from the buffer when it is matched.',
|
||||
),
|
||||
value: alias.isRemovedFromBuffer,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
alias.isRemovedFromBuffer = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Auto Disable'),
|
||||
subtitle: const Text(
|
||||
'If checked, the alias will be disabled after it is matched once.',
|
||||
),
|
||||
value: alias.autoDisable,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
alias.autoDisable = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, alias);
|
||||
},
|
||||
child: const Icon(Icons.save),
|
||||
),
|
||||
return AutomationPage<Alias>(
|
||||
automation: alias,
|
||||
title: 'Alias',
|
||||
onSave: onSave,
|
||||
create: (alias) => alias?.copyWith() ?? Alias.empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
228
lib/pages/automation_page.dart
Normal file
228
lib/pages/automation_page.dart
Normal file
@@ -0,0 +1,228 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/features/action.dart';
|
||||
import '../core/features/alias.dart';
|
||||
import '../core/features/automation.dart';
|
||||
import '../core/platform_utils.dart';
|
||||
|
||||
class AutomationPage<T extends Automation> extends StatefulWidget {
|
||||
const AutomationPage({
|
||||
super.key,
|
||||
required this.automation,
|
||||
required this.create,
|
||||
required this.title,
|
||||
required this.onSave,
|
||||
});
|
||||
|
||||
final T? automation;
|
||||
final T Function(T? automation) create;
|
||||
final void Function(T automation) onSave;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<AutomationPage> createState() => _AutomationPageState<T>();
|
||||
}
|
||||
|
||||
class _AutomationPageState<T extends Automation>
|
||||
extends State<AutomationPage<T>> {
|
||||
late final T automation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
automation = widget.create(widget.automation);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
actions: [
|
||||
Switch.adaptive(
|
||||
value: automation.enabled,
|
||||
onChanged: (value) async {
|
||||
automation.enabled = value;
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SizedBox(
|
||||
width: 1200,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
var automationMatcheeStr =
|
||||
T == Alias ? 'your input' : 'the output';
|
||||
var automationTypeStr = T == Alias ? 'alias' : 'trigger';
|
||||
final patternTextField = TextField(
|
||||
controller: TextEditingController(text: automation.pattern),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Pattern',
|
||||
helperText:
|
||||
'The pattern to match $automationMatcheeStr against',
|
||||
),
|
||||
onChanged: (value) {
|
||||
automation.pattern = value;
|
||||
},
|
||||
);
|
||||
final regexCheckbox = CheckboxListTile(
|
||||
title: const Text('Regular Expression'),
|
||||
// subtitle: const Text(
|
||||
// 'Whether the pattern is a regular expression',
|
||||
// ),
|
||||
value: automation.isRegex,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
automation.isRegex = value ?? false;
|
||||
});
|
||||
},
|
||||
);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (PlatformUtils.isDesktop)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: patternTextField,
|
||||
),
|
||||
SizedBox(
|
||||
width: 270,
|
||||
child: regexCheckbox,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!PlatformUtils.isDesktop) ...[
|
||||
patternTextField,
|
||||
regexCheckbox,
|
||||
],
|
||||
TextField(
|
||||
controller:
|
||||
TextEditingController(text: automation.label),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Label',
|
||||
helperText:
|
||||
'Can be used to identify the $automationTypeStr in scripts',
|
||||
),
|
||||
onChanged: (value) {
|
||||
automation.label = value;
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
controller: TextEditingController(
|
||||
text: automation.action.content,
|
||||
),
|
||||
minLines: PlatformUtils.isDesktop ? 20 : 5,
|
||||
maxLines: PlatformUtils.isDesktop ? 50 : 20,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Action',
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
onChanged: (value) {
|
||||
automation.action.content = value;
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
|
||||
child: Text(
|
||||
'Send To',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
DropdownMenu(
|
||||
initialSelection: automation.action.target,
|
||||
onSelected: (value) {
|
||||
if (value is MUDActionTarget) {
|
||||
automation.action.target = value;
|
||||
}
|
||||
},
|
||||
dropdownMenuEntries: const [
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.world,
|
||||
label: 'World',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.execute,
|
||||
label: 'Execute',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.script,
|
||||
label: 'Script',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.input,
|
||||
label: 'Command Input',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.immediate,
|
||||
label: 'Immediate',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.output,
|
||||
label: 'Output',
|
||||
),
|
||||
],
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Case Sensitive'),
|
||||
subtitle: const Text(
|
||||
'If checked, the pattern will only match if the case matches.',
|
||||
),
|
||||
value: automation.isCaseSensitive,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
automation.isCaseSensitive = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Remove From Output'),
|
||||
subtitle: Text(
|
||||
'If checked, $automationMatcheeStr line will be removed from the buffer when it is matched.',
|
||||
),
|
||||
value: automation.isRemovedFromBuffer,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
automation.isRemovedFromBuffer = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Auto Disable'),
|
||||
subtitle: Text(
|
||||
'If checked, the $automationTypeStr will be disabled after it is matched once.',
|
||||
),
|
||||
value: automation.autoDisable,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
automation.autoDisable = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, automation);
|
||||
},
|
||||
child: const Icon(Icons.save),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,16 +34,16 @@ class HomePageState extends State<HomePage>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
final consoleStyle = const TextStyle(
|
||||
color: Colors.white,
|
||||
fontFamily: 'Fira Code',
|
||||
fontSize: 16,
|
||||
height: 1,
|
||||
);
|
||||
TextStyle get inputStyle => consoleStyle.copyWith(color: Colors.grey);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const consoleStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
fontFamily: 'Fira Code',
|
||||
fontSize: 16,
|
||||
height: 1,
|
||||
);
|
||||
final inputStyle = consoleStyle.copyWith(color: Colors.grey);
|
||||
|
||||
return Shortcuts(
|
||||
shortcuts: numpadKeysIntentMap,
|
||||
child: Actions(
|
||||
@@ -78,42 +78,7 @@ class HomePageState extends State<HomePage>
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Focus(
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
for (final line in lines) ...[
|
||||
for (final segment
|
||||
in ColorUtils.split(line))
|
||||
TextSpan(
|
||||
text: segment.text,
|
||||
style: consoleStyle.copyWith(
|
||||
color: Color(
|
||||
segment.themedFgColor),
|
||||
backgroundColor: Color(
|
||||
segment.themedBgColor),
|
||||
fontWeight: segment.bold
|
||||
? FontWeight.w900
|
||||
: FontWeight.w400,
|
||||
fontStyle: segment.italic
|
||||
? FontStyle.italic
|
||||
: null,
|
||||
decoration: segment.underline
|
||||
? TextDecoration.underline
|
||||
: null,
|
||||
),
|
||||
),
|
||||
// const TextSpan(
|
||||
// text: newline,
|
||||
// style:
|
||||
// consoleStyle, // .copyWith(fontSize: 1),
|
||||
// ),
|
||||
],
|
||||
],
|
||||
),
|
||||
enableInteractiveSelection: true,
|
||||
selectionWidthStyle: BoxWidthStyle.tight,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
),
|
||||
child: _buildGameOutput(lines),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -156,6 +121,37 @@ class HomePageState extends State<HomePage>
|
||||
);
|
||||
}
|
||||
|
||||
SelectableText _buildGameOutput(List<String> lines) {
|
||||
return SelectableText.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
for (final line in lines) ...[
|
||||
for (final segment in ColorUtils.split(line))
|
||||
TextSpan(
|
||||
text: segment.text,
|
||||
style: consoleStyle.copyWith(
|
||||
color: Color(segment.themedFgColor),
|
||||
backgroundColor: Color(segment.themedBgColor),
|
||||
fontWeight: segment.bold ? FontWeight.w900 : FontWeight.w400,
|
||||
fontStyle: segment.italic ? FontStyle.italic : null,
|
||||
decoration:
|
||||
segment.underline ? TextDecoration.underline : null,
|
||||
),
|
||||
),
|
||||
// const TextSpan(
|
||||
// text: newline,
|
||||
// style:
|
||||
// consoleStyle, // .copyWith(fontSize: 1),
|
||||
// ),
|
||||
],
|
||||
],
|
||||
),
|
||||
enableInteractiveSelection: true,
|
||||
selectionWidthStyle: BoxWidthStyle.tight,
|
||||
selectionHeightStyle: BoxHeightStyle.max,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
onWindowBlur() {
|
||||
debugPrint("Window blurred");
|
||||
|
||||
@@ -106,6 +106,11 @@ class HomeScaffold extends StatelessWidget with GameStoreMixin {
|
||||
onTap: () => Navigator.pushNamed(context, Paths.shortcuts),
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: const Text('Profiles'),
|
||||
leading: const Icon(Icons.cable),
|
||||
onTap: () => Navigator.pushNamed(context, Paths.profiles),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Settings'),
|
||||
leading: const Icon(Icons.settings),
|
||||
|
||||
@@ -1,201 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../core/features/action.dart';
|
||||
import '../core/features/trigger.dart';
|
||||
import '../core/platform_utils.dart';
|
||||
import 'automation_page.dart';
|
||||
|
||||
class TriggerPage extends StatefulWidget {
|
||||
const TriggerPage({super.key, required this.trigger});
|
||||
class TriggerPage extends StatelessWidget {
|
||||
const TriggerPage({
|
||||
super.key,
|
||||
this.trigger,
|
||||
required this.onSave,
|
||||
});
|
||||
|
||||
final Trigger? trigger;
|
||||
|
||||
@override
|
||||
State<TriggerPage> createState() => _TriggerPageState();
|
||||
}
|
||||
|
||||
class _TriggerPageState extends State<TriggerPage> {
|
||||
late final Trigger trigger;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
trigger = widget.trigger?.copyWith() ?? Trigger.empty();
|
||||
super.initState();
|
||||
}
|
||||
final void Function(Trigger) onSave;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Trigger'),
|
||||
actions: [
|
||||
Switch.adaptive(
|
||||
value: trigger.enabled,
|
||||
onChanged: (value) {
|
||||
trigger.enabled = value;
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SizedBox(
|
||||
width: 1200,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final patternTextField = TextField(
|
||||
controller: TextEditingController(text: trigger.pattern),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Pattern',
|
||||
helperText: 'The pattern to match your input against',
|
||||
),
|
||||
onChanged: (value) {
|
||||
trigger.pattern = value;
|
||||
},
|
||||
);
|
||||
final regexCheckbox = CheckboxListTile(
|
||||
title: const Text('Regular Expression'),
|
||||
// subtitle: const Text(
|
||||
// 'Whether the pattern is a regular expression',
|
||||
// ),
|
||||
value: trigger.isRegex,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
trigger.isRegex = value ?? false;
|
||||
});
|
||||
},
|
||||
);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (PlatformUtils.isDesktop)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: patternTextField,
|
||||
),
|
||||
SizedBox(
|
||||
width: 270,
|
||||
child: regexCheckbox,
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!PlatformUtils.isDesktop) ...[
|
||||
patternTextField,
|
||||
regexCheckbox,
|
||||
],
|
||||
TextField(
|
||||
controller: TextEditingController(text: trigger.label),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Label',
|
||||
helperText: 'Can be used to identify the trigger in scripts',
|
||||
),
|
||||
onChanged: (value) {
|
||||
trigger.label = value;
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
controller:
|
||||
TextEditingController(text: trigger.action.content),
|
||||
minLines: PlatformUtils.isDesktop ? 20 : 5,
|
||||
maxLines: PlatformUtils.isDesktop ? 50 : 20,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Action',
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
),
|
||||
onChanged: (value) {
|
||||
trigger.action.content = value;
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0, bottom: 8.0),
|
||||
child: Text(
|
||||
'Send To',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
DropdownMenu(
|
||||
initialSelection: trigger.action.target,
|
||||
onSelected: (value) {
|
||||
if (value is MUDActionTarget) {
|
||||
trigger.action.target = value;
|
||||
}
|
||||
},
|
||||
dropdownMenuEntries: const [
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.world,
|
||||
label: 'World',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.execute,
|
||||
label: 'Execute',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.script,
|
||||
label: 'Script',
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: MUDActionTarget.input,
|
||||
label: 'Command Input',
|
||||
),
|
||||
],
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Case Sensitive'),
|
||||
subtitle: const Text(
|
||||
'If checked, the pattern will only match if the case matches.',
|
||||
),
|
||||
value: trigger.isCaseSensitive,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
trigger.isCaseSensitive = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Remove From Output'),
|
||||
subtitle: const Text(
|
||||
'If checked, the output line will be removed from the buffer when it is matched.',
|
||||
),
|
||||
value: trigger.isRemovedFromBuffer,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
trigger.isRemovedFromBuffer = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: const Text('Auto Disable'),
|
||||
subtitle: const Text(
|
||||
'If checked, the trigger will be disabled after it is matched once.',
|
||||
),
|
||||
value: trigger.autoDisable,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
trigger.autoDisable = value ?? false;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, trigger);
|
||||
},
|
||||
child: const Icon(Icons.save),
|
||||
),
|
||||
return AutomationPage<Trigger>(
|
||||
automation: trigger,
|
||||
title: 'Trigger',
|
||||
onSave: onSave,
|
||||
create: (trigger) => trigger?.copyWith() ?? Trigger.empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user