UI debugging + git ignore
This commit is contained in:
parent
ceeebfc964
commit
1db9ef0794
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
@ -9,6 +9,8 @@ ARG WORKSPACE_HOST="https://localhost:8089"
|
||||
ARG WORKFLOW_HOST="https://localhost:8088"
|
||||
ARG SEARCH_HOST="https://localhost:49618"
|
||||
ARG ITEM_HOST="https://localhost:8087"
|
||||
ARG SCHEDULER_HOST="http://localhost:8090"
|
||||
ARG LOGS_HOST="http://localhost:3100"
|
||||
|
||||
# define variables
|
||||
ARG FLUTTER_SDK=/usr/local/flutter
|
||||
|
@ -34,6 +34,7 @@ class WorkspaceLocal {
|
||||
_service.put(context, ws.id!, { "active" : true }, {});
|
||||
}
|
||||
if (ws.active == true && changeCurrent) {
|
||||
print(ws.serialize());
|
||||
current = ws.id;
|
||||
}
|
||||
fill();
|
||||
@ -95,7 +96,13 @@ class WorkspaceLocal {
|
||||
});
|
||||
}
|
||||
|
||||
static void changeWorkspaceByName(String name) {
|
||||
var id = workspaces.entries.firstWhere((element) => element.value.name == "${name}_workspace").key;
|
||||
changeWorkspace(id);
|
||||
}
|
||||
|
||||
static void changeWorkspace(String id) {
|
||||
_service.put(null, id, { "active" : true }, {});
|
||||
current = id;
|
||||
fill();
|
||||
endDrawerKey.currentState?.setState(() {});
|
||||
|
@ -61,7 +61,7 @@ class SearchWidgetState extends State<SearchWidget> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width - 400 > 0 ? MediaQuery.of(context).size.width - 300 - 100 : MediaQuery.of(context).size.width,
|
||||
width: MediaQuery.of(context).size.width - 400 > 0 ? MediaQuery.of(context).size.width - 300 - 100 : 200,
|
||||
height: 50,
|
||||
color: Colors.white,
|
||||
child: TextField(
|
||||
|
@ -74,15 +74,10 @@ class APIService<T extends SerializerDeserializer> {
|
||||
Future<APIResponse<T>> _main(String url, dynamic body, String method, String succeed, bool force,
|
||||
BuildContext? context, Options? options) async {
|
||||
var err = "";
|
||||
|
||||
if ((!force) && cache.containsKey(url) && cache[url] != null ) {
|
||||
return cache[url]! as APIResponse<T>;
|
||||
}
|
||||
try {
|
||||
_dio.options.headers["authorization"] = auth;
|
||||
_dio.interceptors.clear();
|
||||
var response = await _request(url, method, body, options);
|
||||
print(response);
|
||||
if (response.statusCode != null && response.statusCode! < 400) {
|
||||
if (method == "delete") { cache.remove(url); return APIResponse<T>(); }
|
||||
APIResponse<T> resp = APIResponse<T>().deserialize(response.data);
|
||||
@ -117,6 +112,7 @@ class APIService<T extends SerializerDeserializer> {
|
||||
try {
|
||||
_dio.options.headers["authorization"] = auth;
|
||||
_dio.interceptors.clear();
|
||||
|
||||
var response = await _request(url, method, body, null);
|
||||
if (response.statusCode != null && response.statusCode! < 400) {
|
||||
if (method == "delete") { cache.remove(url); return APIResponse<RawData>(); }
|
||||
|
36
lib/core/services/specialized_services/check_service.dart
Normal file
36
lib/core/services/specialized_services/check_service.dart
Normal file
@ -0,0 +1,36 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:oc_front/core/services/api_service.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/abstract_service.dart';
|
||||
import 'package:oc_front/models/response.dart';
|
||||
import 'package:oc_front/models/workflow.dart';
|
||||
|
||||
class CheckService extends AbstractService<Check> {
|
||||
@override APIService<Check> service = APIService<Check>(
|
||||
baseURL: const String.fromEnvironment('WORKFLOW_HOST', defaultValue: 'http://localhost:8088')
|
||||
);
|
||||
@override String subPath = "/oc/workflow/check/";
|
||||
|
||||
Future<APIResponse<Check>> search(BuildContext? context, List<String> words, Map<String, dynamic> params) {
|
||||
return service.get("$subPath${words.join("/")}", true, context);
|
||||
}
|
||||
@override
|
||||
Future<APIResponse<RawData>> all(BuildContext? context) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@override
|
||||
Future<APIResponse<Check>> get(BuildContext? context, String id) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@override
|
||||
Future<APIResponse<Check>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@override
|
||||
Future<APIResponse<Check>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@override
|
||||
Future<APIResponse<Check>> delete(BuildContext? context, String id, Map<String, String> params) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import 'package:oc_front/core/services/api_service.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/abstract_service.dart';
|
||||
import 'package:oc_front/models/abstract.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
|
||||
class ItemService<S extends AbstractItem, T extends SerializerDeserializer<S>> extends AbstractService<T> {
|
||||
@override APIService<T> service = APIService<T>(
|
||||
baseURL: const String.fromEnvironment('ITEM_HOST', defaultValue: 'http://localhost:8087')
|
||||
);
|
||||
@override String subPath = "/oc/${getTopic(S)}/";
|
||||
}
|
@ -4,31 +4,31 @@ import 'package:oc_front/core/services/specialized_services/abstract_service.dar
|
||||
import 'package:oc_front/models/logs.dart';
|
||||
import 'package:oc_front/models/response.dart';
|
||||
|
||||
class LogsService extends AbstractService<LogResults> {
|
||||
@override APIService<LogResults> service = APIService<LogResults>(
|
||||
baseURL: const String.fromEnvironment('SCHEDULER_HOST', defaultValue: 'http://localhost:3100')
|
||||
class LogsService extends AbstractService<LogsResult> {
|
||||
@override APIService<LogsResult> service = APIService<LogsResult>(
|
||||
baseURL: const String.fromEnvironment('LOGS_HOST', defaultValue: 'http://localhost:3100')
|
||||
);
|
||||
@override String subPath = "/loki/api/v1/";
|
||||
|
||||
@override Future<APIResponse<LogResults>> search(BuildContext? context, List<String> words, Map<String, dynamic> params) {
|
||||
@override Future<APIResponse<LogsResult>> search(BuildContext? context, List<String> words, Map<String, dynamic> params) {
|
||||
List<String> v = [];
|
||||
for (var p in params.keys) {
|
||||
if (p == "start" || p == "end") { continue; }
|
||||
v.add("$p=\"${params[p]}\"");
|
||||
}
|
||||
return service.get("${subPath}query_range?query={${v.join(", ")}}&start=${params["start"]}&end=${params["end"]}", false, context);
|
||||
return service.get("${subPath}query_range?query={${v.join(", ")}}&start=${params["start"].toString().substring(0, 10)}&end=${params["end"].toString().substring(0, 10)}", false, context);
|
||||
}
|
||||
|
||||
@override Future<APIResponse<LogResults>> get(BuildContext? context, String id) {
|
||||
@override Future<APIResponse<LogsResult>> get(BuildContext? context, String id) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@override Future<APIResponse<LogResults>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) {
|
||||
@override Future<APIResponse<LogsResult>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@override Future<APIResponse<LogResults>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) {
|
||||
@override Future<APIResponse<LogsResult>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@override Future<APIResponse<LogResults>> delete(BuildContext? context, String id, Map<String, String> params) {
|
||||
@override Future<APIResponse<LogsResult>> delete(BuildContext? context, String id, Map<String, String> params) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,6 @@
|
||||
import 'package:oc_front/models/abstract.dart';
|
||||
import 'package:json_string/json_string.dart';
|
||||
|
||||
class LogResults extends SerializerDeserializer<LogResults> {
|
||||
String? status;
|
||||
LogsResult? data;
|
||||
|
||||
LogResults({
|
||||
this.status,
|
||||
this.data,
|
||||
});
|
||||
|
||||
String getID() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return LogResults(); }
|
||||
return LogResults(
|
||||
status: json.containsKey("status") ? json["status"] : "",
|
||||
data: json.containsKey("data") ? LogsResult().deserialize(json["data"]) : null,
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() {
|
||||
return { };
|
||||
}
|
||||
}
|
||||
|
||||
class LogsResult extends SerializerDeserializer<LogsResult> {
|
||||
List<Logs> result;
|
||||
LogsResult({
|
||||
@ -45,7 +19,9 @@ class LogsResult extends SerializerDeserializer<LogsResult> {
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() {
|
||||
return { };
|
||||
return {
|
||||
"result": toListJson(result),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +30,7 @@ class Logs extends SerializerDeserializer<Logs> {
|
||||
List<Log> logs = [];
|
||||
Logs({
|
||||
this.level,
|
||||
this.logs = const [],
|
||||
});
|
||||
|
||||
String getID() {
|
||||
@ -65,11 +42,13 @@ class Logs extends SerializerDeserializer<Logs> {
|
||||
} catch (e) { return Logs(); }
|
||||
return Logs(
|
||||
level: json.containsKey("stream") && (json["stream"] as Map<String, dynamic>).containsKey("level") ? json["stream"]["level"] : "",
|
||||
|
||||
logs: json.containsKey("values") ? fromListJson(json["values"], Log()) : [],
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() {
|
||||
return { };
|
||||
return {
|
||||
"level": level,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,10 +57,12 @@ class Log extends SerializerDeserializer<Log> {
|
||||
String? message;
|
||||
|
||||
String? level;
|
||||
String? rawMessage;
|
||||
Map<String, dynamic> map = {};
|
||||
Log({
|
||||
this.timestamp,
|
||||
this.message,
|
||||
this.rawMessage,
|
||||
this.level
|
||||
});
|
||||
|
||||
@ -97,7 +78,7 @@ class Log extends SerializerDeserializer<Log> {
|
||||
if (j["Status"] == "Pending") {
|
||||
jsonString = "${j["Name"]} : [${j["Namespace"]}] Status: ${j["Status"]}... \nCreated at ${j["Created"].toString().replaceAllMapped(RegExp(r'\(\w+\)'), (match) { return ''; }).replaceAllMapped(RegExp(r'\+\w+'), (match) { return ''; })}";
|
||||
} else {
|
||||
jsonString = "${j["Name"]} : [${j["Namespace"]}] ${j["Status"]} ${j["Progress"]} (${j["Duration"].toString().replaceAll("seconds", "s")})\nStarted at ${j["Created"].toString().replaceAllMapped(RegExp(r'\(\w+\)'), (match) { return ''; }).replaceAllMapped(RegExp(r'\+\w+'), (match) { return ''; })}";
|
||||
jsonString = "${j["Name"]} : [${j["Namespace"]}] ${j["Status"]} ${j["Progress"]} (${j["Duration"].toString()})\nCreated at ${j["Created"].toString().replaceAllMapped(RegExp(r'\(\w+\)'), (match) { return ''; }).replaceAllMapped(RegExp(r'\+\w+'), (match) { return ''; })}; Started at ${j["Created"].toString().replaceAllMapped(RegExp(r'\(\w+\)'), (match) { return ''; }).replaceAllMapped(RegExp(r'\+\w+'), (match) { return ''; })}";
|
||||
}
|
||||
} on JsonFormatException catch (e) { /* */ }
|
||||
message = jsonString;
|
||||
@ -107,10 +88,13 @@ class Log extends SerializerDeserializer<Log> {
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as List<dynamic>;
|
||||
} catch (e) { return Log(); }
|
||||
return Log(
|
||||
timestamp: json.isNotEmpty ? DateTime.parse(json[0]) : null,
|
||||
var l = Log(
|
||||
timestamp: json.isNotEmpty ? DateTime.fromMillisecondsSinceEpoch(int.parse(json[0]) ~/ 1000) : null,
|
||||
message: json.length > 1 ? getMessage(json[1].toString()) : null,
|
||||
rawMessage : json.length > 1 ? json[1].toString() : null,
|
||||
);
|
||||
l.getMessage(l.message ?? "");
|
||||
return l;
|
||||
}
|
||||
@override Map<String, dynamic> serialize() { return { }; }
|
||||
}
|
@ -15,7 +15,8 @@ Map<Type, SerializerDeserializer> refs = <Type, SerializerDeserializer> {
|
||||
Workflow: Workflow(),
|
||||
Resource: Resource(),
|
||||
WorkflowExecutions: WorkflowExecutions(),
|
||||
LogResults: LogResults(),
|
||||
LogsResult: LogsResult(),
|
||||
Check: Check(),
|
||||
};
|
||||
|
||||
class APIResponse<T extends SerializerDeserializer> {
|
||||
|
@ -1,3 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
|
||||
import 'package:oc_front/models/abstract.dart';
|
||||
|
||||
@ -248,10 +251,21 @@ class ProcessingItem extends SerializerDeserializer<ProcessingItem> implements A
|
||||
@override String getName() {
|
||||
return name ?? "";
|
||||
}
|
||||
double? width;
|
||||
double? height;
|
||||
@override
|
||||
double? getWidth() {
|
||||
return width;
|
||||
}
|
||||
@override
|
||||
double? getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return ProcessingItem(); }
|
||||
return ProcessingItem(
|
||||
var w = ProcessingItem(
|
||||
id: json.containsKey("id") ? json["id"] : null,
|
||||
name: json.containsKey("name") ? json["name"] : null,
|
||||
logo: json.containsKey("logo") ? json["logo"] : null,
|
||||
@ -274,6 +288,19 @@ class ProcessingItem extends SerializerDeserializer<ProcessingItem> implements A
|
||||
scallingModel: json.containsKey("scalling_model") ? json["scalling_model"] : null,
|
||||
diskIO: json.containsKey("disk_io") ? json["disk_io"] : null,
|
||||
);
|
||||
if (w.logo != null) {
|
||||
//
|
||||
var image = Image.network(w.logo!);
|
||||
image.image
|
||||
.resolve(const ImageConfiguration())
|
||||
.addListener(
|
||||
ImageStreamListener(
|
||||
(ImageInfo info, bool _) {
|
||||
w.width = info.image.width.toDouble();
|
||||
w.height = info.image.height.toDouble();
|
||||
}));
|
||||
}
|
||||
return w;
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {
|
||||
"id": id,
|
||||
@ -336,13 +363,24 @@ class WorkflowItem extends SerializerDeserializer<WorkflowItem> implements Abstr
|
||||
return id ?? "";
|
||||
}
|
||||
|
||||
double? width;
|
||||
double? height;
|
||||
@override
|
||||
double? getWidth() {
|
||||
return width;
|
||||
}
|
||||
@override
|
||||
double? getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@override String getName() {
|
||||
return name ?? "";
|
||||
}
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return WorkflowItem(); }
|
||||
return WorkflowItem(
|
||||
var w = WorkflowItem(
|
||||
id: json.containsKey("id") ? json["id"] : null,
|
||||
name: json.containsKey("name") ? json["name"] : null,
|
||||
logo: json.containsKey("logo") ? json["logo"] : null,
|
||||
@ -358,6 +396,19 @@ class WorkflowItem extends SerializerDeserializer<WorkflowItem> implements Abstr
|
||||
model: json.containsKey("resource_model") ? ResourceModel().deserialize(json["resource_model"]) : null,
|
||||
workflowID: json.containsKey("workflow_id") ? json["workflow_id"] : null,
|
||||
);
|
||||
if (w.logo != null) {
|
||||
//
|
||||
var image = Image.network(w.logo!);
|
||||
image.image
|
||||
.resolve(const ImageConfiguration())
|
||||
.addListener(
|
||||
ImageStreamListener(
|
||||
(ImageInfo info, bool _) {
|
||||
w.width = info.image.width.toDouble();
|
||||
w.height = info.image.height.toDouble();
|
||||
}));
|
||||
}
|
||||
return w;
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {
|
||||
"id": id,
|
||||
@ -420,10 +471,21 @@ class DataItem extends SerializerDeserializer<DataItem> implements AbstractItem<
|
||||
@override String getID() {
|
||||
return id ?? "";
|
||||
}
|
||||
|
||||
double? width;
|
||||
double? height;
|
||||
@override
|
||||
double? getWidth() {
|
||||
return width;
|
||||
}
|
||||
@override
|
||||
double? getHeight() {
|
||||
return height;
|
||||
}
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return DataItem(); }
|
||||
return DataItem(
|
||||
var w = DataItem(
|
||||
id: json.containsKey("id") ? json["id"] : null,
|
||||
name: json.containsKey("name") ? json["name"] : null,
|
||||
logo: json.containsKey("logo") ? json["logo"] : null,
|
||||
@ -441,6 +503,19 @@ class DataItem extends SerializerDeserializer<DataItem> implements AbstractItem<
|
||||
dataType: json.containsKey("data_type") ? json["data_type"] : null,
|
||||
exemple: json.containsKey("exemple") ? json["exemple"] : null,
|
||||
);
|
||||
if (w.logo != null) {
|
||||
//
|
||||
var image = Image.network(w.logo!);
|
||||
image.image
|
||||
.resolve(const ImageConfiguration())
|
||||
.addListener(
|
||||
ImageStreamListener(
|
||||
(ImageInfo info, bool _) {
|
||||
w.width = info.image.width.toDouble();
|
||||
w.height = info.image.height.toDouble();
|
||||
}));
|
||||
}
|
||||
return w;
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {
|
||||
"id": id,
|
||||
@ -505,10 +580,21 @@ class DataCenterItem extends SerializerDeserializer<DataCenterItem> implements A
|
||||
@override String getName() {
|
||||
return name ?? "";
|
||||
}
|
||||
double? width;
|
||||
double? height;
|
||||
@override
|
||||
double? getWidth() {
|
||||
return width;
|
||||
}
|
||||
@override
|
||||
double? getHeight() {
|
||||
return height;
|
||||
}
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return DataCenterItem(); }
|
||||
return DataCenterItem(
|
||||
|
||||
var w = DataCenterItem(
|
||||
id: json.containsKey("id") ? json["id"] : null,
|
||||
name: json.containsKey("name") ? json["name"] : null,
|
||||
logo: json.containsKey("logo") ? json["logo"] : null,
|
||||
@ -526,6 +612,19 @@ class DataCenterItem extends SerializerDeserializer<DataCenterItem> implements A
|
||||
gpus: json.containsKey("gpus") ? fromListJson(json["gpus"], GPU()) : [],
|
||||
ram: json.containsKey("ram") ? RAM().deserialize(json["ram"]) : null,
|
||||
);
|
||||
if (w.logo != null) {
|
||||
//
|
||||
var image = Image.network(w.logo!);
|
||||
image.image
|
||||
.resolve(const ImageConfiguration())
|
||||
.addListener(
|
||||
ImageStreamListener(
|
||||
(ImageInfo info, bool _) {
|
||||
w.width = info.image.width.toDouble();
|
||||
w.height = info.image.height.toDouble();
|
||||
}));
|
||||
}
|
||||
return w;
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {
|
||||
"id": id,
|
||||
@ -680,10 +779,20 @@ class StorageItem extends SerializerDeserializer<StorageItem> implements Abstrac
|
||||
@override String getID() {
|
||||
return id ?? "";
|
||||
}
|
||||
double? width;
|
||||
double? height;
|
||||
@override
|
||||
double? getWidth() {
|
||||
return width;
|
||||
}
|
||||
@override
|
||||
double? getHeight() {
|
||||
return height;
|
||||
}
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return StorageItem(); }
|
||||
return StorageItem(
|
||||
var w = StorageItem(
|
||||
id: json.containsKey("id") ? json["id"] : null,
|
||||
name: json.containsKey("name") ? json["name"] : null,
|
||||
logo: json.containsKey("logo") ? json["logo"] : null,
|
||||
@ -705,6 +814,19 @@ class StorageItem extends SerializerDeserializer<StorageItem> implements Abstrac
|
||||
redundancy: json.containsKey("redundancy") ? json["redundancy"] : null,
|
||||
throughput: json.containsKey("throughput") ? json["throughput"] : null,
|
||||
);
|
||||
if (w.logo != null) {
|
||||
//
|
||||
var image = Image.network(w.logo!);
|
||||
image.image
|
||||
.resolve(const ImageConfiguration())
|
||||
.addListener(
|
||||
ImageStreamListener(
|
||||
(ImageInfo info, bool _) {
|
||||
w.width = info.image.width.toDouble();
|
||||
w.height = info.image.height.toDouble();
|
||||
}));
|
||||
}
|
||||
return w;
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {
|
||||
"id": id,
|
||||
|
@ -3,8 +3,31 @@ import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
|
||||
import 'package:oc_front/core/models/workspace_local.dart';
|
||||
import 'package:oc_front/models/abstract.dart';
|
||||
import 'package:oc_front/models/logs.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
|
||||
class Check extends SerializerDeserializer<Check> {
|
||||
bool is_available = false;
|
||||
|
||||
Check({
|
||||
this.is_available = false,
|
||||
});
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return Check(); }
|
||||
return Check(
|
||||
is_available: json.containsKey("is_available") ? json["is_available"] : false,
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() {
|
||||
return {
|
||||
"is_available": is_available,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class WorkflowExecutions extends SerializerDeserializer<WorkflowExecutions> {
|
||||
List<WorkflowExecution> executions = [];
|
||||
String? executionData;
|
||||
@ -36,6 +59,8 @@ class WorkflowExecution extends SerializerDeserializer<WorkflowExecution> {
|
||||
String? endDate;
|
||||
int? status;
|
||||
String? workflowId;
|
||||
|
||||
List<Log>? logs;
|
||||
|
||||
|
||||
WorkflowExecution({
|
||||
@ -83,6 +108,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
|
||||
List<dynamic> workflows;
|
||||
Graph? graph;
|
||||
Scheduler? schedule;
|
||||
bool scheduleActive = false;
|
||||
|
||||
Workflow({
|
||||
this.id,
|
||||
@ -94,6 +120,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
|
||||
this.workflows = const [],
|
||||
this.graph,
|
||||
this.schedule,
|
||||
this.scheduleActive = false,
|
||||
});
|
||||
|
||||
String getID() {
|
||||
@ -110,6 +137,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
|
||||
processing: json.containsKey("processings") ? json["processings"] : [],
|
||||
datacenter: json.containsKey("datacenters") ? json["datacenters"] : [],
|
||||
data: json.containsKey("datas") ? json["datas"] : [],
|
||||
scheduleActive: json.containsKey("schedule_active") ? json["schedule_active"] : false,
|
||||
storage: json.containsKey("storages") ? json["storages"] : [],
|
||||
graph: json.containsKey("graph") ? Graph().deserialize(json["graph"]) : null,
|
||||
schedule: json.containsKey("schedule") ? Scheduler().deserialize(json["schedule"]) : null,
|
||||
@ -124,6 +152,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
|
||||
"storages": storage,
|
||||
"processings": processing,
|
||||
"workflows": workflows,
|
||||
"schedule_active": scheduleActive,
|
||||
"schedule": schedule?.serialize(),
|
||||
};
|
||||
if (graph != null) {
|
||||
@ -135,6 +164,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
|
||||
void fromDashboard(Map<String, dynamic> j) {
|
||||
id = j["id"];
|
||||
name = j["name"];
|
||||
scheduleActive = j["schedule_active"];
|
||||
if (j.containsKey("graph")) {
|
||||
graph = Graph();
|
||||
graph!.fromDashboard(j["graph"]);
|
||||
@ -149,6 +179,7 @@ class Workflow extends SerializerDeserializer<Workflow> {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"graph": graph?.toDashboard(),
|
||||
"schedule_active": scheduleActive,
|
||||
"schedule": schedule?.toDashboard(),
|
||||
};
|
||||
}
|
||||
@ -160,13 +191,15 @@ class Scheduler extends SerializerDeserializer<Scheduler> {
|
||||
String? cron;
|
||||
DateTime? start;
|
||||
DateTime? end;
|
||||
int? mode;
|
||||
|
||||
Scheduler({
|
||||
this.id,
|
||||
this.name,
|
||||
this.cron,
|
||||
this.start,
|
||||
this.end
|
||||
this.end,
|
||||
this.mode,
|
||||
});
|
||||
|
||||
void fromDashboard(Map<String, dynamic> j) {
|
||||
@ -177,13 +210,14 @@ class Scheduler extends SerializerDeserializer<Scheduler> {
|
||||
if (j.containsKey("end") && j["end"] != null) {
|
||||
end = DateTime.parse(j["end"]);
|
||||
}
|
||||
|
||||
mode = int.parse(j["mode"].toString());
|
||||
}
|
||||
Map<String, dynamic> toDashboard() {
|
||||
return {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"cron": cron,
|
||||
"mode": int.parse(mode.toString()),
|
||||
"start": start?.toIso8601String(),
|
||||
"end": end?.toIso8601String(),
|
||||
};
|
||||
@ -196,6 +230,7 @@ class Scheduler extends SerializerDeserializer<Scheduler> {
|
||||
id: json.containsKey("id") ? json["id"] : null,
|
||||
name: json.containsKey("name") ? json["name"] : "",
|
||||
cron: json.containsKey("cron") ? json["cron"] : "",
|
||||
mode: json.containsKey("mode") ? json["mode"] : "",
|
||||
start: json.containsKey("start") ? DateTime.parse(json["start"]) : null,
|
||||
end: json.containsKey("end") ? DateTime.parse(json["end"]) : null,
|
||||
);
|
||||
@ -204,6 +239,7 @@ class Scheduler extends SerializerDeserializer<Scheduler> {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"cron": cron ?? "",
|
||||
"mode": int.parse(mode.toString()),
|
||||
"start": start?.toIso8601String(),
|
||||
"end": end?.toIso8601String(),
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/logs_service.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/workflow_execution_service.dart';
|
||||
import 'package:oc_front/models/workflow.dart';
|
||||
import 'package:oc_front/pages/abstract_page.dart';
|
||||
@ -35,6 +36,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
|
||||
"${widget.end.year}-${widget.end.month > 9 ? widget.end.month : "0${widget.end.month}"}-${widget.end.day > 9 ? widget.end.day : "0${widget.end.day}"}"], {}),
|
||||
builder: (ctx, as) {
|
||||
Map<String, List<WorkflowExecution>> data = {};
|
||||
|
||||
if (as.hasData && as.data!.data != null) {
|
||||
for (var element in as.data!.data!.executions) {
|
||||
if (element.executionData == null) { continue; }
|
||||
@ -47,6 +49,49 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
|
||||
}
|
||||
}
|
||||
GlobalKey<ScheduleWidgetState> k = GlobalKey<ScheduleWidgetState>();
|
||||
for (var da in data.keys) {
|
||||
for (var exec in data[da]!) {
|
||||
String start = "";
|
||||
String end = "";
|
||||
try {
|
||||
if (exec.endDate != null && exec.endDate != "") {
|
||||
var startD = DateTime.parse(exec.executionData!);
|
||||
var endD = DateTime.parse(exec.endDate!);
|
||||
var diff = endD.difference(startD);
|
||||
if (diff.inDays < 30) {
|
||||
var rest = ((30 - diff.inDays) ~/ 2) - 1;
|
||||
start = (startD.subtract(Duration(days: rest)).microsecondsSinceEpoch).toString();
|
||||
end = (endD.add(Duration(days: rest)).microsecondsSinceEpoch).toString();
|
||||
} else {
|
||||
start = (startD.microsecondsSinceEpoch).toString();
|
||||
end = (startD.add( const Duration(days: 29)).microsecondsSinceEpoch).toString();
|
||||
}
|
||||
} else {
|
||||
start = (DateTime.parse(exec.executionData!).subtract( const Duration(days: 14)).microsecondsSinceEpoch).toString();
|
||||
end = (DateTime.parse(exec.executionData!).add( const Duration(days: 14)).microsecondsSinceEpoch).toString();
|
||||
}
|
||||
} catch(e) { /* */ }
|
||||
k.currentState?.setState(() { k.currentState?.widget.loading = true; });
|
||||
LogsService().search(context, [], {
|
||||
"workflow_execution_id": exec.id,
|
||||
"start": start,
|
||||
"end": end
|
||||
}).then((value) {
|
||||
if (value.data != null) {
|
||||
var d = value.data!;
|
||||
for( var r in d.result) {
|
||||
for (var element in r.logs) {
|
||||
element.level = r.level;
|
||||
exec.logs ??= [];
|
||||
exec.logs!.add(element);
|
||||
}
|
||||
exec.logs?.sort((a, b) => a.timestamp!.compareTo(b.timestamp!));
|
||||
}
|
||||
}
|
||||
k.currentState?.setState(() { k.currentState?.widget.loading = false; });
|
||||
});
|
||||
}
|
||||
}
|
||||
return Column( children: [
|
||||
Container( color: const Color.fromRGBO(38, 166, 154, 1),
|
||||
height: 50, width: MediaQuery.of(context).size.width,
|
||||
@ -69,7 +114,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
|
||||
validator: (value) {
|
||||
return null;
|
||||
},
|
||||
resetIcon: const Icon(Icons.close, size: 15),
|
||||
resetIcon: null,
|
||||
onShowPicker: (context, currentValue) async {
|
||||
var date = await showDatePicker(
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
@ -94,6 +139,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
|
||||
);
|
||||
return date;
|
||||
},
|
||||
|
||||
format: intl.DateFormat('y-M-dd hh:mm:ss'),
|
||||
initialValue: widget.start,
|
||||
onChanged: (value) {
|
||||
@ -126,7 +172,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
|
||||
validator: (value) {
|
||||
return null;
|
||||
},
|
||||
resetIcon: const Icon(Icons.close, size: 15),
|
||||
resetIcon: null,
|
||||
onShowPicker: (context, currentValue) async {
|
||||
var date = await showDatePicker(
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
@ -176,7 +222,7 @@ class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
|
||||
)
|
||||
]))
|
||||
),
|
||||
ScheduleWidget( key: k, data: data, start: widget.start, end : widget.end, isList: widget.isList )
|
||||
ScheduleWidget( key: k, data: data, start: widget.start, end : widget.end, isList: widget.isList, )
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ class WorkflowFactory implements AbstractFactory {
|
||||
@override Widget factory(GoRouterState state, List<String> args) { return WorkflowPageWidget(); }
|
||||
@override void search(BuildContext context) { }
|
||||
}
|
||||
bool getAll = true;
|
||||
|
||||
class WorkflowPageWidget extends StatefulWidget {
|
||||
WorkflowPageWidget () : super(key: WorkflowFactory.key);
|
||||
@ -107,7 +108,6 @@ final WorflowService _service = WorflowService();
|
||||
item.position?.x = (item.position?.x ?? 0) + 52.5;
|
||||
item.position?.y = (item.position?.y ?? 0) + 52.5;
|
||||
}
|
||||
print(dash.getZoomFactor());
|
||||
updateW.graph?.zoom = dash.getZoomFactor();
|
||||
await _service.put(context, id, updateW.serialize(), {});
|
||||
}
|
||||
@ -128,7 +128,9 @@ final WorflowService _service = WorflowService();
|
||||
Widget menuExtension() {
|
||||
var quart = MediaQuery.of(context).size.width / 6;
|
||||
return MenuWorkspaceWidget(simpliest: true, width: quart > 80 ? quart : 80,
|
||||
onWorkspaceChange: () { dash.selectedLeftMenuKey.currentState?.setState(() { }); });
|
||||
onWorkspaceChange: () {
|
||||
dash.selectedLeftMenuKey.currentState?.setState(() { });
|
||||
});
|
||||
}
|
||||
|
||||
Widget onDashboardAlertOpened(BuildContext context, Dashboard dash) {
|
||||
@ -148,7 +150,7 @@ final WorflowService _service = WorflowService();
|
||||
dashboard: dash,
|
||||
itemWidget: itemBuild,
|
||||
categories: const ["processing", "data", "datacenter", "storage", "workflows"],
|
||||
draggableItemBuilder: (cat) => WorkspaceLocal.byTopic(cat, true).toList(),
|
||||
draggableItemBuilder: (cat) => WorkspaceLocal.byTopic(cat, false),
|
||||
itemWidgetTooltip: itemTooltipBuild,
|
||||
innerMenuWidth: quart > 80 ? quart : 80,
|
||||
menuExtension: menuExtension,
|
||||
|
@ -113,11 +113,11 @@ class NewBoxWidgetState<T extends SerializerDeserializer<dynamic>> extends State
|
||||
? MouseCursor.defer : SystemMouseCursors.click,
|
||||
onTap: () async {
|
||||
if (widget._selected == null || widget._selected!.isEmpty) { return; }
|
||||
if (widget._selected != null && widget.dash.load != null) {
|
||||
await widget.dash.load!(widget._selected ?? "");
|
||||
WorkspaceLocal.init(context, true);
|
||||
}
|
||||
widget.dash.isOpened = true;
|
||||
if (widget._selected != null && widget.dash.load != null) {
|
||||
WorkspaceLocal.changeWorkspaceByName(widget._selected!.split("~")[1]);
|
||||
await widget.dash.load!(widget._selected ?? "");
|
||||
}
|
||||
widget.dash.notifyListeners();
|
||||
Navigator.pop(context);
|
||||
},
|
||||
|
@ -1,20 +1,46 @@
|
||||
import 'package:alert_banner/exports.dart';
|
||||
import 'package:cron/cron.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
|
||||
import 'package:flutter_advanced_switch/flutter_advanced_switch.dart';
|
||||
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/check_service.dart';
|
||||
import 'package:oc_front/pages/workflow.dart';
|
||||
import 'package:oc_front/widgets/dialog/alert.dart';
|
||||
|
||||
class SchedulerFormsWidget extends StatefulWidget {
|
||||
Dashboard item;
|
||||
String purpose = "";
|
||||
bool? booking;
|
||||
Function validate = () {};
|
||||
SchedulerFormsWidget ({ super.key, required this.item });
|
||||
SchedulerFormsWidget ({ super.key, required this.item, });
|
||||
@override SchedulerFormsWidgetState createState() => SchedulerFormsWidgetState();
|
||||
}
|
||||
class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
CheckService check = CheckService();
|
||||
@override Widget build(BuildContext context) {
|
||||
if (widget.item.schedulerState["service"] == null) { widget.item.schedulerState["service"] = true; }
|
||||
try {
|
||||
if (widget.item.scheduler["mode"] == null) { widget.item.scheduler["mode"] = 1; }
|
||||
} catch (e) {
|
||||
widget.item.scheduler = { "mode": 1 };
|
||||
}
|
||||
DateTime? start;
|
||||
DateTime? end;
|
||||
if (widget.item.scheduler["start"] != null) {
|
||||
start = DateTime.parse(widget.item.scheduler["start"]!);
|
||||
if (start.isBefore(DateTime.now()) && !dash.scheduleActive) {
|
||||
start = DateTime.now().add(const Duration(minutes: 5));
|
||||
widget.item.scheduler["start"] = start.toUtc().toIso8601String();
|
||||
}
|
||||
}
|
||||
if (widget.item.scheduler["end"] != null) {
|
||||
end = DateTime.parse(widget.item.scheduler["end"]!);
|
||||
if (end.isBefore(DateTime.now()) && !dash.scheduleActive) {
|
||||
end = DateTime.now().add(const Duration(minutes: 5));
|
||||
widget.item.scheduler["end"] = end.toUtc().toIso8601String();
|
||||
}
|
||||
}
|
||||
List<GlobalKey<FormFieldState>> formKeys = [GlobalKey<FormFieldState>(), GlobalKey<FormFieldState>(),
|
||||
GlobalKey<FormFieldState>(), GlobalKey<FormFieldState>()];
|
||||
return Column( children: [
|
||||
@ -27,7 +53,7 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
Container(height: 20),
|
||||
AdvancedSwitch(
|
||||
width: 140,
|
||||
initialValue: widget.item.schedulerState["service"] == true,
|
||||
initialValue: widget.item.scheduler["mode"] == 1,
|
||||
activeColor: Colors.green, inactiveColor: Colors.green,
|
||||
activeChild: const Text("service", style: TextStyle(color: Colors.white)),
|
||||
inactiveChild: const Text("cron task", style: TextStyle(color: Colors.white)),
|
||||
@ -35,8 +61,8 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
onChanged: (value) {
|
||||
Future.delayed(const Duration(milliseconds: 100), () =>
|
||||
setState(() {
|
||||
widget.item.schedulerState["service"] = value;
|
||||
if ((widget.item.schedulerState["service"] == true )) { widget.item.scheduler.remove("cron"); }
|
||||
widget.item.scheduler["mode"] = value == true ? 1 : 0;
|
||||
if ((widget.item.scheduler["mode"] == 1 )) { widget.item.scheduler.remove("cron"); }
|
||||
}));
|
||||
},),
|
||||
Container(height: 5),
|
||||
@ -44,12 +70,18 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
child: Container( height: 40, margin: const EdgeInsets.only(top: 5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
|
||||
child: TextFormField( key: formKeys[0],
|
||||
initialValue: "${widget.item.schedulerState["service"] == true ? "" : "cron_"}${widget.item.scheduler["name"] ?? "${widget.item.name}_event"}",
|
||||
initialValue: "${widget.item.scheduler["mode"] == 1 ? "" : "cron_"}${widget.item.scheduler["name"] ?? "${widget.item.name}_event"}",
|
||||
enabled: !dash.scheduleActive,
|
||||
onChanged: (value) {
|
||||
Future.delayed(const Duration(seconds: 100), () {
|
||||
if (widget.item.scheduler["name"] == value) {
|
||||
widget.item.save!(widget.item.id);
|
||||
}
|
||||
});
|
||||
widget.item.scheduler["name"] = value;
|
||||
},
|
||||
onSaved: (value) {
|
||||
widget.item.scheduler["name"] = value ?? "${widget.item.schedulerState["service"] == true ? "" : "cron_"}${widget.item.scheduler["name"] ?? "${widget.item.name}_event"}";
|
||||
widget.item.scheduler["name"] = value ?? "${widget.item.scheduler["mode"] == 1 ? "" : "cron_"}${widget.item.scheduler["name"] ?? "${widget.item.name}_event"}";
|
||||
},
|
||||
validator: (value) => value == null || value.isEmpty ? "not empty" : null,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
@ -72,10 +104,8 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
child: Container( height: 40, margin: const EdgeInsets.only(top: 5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
|
||||
child: DateTimeField( key: formKeys[1],
|
||||
resetIcon: const Icon(Icons.close, size: 15),
|
||||
onSaved: (value) {
|
||||
widget.item.scheduler["start"] = "${(value ?? DateTime.now()).toIso8601String()}Z";
|
||||
},
|
||||
enabled: !dash.scheduleActive,
|
||||
resetIcon: null,
|
||||
onShowPicker: (context, currentValue) async {
|
||||
var date = await showDatePicker(
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
@ -94,38 +124,53 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
return w;
|
||||
},
|
||||
context: context,
|
||||
firstDate: DateTime(1900),
|
||||
initialDate: DateTime.parse(widget.item.scheduler["start"] ?? currentValue?.toIso8601String() ?? ""),
|
||||
firstDate: dash.scheduleActive ? DateTime(1900) : DateTime.now().add(const Duration(minutes: 5)),
|
||||
initialDate: DateTime.parse( start?.toLocal().toIso8601String()
|
||||
?? currentValue?.toIso8601String()
|
||||
?? DateTime.now().add(const Duration(minutes: 5)).toUtc().toIso8601String()).toLocal(),
|
||||
lastDate: DateTime(2100)
|
||||
);
|
||||
if (date != null) {
|
||||
var time = await showTimePicker(context: context,
|
||||
initialTime: TimeOfDay(hour: date.hour, minute: date.minute),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
Widget w = Theme(
|
||||
data: ThemeData(
|
||||
cardTheme: CardTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
|
||||
dialogTheme: DialogTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
|
||||
colorScheme: ColorScheme.light(
|
||||
background: Colors.grey.shade300,
|
||||
tertiary: Colors.grey,
|
||||
secondary: Colors.grey,
|
||||
primary: Colors.black),
|
||||
),
|
||||
child: child ?? Container(),
|
||||
var n = TimeOfDay.now();
|
||||
TimeOfDay? time = n;
|
||||
var count = 0;
|
||||
while(((time?.hour ?? 0) + ((time?.minute ?? 0) / 100)) <= (n.hour + ((n.minute + 1) / 100)) ) {
|
||||
if (count > 0 && time != null) {
|
||||
showAlertBanner( context, () {},
|
||||
const AlertAlertBannerChild(
|
||||
text: "must be at least 1 minute from now to let system check info"),// <-- Put any widget here you want!
|
||||
alertBannerLocation: AlertBannerLocation.bottom,);
|
||||
}
|
||||
time = await showTimePicker(context: context,
|
||||
initialTime: TimeOfDay(hour: date.hour, minute: date.minute),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
Widget w = Theme(
|
||||
data: ThemeData(
|
||||
cardTheme: CardTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
|
||||
dialogTheme: DialogTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
|
||||
colorScheme: ColorScheme.light(
|
||||
background: Colors.grey.shade300,
|
||||
tertiary: Colors.grey,
|
||||
secondary: Colors.grey,
|
||||
primary: Colors.black),
|
||||
),
|
||||
child: child ?? Container(),
|
||||
);
|
||||
return w;
|
||||
},
|
||||
);
|
||||
return w;
|
||||
},
|
||||
);
|
||||
date = date.add(Duration(hours: time?.hour ?? 0, minutes: time?.minute ?? 0));
|
||||
if (time == null) { return DateTime.now().add( const Duration(minutes: 1)); }
|
||||
count++;
|
||||
}
|
||||
date = date.add(Duration(hours: time?.hour ?? 0, minutes: time?.minute ?? 0));
|
||||
widget.item.scheduler["start"] = date.toUtc().toIso8601String();
|
||||
widget.item.save!(widget.item.id);
|
||||
}
|
||||
return date;
|
||||
},
|
||||
format: intl.DateFormat('y-M-dd hh:mm:ss'),
|
||||
initialValue: DateTime.parse(widget.item.scheduler["start"] ?? DateTime.now().toIso8601String()),
|
||||
onChanged: (value) {
|
||||
widget.item.scheduler["start"] = "${(value ?? DateTime.now()).toIso8601String()}Z";
|
||||
},
|
||||
format: intl.DateFormat('y-M-dd HH:mm:ss'),
|
||||
initialValue: start?.toLocal() ?? DateTime.now(),
|
||||
onChanged: (value) { },
|
||||
validator: (value) => value == null ? "not empty" : null,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: const InputDecoration(
|
||||
@ -147,8 +192,14 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
child: Container( height: 40, margin: const EdgeInsets.only(top: 5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
|
||||
child: DateTimeField( key: formKeys[2],
|
||||
enabled: !dash.scheduleActive,
|
||||
validator: (value) {
|
||||
return value == null && !(widget.item.schedulerState["service"] == true ) ? "not empty" : null;
|
||||
return value == null && !(widget.item.scheduler["mode"] == 1 ) ? "not empty" : null;
|
||||
},
|
||||
onChanged: (value) {
|
||||
if (value == null) {
|
||||
widget.item.scheduler.remove("end");
|
||||
}
|
||||
},
|
||||
resetIcon: const Icon(Icons.close, size: 15),
|
||||
onShowPicker: (context, currentValue) async {
|
||||
@ -169,45 +220,52 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
return w;
|
||||
},
|
||||
context: context,
|
||||
firstDate: DateTime(1900),
|
||||
initialDate: DateTime.parse(widget.item.scheduler["start"] ?? currentValue?.toIso8601String() ?? ""),
|
||||
firstDate: dash.scheduleActive ? DateTime(1900) : DateTime.now().add(const Duration(minutes: 5)),
|
||||
initialDate: DateTime.parse( end?.toLocal().toIso8601String()
|
||||
?? currentValue?.toIso8601String()
|
||||
?? DateTime.now().add(const Duration(minutes: 5)).toUtc().toIso8601String()).toLocal(),
|
||||
lastDate: DateTime(2100)
|
||||
);
|
||||
if (date != null) {
|
||||
var time = await showTimePicker(context: context,
|
||||
initialTime: TimeOfDay(hour: date.hour, minute: date.minute),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
Widget w = Theme(
|
||||
data: ThemeData(
|
||||
cardTheme: CardTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
|
||||
dialogTheme: DialogTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
|
||||
colorScheme: ColorScheme.light(
|
||||
background: Colors.grey.shade300,
|
||||
tertiary: Colors.grey,
|
||||
secondary: Colors.grey,
|
||||
primary: Colors.black),
|
||||
),
|
||||
child: child ?? Container(),
|
||||
// ignore: use_build_context_synchronously
|
||||
var n = TimeOfDay.now();
|
||||
TimeOfDay? time = TimeOfDay(hour: date.hour, minute: date.minute);
|
||||
var count = 0;
|
||||
while(((time?.hour ?? 0) + (time?.minute ?? 0 / 100)) <= (n.hour + ((n.minute + 1) / 100)) ) {
|
||||
if (count > 0 && time != null) {
|
||||
showAlertBanner( context, () {},
|
||||
const AlertAlertBannerChild(text: "must be at least 1 minute from now to let system check info"),// <-- Put any widget here you want!
|
||||
alertBannerLocation: AlertBannerLocation.bottom,);
|
||||
}
|
||||
time = await showTimePicker(context: context,
|
||||
initialTime: TimeOfDay(hour: date.hour, minute: date.minute),
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
Widget w = Theme(
|
||||
data: ThemeData(
|
||||
cardTheme: CardTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
|
||||
dialogTheme: DialogTheme(elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0))),
|
||||
colorScheme: ColorScheme.light(
|
||||
background: Colors.grey.shade300,
|
||||
tertiary: Colors.grey,
|
||||
secondary: Colors.grey,
|
||||
primary: Colors.black),
|
||||
),
|
||||
child: child ?? Container(),
|
||||
);
|
||||
return w;
|
||||
},
|
||||
);
|
||||
return w;
|
||||
},
|
||||
);
|
||||
date = date.add(Duration(hours: time?.hour ?? 0, minutes: time?.minute ?? 0));
|
||||
if (time == null) { return null; }
|
||||
count++;
|
||||
}
|
||||
date = date.add(Duration(hours: time?.hour ?? 0, minutes: time?.minute ?? 0));
|
||||
widget.item.scheduler["end"] = date.toUtc().toIso8601String();
|
||||
widget.item.save!(widget.item.id);
|
||||
}
|
||||
return date;
|
||||
},
|
||||
|
||||
format: intl.DateFormat('y-M-dd hh:mm:ss'),
|
||||
initialValue: widget.item.scheduler["end"] != null ? DateTime.parse(widget.item.scheduler["end"]!) : null,
|
||||
onSaved: (value) {
|
||||
if (value != null) {
|
||||
widget.item.scheduler["end"] = "${(value).toIso8601String()}Z";
|
||||
}
|
||||
},
|
||||
onChanged: (value) {
|
||||
if (value == null) { return; }
|
||||
widget.item.scheduler["end"] = "${value.toIso8601String()}Z";
|
||||
},
|
||||
format: intl.DateFormat('y-M-dd HH:mm:ss'),
|
||||
initialValue: end?.toLocal(),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: InputDecoration(
|
||||
fillColor: Colors.white,
|
||||
@ -215,7 +273,7 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
filled: true,
|
||||
alignLabelWithHint: false,
|
||||
hintText: "enter end event...",
|
||||
labelText: "end event${!(widget.item.schedulerState["service"] == true) ? "*" : ""}",
|
||||
labelText: "end event${!(widget.item.scheduler["mode"] == 1) ? "*" : ""}",
|
||||
errorStyle: const TextStyle(fontSize: 0),
|
||||
hintStyle: const TextStyle(fontSize: 10),
|
||||
labelStyle: const TextStyle(fontSize: 10),
|
||||
@ -224,12 +282,18 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
),
|
||||
))),
|
||||
widget.item.schedulerState["service"] == true ? Container() : Tooltip( message: "schedule",
|
||||
widget.item.scheduler["mode"] == 1 ? Container() : Tooltip( message: "schedule",
|
||||
child: Container( height: 40, margin: const EdgeInsets.only(top: 5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
|
||||
child: TextFormField( key: formKeys[3],
|
||||
enabled: !dash.scheduleActive,
|
||||
initialValue: widget.item.scheduler["cron"],
|
||||
onChanged: (value) {
|
||||
Future.delayed(const Duration(seconds: 100), () {
|
||||
if (widget.item.scheduler["cron"] == value) {
|
||||
widget.item.save!(widget.item.id);
|
||||
}
|
||||
});
|
||||
widget.item.scheduler["cron"] = value;
|
||||
},
|
||||
onSaved: (value) {
|
||||
@ -263,9 +327,48 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
),
|
||||
))),
|
||||
const Divider(color: Colors.grey),
|
||||
Tooltip( message: "save",
|
||||
Tooltip( message: "check booking",
|
||||
child: InkWell( mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () {
|
||||
if (dash.scheduler["start"] == null ) {
|
||||
DateTime now = DateTime.now().add(const Duration(minutes: 5));
|
||||
dash.scheduler["start"] = now.toUtc().toIso8601String();
|
||||
}
|
||||
var s = DateTime.parse(dash.scheduler["start"]).toUtc().toIso8601String();
|
||||
var e = "";
|
||||
if (dash.scheduler["end"] == null) {
|
||||
e = DateTime.parse(dash.scheduler["start"]).add(const Duration(seconds: 5)).toUtc().toIso8601String();
|
||||
} else {
|
||||
e = DateTime.parse(dash.scheduler["end"]).toUtc().toIso8601String();
|
||||
}
|
||||
check.search(context, [widget.item.id ?? "", s.substring(0, 19), e.substring(0, 19)], {}).then(
|
||||
(v) {
|
||||
if (v.data == null) { return; }
|
||||
widget.booking = v.data!.is_available;
|
||||
if (v.data!.is_available) {
|
||||
showAlertBanner( context, () {},
|
||||
const InfoAlertBannerChild(text: "no booking found at this date"),// <-- Put any widget here you want!
|
||||
alertBannerLocation: AlertBannerLocation.bottom,);
|
||||
} else {
|
||||
showAlertBanner( context, () {},
|
||||
const AlertAlertBannerChild(text: "booking found at this date"),// <-- Put any widget here you want!
|
||||
alertBannerLocation: AlertBannerLocation.bottom,);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
);
|
||||
}, child: Container( margin: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5),
|
||||
border: Border.all(color: widget.booking == null ? Colors.black : (widget.booking == true ? Colors.green : Colors.red), width: 1)),
|
||||
width: 140, height: 30,
|
||||
child: Icon(
|
||||
Icons.verified_outlined, color:widget.booking == null ? Colors.black : (widget.booking == true? Colors.green : Colors.red)),
|
||||
))
|
||||
),
|
||||
Tooltip( message: dash.scheduleActive ? "unbook" : "book",
|
||||
child: InkWell( mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () {
|
||||
dash.scheduleActive = !dash.scheduleActive;
|
||||
for (var k in formKeys) {
|
||||
if (k.currentState != null) {
|
||||
if (!k.currentState!.validate()) {
|
||||
@ -273,12 +376,18 @@ class SchedulerFormsWidgetState extends State<SchedulerFormsWidget> {
|
||||
} else { k.currentState!.save();}
|
||||
}
|
||||
}
|
||||
widget.item.schedulerSave = true;
|
||||
if (dash.scheduler["start"] == null ) {
|
||||
DateTime now = DateTime.now().add(const Duration(minutes: 5));
|
||||
dash.scheduler["start"] = now.toUtc().toIso8601String();
|
||||
}
|
||||
widget.item.save!(widget.item.id);
|
||||
setState(() { });
|
||||
}, child: Container( margin: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5), border: Border.all(color: Colors.black, width: 1)),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(5),
|
||||
border: Border.all(color: dash.scheduleActive ? Colors.green : Colors.black, width: 1)),
|
||||
width: 140, height: 30,
|
||||
child: const Icon(Icons.save_outlined, color: Colors.black),
|
||||
child: Icon(
|
||||
dash.scheduleActive ? Icons.cancel_schedule_send : Icons.schedule_send, color: dash.scheduleActive ? Colors.green : Colors.black),
|
||||
))
|
||||
),
|
||||
]);
|
||||
|
@ -32,7 +32,8 @@ class ItemRowWidgetState extends State<ItemRowWidget> {
|
||||
height: 100,
|
||||
decoration: BoxDecoration( border: Border(bottom: BorderSide(color: Colors.grey.shade300)) ),
|
||||
child: Row( children: [
|
||||
widget.low ? Container( padding: const EdgeInsets.only(left: 10),) : Padding( padding: const EdgeInsets.all(10),
|
||||
widget.low ? Container( padding: const EdgeInsets.only(left: 10),) : Container( padding: const EdgeInsets.all(10),
|
||||
constraints: BoxConstraints(maxWidth: imageSize, minWidth: imageSize),
|
||||
child: image ?? Image.network('https://get-picto.com/wp-content/uploads/2024/01/logo-instagram-png.webp',
|
||||
height: imageSize, width: imageSize)),
|
||||
Container(
|
||||
|
824
lib/widgets/lib/tranformablebox.dart
Normal file
824
lib/widgets/lib/tranformablebox.dart
Normal file
@ -0,0 +1,824 @@
|
||||
/*
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2023, Birju Vachhani
|
||||
*/
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:box_transform/box_transform.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_box_transform/flutter_box_transform.dart';
|
||||
|
||||
/// A widget that allows you to resize and drag a box around a widget.
|
||||
class TransformableBox extends StatefulWidget {
|
||||
/// If you need more control over the [TransformableBox] you can pass a
|
||||
/// custom [TransformableBoxController] instance through the [controller]
|
||||
/// parameter.
|
||||
///
|
||||
/// If you do not specify one, a default [TransformableBoxController] instance
|
||||
/// will be created internally, along with its lifecycle.
|
||||
final TransformableBoxController? controller;
|
||||
|
||||
/// A builder function that is used to build the content of the
|
||||
/// [TransformableBox]. This is the physical widget you wish to show resizable
|
||||
/// handles on. It's most commonly something like an image widget, but it
|
||||
/// could be anything you want to have resizable & draggable box handles on.
|
||||
final TransformableChildBuilder contentBuilder;
|
||||
|
||||
/// A builder function that is used to build the corners handles of the
|
||||
/// [TransformableBox]. If you don't specify it, the default handles will be
|
||||
/// used.
|
||||
///
|
||||
/// Note that this will build for all four corners of the rectangle.
|
||||
final HandleBuilder cornerHandleBuilder;
|
||||
|
||||
/// A builder function that is used to build the side handles of the
|
||||
/// [TransformableBox]. If you don't specify it, the default handles will be
|
||||
/// used.
|
||||
///
|
||||
/// Note that this will build for all four sides of the rectangle.
|
||||
final HandleBuilder sideHandleBuilder;
|
||||
|
||||
/// The size of the gesture response area of the handles. If you don't
|
||||
/// specify it, the default value will be used.
|
||||
///
|
||||
/// This is similar to Flutter's [MaterialTapTargetSize] property, in which
|
||||
/// the actual handle size is smaller than the gesture response area. This is
|
||||
/// done to improve accessibility and usability of the handles; users will not
|
||||
/// need cursor precision over the handle's pixels to be able to perform
|
||||
/// operations with them, they need only to be able to reach the handle's
|
||||
/// gesture response area to make it forgiving.
|
||||
///
|
||||
/// The default value is 24 pixels in diameter.
|
||||
final double handleTapSize;
|
||||
|
||||
/// A set containing handles that are enabled. This is different from
|
||||
/// [visibleHandles].
|
||||
///
|
||||
/// [enabledHandles] determines which handles are
|
||||
/// interactive and can be used to resize the box. [visibleHandles]
|
||||
/// determines which handles are visible. If a handle is visible but not
|
||||
/// enabled, it will not be interactive. If a handle is enabled but not
|
||||
/// visible, it will not be shown and will not be interactive.
|
||||
final Set<HandlePosition> enabledHandles;
|
||||
|
||||
/// A set containing which handles to show. This is different from
|
||||
/// [enabledHandles].
|
||||
///
|
||||
/// [enabledHandles] determines which handles are
|
||||
/// interactive and can be used to resize the box. [visibleHandles]
|
||||
/// determines which handles are visible. If a handle is visible but not
|
||||
/// enabled, it will not be interactive. If a handle is enabled but not
|
||||
/// visible, it will not be shown and will not be interactive.
|
||||
final Set<HandlePosition> visibleHandles;
|
||||
|
||||
/// The initial box that will be used to position set the initial size of
|
||||
/// the [TransformableBox] widget.
|
||||
///
|
||||
/// This initial box will be mutated by the [TransformableBoxController] through
|
||||
/// different dragging, panning, and resizing operations.
|
||||
///
|
||||
/// [Rect] is immutable, so a new [Rect] instance will be created every time
|
||||
/// the [TransformableBoxController] mutates the box. You can acquire your
|
||||
/// updated box through the [onChanged] callback or through an externally
|
||||
/// provided [TransformableBoxController] instance.
|
||||
final Rect rect;
|
||||
|
||||
/// The initial flip that will be used to set the initial flip of the
|
||||
/// [TransformableBox] widget. Normally, flipping is done by the user through
|
||||
/// the handles, but you can set the initial flip through this parameter in
|
||||
/// case the initial state of the box is in a flipped state.
|
||||
///
|
||||
/// This utility cannot predicate if a box is flipped or not, so you will
|
||||
/// need to provide the correct initial flip state.
|
||||
///
|
||||
/// Note that the flip is optional, if you're resizing an image, for example,
|
||||
/// you might want to allow flipping of the image when the user drags the
|
||||
/// handles to opposite corners of the box. This flip behavior is entirely
|
||||
/// optional and will allow handling such cases.
|
||||
///
|
||||
/// You can leave it at the default [Flip.none] if flipping is not desired.
|
||||
/// Note that this will not prevent the drag handles from crossing to
|
||||
/// opposite corners of the box, it will only give oyu a lack of information
|
||||
/// on the state of the box if flipping were to occur.
|
||||
final Flip flip;
|
||||
|
||||
/// A box that will contain the [rect] inside of itself, forcing [rect] to
|
||||
/// be clamped inside of this [clampingRect].
|
||||
final Rect clampingRect;
|
||||
|
||||
/// A set of constraints that will be applied to the [rect] when it is
|
||||
/// resized by the [TransformableBoxController].
|
||||
final BoxConstraints constraints;
|
||||
|
||||
/// Whether the box is resizable or not. Setting this to false will disable
|
||||
/// all resizing operations. This is a convenience parameter that will ignore
|
||||
/// the [enabledHandles] parameter and set all handles to disabled.
|
||||
final bool resizable;
|
||||
|
||||
/// Whether the box is movable or not. Setting this to false will disable
|
||||
/// all moving operations.
|
||||
final bool draggable;
|
||||
|
||||
/// Whether to allow flipping of the box while resizing. If this is set to
|
||||
/// true, the box will flip when the user drags the handles to opposite
|
||||
/// corners of the rect.
|
||||
final bool allowFlippingWhileResizing;
|
||||
|
||||
/// Decides whether to flip the contents of the box when the box is flipped.
|
||||
/// If this is set to true, the contents will be flipped when the box is
|
||||
/// flipped.
|
||||
final bool allowContentFlipping;
|
||||
|
||||
/// How to align the handles.
|
||||
final HandleAlignment handleAlignment;
|
||||
|
||||
/// The callback function that is used to resolve the [ResizeMode] based on
|
||||
/// the pressed keys on the keyboard.
|
||||
final ValueGetter<ResizeMode> resizeModeResolver;
|
||||
|
||||
/// A callback that is called every time the [TransformableBox] is updated.
|
||||
/// This is called every time the [TransformableBoxController] mutates the box
|
||||
/// or the flip.
|
||||
final RectChangeEvent? onChanged;
|
||||
|
||||
/// A callback that is called when [TransformableBox] triggers a pointer down
|
||||
/// event to begin a drag operation.
|
||||
final RectDragStartEvent? onDragStart;
|
||||
|
||||
/// A callback that is called every time the [TransformableBox] is moved.
|
||||
/// This is called every time the [TransformableBoxController] mutates the
|
||||
/// box through a drag operation.
|
||||
///
|
||||
/// This is different from [onChanged] in that it is only called when the
|
||||
/// box is moved, not when the box is resized.
|
||||
final RectDragUpdateEvent? onDragUpdate;
|
||||
|
||||
/// A callback that is called every time the [TransformableBox] is completes
|
||||
/// its drag operation via the pan end event.
|
||||
final RectDragEndEvent? onDragEnd;
|
||||
|
||||
/// A callback that is called every time the [TransformableBox] cancels
|
||||
/// its drag operation via the pan cancel event.
|
||||
final RectDragCancelEvent? onDragCancel;
|
||||
|
||||
/// A callback function that triggers when the box is about to start resizing.
|
||||
final RectResizeStart? onResizeStart;
|
||||
|
||||
/// A callback that is called every time the [TransformableBox] is resized.
|
||||
/// This is called every time the [TransformableBoxController] mutates the
|
||||
/// box.
|
||||
///
|
||||
/// This is different from [onChanged] in that it is only called when the box
|
||||
/// is resized, not when the box is moved.
|
||||
final RectResizeUpdateEvent? onResizeUpdate;
|
||||
|
||||
/// A callback function that triggers when the box is about to end resizing.
|
||||
final RectResizeEnd? onResizeEnd;
|
||||
|
||||
/// A callback function that triggers when the box cancels resizing.
|
||||
final RectResizeCancel? onResizeCancel;
|
||||
|
||||
/// A callback function that triggers when the box reaches its minimum width
|
||||
/// when resizing.
|
||||
final TerminalEdgeEvent? onMinWidthReached;
|
||||
|
||||
/// A callback function that triggers when the box reaches its maximum width
|
||||
/// when resizing.
|
||||
final TerminalEdgeEvent? onMaxWidthReached;
|
||||
|
||||
/// A callback function that triggers when the box reaches its minimum height
|
||||
/// when resizing.
|
||||
final TerminalEdgeEvent? onMinHeightReached;
|
||||
|
||||
/// A callback function that triggers when the box reaches its maximum height
|
||||
/// when resizing.
|
||||
final TerminalEdgeEvent? onMaxHeightReached;
|
||||
|
||||
/// A callback function that triggers when the box reaches a terminal width
|
||||
/// when resizing. A terminal width is a width that is either the minimum or
|
||||
/// maximum width of the box.
|
||||
///
|
||||
/// This function combines both [onMinWidthReached] and [onMaxWidthReached]
|
||||
/// into one callback function.
|
||||
final TerminalAxisEvent? onTerminalWidthReached;
|
||||
|
||||
/// A callback function that triggers when the box reaches a terminal height
|
||||
/// when resizing. A terminal height is a height that is either the minimum or
|
||||
/// maximum height of the box.
|
||||
///
|
||||
/// This function combines both [onMinHeightReached] and [onMaxHeightReached]
|
||||
/// into one callback function.
|
||||
final TerminalAxisEvent? onTerminalHeightReached;
|
||||
|
||||
/// A callback function that triggers when the box reaches a terminal size
|
||||
/// when resizing. A terminal size is a size that is either the minimum or
|
||||
/// maximum size of the box on either axis.
|
||||
///
|
||||
/// This function combines both [onTerminalWidthReached] and
|
||||
/// [onTerminalHeightReached] into one callback function.
|
||||
final TerminalEvent? onTerminalSizeReached;
|
||||
|
||||
/// Whether to paint the handle's bounds for debugging purposes.
|
||||
final bool debugPaintHandleBounds;
|
||||
final double handleTapLeftSize;
|
||||
/// Creates a [TransformableBox] widget.
|
||||
const TransformableBox({
|
||||
super.key,
|
||||
required this.contentBuilder,
|
||||
this.controller,
|
||||
this.cornerHandleBuilder = _defaultCornerHandleBuilder,
|
||||
this.sideHandleBuilder = _defaultSideHandleBuilder,
|
||||
this.handleTapSize = 24,
|
||||
this.handleTapLeftSize = 24,
|
||||
this.allowContentFlipping = true,
|
||||
this.handleAlignment = HandleAlignment.center,
|
||||
this.enabledHandles = const {...HandlePosition.values},
|
||||
this.visibleHandles = const {...HandlePosition.values},
|
||||
|
||||
// Raw values.
|
||||
Rect? rect,
|
||||
Flip? flip,
|
||||
Rect? clampingRect,
|
||||
BoxConstraints? constraints,
|
||||
ValueGetter<ResizeMode>? resizeModeResolver,
|
||||
|
||||
// Additional controls.
|
||||
this.resizable = true,
|
||||
this.draggable = true,
|
||||
this.allowFlippingWhileResizing = true,
|
||||
|
||||
// Either resize or drag triggers.
|
||||
this.onChanged,
|
||||
|
||||
// Resize events
|
||||
this.onResizeStart,
|
||||
this.onResizeUpdate,
|
||||
this.onResizeEnd,
|
||||
this.onResizeCancel,
|
||||
|
||||
// Drag Events.
|
||||
this.onDragStart,
|
||||
this.onDragUpdate,
|
||||
this.onDragEnd,
|
||||
this.onDragCancel,
|
||||
|
||||
// Terminal update events.
|
||||
this.onMinWidthReached,
|
||||
this.onMaxWidthReached,
|
||||
this.onMinHeightReached,
|
||||
this.onMaxHeightReached,
|
||||
this.onTerminalWidthReached,
|
||||
this.onTerminalHeightReached,
|
||||
this.onTerminalSizeReached,
|
||||
this.debugPaintHandleBounds = false,
|
||||
}) : assert(
|
||||
(controller == null) ||
|
||||
((rect == null) &&
|
||||
(flip == null) &&
|
||||
(clampingRect == null) &&
|
||||
(constraints == null) &&
|
||||
(resizeModeResolver == null)),
|
||||
'If a controller is provided, the raw values should not be provided.',
|
||||
),
|
||||
rect = rect ?? Rect.zero,
|
||||
flip = flip ?? Flip.none,
|
||||
clampingRect = clampingRect ?? Rect.largest,
|
||||
constraints = constraints ?? const BoxConstraints.expand(),
|
||||
resizeModeResolver = resizeModeResolver ?? defaultResizeModeResolver;
|
||||
|
||||
/// Returns the [TransformableBox] of the closest ancestor.
|
||||
static TransformableBox? widgetOf(BuildContext context) {
|
||||
return context.findAncestorWidgetOfExactType<TransformableBox>();
|
||||
}
|
||||
|
||||
/// Returns the [TransformableBoxController] of the closest ancestor.
|
||||
static TransformableBoxController? controllerOf(BuildContext context) {
|
||||
return context
|
||||
.findAncestorStateOfType<_TransformableBoxState>()
|
||||
?.controller;
|
||||
}
|
||||
|
||||
@override
|
||||
State<TransformableBox> createState() => _TransformableBoxState();
|
||||
}
|
||||
|
||||
class _TransformableBoxState extends State<TransformableBox> {
|
||||
late TransformableBoxController controller;
|
||||
|
||||
bool isLegalGesture = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.controller != null) {
|
||||
controller = widget.controller!;
|
||||
// We only want to listen to the controller if it is provided externally.
|
||||
controller.addListener(onControllerUpdate);
|
||||
} else {
|
||||
// If it is provided internally, we should not listen to it.
|
||||
controller = TransformableBoxController(
|
||||
rect: widget.rect,
|
||||
flip: widget.flip,
|
||||
clampingRect: widget.clampingRect,
|
||||
constraints: widget.constraints,
|
||||
resizeModeResolver: widget.resizeModeResolver,
|
||||
allowFlippingWhileResizing: widget.allowFlippingWhileResizing,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant TransformableBox oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (widget.controller != null && oldWidget.controller == null ||
|
||||
widget.controller != oldWidget.controller) {
|
||||
// New explicit controller provided or explicit controller changed.
|
||||
controller.removeListener(onControllerUpdate);
|
||||
controller = widget.controller!;
|
||||
controller.addListener(onControllerUpdate);
|
||||
} else if (oldWidget.controller != null && widget.controller == null) {
|
||||
// Explicit controller removed.
|
||||
controller.removeListener(onControllerUpdate);
|
||||
controller = TransformableBoxController(
|
||||
rect: widget.rect,
|
||||
flip: widget.flip,
|
||||
clampingRect: widget.clampingRect,
|
||||
constraints: widget.constraints,
|
||||
resizeModeResolver: widget.resizeModeResolver,
|
||||
allowFlippingWhileResizing: widget.allowFlippingWhileResizing,
|
||||
);
|
||||
}
|
||||
|
||||
// Return if the controller is external.
|
||||
if (widget.controller != null) return;
|
||||
|
||||
// Below code should only be executed if the controller is internal.
|
||||
bool shouldRecalculatePosition = false;
|
||||
bool shouldRecalculateSize = false;
|
||||
|
||||
if (oldWidget.rect != widget.rect) {
|
||||
controller.setRect(widget.rect, notify: false);
|
||||
}
|
||||
|
||||
if (oldWidget.flip != widget.flip) {
|
||||
controller.setFlip(widget.flip, notify: false);
|
||||
}
|
||||
|
||||
if (oldWidget.resizeModeResolver != widget.resizeModeResolver) {
|
||||
controller.setResizeModeResolver(
|
||||
widget.resizeModeResolver,
|
||||
notify: false,
|
||||
);
|
||||
}
|
||||
|
||||
if (oldWidget.clampingRect != widget.clampingRect) {
|
||||
controller.setClampingRect(widget.clampingRect, notify: false);
|
||||
shouldRecalculatePosition = true;
|
||||
}
|
||||
|
||||
if (oldWidget.constraints != widget.constraints) {
|
||||
controller.setConstraints(widget.constraints, notify: false);
|
||||
shouldRecalculateSize = true;
|
||||
}
|
||||
|
||||
if (oldWidget.allowFlippingWhileResizing !=
|
||||
widget.allowFlippingWhileResizing) {
|
||||
controller.setAllowFlippingWhileResizing(
|
||||
widget.allowFlippingWhileResizing,
|
||||
notify: false,
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldRecalculatePosition) {
|
||||
controller.recalculatePosition(notify: false);
|
||||
}
|
||||
|
||||
if (shouldRecalculateSize) {
|
||||
controller.recalculateSize(notify: false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.removeListener(onControllerUpdate);
|
||||
if (widget.controller == null) controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Called when the controller is updated.
|
||||
void onControllerUpdate() {
|
||||
if (widget.rect != controller.rect || widget.flip != controller.flip) {
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when the handle drag starts.
|
||||
void onHandlePanStart(DragStartDetails event, HandlePosition handle) {
|
||||
// Two fingers were used to start the drag. This produces issues with
|
||||
// the box drag event. Therefore, we ignore it.
|
||||
if (event.kind == PointerDeviceKind.trackpad) {
|
||||
isLegalGesture = false;
|
||||
return;
|
||||
} else {
|
||||
isLegalGesture = true;
|
||||
}
|
||||
|
||||
controller.onResizeStart(event.localPosition);
|
||||
widget.onResizeStart?.call(handle, event);
|
||||
}
|
||||
|
||||
/// Called when the handle drag updates.
|
||||
void onHandlePanUpdate(DragUpdateDetails event, HandlePosition handle) {
|
||||
if (!isLegalGesture) return;
|
||||
|
||||
final UIResizeResult result = controller.onResizeUpdate(
|
||||
event.localPosition,
|
||||
handle,
|
||||
);
|
||||
|
||||
widget.onChanged?.call(result, event);
|
||||
widget.onResizeUpdate?.call(result, event);
|
||||
widget.onMinWidthReached?.call(result.minWidthReached);
|
||||
widget.onMaxWidthReached?.call(result.maxWidthReached);
|
||||
widget.onMinHeightReached?.call(result.minHeightReached);
|
||||
widget.onMaxHeightReached?.call(result.maxHeightReached);
|
||||
widget.onTerminalWidthReached?.call(
|
||||
result.minWidthReached,
|
||||
result.maxWidthReached,
|
||||
);
|
||||
widget.onTerminalHeightReached?.call(
|
||||
result.minHeightReached,
|
||||
result.maxHeightReached,
|
||||
);
|
||||
widget.onTerminalSizeReached?.call(
|
||||
result.minWidthReached,
|
||||
result.maxWidthReached,
|
||||
result.minHeightReached,
|
||||
result.maxHeightReached,
|
||||
);
|
||||
}
|
||||
|
||||
/// Called when the handle drag ends.
|
||||
void onHandlePanEnd(DragEndDetails event, HandlePosition handle) {
|
||||
if (!isLegalGesture) return;
|
||||
|
||||
controller.onResizeEnd();
|
||||
widget.onResizeEnd?.call(handle, event);
|
||||
widget.onMinWidthReached?.call(false);
|
||||
widget.onMaxWidthReached?.call(false);
|
||||
widget.onMinHeightReached?.call(false);
|
||||
widget.onMaxHeightReached?.call(false);
|
||||
widget.onTerminalWidthReached?.call(false, false);
|
||||
widget.onTerminalHeightReached?.call(false, false);
|
||||
widget.onTerminalSizeReached?.call(false, false, false, false);
|
||||
}
|
||||
|
||||
void onHandlePanCancel(HandlePosition handle) {
|
||||
if (!isLegalGesture) return;
|
||||
|
||||
controller.onResizeEnd();
|
||||
widget.onResizeCancel?.call(handle);
|
||||
widget.onMinWidthReached?.call(false);
|
||||
widget.onMaxWidthReached?.call(false);
|
||||
widget.onMinHeightReached?.call(false);
|
||||
widget.onMaxHeightReached?.call(false);
|
||||
widget.onTerminalWidthReached?.call(false, false);
|
||||
widget.onTerminalHeightReached?.call(false, false);
|
||||
widget.onTerminalSizeReached?.call(false, false, false, false);
|
||||
}
|
||||
|
||||
/// Called when the box drag event starts.
|
||||
void onDragPanStart(DragStartDetails event) {
|
||||
// Two fingers were used to start the drag. This produces issues with
|
||||
// the box drag event. Therefore, we ignore it.
|
||||
if (event.kind == PointerDeviceKind.trackpad) {
|
||||
isLegalGesture = false;
|
||||
return;
|
||||
} else {
|
||||
isLegalGesture = true;
|
||||
}
|
||||
|
||||
controller.onDragStart(event.localPosition);
|
||||
widget.onDragStart?.call(event);
|
||||
}
|
||||
|
||||
/// Called when the box drag event updates.
|
||||
void onDragPanUpdate(DragUpdateDetails event) {
|
||||
if (!isLegalGesture) return;
|
||||
|
||||
final UIMoveResult result = controller.onDragUpdate(
|
||||
event.localPosition,
|
||||
);
|
||||
|
||||
widget.onChanged?.call(result, event);
|
||||
widget.onDragUpdate?.call(result, event);
|
||||
}
|
||||
|
||||
/// Called when the box drag event ends.
|
||||
void onDragPanEnd(DragEndDetails event) {
|
||||
if (!isLegalGesture) return;
|
||||
|
||||
controller.onDragEnd();
|
||||
widget.onDragEnd?.call(event);
|
||||
}
|
||||
|
||||
void onDragPanCancel() {
|
||||
if (!isLegalGesture) return;
|
||||
|
||||
controller.onDragEnd();
|
||||
widget.onDragCancel?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Flip flip = controller.flip;
|
||||
final Rect rect = controller.rect;
|
||||
|
||||
Widget content = Transform.scale(
|
||||
scaleX: widget.allowContentFlipping && flip.isHorizontal ? -1 : 1,
|
||||
scaleY: widget.allowContentFlipping && flip.isVertical ? -1 : 1,
|
||||
child: widget.contentBuilder(context, rect, flip),
|
||||
);
|
||||
|
||||
if (widget.draggable) {
|
||||
content = GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPanStart: onDragPanStart,
|
||||
onPanUpdate: onDragPanUpdate,
|
||||
onPanEnd: onDragPanEnd,
|
||||
onPanCancel: onDragPanCancel,
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Positioned(
|
||||
left: widget.handleAlignment.offset(widget.handleTapLeftSize),
|
||||
top: widget.handleAlignment.offset(widget.handleTapSize),
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
child: content,
|
||||
),
|
||||
if (widget.resizable)
|
||||
for (final handle in HandlePosition.corners.where((handle) =>
|
||||
widget.visibleHandles.contains(handle) ||
|
||||
widget.enabledHandles.contains(handle)))
|
||||
CornerHandleWidget(
|
||||
key: ValueKey(handle),
|
||||
handlePosition: handle,
|
||||
handleTapSize: widget.handleTapSize,
|
||||
enabled: widget.enabledHandles.contains(handle),
|
||||
visible: widget.visibleHandles.contains(handle),
|
||||
onPanStart: (event) => onHandlePanStart(event, handle),
|
||||
onPanUpdate: (event) => onHandlePanUpdate(event, handle),
|
||||
onPanEnd: (event) => onHandlePanEnd(event, handle),
|
||||
onPanCancel: () => onHandlePanCancel(handle),
|
||||
builder: widget.cornerHandleBuilder,
|
||||
),
|
||||
if (widget.resizable)
|
||||
for (final handle in HandlePosition.sides.where((handle) =>
|
||||
widget.visibleHandles.contains(handle) ||
|
||||
widget.enabledHandles.contains(handle)))
|
||||
SideHandleWidget(
|
||||
key: ValueKey(handle),
|
||||
handlePosition: handle,
|
||||
handleTapSize: widget.handleTapSize,
|
||||
enabled: widget.enabledHandles.contains(handle),
|
||||
visible: widget.visibleHandles.contains(handle),
|
||||
onPanStart: (event) => onHandlePanStart(event, handle),
|
||||
onPanUpdate: (event) => onHandlePanUpdate(event, handle),
|
||||
onPanEnd: (event) => onHandlePanEnd(event, handle),
|
||||
onPanCancel: () => onHandlePanCancel(handle),
|
||||
builder: widget.sideHandleBuilder,
|
||||
),
|
||||
],
|
||||
) ,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A default implementation of the corner [HandleBuilder] callback.
|
||||
Widget _defaultCornerHandleBuilder(
|
||||
BuildContext context,
|
||||
HandlePosition handle,
|
||||
) =>
|
||||
DefaultCornerHandle(handle: handle);
|
||||
|
||||
/// A default implementation of the side [HandleBuilder] callback.
|
||||
Widget _defaultSideHandleBuilder(
|
||||
BuildContext context,
|
||||
HandlePosition handle,
|
||||
) =>
|
||||
DefaultSideHandle(handle: handle);
|
||||
|
||||
|
||||
@protected
|
||||
class CornerHandleWidget extends StatelessWidget {
|
||||
/// The position of the handle.
|
||||
final HandlePosition handlePosition;
|
||||
|
||||
/// The builder that is used to build the handle widget.
|
||||
final HandleBuilder builder;
|
||||
|
||||
/// The size of the handle's gesture response area.
|
||||
final double handleTapSize;
|
||||
|
||||
/// Called when the handle dragging starts.
|
||||
final GestureDragStartCallback? onPanStart;
|
||||
|
||||
/// Called when the handle dragging is updated.
|
||||
final GestureDragUpdateCallback? onPanUpdate;
|
||||
|
||||
/// Called when the handle dragging ends.
|
||||
final GestureDragEndCallback? onPanEnd;
|
||||
|
||||
/// Called when the handle dragging is canceled.
|
||||
final GestureDragCancelCallback? onPanCancel;
|
||||
|
||||
/// Whether the handle is resizable.
|
||||
final bool enabled;
|
||||
|
||||
/// Whether the handle is visible.
|
||||
final bool visible;
|
||||
|
||||
/// Whether to paint the handle's bounds for debugging purposes.
|
||||
final bool debugPaintHandleBounds;
|
||||
|
||||
/// Creates a new handle widget.
|
||||
CornerHandleWidget({
|
||||
super.key,
|
||||
required this.handlePosition,
|
||||
required this.handleTapSize,
|
||||
required this.builder,
|
||||
this.onPanStart,
|
||||
this.onPanUpdate,
|
||||
this.onPanEnd,
|
||||
this.onPanCancel,
|
||||
this.enabled = true,
|
||||
this.visible = true,
|
||||
this.debugPaintHandleBounds = false,
|
||||
}) : assert(handlePosition.isDiagonal, 'A corner handle must be diagonal.');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget child =
|
||||
visible ? builder(context, handlePosition) : const SizedBox.shrink();
|
||||
|
||||
if (enabled) {
|
||||
child = GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onPanStart: onPanStart,
|
||||
onPanUpdate: onPanUpdate,
|
||||
onPanEnd: onPanEnd,
|
||||
onPanCancel: onPanCancel,
|
||||
child: MouseRegion(
|
||||
cursor: getCursorForHandle(handlePosition),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Positioned(
|
||||
left: handlePosition.influencesLeft ? 0 : null,
|
||||
right: handlePosition.influencesRight ? 0 : null,
|
||||
top: handlePosition.influencesTop ? 0 : null,
|
||||
bottom: handlePosition.influencesBottom ? 0 : null,
|
||||
width: handleTapSize,
|
||||
height: handleTapSize,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the cursor for the given handle position.
|
||||
MouseCursor getCursorForHandle(HandlePosition handle) {
|
||||
switch (handle) {
|
||||
case HandlePosition.topLeft:
|
||||
case HandlePosition.bottomRight:
|
||||
return SystemMouseCursors.resizeUpLeftDownRight;
|
||||
case HandlePosition.topRight:
|
||||
case HandlePosition.bottomLeft:
|
||||
return SystemMouseCursors.resizeUpRightDownLeft;
|
||||
default:
|
||||
throw Exception('Invalid handle position.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new cardinal handle widget, with its appropriate gesture splash
|
||||
/// zone.
|
||||
@protected
|
||||
class SideHandleWidget extends StatelessWidget {
|
||||
/// The position of the handle.
|
||||
final HandlePosition handlePosition;
|
||||
|
||||
/// The builder that is used to build the handle widget.
|
||||
final HandleBuilder builder;
|
||||
|
||||
/// The thickness of the handle that is used for gesture detection.
|
||||
final double handleTapSize;
|
||||
|
||||
/// Called when the handle dragging starts.
|
||||
final GestureDragStartCallback? onPanStart;
|
||||
|
||||
/// Called when the handle dragging is updated.
|
||||
final GestureDragUpdateCallback? onPanUpdate;
|
||||
|
||||
/// Called when the handle dragging ends.
|
||||
final GestureDragEndCallback? onPanEnd;
|
||||
|
||||
/// Called when the handle dragging is canceled.
|
||||
final GestureDragCancelCallback? onPanCancel;
|
||||
|
||||
/// Whether the handle is resizable.
|
||||
final bool enabled;
|
||||
|
||||
/// Whether the handle is visible.
|
||||
final bool visible;
|
||||
|
||||
/// Whether to paint the handle's bounds for debugging purposes.
|
||||
final bool debugPaintHandleBounds;
|
||||
|
||||
/// Creates a new handle widget.
|
||||
SideHandleWidget({
|
||||
super.key,
|
||||
required this.handlePosition,
|
||||
required this.handleTapSize,
|
||||
required this.builder,
|
||||
this.onPanStart,
|
||||
this.onPanUpdate,
|
||||
this.onPanEnd,
|
||||
this.onPanCancel,
|
||||
this.enabled = true,
|
||||
this.visible = true,
|
||||
this.debugPaintHandleBounds = false,
|
||||
}) : assert(handlePosition.isSide, 'A cardinal handle must be cardinal.');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget child =
|
||||
visible ? builder(context, handlePosition) : const SizedBox.shrink();
|
||||
|
||||
if (enabled) {
|
||||
child = GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onPanStart: onPanStart,
|
||||
onPanUpdate: onPanUpdate,
|
||||
onPanEnd: onPanEnd,
|
||||
onPanCancel: onPanCancel,
|
||||
child: MouseRegion(
|
||||
cursor: getCursorForHandle(handlePosition),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Positioned(
|
||||
left: handlePosition.isVertical
|
||||
? handleTapSize
|
||||
: handlePosition.influencesLeft
|
||||
? 0
|
||||
: null,
|
||||
right: handlePosition.isVertical
|
||||
? handleTapSize
|
||||
: handlePosition.influencesRight
|
||||
? 0
|
||||
: null,
|
||||
top: handlePosition.isHorizontal
|
||||
? handleTapSize
|
||||
: handlePosition.influencesTop
|
||||
? 0
|
||||
: null,
|
||||
bottom: handlePosition.isHorizontal
|
||||
? handleTapSize
|
||||
: handlePosition.influencesBottom
|
||||
? 0
|
||||
: null,
|
||||
width: handlePosition.isHorizontal ? handleTapSize : null,
|
||||
height: handlePosition.isVertical ? handleTapSize : null,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the cursor for the given handle position.
|
||||
MouseCursor getCursorForHandle(HandlePosition handle) {
|
||||
switch (handle) {
|
||||
case HandlePosition.left:
|
||||
case HandlePosition.right:
|
||||
return SystemMouseCursors.resizeLeftRight;
|
||||
case HandlePosition.top:
|
||||
case HandlePosition.bottom:
|
||||
return SystemMouseCursors.resizeUpDown;
|
||||
default:
|
||||
throw Exception('Invalid handle position.');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,28 @@
|
||||
import 'package:alert_banner/exports.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:json_string/json_string.dart';
|
||||
import 'package:oc_front/core/sections/header/header.dart';
|
||||
import 'package:oc_front/models/logs.dart';
|
||||
import 'package:oc_front/widgets/dialog/alert.dart';
|
||||
|
||||
class LogsWidget extends StatefulWidget {
|
||||
final List<Log> items;
|
||||
LogsWidget ({ Key? key, required this.items }): super(key: key);
|
||||
String? level;
|
||||
String search = "";
|
||||
LogsWidget ({ Key? key, this.search = "", required this.items, this.level }): super(key: key);
|
||||
@override LogsWidgetState createState() => LogsWidgetState();
|
||||
}
|
||||
class LogsWidgetState extends State<LogsWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
List<LogWidget> itemRows = widget.items.map((e) => LogWidget(item: e)).toList();
|
||||
return SingleChildScrollView( child: Column( children: itemRows ) );
|
||||
List<LogWidget> itemRows = widget.items.where((element) => (element.message?.toLowerCase().contains(widget.search.toLowerCase()) ?? true)
|
||||
&& (widget.level?.contains(element.level ?? "") ?? true) ).map((e) => LogWidget(item: e)).toList();
|
||||
return Stack( children: [
|
||||
SingleChildScrollView( child: itemRows.isEmpty ?
|
||||
Container( height: MediaQuery.of(context).size.height - 100 - HeaderConstants.height,
|
||||
child: const Center( child: Text("no log registered", style: TextStyle(color: Colors.grey, fontSize: 25 ),)))
|
||||
: Column( children: [...itemRows, Container(height: 50,) ] ) ),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,23 +34,31 @@ class LogWidget extends StatefulWidget {
|
||||
}
|
||||
class LogWidgetState extends State<LogWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return Padding( padding: const EdgeInsets.only(top: 10, left: 30, right: 30), child: Wrap( children: [
|
||||
Map<String, dynamic> map = {};
|
||||
try { map = JsonString(widget.item.rawMessage?.replaceAll("\\", "") ?? "").decodedValue as Map<String, dynamic>;
|
||||
} catch (e) { /* */}
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: Colors.white)),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 10, left: 30, right: 30, bottom: 10),
|
||||
child: Wrap( children: [
|
||||
Row( mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container( width: 10, height: 15, color: widget.item.level?.toLowerCase() == "info" ? Colors.green :
|
||||
( widget.item.level?.toLowerCase() == "error" ? Colors.red : (
|
||||
widget.item.level?.toLowerCase() == "warning" ? Colors.orange : Colors.blue))),
|
||||
InkWell( mouseCursor: widget.item.map.isEmpty ? MouseCursor.defer : SystemMouseCursors.click, onTap: () {
|
||||
if (widget.item.map.isNotEmpty ) {
|
||||
InkWell( mouseCursor: map.isEmpty ? MouseCursor.defer : SystemMouseCursors.click, onTap: () {
|
||||
if (map.isNotEmpty ) {
|
||||
setState(() {
|
||||
widget.expanded = !widget.expanded;
|
||||
});
|
||||
}
|
||||
}, child: Container( height: 20,
|
||||
}, child: SizedBox( height: 20,
|
||||
child: Padding( padding: EdgeInsets.symmetric(horizontal: widget.expanded ? 0 : 5),
|
||||
child: Icon( widget.expanded ? Icons.keyboard_arrow_down_outlined : Icons.arrow_forward_ios, size: widget.expanded ? 25 : 15,
|
||||
color: widget.item.map.isEmpty ? Colors.grey : Colors.black, weight: widget.expanded ? 100 : 1000,)))),
|
||||
color: map.isEmpty ? Colors.grey : Colors.black, weight: widget.expanded ? 100 : 1000,)))),
|
||||
Padding( padding: const EdgeInsets.only(right: 10),
|
||||
child: Text("${widget.item.timestamp?.toString()}",
|
||||
style: const TextStyle(fontSize: 13, color: Colors.black, fontWeight: FontWeight.w500))),
|
||||
@ -57,10 +75,10 @@ class LogWidgetState extends State<LogWidget> {
|
||||
decoration: BoxDecoration( color: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(4)),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column( children: widget.item.map.keys.map((e) =>
|
||||
child: Column( children: map.keys.map((e) =>
|
||||
Padding( padding: const EdgeInsets.all(2), child: Row( mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [Flexible( child:Text("$e: \"${widget.item.map[e]}\"",
|
||||
children: [Flexible( child:Text("$e: \"${map[e]}\"",
|
||||
style: const TextStyle(fontSize: 11, color: Colors.white))), ])
|
||||
)).toList()
|
||||
)) : Container(),
|
||||
|
@ -1,7 +1,3 @@
|
||||
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/core/models/workspace_local.dart';
|
||||
class MenuWorkspaceWidget extends StatefulWidget {
|
||||
|
@ -1,26 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_box_transform/flutter_box_transform.dart';
|
||||
import 'package:oc_front/core/sections/header/header.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/logs_service.dart';
|
||||
import 'package:oc_front/models/logs.dart';
|
||||
import 'package:oc_front/models/workflow.dart';
|
||||
import 'package:oc_front/widgets/logs.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:oc_front/widgets/lib/tranformablebox.dart' as fork;
|
||||
import 'package:oc_front/widgets/sheduler_items/scheduler_calendar.dart';
|
||||
import 'package:oc_front/widgets/sheduler_items/scheduler_item.dart';
|
||||
|
||||
double menuSize = 300;
|
||||
// ignore: must_be_immutable
|
||||
class ScheduleWidget extends StatefulWidget {
|
||||
DateTime start;
|
||||
DateTime end;
|
||||
bool isDayPlanner = true;
|
||||
bool loading = true;
|
||||
Map<String, List<WorkflowExecution>> data;
|
||||
bool isList = true;
|
||||
ScheduleWidget ({ super.key, required this.data, required this.start, required this.end, this.isList = true });
|
||||
ScheduleWidget ({ super.key, required this.data, required this.start, required this.end, this.isList = true, this.loading = false});
|
||||
@override ScheduleWidgetState createState() => ScheduleWidgetState();
|
||||
}
|
||||
String? selected;
|
||||
String? selectedReal;
|
||||
class ScheduleWidgetState extends State<ScheduleWidget> {
|
||||
LogsService _service = LogsService();
|
||||
String? selected;
|
||||
String? selectedReal;
|
||||
String search = "";
|
||||
String? level;
|
||||
List<Color> colors = [Colors.blue, Colors.orange, Colors.red, Colors.green];
|
||||
List<String> titles = ["SCHEDULED", "RUNNING", "FAILURE", "SUCCESS"];
|
||||
|
||||
@ -31,7 +39,7 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
|
||||
|
||||
@override Widget build(BuildContext context) {
|
||||
bool isInfo = MediaQuery.of(context).size.width <= 600 && selected != null;
|
||||
double w = selected != null ? MediaQuery.of(context).size.width - 300 : MediaQuery.of(context).size.width;
|
||||
double w = selected != null ? MediaQuery.of(context).size.width - menuSize : MediaQuery.of(context).size.width;
|
||||
List<Widget> children = [];
|
||||
if (selected != null) {
|
||||
for (var wf in widget.data[selected!] ?? (<WorkflowExecution>[])) {
|
||||
@ -40,7 +48,8 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
|
||||
onTap: () => setState(() { selectedReal = wf.executionData; }),
|
||||
child: Container( margin: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: selectedReal != null && selectedReal == wf.executionData ? const Color.fromRGBO(38, 166, 154, 1) : Colors.transparent, width: 2),
|
||||
border: Border.all(color: selectedReal != null && selectedReal == wf.executionData ?
|
||||
const Color.fromRGBO(38, 166, 154, 1) : Colors.transparent, width: 2),
|
||||
borderRadius: BorderRadius.circular(4), color: Colors.white
|
||||
),
|
||||
child: Container(
|
||||
@ -52,13 +61,13 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
Container( width: (400 - 250),
|
||||
SizedBox( width: (menuSize - 140),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 20),
|
||||
child: Text(wf.name?.toUpperCase() ?? "", overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.black, fontSize: 12, fontWeight: FontWeight.w500)),
|
||||
)),
|
||||
Container(
|
||||
SizedBox(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 20),
|
||||
child: Text("${d2.hour > 9 ? d2.hour : "0${d2.hour}"}:${d2.minute > 9 ? d2.minute : "0${d2.minute}"}", overflow: TextOverflow.ellipsis,
|
||||
@ -70,48 +79,45 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
|
||||
}
|
||||
|
||||
}
|
||||
List<Log> logs = [];
|
||||
String? selectedID;
|
||||
String? start;
|
||||
String? end;
|
||||
if (selectedReal != null) {
|
||||
try {
|
||||
var sel = widget.data[selected!]!.firstWhere((element) => element.executionData == selectedReal);
|
||||
selectedID = sel.id;
|
||||
print(sel.endDate);
|
||||
if (sel.endDate != null && sel.endDate != "") {
|
||||
var startD = DateTime.parse(sel.executionData!);
|
||||
var endD = DateTime.parse(sel.endDate!);
|
||||
var diff = endD.difference(startD);
|
||||
if (diff.inDays < 30) {
|
||||
var rest = ((30 - diff.inDays) ~/ 2) - 1;
|
||||
start = (startD.subtract(Duration(days: rest)).microsecondsSinceEpoch).toString();
|
||||
end = (endD.add(Duration(days: rest)).microsecondsSinceEpoch).toString();
|
||||
} else {
|
||||
start = (startD.microsecondsSinceEpoch).toString();
|
||||
end = (startD.add( const Duration(days: 29)).microsecondsSinceEpoch).toString();
|
||||
}
|
||||
} else {
|
||||
start = (DateTime.parse(sel.executionData!).subtract( const Duration(days: 14)).microsecondsSinceEpoch).toString();
|
||||
end = (DateTime.parse(sel.executionData!).add( const Duration(days: 14)).microsecondsSinceEpoch).toString();
|
||||
}
|
||||
logs = sel.logs ?? [];
|
||||
} catch(e) { /* */ }
|
||||
}
|
||||
menuSize = isInfo ? MediaQuery.of(context).size.width : (menuSize > MediaQuery.of(context).size.width / 2 ? MediaQuery.of(context).size.width / 2 : menuSize);
|
||||
Rect rect = Rect.fromCenter( center: MediaQuery.of(context).size.center(Offset.zero),
|
||||
width: selected != null ? menuSize : 0, height: (MediaQuery.of(context).size.height - HeaderConstants.height - 50) > 0 ? (MediaQuery.of(context).size.height - HeaderConstants.height - 50) : 0);
|
||||
return Row( children: [
|
||||
isInfo ? Container() : SizedBox( width: w,
|
||||
child: widget.isList ? SchedulerItemWidget(data: widget.data, parent: this, focusedDay: getFocusedDay(), width: w)
|
||||
: SchedulerCalendarWidget(data: widget.data, start: widget.start,
|
||||
end: widget.end, parent: this, focusedDay: getFocusedDay(),)
|
||||
),
|
||||
Container(
|
||||
fork.TransformableBox(
|
||||
rect: rect, constraints: BoxConstraints(
|
||||
maxWidth: isInfo ? MediaQuery.of(context).size.width : (selected != null ? MediaQuery.of(context).size.width / 2 : 0),
|
||||
minWidth: selected != null ? 300 : 0),
|
||||
handleTapSize: 1, handleTapLeftSize: 0, allowFlippingWhileResizing: false, draggable: false, flip: null,
|
||||
resizeModeResolver: () => ResizeMode.freeform,
|
||||
visibleHandles: const {HandlePosition.left},
|
||||
enabledHandles: const {HandlePosition.left},
|
||||
clampingRect: Offset.zero & MediaQuery.sizeOf(context),
|
||||
handleAlignment: HandleAlignment.inside,
|
||||
onChanged: (result, event) { setState(() { menuSize = result.rect.width; }); },
|
||||
contentBuilder: (context, rect, flip) { return Container(
|
||||
height: MediaQuery.of(context).size.height - HeaderConstants.height - 50,
|
||||
width: isInfo ? MediaQuery.of(context).size.width : (selected != null ? 300 : 0),
|
||||
width: isInfo ? MediaQuery.of(context).size.width : (selected != null ? menuSize : 0),
|
||||
color: Colors.grey.shade300,
|
||||
child: Column(
|
||||
children: [
|
||||
Row( children: [
|
||||
InkWell( onTap: () => setState(() { widget.isDayPlanner = true; }),
|
||||
child: Tooltip( message: "day planning", child:
|
||||
Container( height: 50, width: (isInfo ? MediaQuery.of(context).size.width : (selected != null ? 300 : 0)) / (selectedReal != null ? 2 : 1 ),
|
||||
Container( height: 50, width: (isInfo ? MediaQuery.of(context).size.width : (selected != null ? menuSize : 0)) / (selectedReal != null ? 2 : 1 ),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.isDayPlanner ? Colors.grey : Colors.transparent,
|
||||
@ -121,7 +127,8 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
|
||||
)),
|
||||
InkWell( onTap: () => setState(() { widget.isDayPlanner = false; }),
|
||||
child: Tooltip( message: "monitor task", child:
|
||||
Container( height: 50, width: selectedReal == null ? 0 : ((isInfo ? MediaQuery.of(context).size.width : (selected != null ? 300 : 0)) / 2),
|
||||
Container( height: 50, width: selectedReal == null ? 0 : (
|
||||
(isInfo ? MediaQuery.of(context).size.width : (selected != null ? menuSize : 0)) / 2),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: !widget.isDayPlanner ? Colors.grey : Colors.transparent,
|
||||
@ -132,48 +139,84 @@ class ScheduleWidgetState extends State<ScheduleWidget> {
|
||||
)
|
||||
))
|
||||
]),
|
||||
Container( width: isInfo ? MediaQuery.of(context).size.width : (selected != null ? 300 : 0), height: MediaQuery.of(context).size.height - HeaderConstants.height - 100,
|
||||
child: SingleChildScrollView( child: Column(
|
||||
mainAxisAlignment: children.isEmpty ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||
children: [
|
||||
...( widget.isDayPlanner ? children : ( selectedID != null ? [
|
||||
FutureBuilder(future: _service.search(context, [], {
|
||||
"workflow_execution_id": selectedID,
|
||||
"start": start,
|
||||
"end": end
|
||||
}), builder: (ctx, as) {
|
||||
var speLog = Log(level: "error", timestamp: DateTime.now());
|
||||
speLog.getMessage("{\"Name\":\"oc-monitor-unonip-fauta9hswg\",\"Namespace\":\"argo\",\"Status\":\"Pending\",\"PodRunning\":false,\"Completed\":false,\"Created\":\"Tue Aug 06 11:33:52 +0200 (now)\",\"Started\":\"\",\"Duration\":\"\",\"Progress\":\"\"}");
|
||||
var speLog2 = Log(level: "warning", timestamp: DateTime.now());
|
||||
speLog2.getMessage("{\"Name\":\"oc-monitor-unonip-fauta9hswg\",\"Namespace\":\"argo\",\"Status\":\"Running\",\"PodRunning\":false,\"Completed\":false,\"Created\":\"Tue Aug 06 11:33:52 +0200 (now)\",\"Started\":\"Tue Aug 06 11:33:52 +0200 (now)\",\"Duration\":\"0 seconds\",\"Progress\":\"0/1\"}");
|
||||
List<Log> logs = [
|
||||
Log(
|
||||
level: "info",
|
||||
message: "No logs found",
|
||||
timestamp: DateTime.now()
|
||||
SizedBox( width: isInfo ? MediaQuery.of(context).size.width : (selected != null ? menuSize : 0), height: MediaQuery.of(context).size.height - HeaderConstants.height - (!widget.isDayPlanner && !widget.loading ? 150 : 100 ),
|
||||
child: Stack( children: [
|
||||
SingleChildScrollView( child: Column(
|
||||
mainAxisAlignment: children.isEmpty || widget.loading ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||
children: [
|
||||
...( widget.isDayPlanner ? children : ( selectedID != null ? [
|
||||
widget.loading ? const SpinKitCircle(color: Colors.white,) : LogsWidget(items: logs, search: search, level: level)
|
||||
] : [])),
|
||||
children.isEmpty ? Container( height: 100, alignment: Alignment.center, child: const Text("No event found", style: TextStyle(color: Colors.grey, fontSize: 20))) : Container()
|
||||
]),
|
||||
) ])
|
||||
),
|
||||
!widget.isDayPlanner && !widget.loading ?
|
||||
Row( children: [
|
||||
Container(
|
||||
width: 150,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(left: BorderSide(color: Colors.grey.shade300)),
|
||||
),
|
||||
child: DropdownButtonFormField(
|
||||
isExpanded: true,
|
||||
value: level,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
hint: const Text("by level...", style: TextStyle(fontSize: 12)),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
focusedBorder: const OutlineInputBorder( borderRadius: BorderRadius.zero,
|
||||
borderSide: BorderSide(color: Color.fromARGB(38, 166, 154, 1), width: 0),
|
||||
),
|
||||
fillColor: Colors.white,
|
||||
contentPadding: const EdgeInsets.only(left: 30, right: 30, top: 10, bottom: 30),
|
||||
enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.zero,
|
||||
borderSide: BorderSide(color: Colors.grey.shade300, width: 0),
|
||||
),
|
||||
border: OutlineInputBorder( borderRadius: BorderRadius.zero,
|
||||
borderSide: BorderSide(color: Colors.grey.shade300, width: 0)),
|
||||
),
|
||||
speLog,
|
||||
speLog2
|
||||
];
|
||||
if (as.hasData && as.data!.data != null) {
|
||||
var d = as.data!.data!;
|
||||
for( var r in d.data?.result ?? <Logs> []) {
|
||||
for (var element in r.logs) {
|
||||
element.level = r.level;
|
||||
logs.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
logs.sort((a, b) => a.timestamp!.compareTo(b.timestamp!));
|
||||
return LogsWidget(items: logs);
|
||||
})
|
||||
] : [])),
|
||||
children.isEmpty ? Container( height: 100, alignment: Alignment.center, child: const Text("No event found", style: const TextStyle(color: Colors.grey, fontSize: 20))) : Container()
|
||||
]))
|
||||
)
|
||||
items: [
|
||||
DropdownMenuItem(value: "debug,warning,error,info", child: Row( children: [ Container( width: 10, height: 15, color: Colors.grey), Text(" all", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
|
||||
DropdownMenuItem(value: "debug", child: Row( children: [ Container( width: 10, height: 15, color: Colors.blue), Text(" debug", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
|
||||
DropdownMenuItem(value: "warning", child: Row( children: [ Container( width: 10, height: 15, color: Colors.orange), Text(" warning", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
|
||||
DropdownMenuItem(value: "error", child: Row( children: [ Container( width: 10, height: 15, color: Colors.red), Text(" error", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
|
||||
DropdownMenuItem(value: "info", child: Row( children: [ Container( width: 10, height: 15, color: Colors.green), Text(" info", style: TextStyle(fontSize: 12, color: Colors.black)) ])),
|
||||
],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
level = value;
|
||||
});
|
||||
})),
|
||||
Container(
|
||||
width: menuSize - 150,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(left: BorderSide(color: Colors.grey.shade300)),
|
||||
),
|
||||
child: TextField(
|
||||
onChanged: (value) { setState(() {
|
||||
search = value;
|
||||
});},
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: const InputDecoration(
|
||||
hintText: "by logs...",
|
||||
fillColor: Colors.white,
|
||||
filled: true,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 30),
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w300
|
||||
),
|
||||
border: InputBorder.none
|
||||
)
|
||||
)) ]
|
||||
) : Container( ),
|
||||
],
|
||||
),
|
||||
)
|
||||
); })
|
||||
]);
|
||||
}
|
||||
}
|
@ -50,13 +50,14 @@ class SchedulerCalendarWidgetState extends State<SchedulerCalendarWidget> {
|
||||
markerBuilder: (context, day, events) {
|
||||
List<Widget> children = [];
|
||||
for (var ev in events) {
|
||||
if (children.length == 2 && events.length > 3) {
|
||||
if (children.length == 1 && events.length > 2) {
|
||||
children.add( InkWell( onTap: () => widget.parent!.setState(() {
|
||||
widget.parent!.selected = day.toIso8601String();
|
||||
widget.parent!.selectedReal = null;
|
||||
selected = day.toIso8601String();
|
||||
selectedReal = null;
|
||||
widget.parent!.widget.isDayPlanner = true;
|
||||
}),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
@ -67,13 +68,14 @@ class SchedulerCalendarWidgetState extends State<SchedulerCalendarWidget> {
|
||||
break;
|
||||
}
|
||||
children.add(InkWell( onTap: () => widget.parent!.setState(() {
|
||||
widget.parent!.selected = day.toIso8601String();
|
||||
widget.parent!.selectedReal = ev.executionData;
|
||||
if (widget.parent!.selectedReal == null) {
|
||||
selected = day.toIso8601String();
|
||||
selectedReal = ev.executionData;
|
||||
if (selectedReal == null) {
|
||||
widget.parent!.widget.isDayPlanner = true;
|
||||
}
|
||||
}),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 2.5, top: 25),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
@ -146,8 +148,8 @@ class SchedulerCalendarWidgetState extends State<SchedulerCalendarWidget> {
|
||||
}),
|
||||
onDaySelected: (selectedDay, focusedDay) {
|
||||
widget.parent!.setState(() {
|
||||
widget.parent!.selected = selectedDay.toIso8601String();
|
||||
widget.parent!.selectedReal = null;
|
||||
selected = selectedDay.toIso8601String();
|
||||
selectedReal = null;
|
||||
widget.parent!.widget.isDayPlanner = true;
|
||||
});
|
||||
},
|
||||
|
@ -27,21 +27,21 @@ class SchedulerItemWidgetState extends State<SchedulerItemWidget> {
|
||||
widget.keys[ev.executionData!] = GlobalKey();
|
||||
var d2 = DateTime.parse(ev.executionData!);
|
||||
DateTime? d3;
|
||||
try {
|
||||
d3 = DateTime.parse(ev.endDate!);
|
||||
try { d3 = DateTime.parse(ev.endDate!);
|
||||
} catch (e) { /* */ }
|
||||
widgets.add(InkWell(
|
||||
onTap: () => widget.parent?.setState(() {
|
||||
widget.parent?.selected = widget.parent?.selected != element ? element : null;
|
||||
widget.parent?.selectedReal = widget.parent?.selected == null ? null : ev.executionData;
|
||||
if (widget.parent!.selectedReal == null) {
|
||||
selected = selected != element || ev.executionData != selectedReal ? element : null;
|
||||
selectedReal = selected == null ? null : ev.executionData;
|
||||
if (selectedReal == null) {
|
||||
widget.parent!.widget.isDayPlanner = true;
|
||||
}
|
||||
}),
|
||||
child: Container( key: widget.keys[ev.executionData!],
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 50),
|
||||
decoration: BoxDecoration(
|
||||
border: widget.parent?.selected == element ? Border.all(color: const Color.fromRGBO(38, 166, 154, 1), width: 2)
|
||||
border: selectedReal == ev.executionData ?
|
||||
Border.all(color: const Color.fromRGBO(38, 166, 154, 1), width: 2)
|
||||
: Border(top: BorderSide(color: Colors.grey.shade300)),
|
||||
),
|
||||
child: Row(children: [
|
||||
@ -79,37 +79,39 @@ class SchedulerItemWidgetState extends State<SchedulerItemWidget> {
|
||||
)));
|
||||
}
|
||||
var date = DateTime.parse(element);
|
||||
children.add(Column( children: [Container(
|
||||
child: ExpansionTile(
|
||||
enabled: widget.enabled,
|
||||
shape: ContinuousRectangleBorder(),
|
||||
iconColor: Colors.grey,
|
||||
initiallyExpanded: true,
|
||||
title: SizedBox(
|
||||
child : Row( children: [
|
||||
const Padding(padding: EdgeInsets.only(right: 10),
|
||||
child: Icon(Icons.view_day, color: Colors.grey)),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: Text("${date.day > 9 ? date.day : "0${date.day}"}-${date.hour > 9 ? date.hour : "0${date.hour}"}-${date.year}".toUpperCase(), overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.w500))))
|
||||
])
|
||||
),
|
||||
collapsedIconColor: Colors.grey,
|
||||
children: widgets,
|
||||
)),
|
||||
children.add(Column( children: [ExpansionTile(
|
||||
enabled: widget.enabled,
|
||||
shape: const ContinuousRectangleBorder(),
|
||||
iconColor: Colors.grey,
|
||||
initiallyExpanded: true,
|
||||
title: SizedBox(
|
||||
child : Row( children: [
|
||||
const Padding(padding: EdgeInsets.only(right: 10),
|
||||
child: Icon(Icons.view_day, color: Colors.grey)),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: Text("${date.day > 9 ? date.day : "0${date.day}"}-${date.month > 9 ? date.month : "0${date.month}"}-${date.year}".toUpperCase(), overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.black, fontWeight: FontWeight.w500))))
|
||||
])
|
||||
),
|
||||
collapsedIconColor: Colors.grey,
|
||||
children: widgets,
|
||||
),
|
||||
Divider(color: Colors.grey.shade300, height: 1)
|
||||
]));
|
||||
}
|
||||
Future.delayed( const Duration(milliseconds: 100), () {
|
||||
if (widget.parent?.selectedReal != null) {
|
||||
widget.keys[widget.parent!.selectedReal!]?.currentContext?.findRenderObject()?.showOnScreen();
|
||||
if (selectedReal != null) {
|
||||
widget.keys[selectedReal!]?.currentContext?.findRenderObject()?.showOnScreen();
|
||||
}
|
||||
});
|
||||
return SingleChildScrollView( child: Container(
|
||||
return Container(
|
||||
alignment: children.isNotEmpty ? Alignment.topLeft : Alignment.center,
|
||||
color: children.isNotEmpty ? Colors.transparent : Colors.grey.shade300,
|
||||
width: children.isNotEmpty ? MediaQuery.of(context).size.width : null,
|
||||
height: MediaQuery.of(context).size.height - HeaderConstants.height - 50,
|
||||
child: Column( children: children))
|
||||
child: children.isNotEmpty ? SingleChildScrollView( child: Column( children: children)) : const Text("NO DATA FOUND", style: TextStyle(color: Colors.grey, fontSize: 30))
|
||||
);
|
||||
}
|
||||
}
|
@ -30,8 +30,7 @@ class Dashboard extends ChangeNotifier {
|
||||
List<Map<String, dynamic>> tempHistory = [];
|
||||
List<Map<String, dynamic>> history = [];
|
||||
Map<String, dynamic> scheduler = {};
|
||||
Map<String, bool> schedulerState = {};
|
||||
bool schedulerSave = false;
|
||||
bool scheduleActive = false;
|
||||
String? id;
|
||||
String name;
|
||||
String defaultName = "";
|
||||
@ -108,6 +107,7 @@ class Dashboard extends ChangeNotifier {
|
||||
map['defaultArrowDirection'] as int? ?? 0],
|
||||
defaultArrowStyle: ArrowStyle.values[map['arrowStyle'] as int? ?? 0],
|
||||
);
|
||||
d..scheduleActive = map['schedule_active'] as bool? ?? false;
|
||||
d..arrows = List<ArrowPainter>.from(
|
||||
(map['arrows'] as List<dynamic>).map<ArrowPainter>(
|
||||
(x) => ArrowPainter.fromMap(x as Map<String, dynamic>),
|
||||
@ -137,6 +137,7 @@ class Dashboard extends ChangeNotifier {
|
||||
}
|
||||
|
||||
void copyFromMap(Map<String, dynamic> map) {
|
||||
scheduleActive = map['schedule_active'] as bool? ?? false;
|
||||
scheduler = map['schedule'] as Map<String, String>? ?? {};
|
||||
defaultArrowStyle = ArrowStyle.values[map['arrowStyle'] as int? ?? 0];
|
||||
defaultDashSpace = map['defaultDashSpace'] as double? ?? 0;
|
||||
@ -222,17 +223,16 @@ class Dashboard extends ChangeNotifier {
|
||||
d["id"]=id;
|
||||
d["name"]=name;
|
||||
d["graph"]=graph;
|
||||
if (schedulerSave) {
|
||||
d["schedule"]=scheduler;
|
||||
}
|
||||
d["schedule"]=scheduler;
|
||||
d["schedule_active"]=scheduleActive;
|
||||
return d;
|
||||
}
|
||||
|
||||
void deserialize(Map<String, dynamic> graph) {
|
||||
elements = [];
|
||||
arrows = [];
|
||||
print(graph['schedule']);
|
||||
scheduler = graph['schedule'] ?? {};
|
||||
scheduleActive = graph['schedule_active'] ?? false;
|
||||
setZoomFactor(graph["graph"]?["zoom"] ?? 1.0);
|
||||
for(var el in graph['graph']?['elements'] ?? []) {
|
||||
List<ConnectionParams> nexts = [];
|
||||
|
@ -4,7 +4,6 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_flow_chart/flutter_flow_chart.dart';
|
||||
import 'package:flutter_flow_chart/src/dashboard.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
/// Kinf od element
|
||||
|
@ -19,6 +19,9 @@ import 'package:uuid/uuid.dart';
|
||||
abstract class FlowData {
|
||||
String getID();
|
||||
String getName();
|
||||
double? getWidth();
|
||||
double? getHeight();
|
||||
|
||||
Map<String, dynamic> serialize();
|
||||
FlowData deserialize(Map<String, dynamic> data);
|
||||
}
|
||||
@ -297,6 +300,20 @@ class FlowChartState<T extends FlowData> extends State<FlowChart> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.dashboard.isOpened && widget.onDashboardAlertOpened != null ) {
|
||||
Future.delayed(Duration(milliseconds: 100), () {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context, builder: (context) {
|
||||
return AlertDialog(
|
||||
titlePadding: EdgeInsets.zero,
|
||||
insetPadding: EdgeInsets.zero,
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(0)),
|
||||
title: widget.onDashboardAlertOpened!(context, widget.dashboard));
|
||||
|
||||
}); });
|
||||
}
|
||||
/// get dashboard position after first frame is drawn
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
if (mounted) {
|
||||
@ -379,9 +396,6 @@ class FlowChartState<T extends FlowData> extends State<FlowChart> {
|
||||
if (change) {
|
||||
DrawingArrow.instance.notifyListeners();
|
||||
widget.dashboard.chartKey.currentState?.setState(() { });
|
||||
/*Future.delayed(Duration(milliseconds: 10), () {
|
||||
node.requestFocus();
|
||||
});*/
|
||||
}
|
||||
},
|
||||
child: ClipRect(
|
||||
@ -421,12 +435,16 @@ class FlowChartState<T extends FlowData> extends State<FlowChart> {
|
||||
onAcceptWithDetails: (DragTargetDetails<T> details) {
|
||||
var e = details.data;
|
||||
String newID = const Uuid().v4();
|
||||
double ratio = 1;
|
||||
if (e.getWidth() != null && e.getHeight() != null) {
|
||||
ratio = (e.getHeight()! / (e.getWidth()! - 30));
|
||||
}
|
||||
FlowElement<T> el = FlowElement<T>(
|
||||
dashboard: widget.dashboard,
|
||||
id: newID,
|
||||
element: e,
|
||||
position: details.offset,
|
||||
size: const Size(100, 100),
|
||||
size: Size(100, 100 * ratio),
|
||||
text: '${widget.dashboard.elements.length}',
|
||||
handlerSize: 15,
|
||||
widget: widget.itemWidget(e),
|
||||
@ -477,33 +495,22 @@ class FlowChartState<T extends FlowData> extends State<FlowChart> {
|
||||
onDragEnd: (d) => node.requestFocus(),
|
||||
childWhenDragging: Opacity(opacity: .5,
|
||||
child: Padding( padding: const EdgeInsets.all(10),
|
||||
child: Container( height: realSize - 20, child: widget.itemWidget(e) ))),
|
||||
feedback: Container( height: realSize, child: widget.itemWidget(e) ),
|
||||
child: Container( child: widget.itemWidget(e),
|
||||
constraints: BoxConstraints(maxHeight: realSize - 20, maxWidth: realSize - 20), ))),
|
||||
feedback: Container( constraints: BoxConstraints(maxHeight: realSize, maxWidth: realSize),
|
||||
child: widget.itemWidget(e) ),
|
||||
child: InkWell( mouseCursor: SystemMouseCursors.grab, child: Padding( padding: const EdgeInsets.all(10),
|
||||
child: widget.itemWidgetTooltip != null ? HoverMenu( key: hoverKey, width: 400, title: Container(
|
||||
height: realSize - 20, child: widget.itemWidget(e) ),
|
||||
constraints: BoxConstraints( maxHeight: realSize - 20, maxWidth: realSize - 20),
|
||||
child: widget.itemWidget(e) ),
|
||||
items: [
|
||||
Container(child: widget.itemWidgetTooltip!(e)),
|
||||
]
|
||||
) : Container(
|
||||
height: realSize - 20, child: widget.itemWidget(e)
|
||||
constraints: BoxConstraints(maxHeight: realSize - 20, maxWidth: realSize - 20), child: widget.itemWidget(e)
|
||||
)
|
||||
) )));
|
||||
}
|
||||
if (!widget.dashboard.isOpened && widget.onDashboardAlertOpened != null ) {
|
||||
Future.delayed(Duration(milliseconds: 100), () {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context, builder: (context) {
|
||||
return AlertDialog(
|
||||
titlePadding: EdgeInsets.zero,
|
||||
insetPadding: EdgeInsets.zero,
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(0)),
|
||||
title: widget.onDashboardAlertOpened!(context, widget.dashboard));
|
||||
|
||||
}); });
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user