Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/data/yaml_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,9 @@ Action? _parseAction(YamlMap m) {
if (m.containsKey('sleep')) {
return SleepAction(milliseconds: m['sleep'] as int? ?? 0);
}
if (m.containsKey('one')) {
return OneAction(cases: _parseActions(m['one']));
}
if (m.containsKey('function')) {
return FunctionAction(expression: m['function'].toString());
}
Expand Down
3 changes: 3 additions & 0 deletions lib/data/yaml_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,9 @@ Map<String, dynamic> actionToMap(Action action) => switch (action) {
},
SleepAction(:final milliseconds) => {'sleep': milliseconds},
FunctionAction(:final expression) => {'function': expression},
OneAction(:final cases) => {
'one': cases.map(triggerActionToMap).toList(),
},
RawAction(:final raw) => {'__raw': raw},
};

Expand Down
77 changes: 77 additions & 0 deletions lib/domain/edit/schema/edit_schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,70 @@ final TreeNode<SwipeMode> swipeModeNode = subtree<SwipeMode>(
],
);

// Non-recursive TriggerAction subtree used for the cases inside a `one:`
// branch. Mirrors [actionNode] but excludes the OneAction case, which both
// avoids schema recursion and enforces the one-level nesting cap (no branch
// inside a branch). The `branchCase` scope on the list keeps the generated lens
// names distinct from the top-level `action*` family.
final TreeNode<TriggerAction> branchCaseNode = subtree<TriggerAction>(
fields: [
sealed(
TriggerActionMeta.action,
cases: [
valueCase<CommandAction>(
'command',
fields: [
prop(CommandActionMeta.command),
prop(
CommandActionMeta.wait,
compare: projected<CommandAction, bool?>((v) => v?.effectiveWait),
),
],
),
valueCase<PlasmaShortcutAction>(
'plasma',
fields: [
prop(PlasmaShortcutActionMeta.component),
prop(PlasmaShortcutActionMeta.shortcut),
],
),
valueCase<ActivateWindowAction>(
'activateWindow',
fields: [prop(ActivateWindowActionMeta.windowId)],
),
valueCase<ReplaceTextAction>(
'replaceText',
fields: [prop(ReplaceTextActionMeta.rules)],
),
valueCase<SleepAction>(
'sleep',
fields: [prop('duration', property: SleepActionMeta.milliseconds)],
),
valueCase<FunctionAction>(
'function',
fields: [prop(FunctionActionMeta.expression)],
),
valueCase<RawAction>('raw', fields: [prop(RawActionMeta.raw)]),
valueCase<InputAction>(
'input',
fields: [prop('inputEntries', property: InputActionMeta.entries)],
),
],
),
prop('triggerOn', property: TriggerActionMeta.on),
prop(TriggerActionMeta.interval, adapter: nullableText()),
prop(TriggerActionMeta.threshold, adapter: nullableText()),
prop(TriggerActionMeta.limit, adapter: nullableInt()),
prop(
TriggerActionMeta.enabled,
compare: projected<TriggerAction, bool?>((v) => v?.effectiveEnabled),
),
prop(TriggerActionMeta.conflicting),
prop(TriggerActionMeta.conditions),
prop(TriggerActionMeta.id, adapter: nullableText()),
],
);

final TreeNode<TriggerAction> actionNode = subtree<TriggerAction>(
fields: [
sealed(
Expand Down Expand Up @@ -98,6 +162,19 @@ final TreeNode<TriggerAction> actionNode = subtree<TriggerAction>(
fields: [prop(FunctionActionMeta.expression)],
),
valueCase<RawAction>('raw', fields: [prop(RawActionMeta.raw)]),
valueCase<OneAction>(
'one',
fields: [
list(
OneActionMeta.cases,
of: branchCaseNode,
scope: 'branchCase',
location: 'BranchCaseLocation',
parentField: 'action',
indexField: 'caseIndex',
),
],
),
valueCase<InputAction>(
'input',
fields: [prop('inputEntries', property: InputActionMeta.entries)],
Expand Down
48 changes: 48 additions & 0 deletions lib/domain/edit/schema/edit_schema_extra.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,41 @@ final _gestureActionsPart = LensPart<Gesture, List<TriggerAction>>(
Lens<List<TriggerAction>> gestureActionsLens(GestureLocation location) =>
gestureLens(location).then(_gestureActionsPart);

/// Whole-list lens for the cases of a `one:` ([OneAction]) branch at
/// [location]. Reads `[]` when the addressed action isn't a [OneAction] or the
/// index is out of range; writes by replacing that action with a new
/// [OneAction] carrying the given cases (a no-op when the target isn't a
/// branch). Mirrors [gestureActionsLens] for the nested case list — the
/// per-case field lenses (`branchCase*Lens`) are generated.
Lens<List<TriggerAction>> branchCasesLens(
ActionLocation location,
) => Lens<List<TriggerAction>>(
get: (config) {
final actions = gestureActionsLens(location.gesture).get(config);
if (location.actionIndex < 0 || location.actionIndex >= actions.length) {
return const [];
}
final action = actions[location.actionIndex].action;
return action is OneAction ? action.cases : const [];
},
set: (config, cases) {
final actions = gestureActionsLens(location.gesture).get(config);
if (location.actionIndex < 0 || location.actionIndex >= actions.length) {
return config;
}
final current = actions[location.actionIndex];
if (current.action is! OneAction) return config;
final next = List<TriggerAction>.of(actions);
next[location.actionIndex] = current.copyWith(
action: OneAction(cases: cases),
);
return gestureActionsLens(location.gesture).set(config, next);
},
name:
'gesture[${location.gesture}].action'
'[${location.actionIndex}].cases',
);

Lens<DeviceRuleProperties> defaultDevicePropertiesLens(DeviceType device) =>
Lens<DeviceRuleProperties>(
get: (config) =>
Expand Down Expand Up @@ -343,3 +378,16 @@ TriggerAction? actionAt(Config? config, ActionLocation location) {
}
return common.actions[location.actionIndex];
}

/// The [TriggerAction] for a single case of a `one:` branch, or null when the
/// addressed action isn't a [OneAction] or any index is out of range.
TriggerAction? branchCaseAt(Config? config, BranchCaseLocation location) {
if (config == null) return null;
final parent = ActionLocation(
gesture: location.action,
actionIndex: location.actionIndex,
);
final cases = branchCasesLens(parent).get(config);
if (location.caseIndex < 0 || location.caseIndex >= cases.length) return null;
return cases[location.caseIndex];
}
35 changes: 35 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,16 @@
"actionMetaFunctionSubtitle": "Run a JavaScript function",
"actionMetaRawLabel": "Raw YAML",
"actionMetaRawSubtitle": "Hand-authored unsupported action config",
"actionMetaOneLabel": "First match",
"actionMetaOneSubtitle": "Run one action depending on conditions",
"actionMetaOneSummary": "{count, plural, =0{no cases} =1{1 case} other{{count} cases}}",
"@actionMetaOneSummary": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"actionSummaryNoCommand": "No command",
"actionSummaryNotConfigured": "Not configured",
"actionSummaryEmpty": "Empty",
Expand Down Expand Up @@ -657,6 +667,31 @@
"dialogUnsavedChangesBody": "You have unsaved changes. Apply them or discard before leaving.",
"actionApply": "Apply",
"addAction": "Add Action",
"branchHeader_firstMatch": "First match",
"branchHeader_switchOn": "Switch on",
"branchHeader_caseCount": "{count, plural, =0{No cases yet} =1{1 case} other{{count} cases}}",
"@branchHeader_caseCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"branchAddCase": "Add case",
"branchAddDefault": "Add default",
"branchCase_anyValue": "any value",
"branchCase_deadWarning": "Never runs: a default above always matches",
"branchCase_conditionTitle": "Runs when",
"branchAddCaseFor": "Add {discriminator}",
"@branchAddCaseFor": {
"placeholders": {
"discriminator": {
"type": "String"
}
}
},
"branchCase_when": "when",
"branchCase_otherwise": "otherwise",
"dialogAddActionTitle": "Add action",
"actionIntervalHint": "+, -, or number",
"actionLimitHint": "0 = unlimited",
Expand Down
84 changes: 84 additions & 0 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,24 @@ abstract class AppLocalizations {
/// **'Hand-authored unsupported action config'**
String get actionMetaRawSubtitle;

/// No description provided for @actionMetaOneLabel.
///
/// In en, this message translates to:
/// **'First match'**
String get actionMetaOneLabel;

/// No description provided for @actionMetaOneSubtitle.
///
/// In en, this message translates to:
/// **'Run one action depending on conditions'**
String get actionMetaOneSubtitle;

/// No description provided for @actionMetaOneSummary.
///
/// In en, this message translates to:
/// **'{count, plural, =0{no cases} =1{1 case} other{{count} cases}}'**
String actionMetaOneSummary(int count);

/// No description provided for @actionSummaryNoCommand.
///
/// In en, this message translates to:
Expand Down Expand Up @@ -3382,6 +3400,72 @@ abstract class AppLocalizations {
/// **'Add Action'**
String get addAction;

/// No description provided for @branchHeader_firstMatch.
///
/// In en, this message translates to:
/// **'First match'**
String get branchHeader_firstMatch;

/// No description provided for @branchHeader_switchOn.
///
/// In en, this message translates to:
/// **'Switch on'**
String get branchHeader_switchOn;

/// No description provided for @branchHeader_caseCount.
///
/// In en, this message translates to:
/// **'{count, plural, =0{No cases yet} =1{1 case} other{{count} cases}}'**
String branchHeader_caseCount(int count);

/// No description provided for @branchAddCase.
///
/// In en, this message translates to:
/// **'Add case'**
String get branchAddCase;

/// No description provided for @branchAddDefault.
///
/// In en, this message translates to:
/// **'Add default'**
String get branchAddDefault;

/// No description provided for @branchCase_anyValue.
///
/// In en, this message translates to:
/// **'any value'**
String get branchCase_anyValue;

/// No description provided for @branchCase_deadWarning.
///
/// In en, this message translates to:
/// **'Never runs: a default above always matches'**
String get branchCase_deadWarning;

/// No description provided for @branchCase_conditionTitle.
///
/// In en, this message translates to:
/// **'Runs when'**
String get branchCase_conditionTitle;

/// No description provided for @branchAddCaseFor.
///
/// In en, this message translates to:
/// **'Add {discriminator}'**
String branchAddCaseFor(String discriminator);

/// No description provided for @branchCase_when.
///
/// In en, this message translates to:
/// **'when'**
String get branchCase_when;

/// No description provided for @branchCase_otherwise.
///
/// In en, this message translates to:
/// **'otherwise'**
String get branchCase_otherwise;

/// No description provided for @dialogAddActionTitle.
///
/// In en, this message translates to:
Expand Down
63 changes: 63 additions & 0 deletions lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,24 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get actionMetaRawSubtitle => 'Hand-authored unsupported action config';

@override
String get actionMetaOneLabel => 'First match';

@override
String get actionMetaOneSubtitle => 'Run one action depending on conditions';

@override
String actionMetaOneSummary(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count cases',
one: '1 case',
zero: 'no cases',
);
return '$_temp0';
}

@override
String get actionSummaryNoCommand => 'No command';

Expand Down Expand Up @@ -1881,6 +1899,51 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get addAction => 'Add Action';

@override
String get branchHeader_firstMatch => 'First match';

@override
String get branchHeader_switchOn => 'Switch on';

@override
String branchHeader_caseCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count cases',
one: '1 case',
zero: 'No cases yet',
);
return '$_temp0';
}

@override
String get branchAddCase => 'Add case';

@override
String get branchAddDefault => 'Add default';

@override
String get branchCase_anyValue => 'any value';

@override
String get branchCase_deadWarning =>
'Never runs: a default above always matches';

@override
String get branchCase_conditionTitle => 'Runs when';

@override
String branchAddCaseFor(String discriminator) {
return 'Add $discriminator';
}

@override
String get branchCase_when => 'when';

@override
String get branchCase_otherwise => 'otherwise';

@override
String get dialogAddActionTitle => 'Add action';

Expand Down
Loading