#146 new: option of server tab old ui

This commit is contained in:
lollipopkit
2023-08-30 15:41:41 +08:00
parent f7278fc890
commit 8004c41094
5 changed files with 198 additions and 128 deletions

View File

@@ -26,6 +26,23 @@ enum ServerTabMenuType {
return Icons.terminal; return Icons.terminal;
} }
} }
String text(S s) {
switch (this) {
case ServerTabMenuType.sftp:
return 'SFTP';
case ServerTabMenuType.snippet:
return s.snippet;
case ServerTabMenuType.pkg:
return s.pkg;
case ServerTabMenuType.docker:
return 'Docker';
case ServerTabMenuType.process:
return s.process;
case ServerTabMenuType.terminal:
return s.terminal;
}
}
} }
enum DockerMenuType { enum DockerMenuType {

View File

@@ -146,4 +146,11 @@ class SettingStore extends PersistentStore {
'sftpRmrfDir', 'sftpRmrfDir',
true, true,
); );
/// Discussion #146
late final serverTabUseOldUI = StoreProperty(
box,
'serverTabUseOldUI',
false,
);
} }

View File

@@ -238,7 +238,9 @@ class _ServerPageState extends State<ServerPage>
), ),
), ),
height13, height13,
if (_settingStore.moveOutServerTabFuncBtns.fetch()) if (_settingStore.moveOutServerTabFuncBtns.fetch() &&
// Discussion #146
!_settingStore.serverTabUseOldUI.fetch())
SizedBox( SizedBox(
height: 27, height: 27,
child: ServerFuncBtns(spi: spi, s: _s), child: ServerFuncBtns(spi: spi, s: _s),
@@ -283,7 +285,13 @@ class _ServerPageState extends State<ServerPage>
) )
], ],
), ),
_buildTopRightText(ss, cs), Row(
children: [
_buildTopRightText(ss, cs),
if (_settingStore.serverTabUseOldUI.fetch())
ServerFuncBtnsTopRight(spi: spi, s: _s)
],
)
], ],
), ),
); );
@@ -446,7 +454,9 @@ class _ServerPageState extends State<ServerPage>
if (cs != ServerState.finished) { if (cs != ServerState.finished) {
return 23.0; return 23.0;
} }
if (_settingStore.moveOutServerTabFuncBtns.fetch()) { if (_settingStore.moveOutServerTabFuncBtns.fetch() &&
// Discussion #146
!_settingStore.serverTabUseOldUI.fetch()) {
return 132; return 132;
} }
return 107; return 107;

View File

@@ -345,11 +345,12 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
onTap: () => _download(context, file), onTap: () => _download(context, file),
), ),
// Only show decompress option when the file is a compressed file // Only show decompress option when the file is a compressed file
if (_canDecompress(file.filename)) ListTile( if (_canDecompress(file.filename))
leading: const Icon(Icons.folder_zip), ListTile(
title: Text(_s.decompress), leading: const Icon(Icons.folder_zip),
onTap: () => _decompress(context, file), title: Text(_s.decompress),
), onTap: () => _decompress(context, file),
),
]); ]);
} }
showRoundDialog( showRoundDialog(

View File

@@ -14,8 +14,42 @@ import '../../data/model/server/server_private_info.dart';
import '../../data/model/server/snippet.dart'; import '../../data/model/server/snippet.dart';
import '../../data/provider/snippet.dart'; import '../../data/provider/snippet.dart';
import '../../locator.dart'; import '../../locator.dart';
import 'popup_menu.dart';
import 'tag.dart'; import 'tag.dart';
class ServerFuncBtnsTopRight extends StatelessWidget {
final ServerPrivateInfo spi;
final S s;
const ServerFuncBtnsTopRight({
super.key,
required this.spi,
required this.s,
});
@override
Widget build(BuildContext context) {
return PopupMenu<ServerTabMenuType>(
items: ServerTabMenuType.values
.map((e) => PopupMenuItem<ServerTabMenuType>(
value: e,
child: Row(
children: [
Icon(e.icon),
const SizedBox(
width: 10,
),
Text(e.text(s)),
],
),
))
.toList(),
padding: const EdgeInsets.symmetric(horizontal: 10),
onSelected: (val) => _onTapMoreBtns(val, spi, context, s),
);
}
}
class ServerFuncBtns extends StatelessWidget { class ServerFuncBtns extends StatelessWidget {
const ServerFuncBtns({ const ServerFuncBtns({
super.key, super.key,
@@ -28,77 +62,13 @@ class ServerFuncBtns extends StatelessWidget {
final S s; final S s;
final double? iconSize; final double? iconSize;
void _onTapMoreBtns(
ServerTabMenuType value,
ServerPrivateInfo spi,
BuildContext context,
) async {
switch (value) {
case ServerTabMenuType.pkg:
AppRoute.pkg(spi: spi).checkGo(
context: context,
check: () => _checkClient(context, spi.id),
);
break;
case ServerTabMenuType.sftp:
AppRoute.sftp(spi: spi).checkGo(
context: context,
check: () => _checkClient(context, spi.id),
);
break;
case ServerTabMenuType.snippet:
final provider = locator<SnippetProvider>();
final snippets = await showDialog<List<Snippet>>(
context: context,
builder: (_) => TagPicker<Snippet>(
items: provider.snippets,
tags: provider.tags.toSet(),
),
);
if (snippets == null) {
return;
}
final result =
await locator<ServerProvider>().runSnippets(spi.id, snippets);
if (result != null && result.isNotEmpty) {
showRoundDialog(
context: context,
title: Text(s.result),
child: Text(result),
actions: [
TextButton(
onPressed: () => copy2Clipboard(result),
child: Text(s.copy),
)
],
);
}
break;
case ServerTabMenuType.docker:
AppRoute.docker(spi: spi).checkGo(
context: context,
check: () => _checkClient(context, spi.id),
);
break;
case ServerTabMenuType.process:
AppRoute.process(spi: spi).checkGo(
context: context,
check: () => _checkClient(context, spi.id),
);
break;
case ServerTabMenuType.terminal:
_gotoSSH(spi, context);
break;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: ServerTabMenuType.values children: ServerTabMenuType.values
.map((e) => IconButton( .map((e) => IconButton(
onPressed: () => _onTapMoreBtns(e, spi, context), onPressed: () => _onTapMoreBtns(e, spi, context, s),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
tooltip: e.name, tooltip: e.name,
icon: Icon(e.icon, size: iconSize ?? 15), icon: Icon(e.icon, size: iconSize ?? 15),
@@ -106,62 +76,127 @@ class ServerFuncBtns extends StatelessWidget {
.toList(), .toList(),
); );
} }
}
Future<void> _gotoSSH( void _onTapMoreBtns(
ServerPrivateInfo spi, ServerTabMenuType value,
BuildContext context, ServerPrivateInfo spi,
) async { BuildContext context,
// as a `Mobile first` app -> handle mobile first S s,
// ) async {
// run built-in ssh on macOS due to incompatibility switch (value) {
if (!isDesktop || isMacOS) { case ServerTabMenuType.pkg:
AppRoute.ssh(spi: spi).go(context); AppRoute.pkg(spi: spi).checkGo(
return; context: context,
} check: () => _checkClient(context, spi.id, s.waitConnection),
final extraArgs = <String>[]; );
if (spi.port != 22) { break;
extraArgs.addAll(['-p', '${spi.port}']); case ServerTabMenuType.sftp:
} AppRoute.sftp(spi: spi).checkGo(
context: context,
final path = () { check: () => _checkClient(context, spi.id, s.waitConnection),
final tempKeyFileName = 'srvbox_pk_${spi.pubKeyId}'; );
return pathJoin(Directory.systemTemp.path, tempKeyFileName); break;
}(); case ServerTabMenuType.snippet:
final file = File(path); final provider = locator<SnippetProvider>();
final shouldGenKey = spi.pubKeyId != null; final snippets = await showDialog<List<Snippet>>(
if (shouldGenKey) { context: context,
if (await file.exists()) { builder: (_) => TagPicker<Snippet>(
await file.delete(); items: provider.snippets,
tags: provider.tags.toSet(),
),
);
if (snippets == null) {
return;
} }
await file.writeAsString(getPrivateKey(spi.pubKeyId!)); final result =
extraArgs.addAll(["-i", path]); await locator<ServerProvider>().runSnippets(spi.id, snippets);
} if (result != null && result.isNotEmpty) {
showRoundDialog(
List<String> sshCommand = ["ssh", "${spi.user}@${spi.ip}"] + extraArgs; context: context,
final system = Platform.operatingSystem; title: Text(s.result),
switch (system) { child: Text(result),
case "windows": actions: [
await Process.start("cmd", ["/c", "start"] + sshCommand); TextButton(
break; onPressed: () => copy2Clipboard(result),
case "linux": child: Text(s.copy),
await Process.start("x-terminal-emulator", ["-e"] + sshCommand); )
break; ],
default: );
showSnackBar(context, Text('Mismatch system: $system')); }
} break;
// For security reason, delete the private key file after use case ServerTabMenuType.docker:
if (shouldGenKey) { AppRoute.docker(spi: spi).checkGo(
if (!await file.exists()) return; context: context,
await Future.delayed(const Duration(seconds: 2), file.delete); check: () => _checkClient(context, spi.id, s.waitConnection),
} );
} break;
case ServerTabMenuType.process:
bool _checkClient(BuildContext context, String id) { AppRoute.process(spi: spi).checkGo(
final server = locator<ServerProvider>().servers[id]; context: context,
if (server == null || server.client == null) { check: () => _checkClient(context, spi.id, s.waitConnection),
showSnackBar(context, Text(s.waitConnection)); );
return false; break;
} case ServerTabMenuType.terminal:
return true; _gotoSSH(spi, context);
break;
} }
} }
Future<void> _gotoSSH(
ServerPrivateInfo spi,
BuildContext context,
) async {
// as a `Mobile first` app -> handle mobile first
//
// run built-in ssh on macOS due to incompatibility
if (!isDesktop || isMacOS) {
AppRoute.ssh(spi: spi).go(context);
return;
}
final extraArgs = <String>[];
if (spi.port != 22) {
extraArgs.addAll(['-p', '${spi.port}']);
}
final path = () {
final tempKeyFileName = 'srvbox_pk_${spi.pubKeyId}';
return pathJoin(Directory.systemTemp.path, tempKeyFileName);
}();
final file = File(path);
final shouldGenKey = spi.pubKeyId != null;
if (shouldGenKey) {
if (await file.exists()) {
await file.delete();
}
await file.writeAsString(getPrivateKey(spi.pubKeyId!));
extraArgs.addAll(["-i", path]);
}
List<String> sshCommand = ["ssh", "${spi.user}@${spi.ip}"] + extraArgs;
final system = Platform.operatingSystem;
switch (system) {
case "windows":
await Process.start("cmd", ["/c", "start"] + sshCommand);
break;
case "linux":
await Process.start("x-terminal-emulator", ["-e"] + sshCommand);
break;
default:
showSnackBar(context, Text('Mismatch system: $system'));
}
// For security reason, delete the private key file after use
if (shouldGenKey) {
if (!await file.exists()) return;
await Future.delayed(const Duration(seconds: 2), file.delete);
}
}
bool _checkClient(BuildContext context, String id, String msg) {
final server = locator<ServerProvider>().servers[id];
if (server == null || server.client == null) {
showSnackBar(context, Text(msg));
return false;
}
return true;
}