Skip to content
Merged
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
6 changes: 5 additions & 1 deletion falcon_gui/lib/graph_editor/graph_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ class _GraphEditorState extends State<GraphEditor> {
Expanded(
child: Stack(
children: [
const EditorView(),
Listener(
onPointerDown: (_) =>
setState(() => _activeCategory = null),
child: const EditorView(),
),
if (_activeCategory != null) ...[
Positioned(
top: 0,
Expand Down
6 changes: 4 additions & 2 deletions falcon_gui/lib/graph_editor/status_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ class StatusBar extends StatelessWidget {
Widget build(BuildContext context) {
return MultiListener(
builder: (context) {
return Container(
return AnimatedContainer(
duration: const Duration(milliseconds: 500),

height: 24,
color: _stateColor(falconManager.falconState),
child: Row(
Expand Down Expand Up @@ -204,7 +206,7 @@ Color _stateColor(FalconState falconState) => switch (falconState) {
FalconState.noGraph => Colors.orange,
FalconState.constructing => Colors.purple,
FalconState.preparing => Colors.indigo,
FalconState.stopping => Colors.red,
FalconState.stopping => Colors.orangeAccent,
FalconState.error => Colors.redAccent,
FalconState.starting => Colors.blueAccent,
};
Expand Down
6 changes: 2 additions & 4 deletions falcon_gui/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'dart:io';

import 'package:falcon_gui/dialogs/on_close_gui_dialog.dart';
import 'package:falcon_gui/graph_editor/graph_editor.dart';
import 'package:falcon_gui/settings/theme_mode_setting.dart';
import 'package:falcon_gui/state/falcon_manager.dart';
import 'package:falcon_gui/state/graph_manager.dart';
import 'package:falcon_gui/utils/local_config.dart';
Expand Down Expand Up @@ -45,7 +44,6 @@ Future<void> _entrypoint() async {

await LocalConfigManager.loadConfig();

await setThemeModeFromConfig();
_listenForLoadedGraphFile();

unawaited(falconManager.initialize());
Expand Down Expand Up @@ -80,13 +78,13 @@ class DesktopApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: themeNotifier,
animation: localConfigNotifier,
builder: (context, _) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const RootPage(),
navigatorKey: globalNavigatorKey,
themeMode: themeNotifier.value,
themeMode: themeModeFromString(localConfigNotifier.value.themeMode),
theme: FalconTheme(Theme.of(context).textTheme).light(),
darkTheme: FalconTheme(Theme.of(context).textTheme).dark(),
);
Expand Down
148 changes: 148 additions & 0 deletions falcon_gui/lib/settings/local_backend_settings.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import 'package:falcon_gui/utils/file_picker.dart';
import 'package:falcon_gui/utils/local_config.dart';
import 'package:falcon_gui/utils/theme.dart';
import 'package:flutter/material.dart';
import 'package:remixicon/remixicon.dart';

enum _PathConfigType { resources, output, logs }

class LocalBackendSettings extends StatelessWidget {
const LocalBackendSettings({super.key});

Future<void> _onPathSelect(_PathConfigType type) async {
switch (type) {
case _PathConfigType.resources:
final result = await FalconFilePicker.pickDirectory(
initialDirectory:
localConfigNotifier.value.serverSideStorageResources,
dialogTitle: 'Select Resources Directory',
);

if (result != null) {
await LocalConfigManager.setServerSideStorageResources(result.path);
}
case _PathConfigType.output:
final result = await FalconFilePicker.pickDirectory(
initialDirectory:
localConfigNotifier.value.serverSideStorageEnvironment,
dialogTitle: 'Select Output Directory',
);

if (result != null) {
await LocalConfigManager.setServerSideStorageEnvironment(result.path);
}
case _PathConfigType.logs:
final result = await FalconFilePicker.pickDirectory(
initialDirectory: localConfigNotifier.value.loggingPath,
dialogTitle: 'Select Logs Directory',
);

if (result != null) {
await LocalConfigManager.setLoggingPath(result.path);
}
}
}

@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: localConfigNotifier,
builder: (context, localConfig, _) {
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Local Backend Configuration',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const Text(
'Changes will take effect after restarting the local backend.',
),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: context.c.surfaceContainerLow,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: context.c.outlineVariant),
),
padding: const EdgeInsets.all(8),
width: 600,

child: Column(
children: [
_PathSettingRow(
label: 'Resources',
path: localConfig.serverSideStorageResources,
onSelect: () => _onPathSelect(_PathConfigType.resources),
),
const SizedBox(height: 16),
_PathSettingRow(
label: 'Output',
path: localConfig.serverSideStorageEnvironment,
onSelect: () => _onPathSelect(_PathConfigType.output),
),
const SizedBox(height: 16),
_PathSettingRow(
label: 'Logs',
path: localConfig.loggingPath,
onSelect: () => _onPathSelect(_PathConfigType.logs),
),
],
),
),
],
),
);
},
);
}
}

class _PathSettingRow extends StatelessWidget {
const _PathSettingRow({
required this.label,
required this.path,
required this.onSelect,
});

final String label;
final String path;
final VoidCallback onSelect;

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontWeight: FontWeight.w600,
color: context.c.onSurfaceVariant,
),
),

Row(
children: [
Expanded(
child: Text(
path,
style: TextStyle(
fontSize: 14,
color: context.c.onSurfaceVariant,
),
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(RemixIcons.folder_2_line),
onPressed: onSelect,
),
],
),
],
);
}
}
22 changes: 13 additions & 9 deletions falcon_gui/lib/settings/settings_view.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:falcon_gui/dialogs/dialog_view.dart';
import 'package:falcon_gui/settings/local_backend_settings.dart';
import 'package:falcon_gui/settings/theme_mode_setting.dart';
import 'package:flutter/material.dart';

Expand All @@ -7,16 +8,19 @@ class SettingsView extends StatelessWidget {

@override
Widget build(BuildContext context) {
return const DialogView(
return DialogView(
title: 'Settings',
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// FalconProcessPriorityStatus(),
// SizedBox(height: 12),
ThemeModeSetting(),
SizedBox(height: 12),
],
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 600),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocalBackendSettings(),
SizedBox(height: 12),
ThemeModeSetting(),
SizedBox(height: 12),
],
),
),
);
}
Expand Down
29 changes: 7 additions & 22 deletions falcon_gui/lib/settings/theme_mode_setting.dart
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import 'dart:async';

import 'package:falcon_gui/utils/local_config.dart';
import 'package:falcon_gui/utils/logger.dart';
import 'package:falcon_gui/utils/misc.dart';
import 'package:flutter/material.dart';

final ValueNotifier<ThemeMode> themeNotifier = ValueNotifier(ThemeMode.system);

Future<void> setThemeModeFromConfig() async {
try {
final themeModeName = localConfig.themeMode;
final themeMode = themeModeName == 'light'
? ThemeMode.light
: themeModeName == 'dark'
? ThemeMode.dark
: ThemeMode.system;
themeNotifier.value = themeMode;
} catch (e, s) {
logError('Error loading theme mode from shared preferences: $e', s);
}
}

Future<void> _saveThemeModeToLocalConfig(ThemeMode mode) async {
final modeName = switch (mode) {
ThemeMode.light => 'light',
Expand All @@ -35,9 +19,9 @@ class ThemeModeSetting extends StatelessWidget {
@override
Widget build(BuildContext context) {
// use CupertinoSegmentedControl to show 3 options: light, dark, system
return ValueListenableBuilder<ThemeMode>(
valueListenable: themeNotifier,
builder: (context, mode, _) {
return ValueListenableBuilder<LocalConfig>(
valueListenable: localConfigNotifier,
builder: (context, localConfig, _) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
Expand All @@ -63,10 +47,11 @@ class ThemeModeSetting extends StatelessWidget {
label: Text('System'),
),
],
selected: <ThemeMode>{mode},
selected: <ThemeMode>{
themeModeFromString(localConfig.themeMode),
},
onSelectionChanged: (newSelection) {
final newMode = newSelection.first;
themeNotifier.value = newMode;
unawaited(_saveThemeModeToLocalConfig(newMode));
},
),
Expand Down
22 changes: 15 additions & 7 deletions falcon_gui/lib/state/falcon_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,17 @@ class FalconManager extends ChangeNotifier {
// let user choose remote vs local on startup
await initLocalBackend();

final lastGraphPath = localConfig.lastOpenedGraph;
var initialFile = _defaultGraphFile;

final lastGraphPath = localConfigNotifier.value.lastOpenedGraph;

if (lastGraphPath != null) {
final file = File(lastGraphPath);
if (file.existsSync()) {
unawaited(falconManager.loadFile(file: file));
initialFile = file;
}
} else {
unawaited(loadFile(file: _defaultGraphFile));
}
unawaited(loadFile(file: initialFile));
}

Future<void> openFile() async {
Expand Down Expand Up @@ -190,6 +192,8 @@ class FalconManager extends ChangeNotifier {

_localFalconBackendPid = existingPid;
} else {
// TODO(ben): start process using linux command and pipe the
// output to a file in logs directory
final localBackendProcess = await Process.start(
_falconBackendBinPath,
[
Expand Down Expand Up @@ -346,26 +350,30 @@ class FalconManager extends ChangeNotifier {
return;
}

final graphAsYaml = graph.toYaml();
await _currentGraphFile!.writeAsString(graphAsYaml);
unawaited(_sendGraphToBackend(graphAsYaml));
}

Future<void> _sendGraphToBackend(String graphAsYaml) async {
if (falconState == FalconState.unknown) {
// TODO(ben): dont create infinite Futures, instead, overwrite
// the previous one, perhaps just use the debouncer
Future.delayed(
const Duration(milliseconds: 100),
() => onGraphChanged(graph),
() => _sendGraphToBackend(graphAsYaml),
);
return;
}

if (falconState != FalconState.noGraph) {
await sendCommand(FalconZmqCommand.graphDestroy);
}
final graphAsYaml = graph.toYaml();

if (graphAsYaml.trim().isEmpty) {
return;
}

await _currentGraphFile!.writeAsString(graphAsYaml);
await _falconZMQ!.sendCommandParts(
FalconZmqCommand.graphBuild(_currentGraphFile!.absolute.path),
);
Expand Down
21 changes: 21 additions & 0 deletions falcon_gui/lib/utils/file_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,25 @@ class FalconFilePicker {

return null;
}

static Future<Directory?> pickDirectory({
required String initialDirectory,
required String dialogTitle,
}) async {
try {
final result = await FilePicker.platform.getDirectoryPath(
initialDirectory: Directory(initialDirectory).absolute.path,
dialogTitle: dialogTitle,
lockParentWindow: true,
);

if (result != null) {
return Directory(result);
}
} catch (e, s) {
logError('Error picking directory: $e', s);
}

return null;
}
}
Loading