oc-search porting to flutter (missing compose + workflow editor)
This commit is contained in:
66
lib/core/models/cart.dart
Normal file
66
lib/core/models/cart.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
import 'package:oc_front/models/workspace.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/item_service.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/workspace_service.dart';
|
||||
|
||||
|
||||
class WorkspaceLocal {
|
||||
static Workspace? workspace;
|
||||
static final WorkspaceService _service = WorkspaceService();
|
||||
static List<AbstractItem> items = [];
|
||||
static void init(BuildContext context) {
|
||||
_service.all(context).then((value) {
|
||||
workspace = value.data;
|
||||
if (workspace != null) {
|
||||
if (workspace!.data.isNotEmpty) {
|
||||
ItemService<DataItem, DataItem> dataService = ItemService<DataItem, DataItem>();
|
||||
dataService.get(context, workspace!.data.join(",")).then(
|
||||
(value) { if (value.data != null) { items.add(value.data!); } }
|
||||
);
|
||||
}
|
||||
if (workspace!.computing.isNotEmpty) {
|
||||
ItemService<ComputingItem, ComputingItem> computingService = ItemService<ComputingItem, ComputingItem>();
|
||||
computingService.get(context, workspace!.computing.join(",")).then(
|
||||
(value) { if (value.data != null) { items.add(value.data!); } }
|
||||
);
|
||||
}
|
||||
if (workspace!.datacenter.isNotEmpty) {
|
||||
ItemService<DataCenterItem, DataCenterItem> dataCenterService = ItemService<DataCenterItem, DataCenterItem>();
|
||||
dataCenterService.get(context, workspace!.datacenter.join(",")).then(
|
||||
(value) { if (value.data != null) { items.add(value.data!); } }
|
||||
);
|
||||
}
|
||||
if (workspace!.storage.isNotEmpty) {
|
||||
ItemService<StorageItem, StorageItem> storageService = ItemService<StorageItem, StorageItem>();
|
||||
storageService.get(context, workspace!.storage.join(",")).then(
|
||||
(value) { if (value.data != null) { items.add(value.data!); } }
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
static AbstractItem? getItem(String id) {
|
||||
var found = WorkspaceLocal.items.where((element) => element.id.toString() == id);
|
||||
return found.isEmpty ? null : found.first;
|
||||
}
|
||||
|
||||
static void addItem(AbstractItem item) {
|
||||
if (!WorkspaceLocal.hasItem(item)) {
|
||||
items.add(item);
|
||||
try {
|
||||
_service.post(null, {}, { "id" : item.id.toString(), "rtype" : item.type.toString() });
|
||||
} catch (e) { /* */ }
|
||||
|
||||
}
|
||||
}
|
||||
static void removeItem(AbstractItem item) {
|
||||
items = items.where((element) => element.name != item.name).toList();
|
||||
try { _service.delete(null, { "id" : item.id.toString(), "rtype" : item.type.toString() });
|
||||
} catch (e) { /* */ }
|
||||
}
|
||||
static bool hasItem(AbstractItem item) {
|
||||
return items.where((element) => element.name == item.name).isNotEmpty;
|
||||
}
|
||||
static void clear() => items.clear();
|
||||
}
|
||||
48
lib/core/sections/end_drawer.dart
Normal file
48
lib/core/sections/end_drawer.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
import 'package:oc_front/pages/catalog.dart';
|
||||
import 'package:oc_front/core/models/cart.dart';
|
||||
import 'package:oc_front/widgets/items/item_row.dart';
|
||||
|
||||
GlobalKey<EndDrawerWidgetState> endDrawerKey = GlobalKey<EndDrawerWidgetState>();
|
||||
class EndDrawerWidget extends StatefulWidget {
|
||||
final List<AbstractItem>? items;
|
||||
EndDrawerWidget ({ this.items }): super(key: endDrawerKey);
|
||||
@override EndDrawerWidgetState createState() => EndDrawerWidgetState();
|
||||
}
|
||||
class EndDrawerWidgetState extends State<EndDrawerWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
List<ItemRowWidget> itemRows = WorkspaceLocal.items.map(
|
||||
(e) => ItemRowWidget(contextWidth: 400, item: e, keys: [endDrawerKey, CatalogFactory.key],)).toList();
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
width: 400,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: SingleChildScrollView(
|
||||
child: Column( children: [
|
||||
Container(
|
||||
width: 400,
|
||||
height: 50,
|
||||
decoration: const BoxDecoration(color: Color.fromRGBO(38, 166, 154, 1)),
|
||||
child: const Center(
|
||||
child: Row( mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(padding: EdgeInsets.only(right: 20), child: Icon(Icons.shopping_cart_outlined, size: 18, color: Colors.white)),
|
||||
Text("Workspace", style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.w600))
|
||||
])
|
||||
|
||||
),
|
||||
),
|
||||
itemRows.isEmpty ? Container( height: MediaQuery.of(context).size.height - 50,
|
||||
color: Colors.grey.shade300,
|
||||
child: const Center(child: Text("WORKSPACE IS EMPTY",
|
||||
style: TextStyle(fontSize: 25, fontWeight: FontWeight.w600, color: Colors.white))))
|
||||
: Container( child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row( children: itemRows)
|
||||
)),
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
20
lib/core/sections/header/header.dart
Normal file
20
lib/core/sections/header/header.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/core/sections/header/menu.dart';
|
||||
import 'package:oc_front/core/sections/header/search.dart';
|
||||
import 'package:oc_front/utils/clipper_menu.dart';
|
||||
|
||||
GlobalKey<HeaderWidgetState> headerWidgetKey = GlobalKey<HeaderWidgetState>();
|
||||
class HeaderWidget extends StatefulWidget {
|
||||
HeaderWidget () : super(key: headerWidgetKey);
|
||||
@override HeaderWidgetState createState() => HeaderWidgetState();
|
||||
}
|
||||
class HeaderWidgetState extends State<HeaderWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
headerWidgetKey = GlobalKey<HeaderWidgetState>();
|
||||
headerMenuKey.currentState?.closeMenu();
|
||||
return Column( children: [
|
||||
const HeaderMenuWidget(),
|
||||
SearchWidget()
|
||||
],);
|
||||
}
|
||||
}
|
||||
52
lib/core/sections/header/menu.dart
Normal file
52
lib/core/sections/header/menu.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:oc_front/main.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/utils/clipper_menu.dart';
|
||||
import 'package:oc_front/utils/dialog/login.dart';
|
||||
|
||||
class HeaderMenuWidget extends StatefulWidget{
|
||||
const HeaderMenuWidget ({ super.key });
|
||||
@override HeaderMenuWidgetState createState() => HeaderMenuWidgetState();
|
||||
}
|
||||
class HeaderMenuWidgetState extends State<HeaderMenuWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: Colors.grey.shade300))
|
||||
),
|
||||
child: Padding(padding: const EdgeInsets.only(top: 5, bottom: 5, left: 50, right: 50),
|
||||
child: Stack(children: [
|
||||
/*...(searchWidgetKey.currentState == null ? [Positioned( left: -20, top: -5,
|
||||
child: SvgPicture.asset("assets/images/icon.svg", height: 70, semanticsLabel: 'OpenCloud Logo'))] : []),*/
|
||||
Row(crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Tooltip( message: "workspace/cart", child: Padding(padding: const EdgeInsets.only(left: 10),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.shopping_cart_outlined),
|
||||
onPressed: () {
|
||||
headerMenuKey.currentState?.closeMenu();
|
||||
scaffoldKey.currentState?.openEndDrawer();
|
||||
},
|
||||
)
|
||||
)),
|
||||
Tooltip( message: "login", child: Padding(padding: const EdgeInsets.only(left: 10),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.login_outlined),
|
||||
onPressed: () { showDialog(context: context, builder: (context) => LoginWidget()); },
|
||||
)
|
||||
)),
|
||||
Tooltip( message: "navigation", child: Padding(padding: const EdgeInsets.only(left: 10),
|
||||
child: ClipperMenuWidget(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
iconColor: Colors.white,
|
||||
)
|
||||
))
|
||||
]
|
||||
)
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
125
lib/core/sections/header/search.dart
Normal file
125
lib/core/sections/header/search.dart
Normal file
@@ -0,0 +1,125 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:oc_front/core/services/router.dart';
|
||||
|
||||
class SearchConstants {
|
||||
static final Map<String, String?> _searchHost = {};
|
||||
static String? get() { return _searchHost[AppRouter.currentRoute.route]; }
|
||||
static void set(String? search) { _searchHost[AppRouter.currentRoute.route] = search; }
|
||||
static void remove() { _searchHost.remove(AppRouter.currentRoute.route); }
|
||||
static void clear() { _searchHost.clear(); }
|
||||
}
|
||||
|
||||
GlobalKey<SearchWidgetState> searchWidgetKey = GlobalKey<SearchWidgetState>();
|
||||
class SearchWidget extends StatefulWidget {
|
||||
SearchWidget (): super(key: GlobalKey<SearchWidgetState>());
|
||||
@override SearchWidgetState createState() => SearchWidgetState();
|
||||
}
|
||||
class SearchWidgetState extends State<SearchWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
searchWidgetKey = widget.key as GlobalKey<SearchWidgetState>;
|
||||
List<Widget> widgets = [
|
||||
...(MediaQuery.of(context).size.width > 600 || AppRouter.currentRoute.factory.searchFill()
|
||||
? [InkWell(
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () => AppRouter.zones.first.go(context, {}),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(top: 20, left: AppRouter.currentRoute.factory.searchFill() ? 40 : 0),
|
||||
child: SvgPicture.asset(
|
||||
width: 300,
|
||||
height: AppRouter.currentRoute.factory.searchFill() ?
|
||||
((MediaQuery.of(context).size.height - 50) / 2) : 150,
|
||||
"assets/images/logo.svg",
|
||||
semanticsLabel: 'OpenCloud Logo'
|
||||
)
|
||||
)
|
||||
)] : []),
|
||||
AppRouter.currentRoute.description != null ?
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(AppRouter.currentRoute.description!, style: const TextStyle(
|
||||
color: Color.fromRGBO(38, 166, 154, 1),
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600
|
||||
)),
|
||||
Row(children: [
|
||||
...(AppRouter.currentRoute.help == null || AppRouter.currentRoute.help!.isEmpty ? []
|
||||
: [ const Padding( padding: EdgeInsets.only(right: 10),
|
||||
child: Icon(Icons.help_outline, color: Colors.grey, size: 20)),
|
||||
Text(AppRouter.currentRoute.help ?? "", style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400
|
||||
)) ])
|
||||
],)
|
||||
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width - 300 - 100,
|
||||
height: 50,
|
||||
color: Colors.white,
|
||||
child: TextField(
|
||||
onChanged: (value) => SearchConstants.set(value),
|
||||
decoration: InputDecoration(
|
||||
hintText: "Search in ${AppRouter.currentRoute.route}...",
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 30),
|
||||
hintStyle: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w300
|
||||
),
|
||||
border: InputBorder.none
|
||||
)
|
||||
)
|
||||
),
|
||||
Tooltip(
|
||||
message: 'search',
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
AppRouter.currentRoute.factory.search(context);
|
||||
},
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black,
|
||||
border: Border(right: BorderSide(color: Colors.white)),
|
||||
),
|
||||
child: const Icon(Icons.search, color: Colors.white)
|
||||
)
|
||||
)
|
||||
),
|
||||
Tooltip(
|
||||
message: 'distributed search',
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
AppRouter.currentRoute.factory.search(context);
|
||||
},
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
color: Colors.black,
|
||||
child: const Icon(Icons.screen_search_desktop_outlined, color: Colors.white)
|
||||
)
|
||||
)
|
||||
)
|
||||
])
|
||||
];
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: AppRouter.currentRoute.factory.searchFill() ? (MediaQuery.of(context).size.height - 50) : 150,
|
||||
color: Colors.grey.shade300,
|
||||
child: AppRouter.currentRoute.factory.searchFill() ? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: widgets)
|
||||
: Row( mainAxisAlignment: MediaQuery.of(context).size.width < 600
|
||||
? MainAxisAlignment.center : MainAxisAlignment.start, children: widgets)
|
||||
);
|
||||
}
|
||||
}
|
||||
172
lib/core/services/api_service.dart
Normal file
172
lib/core/services/api_service.dart
Normal file
@@ -0,0 +1,172 @@
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:alert_banner/exports.dart';
|
||||
import 'package:oc_front/models/abstract.dart';
|
||||
import 'package:oc_front/models/response.dart';
|
||||
import 'package:oc_front/utils/dialog/alert.dart';
|
||||
import 'package:oc_front/core/services/html.dart' if (kIsWeb) 'dart:html' as http;
|
||||
|
||||
class APIService<T extends SerializerDeserializer> {
|
||||
static bool forceRequest = false;
|
||||
static Map<String, APIResponse<dynamic>> cache = <String, APIResponse<dynamic>>{};
|
||||
static String auth = "";
|
||||
Dio _dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: const String.fromEnvironment('HOST', defaultValue: 'http://localhost:8080'), // you can keep this blank
|
||||
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
|
||||
),
|
||||
)..interceptors.add(LogInterceptor( requestHeader: true, ),);
|
||||
|
||||
APIService({ required String baseURL }) {
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: baseURL, // you can keep this blank
|
||||
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
|
||||
),
|
||||
)..interceptors.add(LogInterceptor( requestHeader: true, ),);
|
||||
}
|
||||
|
||||
Future<APIResponse<T>> call(
|
||||
String url, String method, Map<String, dynamic>? body, bool force, BuildContext? context) async {
|
||||
switch (method.toLowerCase()) {
|
||||
case 'get' : return await get(url, force, context);
|
||||
case 'post' : return await post(url, body!, context);
|
||||
case 'put' : return await put(url, body!, context);
|
||||
case 'delete' : return await delete(url, context);
|
||||
default : return await get(url, force, context);
|
||||
}
|
||||
}
|
||||
Future<Response> _request(String url, String method, dynamic body, Options? options) async {
|
||||
switch (method.toLowerCase()) {
|
||||
case 'get' : return await _dio.get(url, options: options);
|
||||
case 'post' : return await _dio.post(url, data:body, options: options);
|
||||
case 'put' : return await _dio.put(url, data: body!, options: options);
|
||||
case 'delete' : return await _dio.delete(url, options: options);
|
||||
default : return await _dio.get(url, options: options);
|
||||
}
|
||||
}
|
||||
ValueNotifier downloadProgressNotifier = ValueNotifier(0);
|
||||
Future _mainDownload(String url, String method, bool isFilter, String? extend, String savePath, bool isWeb, BuildContext context) async {
|
||||
try {
|
||||
downloadProgressNotifier.value = 0;
|
||||
// dio.options.headers["authorization"] = auth;
|
||||
if (isWeb) {
|
||||
_dio.get("$url${extend ?? ""}").then((value) {
|
||||
var url = http.Url.createObjectUrlFromBlob(http.Blob([value.data]));
|
||||
http.AnchorElement(href: url)..setAttribute('download', savePath.split("/").last)..click();
|
||||
downloadProgressNotifier.value = 100;
|
||||
Future.delayed(const Duration(seconds: 1), () { Navigator.of(context).pop(); });
|
||||
});
|
||||
} else {
|
||||
_dio.download("$url${extend ?? ""}", savePath, onReceiveProgress: (actualBytes, int totalBytes) {
|
||||
Future.delayed(const Duration(seconds: 1), () {
|
||||
downloadProgressNotifier.value = (actualBytes / totalBytes * 100).floor();
|
||||
if (downloadProgressNotifier.value == 100) { Navigator.of(context).pop(); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} catch (e) { /* */ }
|
||||
}
|
||||
|
||||
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);
|
||||
if (response.statusCode != null && response.statusCode! < 400) {
|
||||
if (method == "delete") { cache.remove(url); return APIResponse<T>(); }
|
||||
APIResponse<T> resp = APIResponse<T>().deserialize(response.data);
|
||||
if (resp.error == "") {
|
||||
if (method == "get") { cache[url]=resp; }
|
||||
if (context != null && succeed != "") {
|
||||
// ignore: use_build_context_synchronously
|
||||
showAlertBanner(context, () {}, InfoAlertBannerChild(text: succeed), // <-- Put any widget here you want!
|
||||
alertBannerLocation: AlertBannerLocation.bottom,);
|
||||
}
|
||||
try { return cache[url] as APIResponse<T>;
|
||||
} catch (e) { return APIResponse(); }
|
||||
}
|
||||
err = resp.error ?? "internal error";
|
||||
}
|
||||
if (response.statusCode == 401) { err = "not authorized"; }
|
||||
} catch(e, s) {
|
||||
print(e);
|
||||
print(s);
|
||||
err = e.toString();
|
||||
}
|
||||
//if (err.contains("token") && err.contains("expired")) { AuthService().unAuthenticate(); }
|
||||
if (context != null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
showAlertBanner( context, () {}, AlertAlertBannerChild(text: err),// <-- Put any widget here you want!
|
||||
alertBannerLocation: AlertBannerLocation.bottom,);
|
||||
}
|
||||
throw Exception(err);
|
||||
}
|
||||
|
||||
Future<APIResponse<RawData>> raw(String url, dynamic body, String method) async {
|
||||
var err = "";
|
||||
if (url != "") {
|
||||
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>(); }
|
||||
APIResponse<RawData> resp = APIResponse<RawData>().deserialize(response.data);
|
||||
if (resp.error == "") { return resp; }
|
||||
err = resp.error ?? "internal error";
|
||||
}
|
||||
if (response.statusCode == 401) { err = "not authorized"; }
|
||||
} catch(e, s) { print(e); print(s);
|
||||
err = "${e.toString()} ${const String.fromEnvironment('HOST', defaultValue: 'http://localhost:8080')}"; }
|
||||
} else { err = "no url"; }
|
||||
// if (err.contains("token") && err.contains("expired")) { AuthService().unAuthenticate(); }
|
||||
throw Exception(err);
|
||||
}
|
||||
|
||||
Future<APIResponse<T>> sendFile(String url, File file, BuildContext context) async {
|
||||
FormData formData = FormData.fromMap({
|
||||
"file": await MultipartFile.fromFile(file.path, filename:file.path.split("/").last),
|
||||
});
|
||||
// ignore: use_build_context_synchronously
|
||||
return _main(url, formData, "post", "send succeed", true, context, Options(contentType: 'multipart/form-data'));
|
||||
}
|
||||
|
||||
Future getWithDownload(String url, String format, Map<String,dynamic> cache, String savePath, bool isWeb, BuildContext context) async {
|
||||
String asLabel = "";
|
||||
for (var key in cache.keys) {
|
||||
if (!asLabel.contains(key)) { asLabel += "&${key}_aslabel=${cache[key]!}"; }
|
||||
}
|
||||
try { _mainDownload(url, "get", true, "&export=$format$asLabel", savePath, isWeb, context);
|
||||
} catch (e) { /* */ }
|
||||
}
|
||||
|
||||
Future<APIResponse<T>> getWithOffset(String url, bool force, BuildContext? context) async {
|
||||
return _main(url, null, "get", "", force, context, null);
|
||||
}
|
||||
|
||||
Future<APIResponse<T>> get(String url, bool force, BuildContext? context) async {
|
||||
return _main(url, null, "get", "", force, context, null);
|
||||
}
|
||||
|
||||
Future<APIResponse<T>> post(String url, Map<String, dynamic> values, BuildContext? context) async {
|
||||
return _main(url, values, "post", "send succeed", true, context, null);
|
||||
}
|
||||
|
||||
Future<APIResponse<T>> put(String url, Map<String, dynamic> values, BuildContext? context) async {
|
||||
return _main(url, values, "put", "save succeed", true, context, null);
|
||||
}
|
||||
|
||||
Future<APIResponse<T>> delete(String url, BuildContext? context) async {
|
||||
return _main(url, null, "delete", "deletion succeed", true, context, null);
|
||||
}
|
||||
}
|
||||
17
lib/core/services/html.dart
Normal file
17
lib/core/services/html.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
class Blob {
|
||||
Blob(List blobParts, [String? type, String? endings]) {
|
||||
// logic
|
||||
}
|
||||
}
|
||||
|
||||
class Url {
|
||||
static String createObjectUrlFromBlob(Blob blob) => "";
|
||||
}
|
||||
|
||||
class AnchorElement {
|
||||
AnchorElement({String? href}) {
|
||||
// logic
|
||||
}
|
||||
void setAttribute(String name, Object value) {}
|
||||
void click() { }
|
||||
}
|
||||
145
lib/core/services/router.dart
Normal file
145
lib/core/services/router.dart
Normal file
@@ -0,0 +1,145 @@
|
||||
import 'package:oc_front/main.dart';
|
||||
import 'package:oc_front/pages/abstract_page.dart';
|
||||
import 'package:oc_front/pages/catalog.dart';
|
||||
import 'package:oc_front/pages/catalog_item.dart';
|
||||
import 'package:oc_front/pages/datacenter.dart';
|
||||
import 'package:oc_front/pages/map.dart';
|
||||
import 'package:oc_front/pages/scheduler.dart';
|
||||
import 'package:oc_front/pages/workflow.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
GlobalKey<RouterWidgetState> routerKey = GlobalKey<RouterWidgetState>();
|
||||
|
||||
class RouterWidget extends StatefulWidget {
|
||||
const RouterWidget({Key? key}) : super(key: key);
|
||||
@override RouterWidgetState createState() => RouterWidgetState();
|
||||
}
|
||||
|
||||
class RouterWidgetState extends State<RouterWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return Padding( padding: const EdgeInsets.only(right: 20), child: Row(children: [
|
||||
IconButton(onPressed: () async => AppRouter.realHistory.length > 1 ? AppRouter.back() : null, icon: Icon(Icons.arrow_back, color: AppRouter.realHistory.length > 1 ? Colors.white : Theme.of(context).splashColor)),
|
||||
IconButton(onPressed: () async => AppRouter.canForward() ? AppRouter.forward() : null, icon: Icon(Icons.arrow_forward, color: AppRouter.canForward() ? Colors.white : Theme.of(context).splashColor)),
|
||||
],));
|
||||
}
|
||||
}
|
||||
|
||||
class RouterItem {
|
||||
final IconData? icon;
|
||||
final String? label;
|
||||
final String route;
|
||||
final String? description;
|
||||
final String? help;
|
||||
final AbstractFactory factory;
|
||||
List<String> args = [];
|
||||
RouterItem({this.icon, this.label, this.description, this.help,
|
||||
required this.route, required this.factory, this.args = const []});
|
||||
String get path => "/${route == AppRouter.home ? "" : route}";
|
||||
|
||||
void go(BuildContext context, Map<String, String> params) {
|
||||
AppRouter.currentRoute = this;
|
||||
var newPath = "$path";
|
||||
for (var arg in args) { newPath = newPath.replaceAll(":$arg", params[arg] ?? ""); }
|
||||
context.go(newPath);
|
||||
}
|
||||
}
|
||||
|
||||
class AppRouter {
|
||||
static const String home = "catalog";
|
||||
static List<RouterItem> zones = [
|
||||
RouterItem(icon: Icons.book_outlined, label: "catalog searcher", route: home, factory: CatalogFactory()),
|
||||
RouterItem(icon: Icons.rebase_edit, label: "workflow manager", route: "workflow",
|
||||
description: "View to select & create new workflow.", help: "Workflow only access to your workspace datas. If a an element of your flow is missing, perhaps means it's missing in workspace.",
|
||||
factory: WorkflowFactory()),
|
||||
RouterItem(icon: Icons.schedule, label: "scheduled tasks", route: "scheduler", factory: SchedulerFactory()),
|
||||
RouterItem(icon: Icons.dns_outlined, label: "my datacenter", route: "datacenter",
|
||||
description: "Manage & monitor your datacenter.", help: "not implemented for now",
|
||||
factory: DatacenterFactory()),
|
||||
RouterItem(icon: Icons.public_outlined, label: "localisations", route: "map", factory: MapFactory()),
|
||||
RouterItem(description: "", help: "", route: "catalog/:id", factory: CatalogItemFactory(), args: ["id"]),
|
||||
];
|
||||
static List<String> history = [];
|
||||
static List<String> realHistory = [];
|
||||
static final AppRouter _instance = AppRouter._internal();
|
||||
factory AppRouter() { return _instance; }
|
||||
AppRouter._internal() {
|
||||
SharedPreferences.getInstance().then((prefs) {
|
||||
if (prefs.containsKey("history")) {
|
||||
realHistory = prefs.getString("history")!.split(",");
|
||||
history = prefs.getString("history")!.split(",");
|
||||
routerKey.currentState?.setState(() { });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static Future<String?> getRouteCookie() async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString("url") != "" ? prefs.getString("url") : null;
|
||||
}
|
||||
|
||||
static removeRouteCookie() async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove("url");
|
||||
}
|
||||
|
||||
static setRouteCookie( String path , BuildContext context ) async {
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString("url", path);
|
||||
if (realHistory.isNotEmpty && realHistory.last != path || realHistory.isEmpty) {
|
||||
try {
|
||||
var index = history.indexOf(realHistory.last);
|
||||
history = history.sublist(0, index + 1);
|
||||
} catch (e) { /* */ }
|
||||
realHistory.add(path);
|
||||
history.add(path);
|
||||
if (history.length > 10) {
|
||||
realHistory.removeAt(0);
|
||||
history.removeAt(0);
|
||||
}
|
||||
prefs.setString("history", realHistory.join(","));
|
||||
routerKey.currentState?.setState(() { });
|
||||
}
|
||||
}
|
||||
|
||||
static back() async {
|
||||
if (realHistory.length <= 1) { return; }
|
||||
realHistory.removeLast();
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString("url", realHistory.last);
|
||||
prefs.setString("history", realHistory.join(","));
|
||||
var splitted = realHistory.last.split(":");
|
||||
routerKey.currentState?.setState(() { });
|
||||
scaffoldKey.currentState?.setState(() {});
|
||||
}
|
||||
static bool canForward() {
|
||||
try {
|
||||
var index = history.indexOf(realHistory.last);
|
||||
return (index + 1) < history.length;
|
||||
} catch (e) { return false; }
|
||||
|
||||
}
|
||||
static forward() async {
|
||||
if (canForward()) {
|
||||
var index = history.indexOf(realHistory.last);
|
||||
realHistory.add(history[index + 1]);
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString("url", realHistory.last);
|
||||
var splitted = realHistory.last.split(":");
|
||||
prefs.setString("history", realHistory.join(","));
|
||||
routerKey.currentState?.setState(() { });
|
||||
scaffoldKey.currentState?.setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
static RouterItem currentRoute = zones.first;
|
||||
static List<RouteBase> get routes => zones.map( (k) => GoRoute(
|
||||
name: k.route,
|
||||
path: k.path,
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return MainPage(page: k.factory.factory(state, k.args));
|
||||
},
|
||||
)).toList();
|
||||
}
|
||||
// ROUTER SHOULD INVOKE MAIN TO ACCESS VIEW, VIEW ARE MENU SECTION
|
||||
14
lib/core/services/specialized_services/abstract_service.dart
Normal file
14
lib/core/services/specialized_services/abstract_service.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/models/abstract.dart';
|
||||
import 'package:oc_front/models/response.dart';
|
||||
import 'package:oc_front/core/services/api_service.dart';
|
||||
|
||||
abstract class AbstractService<T extends SerializerDeserializer> {
|
||||
abstract APIService<T> service;
|
||||
|
||||
Future<APIResponse<T>> all(BuildContext? context) { throw UnimplementedError(); }
|
||||
Future<APIResponse<T>> get(BuildContext? context, String id);
|
||||
Future<APIResponse<T>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params);
|
||||
Future<APIResponse<T>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) { throw UnimplementedError(); }
|
||||
Future<APIResponse<T>> delete(BuildContext? context, Map<String, String> params) { throw UnimplementedError(); }
|
||||
}
|
||||
23
lib/core/services/specialized_services/item_service.dart
Normal file
23
lib/core/services/specialized_services/item_service.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.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/abstract.dart';
|
||||
import 'package:oc_front/models/response.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: String.fromEnvironment('SEARCH_HOST', defaultValue: 'http://localhost:49618/v1/${getTopic(S)}')
|
||||
);
|
||||
|
||||
@override Future<APIResponse<T>> all(BuildContext? context) { throw UnimplementedError(); }
|
||||
@override Future<APIResponse<T>> get(BuildContext? context, String id) {
|
||||
if (id.contains(",")) { return service.get("/multi/$id", true, context); }
|
||||
return service.get("/$id", true, context);
|
||||
}
|
||||
@override Future<APIResponse<T>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) {
|
||||
return service.post("/", body, context);
|
||||
}
|
||||
@override Future<APIResponse<T>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) { throw UnimplementedError(); }
|
||||
@override Future<APIResponse<T>> delete(BuildContext? context, Map<String, String> params) { throw UnimplementedError(); }
|
||||
}
|
||||
19
lib/core/services/specialized_services/search_service.dart
Normal file
19
lib/core/services/specialized_services/search_service.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/material.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/search.dart';
|
||||
|
||||
class SearchService extends AbstractService<Search> {
|
||||
@override APIService<Search> service = APIService<Search>(
|
||||
baseURL: const String.fromEnvironment('SEARCH_HOST', defaultValue: 'http://localhost:49618/v1/search')
|
||||
);
|
||||
|
||||
@override Future<APIResponse<Search>> all(BuildContext? context) { throw UnimplementedError(); }
|
||||
@override Future<APIResponse<Search>> get(BuildContext? context, String id) {
|
||||
return service.get("/byWord?word=$id", true, context);
|
||||
}
|
||||
@override Future<APIResponse<Search>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) { throw UnimplementedError(); }
|
||||
@override Future<APIResponse<Search>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) { throw UnimplementedError(); }
|
||||
@override Future<APIResponse<Search>> delete(BuildContext? context, Map<String, String> params) { throw UnimplementedError(); }
|
||||
}
|
||||
27
lib/core/services/specialized_services/workflow_service.dart
Normal file
27
lib/core/services/specialized_services/workflow_service.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.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';
|
||||
|
||||
class WorflowService extends AbstractService<RawData> {
|
||||
@override APIService<RawData> service = APIService<RawData>(
|
||||
baseURL: const String.fromEnvironment('SEARCH_HOST', defaultValue: 'http://localhost:49618/v1/workflow/')
|
||||
);
|
||||
|
||||
@override Future<APIResponse<RawData>> all(BuildContext? context) {
|
||||
print("WorkflowService.all");
|
||||
return service.get("", true, context);
|
||||
}
|
||||
@override Future<APIResponse<RawData>> get(BuildContext? context, String id) { throw UnimplementedError(); }
|
||||
@override Future<APIResponse<RawData>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) {
|
||||
String path = "?";
|
||||
for (var key in params.keys) { path += "$key=${params[key]}&"; }
|
||||
return service.post(path, body, context);
|
||||
}
|
||||
@override Future<APIResponse<RawData>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@override Future<APIResponse<RawData>> delete(BuildContext? context, Map<String, String> params) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.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/workspace.dart';
|
||||
|
||||
class WorkspaceService extends AbstractService<Workspace> {
|
||||
@override APIService<Workspace> service = APIService<Workspace>(
|
||||
baseURL: const String.fromEnvironment('SEARCH_HOST', defaultValue: 'http://localhost:49618/v1/workspace/')
|
||||
);
|
||||
|
||||
@override Future<APIResponse<Workspace>> all(BuildContext? context) {
|
||||
return service.get("/list", true, context);
|
||||
}
|
||||
@override Future<APIResponse<Workspace>> get(BuildContext? context, String id) { throw UnimplementedError(); }
|
||||
@override Future<APIResponse<Workspace>> post(BuildContext? context, Map<String, dynamic> body, Map<String, String> params) {
|
||||
String path = "?";
|
||||
for (var key in params.keys) { path += "$key=${params[key]}&"; }
|
||||
return service.post(path, body, context);
|
||||
}
|
||||
@override Future<APIResponse<Workspace>> put(BuildContext? context, String id, Map<String, dynamic> body, Map<String, String> params) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@override Future<APIResponse<Workspace>> delete(BuildContext? context, Map<String, String> params) {
|
||||
String path = "?";
|
||||
for (var key in params.keys) { path += "$key=${params[key]}&"; }
|
||||
return service.delete(path, context);
|
||||
}
|
||||
}
|
||||
80
lib/main.dart
Normal file
80
lib/main.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:oc_front/core/models/cart.dart';
|
||||
import 'package:oc_front/core/services/router.dart';
|
||||
import 'package:oc_front/core/sections/end_drawer.dart';
|
||||
import 'package:oc_front/core/sections/header/header.dart';
|
||||
import 'package:desktop_window/desktop_window.dart' if (kIsWeb) '';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!kIsWeb) { DesktopWindow.setMinWindowSize(const Size(400, 400)); }
|
||||
return MaterialApp.router(
|
||||
routerConfig: GoRouter( routes: AppRouter.routes ),
|
||||
);
|
||||
}
|
||||
}
|
||||
// ignore: must_be_immutable
|
||||
class MainPage extends StatefulWidget {
|
||||
Widget page;
|
||||
MainPage({super.key, required this.page});
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
@override
|
||||
State<MainPage> createState() => _MainPageState();
|
||||
}
|
||||
|
||||
class _MainPageState extends State<MainPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
WorkspaceLocal.init(context);
|
||||
scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
return Scaffold(
|
||||
key: scaffoldKey,
|
||||
endDrawer: EndDrawerWidget(),
|
||||
body: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
//
|
||||
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
|
||||
// action in the IDE, or press "p" in the console), to see the
|
||||
// wireframe for each widget.
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
HeaderWidget(),
|
||||
widget.page // CatalogPageWidget(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
43
lib/models/abstract.dart
Normal file
43
lib/models/abstract.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
abstract class SerializerDeserializer<T> {
|
||||
T deserialize(dynamic json);
|
||||
Map<String, dynamic> serialize();
|
||||
}
|
||||
|
||||
Map<String, List<T>> fromMapListJson<T extends SerializerDeserializer>(Map<String, dynamic> j, SerializerDeserializer ref) {
|
||||
var map = <String, List<T>>{};
|
||||
for (var key in j.keys) {
|
||||
if(j[key] != null) { map[key] = fromListJson(j[key], ref); }
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
Map<String, T> fromMapJson<T extends SerializerDeserializer>(Map<String, dynamic> j, SerializerDeserializer ref) {
|
||||
var map = <String, T>{};
|
||||
for (var key in j.keys) {
|
||||
if(j[key] != null) { map[key] = ref.deserialize(json.decode(json.encode(j[key]))); }
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
List<T> fromListJson<T extends SerializerDeserializer>(List<dynamic> jss, SerializerDeserializer ref) {
|
||||
var list = <T>[];
|
||||
for (var js in jss) { list.add(ref.deserialize(json.decode(json.encode(js)))); }
|
||||
return list;
|
||||
}
|
||||
|
||||
Map<String, Map<String, dynamic>> toMapJson<T extends SerializerDeserializer>(Map<String, T> json) {
|
||||
var map = <String, Map<String, dynamic>>{};
|
||||
for (var key in json.keys) {
|
||||
if(json[key] != null) { map[key] = json[key]!.serialize(); }
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> toListJson<T extends SerializerDeserializer>(List<T> obj) {
|
||||
var list = <Map<String, dynamic>>[];
|
||||
for (var js in obj) { list.add(js.serialize()); }
|
||||
return list;
|
||||
}
|
||||
45
lib/models/response.dart
Normal file
45
lib/models/response.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'package:oc_front/models/abstract.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
import 'package:oc_front/models/workspace.dart';
|
||||
|
||||
Map<Type, SerializerDeserializer> refs = <Type, SerializerDeserializer> {
|
||||
RawData: RawData(),
|
||||
Search: Search(),
|
||||
Workspace: Workspace(),
|
||||
DataItem: DataItem(),
|
||||
DataCenterItem: DataCenterItem(),
|
||||
StorageItem: StorageItem(),
|
||||
ComputingItem: ComputingItem(),
|
||||
};
|
||||
|
||||
class APIResponse<T extends SerializerDeserializer> {
|
||||
APIResponse({
|
||||
this.data,
|
||||
this.error = "",
|
||||
this.offset = 0,
|
||||
});
|
||||
int offset = 0;
|
||||
T? data ;
|
||||
String? error = "";
|
||||
|
||||
SerializerDeserializer? getTypeString() {
|
||||
try { return refs[refs.keys.firstWhere((ref) => '$ref' == '$T')];
|
||||
} catch (e) { return null; }
|
||||
}
|
||||
|
||||
APIResponse<T> deserialize(dynamic j) {
|
||||
try {
|
||||
return APIResponse<T>(
|
||||
data: refs[T]!.deserialize(j),
|
||||
error: j.containsKey("error") && j["error"] != null ? j["error"] : "",
|
||||
);
|
||||
} catch (e) { return APIResponse<T>( data: refs[T]!.deserialize(j), ); }
|
||||
|
||||
}
|
||||
}
|
||||
class RawData extends SerializerDeserializer<RawData> {
|
||||
RawData({ this.values = const []});
|
||||
dynamic values;
|
||||
@override deserialize(dynamic json) { return RawData(values: json); }
|
||||
@override Map<String, dynamic> serialize() => { };
|
||||
}
|
||||
381
lib/models/search.dart
Normal file
381
lib/models/search.dart
Normal file
@@ -0,0 +1,381 @@
|
||||
import 'package:oc_front/models/abstract.dart';
|
||||
|
||||
const List<ComputingItem> _emptyComputing = [];
|
||||
const List<DataItem> _emptyData = [];
|
||||
const List<DataCenterItem> _emptyDataCenter = [];
|
||||
const List<StorageItem> _emptyStorage = [];
|
||||
class Search extends SerializerDeserializer<Search> {
|
||||
Search({
|
||||
this.computing = _emptyComputing,
|
||||
this.datacenter = _emptyDataCenter,
|
||||
this.data = _emptyData,
|
||||
this.storage = _emptyStorage,
|
||||
});
|
||||
List<ComputingItem> computing;
|
||||
List<DataCenterItem> datacenter;
|
||||
List<DataItem> data;
|
||||
List<StorageItem> storage;
|
||||
@override deserialize(dynamic json) {
|
||||
json = json as Map<String, dynamic>;
|
||||
return Search(
|
||||
computing: json.containsKey("computing") ? fromListJson(json["computing"], ComputingItem()) : [],
|
||||
datacenter: json.containsKey("datacenter") ? fromListJson(json["datacenter"], DataCenterItem()) : [],
|
||||
data: json.containsKey("data") ? fromListJson(json["data"], DataItem()) : [],
|
||||
storage: json.containsKey("storage") ? fromListJson(json["storage"], StorageItem()) : [],
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
const List<String> _empty = [];
|
||||
abstract class AbstractItem {
|
||||
String? id;
|
||||
String? name;
|
||||
String? logo;
|
||||
String? type;
|
||||
String? owner;
|
||||
String? description;
|
||||
String? shortDescription;
|
||||
String topic = "";
|
||||
}
|
||||
|
||||
Type? getTopicType(String topic) {
|
||||
if (topic == "computing") { return ComputingItem; }
|
||||
else if (topic == "data") { return DataItem; }
|
||||
else if (topic == "datacenter") { return DataCenterItem; }
|
||||
else if (topic == "storage") { return StorageItem; }
|
||||
else { return null; }
|
||||
}
|
||||
|
||||
String getTopic(Type type) {
|
||||
if (type == ComputingItem) { return "computing"; }
|
||||
if (type == DataItem) { return "data"; }
|
||||
if (type == DataCenterItem) { return "datacenter"; }
|
||||
if (type == StorageItem) { return "storage"; }
|
||||
return "";
|
||||
}
|
||||
|
||||
bool isComputing(String topic) => topic == "computing";
|
||||
bool isData(String topic) => topic == "data";
|
||||
bool isDataCenter(String topic) => topic == "datacenter";
|
||||
bool isStorage(String topic) => topic == "storage";
|
||||
|
||||
class ComputingItem extends SerializerDeserializer<ComputingItem> implements AbstractItem {
|
||||
ComputingItem({
|
||||
this.id,
|
||||
this.name,
|
||||
this.logo,
|
||||
this.type,
|
||||
this.owner,
|
||||
this.price,
|
||||
this.image,
|
||||
this.command,
|
||||
this.licence,
|
||||
this.description,
|
||||
this.requirements,
|
||||
this.ports = _empty,
|
||||
this.shortDescription,
|
||||
this.dinputs = _empty,
|
||||
this.doutputs = _empty,
|
||||
this.arguments = _empty,
|
||||
this.environment = _empty,
|
||||
});
|
||||
@override String? id;
|
||||
@override String? name;
|
||||
@override String? logo;
|
||||
@override String? type;
|
||||
@override String? owner;
|
||||
@override String topic = "computing";
|
||||
double? price;
|
||||
String? image;
|
||||
String? command;
|
||||
String? licence;
|
||||
List<dynamic> ports;
|
||||
List<dynamic> dinputs;
|
||||
List<dynamic> doutputs;
|
||||
List<dynamic> arguments;
|
||||
@override String? description;
|
||||
@override String? shortDescription;
|
||||
List<dynamic> environment;
|
||||
ExecRequirements? requirements;
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return ComputingItem(); }
|
||||
return ComputingItem(
|
||||
id: json.containsKey("ID") ? json["ID"] : null,
|
||||
name: json.containsKey("name") ? json["name"] : null,
|
||||
logo: json.containsKey("logo") ? json["logo"] : null,
|
||||
type: json.containsKey("type") ? json["type"] : null,
|
||||
owner: json.containsKey("owner") ? json["owner"] : null,
|
||||
price: json.containsKey("price") ? json["price"]?.toDouble() : null,
|
||||
image: json.containsKey("image") ? json["image"] : null,
|
||||
command: json.containsKey("command") ? json["command"] : null,
|
||||
licence: json.containsKey("licence") ? json["licence"] : null,
|
||||
description: json.containsKey("description") ? json["description"] : null,
|
||||
ports: json["ports"] ?? [],
|
||||
shortDescription: json.containsKey("short_description") ? json["short_description"] : null,
|
||||
dinputs: json["dinputs"] ?? [],
|
||||
doutputs: json["doutputs"] ?? [],
|
||||
arguments: json["arguments"] ?? [],
|
||||
environment: json["environment"] ?? [],
|
||||
requirements: json.containsKey("requirements") ? ExecRequirements().deserialize(json["execution_requirements"]) : null,
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
|
||||
class ExecRequirements extends SerializerDeserializer<ExecRequirements> {
|
||||
ExecRequirements({
|
||||
this.ram,
|
||||
this.cpus,
|
||||
this.gpus,
|
||||
this.diskIO,
|
||||
this.scallingModel,
|
||||
this.parallel = false,
|
||||
});
|
||||
double? ram;
|
||||
double? cpus;
|
||||
double? gpus;
|
||||
String? diskIO;
|
||||
bool parallel = false;
|
||||
double? scallingModel;
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return ExecRequirements(); }
|
||||
return ExecRequirements(
|
||||
ram: json.containsKey("ram") ? json["ram"]?.toDouble() : null,
|
||||
cpus: json.containsKey("cpus") ? json["cpus"]?.toDouble() : null,
|
||||
gpus: json.containsKey("gpus") ? json["gpus"]?.toDouble() : null,
|
||||
diskIO: json.containsKey("disk_io") ? json["disk_io"] : null,
|
||||
scallingModel: json.containsKey("scaling_model") ? json["scaling_model"]?.toDouble() : null,
|
||||
parallel: json.containsKey("parallel") ? json["parallel"] : false,
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
|
||||
class DataItem extends SerializerDeserializer<DataItem> implements AbstractItem {
|
||||
DataItem({
|
||||
this.id,
|
||||
this.name,
|
||||
this.logo,
|
||||
this.type,
|
||||
this.dtype,
|
||||
this.owner,
|
||||
this.example,
|
||||
this.location,
|
||||
this.description,
|
||||
this.protocol = _empty,
|
||||
this.shortDescription,
|
||||
});
|
||||
@override String? id;
|
||||
@override String? name;
|
||||
@override String? logo;
|
||||
@override String? type;
|
||||
@override String topic = "data";
|
||||
String? dtype;
|
||||
String? example;
|
||||
String? location;
|
||||
@override String? description;
|
||||
List<dynamic> protocol;
|
||||
@override String? shortDescription;
|
||||
@override String? owner;
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return DataItem(); }
|
||||
return DataItem(
|
||||
id: json.containsKey("ID") ? json["ID"] : null,
|
||||
name: json.containsKey("name") ? json["name"] : null,
|
||||
logo: json.containsKey("logo") ? json["logo"] : null,
|
||||
type: json.containsKey("type") ? json["type"] : null,
|
||||
owner: json.containsKey("owner") ? json["owner"] : null,
|
||||
dtype: json.containsKey("dtype") ? json["dtype"] : null,
|
||||
example: json.containsKey("example") ? json["example"] : null,
|
||||
location: json.containsKey("location") ? json["location"] : null,
|
||||
description: json.containsKey("description") ? json["description"] : null,
|
||||
protocol: json["protocol"] ?? [],
|
||||
shortDescription: json.containsKey("short_description") ? json["short_description"] : null
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
const List<GPU> _emptyGPU = [];
|
||||
class DataCenterItem extends SerializerDeserializer<DataCenterItem> implements AbstractItem {
|
||||
DataCenterItem({
|
||||
this.id,
|
||||
this.cpu,
|
||||
this.ram,
|
||||
this.name,
|
||||
this.logo,
|
||||
this.type,
|
||||
this.owner,
|
||||
this.acronym,
|
||||
this.bookingPrice,
|
||||
this.description,
|
||||
this.hosts = _empty,
|
||||
this.gpus = _emptyGPU,
|
||||
this.shortDescription,
|
||||
});
|
||||
CPU? cpu;
|
||||
RAM? ram;
|
||||
@override String? id;
|
||||
@override String? name;
|
||||
@override String? logo;
|
||||
@override String? type;
|
||||
@override String? owner;
|
||||
@override String topic = "datacenter";
|
||||
String? acronym;
|
||||
List<GPU> gpus = [];
|
||||
@override String? description;
|
||||
List<dynamic> hosts;
|
||||
double? bookingPrice;
|
||||
@override String? shortDescription;
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return DataCenterItem(); }
|
||||
return DataCenterItem(
|
||||
id: json.containsKey("ID") ? json["ID"] : null,
|
||||
ram: json.containsKey("ram") ? RAM().deserialize(json["ram"]) : null,
|
||||
cpu: json.containsKey("cpu") ? CPU().deserialize(json["cpu"]) : null,
|
||||
acronym: json.containsKey("acronym") ? json["acronym"] : null,
|
||||
name: json.containsKey("name") ? json["name"] : null,
|
||||
logo: json.containsKey("logo") ? json["logo"] : null,
|
||||
type: json.containsKey("type") ? json["type"] : null,
|
||||
owner: json.containsKey("owner") ? json["owner"] : null,
|
||||
bookingPrice: json.containsKey("bookingPrice") ? json["bookingPrice"]?.toDouble() : null,
|
||||
description: json.containsKey("description") ? json["description"] : null,
|
||||
hosts: json["hosts"] ?? [],
|
||||
shortDescription: json.containsKey("short_description") ? json["short_description"] : null,
|
||||
gpus: json.containsKey("gpus") ? fromListJson(json["gpus"] ?? [], GPU()) : [],
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
class CPU extends SerializerDeserializer<CPU> {
|
||||
CPU({
|
||||
this.cores,
|
||||
this.platform,
|
||||
this.architecture,
|
||||
this.minimumMemory,
|
||||
this.shared = false,
|
||||
});
|
||||
double? cores;
|
||||
String? platform;
|
||||
bool shared = false;
|
||||
String? architecture;
|
||||
double? minimumMemory;
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return CPU(); }
|
||||
return CPU(
|
||||
cores: json.containsKey("cores") ? json["cores"]?.toDouble() : null,
|
||||
platform: json.containsKey("platform") ? json["platform"] : null,
|
||||
architecture: json.containsKey("architecture") ? json["architecture"] : null,
|
||||
minimumMemory: json.containsKey("minimumMemory") ? json["minimumMemory"]?.toDouble() : null,
|
||||
shared: json.containsKey("shared") ? json["shared"] : false,
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
class GPU extends SerializerDeserializer<GPU> {
|
||||
GPU({
|
||||
this.cudaCores,
|
||||
this.memory,
|
||||
this.model,
|
||||
this.tensorCores,
|
||||
});
|
||||
double? cudaCores;
|
||||
double? memory;
|
||||
String? model;
|
||||
double? tensorCores;
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return GPU(); }
|
||||
return GPU(
|
||||
cudaCores: json.containsKey("cuda_cores") ? json["cuda_cores"]?.toDouble() : null,
|
||||
memory: json.containsKey("memory") ? json["memory"]?.toDouble() : null,
|
||||
model: json.containsKey("model") ? json["model"] : null,
|
||||
tensorCores: json.containsKey("tensor_cores") ? json["tensor_cores"]?.toDouble() : null,
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
class RAM extends SerializerDeserializer<RAM> {
|
||||
RAM({
|
||||
this.ecc = false,
|
||||
this.size,
|
||||
});
|
||||
bool ecc = false;
|
||||
double? size;
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return RAM(); }
|
||||
return RAM(
|
||||
ecc: json.containsKey("ecc") ? json["ecc"] : false,
|
||||
size: json.containsKey("size") ? json["size"]?.toDouble() : null,
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
class StorageItem extends SerializerDeserializer<StorageItem> implements AbstractItem {
|
||||
StorageItem({
|
||||
this.id,
|
||||
this.url,
|
||||
this.size,
|
||||
this.name,
|
||||
this.logo,
|
||||
this.type,
|
||||
this.owner,
|
||||
this.acronym,
|
||||
this.throughput,
|
||||
this.redundancy,
|
||||
this.description,
|
||||
this.bookingPrice,
|
||||
this.shortDescription,
|
||||
this.encryption = false,
|
||||
});
|
||||
@override String? id;
|
||||
String? url;
|
||||
@override String? name;
|
||||
@override String? logo;
|
||||
@override String? type;
|
||||
@override String topic = "storage";
|
||||
double? size;
|
||||
@override String? owner;
|
||||
String? acronym;
|
||||
String? redundancy;
|
||||
String? throughput;
|
||||
@override String? description;
|
||||
double? bookingPrice;
|
||||
bool encryption = false;
|
||||
@override String? shortDescription;
|
||||
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return StorageItem(); }
|
||||
return StorageItem(
|
||||
id: json.containsKey("ID") ? json["ID"] : null,
|
||||
url: json.containsKey("URL") ? json["URL"] : null,
|
||||
size: json.containsKey("size") ? json["size"]?.toDouble() : null,
|
||||
name: json.containsKey("name") ? json["name"] : null,
|
||||
logo: json.containsKey("logo") ? json["logo"] : null,
|
||||
type: json.containsKey("type") ? json["type"] : null,
|
||||
owner: json.containsKey("owner") ? json["owner"] : null,
|
||||
acronym: json.containsKey("DCacronym") ? json["DCacronym"] : null,
|
||||
bookingPrice: json.containsKey("bookingPrice") ? json["bookingPrice"]?.toDouble() : null,
|
||||
description: json.containsKey("description") ? json["description"] : null,
|
||||
throughput: json.containsKey("throughput") ? json["throughput"] : [],
|
||||
shortDescription: json.containsKey("short_description") ? json["short_description"] : null,
|
||||
redundancy: json.containsKey("redundancy") ? json["redundancy"] : [],
|
||||
encryption: json.containsKey("encryption") ? json["encryption"] : false,
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
29
lib/models/workspace.dart
Normal file
29
lib/models/workspace.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:oc_front/models/abstract.dart';
|
||||
|
||||
class Workspace extends SerializerDeserializer<Workspace> {
|
||||
String? id;
|
||||
List<dynamic> data;
|
||||
List<dynamic> datacenter;
|
||||
List<dynamic> storage;
|
||||
List<dynamic> computing;
|
||||
|
||||
Workspace({
|
||||
this.id,
|
||||
this.computing = const [],
|
||||
this.data = const [],
|
||||
this.datacenter = const [],
|
||||
this.storage = const [],
|
||||
});
|
||||
|
||||
@override deserialize(dynamic json) {
|
||||
try { json = json as Map<String, dynamic>;
|
||||
} catch (e) { return Workspace(); }
|
||||
return Workspace(
|
||||
computing: json.containsKey("computing") ? json["computing"] : [],
|
||||
datacenter: json.containsKey("datacenter") ? json["datacenter"] : [],
|
||||
data: json.containsKey("data") ? json["data"] : [],
|
||||
storage: json.containsKey("storage") ? json["storage"] : [],
|
||||
);
|
||||
}
|
||||
@override Map<String, dynamic> serialize() => {};
|
||||
}
|
||||
7
lib/pages/abstract_page.dart
Normal file
7
lib/pages/abstract_page.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
abstract class AbstractFactory {
|
||||
Widget factory(GoRouterState state, List<String> args);
|
||||
bool searchFill();
|
||||
void search(BuildContext context);
|
||||
}
|
||||
40
lib/pages/catalog.dart
Normal file
40
lib/pages/catalog.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
import 'package:oc_front/widgets/catalog.dart';
|
||||
import 'package:oc_front/pages/abstract_page.dart';
|
||||
import 'package:oc_front/core/sections/header/search.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/search_service.dart';
|
||||
|
||||
class CatalogFactory implements AbstractFactory {
|
||||
static List<AbstractItem> items = [];
|
||||
static GlobalKey<CatalogPageWidgetState> key = GlobalKey<CatalogPageWidgetState>();
|
||||
@override bool searchFill() { return CatalogFactory.items.isEmpty; }
|
||||
@override Widget factory(GoRouterState state, List<String> args) { return CatalogPageWidget(); }
|
||||
@override void search(BuildContext context) {
|
||||
CatalogFactory.key.currentState?.widget.search.get(context, SearchConstants.get()!).then((value) {
|
||||
if (value.data == null) { return; }
|
||||
CatalogFactory.items = [
|
||||
...value.data!.computing, ...value.data!.data, ...value.data!.storage, ...value.data!.datacenter,];
|
||||
searchWidgetKey.currentState?.setState(() {});
|
||||
CatalogFactory.key.currentState?.setState(() {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class CatalogPageWidget extends StatefulWidget {
|
||||
final SearchService search = SearchService();
|
||||
CatalogPageWidget (): super(key: CatalogFactory.key);
|
||||
@override CatalogPageWidgetState createState() => CatalogPageWidgetState();
|
||||
}
|
||||
class CatalogPageWidgetState extends State<CatalogPageWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return Column( children : [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: CatalogFactory.items.isEmpty ? 0 : MediaQuery.of(context).size.height - 200,
|
||||
child: CatalogWidget(items: CatalogFactory.items) )
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
39
lib/pages/catalog_item.dart
Normal file
39
lib/pages/catalog_item.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:oc_front/core/models/cart.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
import 'package:oc_front/pages/abstract_page.dart';
|
||||
import 'package:oc_front/pages/catalog.dart';
|
||||
import 'package:oc_front/widgets/items/item.dart';
|
||||
import 'package:oc_front/widgets/items/item_row.dart';
|
||||
|
||||
class CatalogItemFactory implements AbstractFactory {
|
||||
static GlobalKey<CatalogItemPageWidgetState> key = GlobalKey<CatalogItemPageWidgetState>();
|
||||
@override bool searchFill() { return false; }
|
||||
@override Widget factory(GoRouterState state, List<String> args) {
|
||||
var id = state.pathParameters[args.first];
|
||||
try {
|
||||
var item = CatalogFactory.items.firstWhere( (element) => element.id == id );
|
||||
return CatalogItemPageWidget(item : item);
|
||||
} catch (e) {
|
||||
var item = WorkspaceLocal.getItem(id ?? "");
|
||||
if (item != null) { return CatalogItemPageWidget(item : item); }
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
@override void search(BuildContext context) { }
|
||||
}
|
||||
|
||||
class CatalogItemPageWidget extends StatefulWidget {
|
||||
AbstractItem item;
|
||||
CatalogItemPageWidget ({ required this.item }) : super(key: CatalogItemFactory.key);
|
||||
@override CatalogItemPageWidgetState createState() => CatalogItemPageWidgetState();
|
||||
}
|
||||
class CatalogItemPageWidgetState extends State<CatalogItemPageWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return Column( children: [
|
||||
ItemRowWidget(contextWidth: MediaQuery.of(context).size.width, item: widget.item, readOnly: true,),
|
||||
ItemWidget(item: widget.item,),
|
||||
]);
|
||||
}
|
||||
}
|
||||
22
lib/pages/datacenter.dart
Normal file
22
lib/pages/datacenter.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:oc_front/pages/abstract_page.dart';
|
||||
|
||||
class DatacenterFactory implements AbstractFactory {
|
||||
static GlobalKey<DataCenterPageWidgetState> key = GlobalKey<DataCenterPageWidgetState>();
|
||||
@override bool searchFill() { return false; }
|
||||
@override Widget factory(GoRouterState state, List<String> args) { return DataCenterPageWidget(); }
|
||||
@override void search(BuildContext context) { }
|
||||
}
|
||||
|
||||
class DataCenterPageWidget extends StatefulWidget {
|
||||
DataCenterPageWidget () : super(key: DatacenterFactory.key);
|
||||
@override DataCenterPageWidgetState createState() => DataCenterPageWidgetState();
|
||||
|
||||
static Widget factory() { return DataCenterPageWidget(); }
|
||||
}
|
||||
class DataCenterPageWidgetState extends State<DataCenterPageWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return Column( children: []);
|
||||
}
|
||||
}
|
||||
46
lib/pages/map.dart
Normal file
46
lib/pages/map.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:oc_front/pages/abstract_page.dart';
|
||||
|
||||
class MapFactory implements AbstractFactory {
|
||||
static GlobalKey<MapPageWidgetState> key = GlobalKey<MapPageWidgetState>();
|
||||
@override bool searchFill() { return false; }
|
||||
@override Widget factory(GoRouterState state, List<String> args) { return MapPageWidget(); }
|
||||
@override void search(BuildContext context) { }
|
||||
}
|
||||
|
||||
class MapPageWidget extends StatefulWidget {
|
||||
MapPageWidget(): super(key: MapFactory.key);
|
||||
@override MapPageWidgetState createState() => MapPageWidgetState();
|
||||
static void search(BuildContext context) { }
|
||||
static Widget factory() { return MapPageWidget(); }
|
||||
}
|
||||
class MapPageWidgetState extends State<MapPageWidget> {
|
||||
double currentZoom = 2.0;
|
||||
LatLng currentCenter = const LatLng(51.5, -0.09);
|
||||
static final MapController _mapController = MapController();
|
||||
void _zoom() {
|
||||
currentZoom = currentZoom - 1;
|
||||
_mapController.move(currentCenter, currentZoom);
|
||||
}
|
||||
|
||||
@override Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child : FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: MapOptions(
|
||||
initialCenter: currentCenter,
|
||||
initialZoom: currentZoom,
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
28
lib/pages/scheduler.dart
Normal file
28
lib/pages/scheduler.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:oc_front/pages/abstract_page.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
class SchedulerFactory implements AbstractFactory {
|
||||
static GlobalKey<SchedulerPageWidgetState> key = GlobalKey<SchedulerPageWidgetState>();
|
||||
@override bool searchFill() { return false; }
|
||||
@override Widget factory(GoRouterState state, List<String> args) { return SchedulerPageWidget(); }
|
||||
@override void search(BuildContext context) { }
|
||||
}
|
||||
|
||||
class SchedulerPageWidget extends StatefulWidget {
|
||||
SchedulerPageWidget(): super(key: SchedulerFactory.key);
|
||||
@override SchedulerPageWidgetState createState() => SchedulerPageWidgetState();
|
||||
static void search(BuildContext context) { }
|
||||
static Widget factory() { return SchedulerPageWidget(); }
|
||||
}
|
||||
class SchedulerPageWidgetState extends State<SchedulerPageWidget> {
|
||||
|
||||
@override Widget build(BuildContext context) {
|
||||
return TableCalendar(
|
||||
firstDay: DateTime.utc(2010, 10, 16),
|
||||
lastDay: DateTime.utc(2030, 3, 14),
|
||||
focusedDay: DateTime.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
168
lib/pages/workflow.dart
Normal file
168
lib/pages/workflow.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:oc_front/core/services/specialized_services/workflow_service.dart';
|
||||
import 'package:oc_front/models/response.dart';
|
||||
import 'package:oc_front/pages/abstract_page.dart';
|
||||
|
||||
class WorkflowFactory implements AbstractFactory {
|
||||
static GlobalKey<WorkflowPageWidgetState> key = GlobalKey<WorkflowPageWidgetState>();
|
||||
@override bool searchFill() { return false; }
|
||||
@override Widget factory(GoRouterState state, List<String> args) { return WorkflowPageWidget(); }
|
||||
@override void search(BuildContext context) { }
|
||||
}
|
||||
|
||||
class WorkflowPageWidget extends StatefulWidget {
|
||||
String? _selected;
|
||||
TextEditingController _ctrl = TextEditingController();
|
||||
WorkflowPageWidget () : super(key: WorkflowFactory.key);
|
||||
@override WorkflowPageWidgetState createState() => WorkflowPageWidgetState();
|
||||
static void search(BuildContext context) { }
|
||||
static Widget factory() { return WorkflowPageWidget(); }
|
||||
}
|
||||
class WorkflowPageWidgetState extends State<WorkflowPageWidget> {
|
||||
|
||||
final WorflowService _service = WorflowService();
|
||||
@override Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height - 200,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: (MediaQuery.of(context).size.height - 200) / 2,
|
||||
child : Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children : [
|
||||
Container( width: MediaQuery.of(context).size.height / 1.5,
|
||||
height: 50, margin: const EdgeInsets.only(top: 2),
|
||||
child: FutureBuilder<APIResponse<RawData>>(
|
||||
future: _service.all(context),
|
||||
builder: (context, snapshot) {
|
||||
List<DropdownMenuItem> items = [];
|
||||
print(snapshot.error);
|
||||
if (snapshot.hasData && snapshot.data!.data != null
|
||||
&& snapshot.data!.data!.values.isNotEmpty) {
|
||||
items = (snapshot.data!.data!.values as List<dynamic>).map((dynamic value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value.toString(),
|
||||
child: Text(value.toString()),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
if (widget._selected != null
|
||||
&& !items.where((element) => element.value == widget._selected).isNotEmpty) {
|
||||
items.add(DropdownMenuItem<String>(
|
||||
value: widget._selected.toString(),
|
||||
child: Text(widget._selected.toString()),
|
||||
));
|
||||
}
|
||||
return DropdownButtonFormField(
|
||||
value: widget._selected,
|
||||
hint: const Text("select workflow to load...", style: TextStyle(color: Colors.grey, fontSize: 15)),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.zero,
|
||||
borderSide: BorderSide(color: Color.fromARGB(38, 166, 154, 1), width: 2.5),
|
||||
),
|
||||
fillColor: Colors.grey.shade300,
|
||||
contentPadding: const EdgeInsets.only(left: 30, right: 30),
|
||||
enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.zero,
|
||||
borderSide: BorderSide(color: Colors.grey.shade300, width: 2.5),
|
||||
),
|
||||
border: OutlineInputBorder( borderRadius: BorderRadius.zero,
|
||||
borderSide: BorderSide(color: Colors.grey.shade300, width: 2.5)),
|
||||
),
|
||||
items: items, onChanged: (value) {
|
||||
setState(() { widget._selected = value.toString(); });
|
||||
});
|
||||
})
|
||||
),
|
||||
Tooltip(
|
||||
message: 'empty selection',
|
||||
child: InkWell(
|
||||
mouseCursor: widget._selected == null || widget._selected!.isEmpty ? MouseCursor.defer : SystemMouseCursors.click,
|
||||
onTap: () { setState(() { widget._selected = null; }); },
|
||||
child: Container(
|
||||
width: 50, height: 48,
|
||||
decoration: const BoxDecoration( color: Colors.black,
|
||||
border: Border(right: BorderSide(color: Colors.white))),
|
||||
child: Icon(Icons.refresh, color: widget._selected == null || widget._selected!.isEmpty ? Colors.grey : Colors.white),
|
||||
)
|
||||
)
|
||||
),
|
||||
Tooltip(
|
||||
message: 'load workflow selected',
|
||||
child: InkWell(
|
||||
mouseCursor: widget._selected == null || widget._selected!.isEmpty ? MouseCursor.defer : SystemMouseCursors.click,
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
width: 50, height: 48,
|
||||
color: Colors.black,
|
||||
child: Icon(Icons.open_in_browser_outlined,
|
||||
color: widget._selected == null || widget._selected!.isEmpty ? Colors.grey : Colors.white),
|
||||
)
|
||||
)
|
||||
)
|
||||
]),
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: (MediaQuery.of(context).size.height - 200) / 2,
|
||||
color: Colors.grey.shade300,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row( children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width / 1.5,
|
||||
height: 50,
|
||||
child: TextFormField(
|
||||
cursorColor: const Color.fromARGB(38, 166, 154, 1),
|
||||
controller: widget._ctrl,
|
||||
onChanged: (value) => setState(() { widget._ctrl.value = TextEditingValue(text: value); }),
|
||||
validator: (value) => value == null || value.isEmpty ? "name is required" : null,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "name a new workflow...",
|
||||
fillColor: Colors.white,
|
||||
filled: true,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 30),
|
||||
hintStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w300
|
||||
),
|
||||
border: InputBorder.none
|
||||
)
|
||||
)
|
||||
),
|
||||
Tooltip(
|
||||
message: 'add',
|
||||
child:InkWell(
|
||||
mouseCursor: widget._ctrl.value.text.isEmpty ? MouseCursor.defer : SystemMouseCursors.click,
|
||||
onTap: () async {
|
||||
if (widget._ctrl.value.text.isNotEmpty) {
|
||||
await _service.post(context, {}, { "workflowName" : widget._ctrl.value.text });
|
||||
widget._selected = widget._ctrl.value.text;
|
||||
widget._ctrl.value = const TextEditingValue(text: "");
|
||||
setState(() { });
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
color: Colors.black,
|
||||
child: Icon(Icons.add, color: widget._ctrl.value.text.isEmpty ? Colors.grey : Colors.white)
|
||||
)
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
])
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
17
lib/utils/arrow_clipper.dart
Normal file
17
lib/utils/arrow_clipper.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ArrowClipper extends CustomClipper<Path> {
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
Path path = Path();
|
||||
path.moveTo(0, size.height);
|
||||
path.lineTo(size.width / 2, size.height / 2);
|
||||
path.lineTo(size.width, size.height);
|
||||
return path;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomClipper<Path> oldClipper) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
219
lib/utils/clipper_menu.dart
Normal file
219
lib/utils/clipper_menu.dart
Normal file
@@ -0,0 +1,219 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/core/sections/header/menu.dart';
|
||||
import 'package:oc_front/core/services/router.dart';
|
||||
import 'package:oc_front/utils/arrow_clipper.dart';
|
||||
|
||||
class TooltipWidget extends StatefulWidget {
|
||||
int index = -1;
|
||||
Size? buttonSize;
|
||||
List<String> labels;
|
||||
Offset? buttonPosition;
|
||||
TooltipWidget ({ Key? key,
|
||||
required this.labels,
|
||||
required this.index,
|
||||
required this.buttonSize,
|
||||
required this.buttonPosition }): super(key: key);
|
||||
@override TooltipWidgetState createState() => TooltipWidgetState();
|
||||
}
|
||||
class TooltipWidgetState extends State<TooltipWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
double minimal = (widget.index < 0 ? 0 : 7 * widget.labels[widget.index].length).toDouble() + 30;
|
||||
return Positioned(
|
||||
top : ((widget.buttonPosition?.dy ?? 1 ) + 11) + (((widget.buttonSize?.height) ?? 1)* 1.5) * (widget.index + 1),
|
||||
left: (widget.buttonPosition?.dx ?? 1) - minimal,
|
||||
child: Container(
|
||||
width: widget.index < 0 ? 0 : (widget.index < 0 ? 0 : minimal),
|
||||
height: (widget.buttonSize?.height ?? 1) - 2,
|
||||
color: Colors.black,
|
||||
child: Text( widget.index < 0 ? "" : widget.labels[widget.index],
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
decoration: TextDecoration.none,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w300),
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
GlobalKey<ClipperMenuWidgetState> headerMenuKey = GlobalKey<ClipperMenuWidgetState>();
|
||||
class ClipperMenuWidget extends StatefulWidget {
|
||||
final BorderRadius borderRadius;
|
||||
final Color backgroundColor;
|
||||
final Color iconColor;
|
||||
int index = -1;
|
||||
|
||||
ClipperMenuWidget({
|
||||
required this.borderRadius,
|
||||
this.backgroundColor = const Color.fromRGBO(38, 166, 154, 1),
|
||||
this.iconColor = Colors.black,
|
||||
}) : super(key: headerMenuKey);
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
ClipperMenuWidgetState createState() => ClipperMenuWidgetState();
|
||||
}
|
||||
|
||||
class ClipperMenuWidgetState extends State<ClipperMenuWidget> with SingleTickerProviderStateMixin {
|
||||
late GlobalKey _key;
|
||||
bool isMenuOpen = false;
|
||||
Offset? buttonPosition;
|
||||
Size? buttonSize;
|
||||
final GlobalKey<TooltipWidgetState> _tooltipKey = GlobalKey<TooltipWidgetState>();
|
||||
OverlayEntry? _overlayEntry;
|
||||
BorderRadius? _borderRadius;
|
||||
AnimationController? _animationController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
);
|
||||
_borderRadius = widget.borderRadius; // BorderRadius.circular(4)
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_animationController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
findButton() {
|
||||
if (_key.currentContext != null) {
|
||||
RenderBox renderBox = _key.currentContext?.findRenderObject()! as RenderBox;
|
||||
buttonSize = renderBox.size;
|
||||
buttonPosition = renderBox.localToGlobal(Offset.zero);
|
||||
}
|
||||
}
|
||||
|
||||
void closeMenu() {
|
||||
try {
|
||||
_overlayEntry?.remove();
|
||||
_animationController?.reverse();
|
||||
isMenuOpen = false;
|
||||
} catch (e) { }
|
||||
|
||||
}
|
||||
|
||||
void openMenu() {
|
||||
findButton();
|
||||
_animationController?.forward();
|
||||
_overlayEntry = _overlayEntryBuilder();
|
||||
if (_overlayEntry != null) { Overlay.of(context).insert(_overlayEntry!); }
|
||||
isMenuOpen = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_key = GlobalKey();
|
||||
headerMenuKey = widget.key as GlobalKey<ClipperMenuWidgetState>;
|
||||
return Container(
|
||||
key: _key,
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: IconButton(
|
||||
splashRadius: 4,
|
||||
icon: _animationController == null ?
|
||||
const Icon(
|
||||
Icons.close,
|
||||
) : AnimatedIcon(
|
||||
icon: AnimatedIcons.menu_close,
|
||||
progress: _animationController!,
|
||||
),
|
||||
onPressed: () {
|
||||
if (isMenuOpen) { closeMenu();
|
||||
} else { openMenu(); }
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
OverlayEntry _overlayEntryBuilder() {
|
||||
var routes = AppRouter.zones.where(
|
||||
(e) => e.path != AppRouter.currentRoute.path && e.label != null && e.icon != null).toList();
|
||||
return OverlayEntry(
|
||||
builder: (context) {
|
||||
return Stack( children: [
|
||||
TooltipWidget(
|
||||
key: _tooltipKey,
|
||||
labels: routes.map((e) => e.label!).toList(),
|
||||
buttonPosition: buttonPosition,
|
||||
buttonSize: buttonSize,
|
||||
index: widget.index
|
||||
),
|
||||
Positioned(
|
||||
top: (buttonPosition?.dy ?? 1 ) + (buttonSize?.height ?? 1),
|
||||
left: buttonPosition?.dx,
|
||||
width: buttonSize?.width,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ClipPath(
|
||||
clipper: ArrowClipper(),
|
||||
child: Container(
|
||||
width: 17,
|
||||
height: 17,
|
||||
color: widget.backgroundColor ?? Color(0xFFF),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 15.0),
|
||||
child: Container(
|
||||
height: routes.length * ((buttonSize?.height ?? 1) * 1.5),
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: const [BoxShadow(color: Colors.black54, spreadRadius: 1, blurRadius: 1, offset: Offset(0, 1))],
|
||||
color: widget.backgroundColor,
|
||||
borderRadius: _borderRadius,
|
||||
),
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
iconTheme: IconThemeData( color: widget.iconColor ),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: List.generate(routes.length, (index) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (index >= 0) { routes[index].go(context, {}); }
|
||||
closeMenu();
|
||||
},
|
||||
child: Container(
|
||||
decoration: index == (routes.length - 1) ? null : const BoxDecoration(
|
||||
border: Border( bottom: BorderSide(color: Colors.white ), ),
|
||||
),
|
||||
width: (buttonSize?.width ?? 1) * 1.5,
|
||||
height: (buttonSize?.height ?? 1) * 1.5,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (state) {
|
||||
_tooltipKey.currentState?.setState(() {
|
||||
_tooltipKey.currentState?.widget.index = index;
|
||||
});
|
||||
},
|
||||
onExit: (state) {
|
||||
_tooltipKey.currentState?.setState(() { _tooltipKey.currentState?.widget.index = -1; });
|
||||
},
|
||||
child: Icon( routes[index].icon, size: 19)
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
62
lib/utils/dialog/alert.dart
Normal file
62
lib/utils/dialog/alert.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class InfoAlertBannerChild extends StatelessWidget {
|
||||
final String text;
|
||||
const InfoAlertBannerChild({super.key, required this.text});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.8),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.greenAccent,
|
||||
borderRadius: BorderRadius.all(Radius.circular(5)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Text(text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 18),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AlertAlertBannerChild extends StatelessWidget {
|
||||
final String text;
|
||||
const AlertAlertBannerChild({super.key, required this.text});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.8),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.redAccent,
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(5),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Text( text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 18),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/utils/dialog/confirm_box.dart
Normal file
31
lib/utils/dialog/confirm_box.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class ConfirmBoxWidget extends StatefulWidget {
|
||||
String purpose = "";
|
||||
Function validate = () {};
|
||||
ConfirmBoxWidget ({ Key? key, required this.purpose, required this.validate }): super(key: key);
|
||||
@override ConfirmBoxWidgetState createState() => ConfirmBoxWidgetState();
|
||||
}
|
||||
class ConfirmBoxWidgetState extends State<ConfirmBoxWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
content: Padding(padding: EdgeInsets.all(20), child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Center(child: Padding( padding: EdgeInsets.only(bottom: 10),
|
||||
child: Icon(Icons.help_outline_outlined, size: 80, color: Colors.grey,))),
|
||||
Center(child: Text("Are you sure ?", style: TextStyle(fontSize: 25, color: Theme.of(context).primaryColor),)),
|
||||
Center(child: Text("Do you really want to ${widget.purpose.toUpperCase()} ?",
|
||||
style: const TextStyle(fontSize: 12.5, color: Colors.grey),)),
|
||||
const Center(child: Text("You will not able to undo this action.",
|
||||
style: TextStyle(fontSize: 12.5, color: Colors.grey),)),
|
||||
Padding( padding: EdgeInsets.only(top: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Padding( padding: EdgeInsets.only(right: 10), child: TextButton(onPressed: () {
|
||||
widget.validate();
|
||||
context.pop();
|
||||
}, style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Theme.of(context).primaryColor)),
|
||||
child: Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: Text("YES", style: TextStyle(color: Colors.white, fontSize: 15),)))),
|
||||
TextButton(onPressed: () => context.pop(), style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Theme.of(context).splashColor)),
|
||||
child: Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: Text("NO", style: TextStyle(color: Colors.white, fontSize: 15),)))]))
|
||||
],)));
|
||||
}
|
||||
}
|
||||
71
lib/utils/dialog/login.dart
Normal file
71
lib/utils/dialog/login.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class LoginWidget extends StatefulWidget {
|
||||
LoginWidget ({ Key? key }): super(key: key);
|
||||
@override LoginWidgetState createState() => LoginWidgetState();
|
||||
}
|
||||
class LoginWidgetState extends State<LoginWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(0))),
|
||||
content: Padding(padding: const EdgeInsets.all(20), child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Center(child: Padding( padding: EdgeInsets.only(bottom: 10),
|
||||
child: Icon(Icons.person_search, size: 80, color: Colors.grey,))),
|
||||
const Center(child: Text("WELCOME ON OPENCLOUD", style: TextStyle(fontSize: 25, fontWeight: FontWeight.w600,
|
||||
color: Color.fromRGBO(38, 166, 154, 1)),)),
|
||||
Padding(padding: const EdgeInsets.symmetric(vertical: 20), child: Divider(color: Colors.grey.shade300,),),
|
||||
Container( margin: const EdgeInsets.only(bottom: 10), child: Center(child: Row( mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width / 3,
|
||||
alignment : Alignment.center,
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.shade300), borderRadius: BorderRadius.zero),
|
||||
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.shade300), borderRadius: BorderRadius.zero),
|
||||
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.shade300), borderRadius: BorderRadius.zero),
|
||||
hintText: "username...",
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
fillColor: Colors.grey.shade300,
|
||||
filled: true,
|
||||
hintStyle: const TextStyle(fontSize: 12.5, color: Colors.grey)),
|
||||
style: const TextStyle(fontSize: 12.5, color: Colors.grey)),),
|
||||
Container(width: 50, height: 50, color: Colors.black, child: const Icon(Icons.person, color: Colors.white))
|
||||
]))),
|
||||
Container( margin: const EdgeInsets.only(bottom: 20), child: Center(child: Row( mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width / 3,
|
||||
alignment : Alignment.center,
|
||||
child: TextField(
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.shade300), borderRadius: BorderRadius.zero),
|
||||
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.shade300), borderRadius: BorderRadius.zero),
|
||||
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.shade300), borderRadius: BorderRadius.zero),
|
||||
hintText: "password...",
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
fillColor: Colors.grey.shade300,
|
||||
filled: true,
|
||||
hintStyle: const TextStyle(fontSize: 12.5, color: Colors.grey)),
|
||||
style: const TextStyle(fontSize: 12.5, color: Colors.grey)),),
|
||||
Container(width: 50, height: 50, color: Colors.black, child: const Icon(Icons.password, color: Colors.white))
|
||||
]))),
|
||||
Row( mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Padding( padding: const EdgeInsets.only(right: 10), child:
|
||||
InkWell(onTap: () { context.pop(); },
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 20),
|
||||
width: MediaQuery.of(context).size.width / 3,
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
color: const Color.fromRGBO(38, 166, 154, 1),
|
||||
child: const Center( child: Text("LOGIN", style: TextStyle(color: Colors.white, fontSize: 15),))))),
|
||||
])
|
||||
],)));
|
||||
}
|
||||
}
|
||||
17
lib/widgets/catalog.dart
Normal file
17
lib/widgets/catalog.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/core/models/cart.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
import 'package:oc_front/widgets/items/item_row.dart';
|
||||
|
||||
class CatalogWidget extends StatefulWidget {
|
||||
final List<AbstractItem>? items;
|
||||
CatalogWidget ({ Key? key, this.items }): super(key: key);
|
||||
@override CatalogWidgetState createState() => CatalogWidgetState();
|
||||
}
|
||||
class CatalogWidgetState extends State<CatalogWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
var items = widget.items ?? WorkspaceLocal.items;
|
||||
List<ItemRowWidget> itemRows = items.map((e) => ItemRowWidget(contextWidth: MediaQuery.of(context).size.width, item: e)).toList();
|
||||
return SingleChildScrollView( child: Column( children: itemRows) );
|
||||
}
|
||||
}
|
||||
40
lib/widgets/items/item.dart
Normal file
40
lib/widgets/items/item.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
import 'package:oc_front/widgets/items_details/data_item.dart';
|
||||
|
||||
class ItemWidget extends StatefulWidget {
|
||||
AbstractItem item;
|
||||
ItemWidget ({ super.key, required this.item });
|
||||
@override ItemWidgetState createState() => ItemWidgetState();
|
||||
}
|
||||
class ItemWidgetState extends State<ItemWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
Widget w = Container();
|
||||
/* if (isData(widget.item.topic)) { w = DataItemWidget(item: widget.item as DataItem); }
|
||||
else if (isComputing(widget.item.topic)) { w = DataItemWidget(item: widget.item as DataItem); }
|
||||
else if (isDataCenter(widget.item.topic)) { w = DataItemWidget(item: widget.item as DataItem); }
|
||||
else if (isStorage(widget.item.topic)) { w = DataItemWidget(item: widget.item as DataItem); } */
|
||||
|
||||
return Container(
|
||||
height: MediaQuery.of(context).size.height - 300,
|
||||
child: SingleChildScrollView(
|
||||
child: Column( children: [
|
||||
widget.item.description == null ? Container() : Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: Colors.grey.shade300))),
|
||||
padding: const EdgeInsets.all(30),
|
||||
child: Text(widget.item.description!,
|
||||
style: TextStyle(fontSize: 15, color: Colors.grey, fontWeight: FontWeight.w500))),
|
||||
Container(padding: const EdgeInsets.all(30),
|
||||
color: Colors.grey.shade300,
|
||||
width: MediaQuery.of(context).size.width / 2,
|
||||
child: w
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
115
lib/widgets/items/item_row.dart
Normal file
115
lib/widgets/items/item_row.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
import 'package:oc_front/core/models/cart.dart';
|
||||
import 'package:oc_front/core/services/router.dart';
|
||||
|
||||
const List<GlobalKey<State>> _empty = [];
|
||||
// ignore: must_be_immutable
|
||||
class ItemRowWidget extends StatefulWidget {
|
||||
bool readOnly = false;
|
||||
double contextWidth = 0;
|
||||
AbstractItem item;
|
||||
List<GlobalKey<State>> keys = [];
|
||||
ItemRowWidget ({ super.key,
|
||||
required this.contextWidth, this.readOnly = false, required this.item, this.keys = _empty });
|
||||
@override ItemRowWidgetState createState() => ItemRowWidgetState();
|
||||
}
|
||||
class ItemRowWidgetState extends State<ItemRowWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
double imageSize = MediaQuery.of(context).size.width != widget.contextWidth ? 0 : 80;
|
||||
var ratio = MediaQuery.of(context).size.width != widget.contextWidth ? 0.5 : 1; // 2;
|
||||
var itemWidth = (((widget.contextWidth - imageSize) / 3) - 80) / ratio;
|
||||
itemWidth = itemWidth > 100 ? 100 : ( itemWidth < 40 ? 40 : itemWidth );
|
||||
var endWidth = (itemWidth * ratio) + 80;
|
||||
Image? image;
|
||||
if (widget.item.logo != null) {
|
||||
image = Image.memory(base64Decode(widget.item.logo ?? ""), width: imageSize, height: imageSize);
|
||||
}
|
||||
Widget w = Container(
|
||||
width: widget.contextWidth,
|
||||
height: 100,
|
||||
decoration: BoxDecoration( border: Border(bottom: BorderSide(color: Colors.grey.shade300)) ),
|
||||
child: Row( children: [
|
||||
Padding( padding: const EdgeInsets.all(10),
|
||||
child: image ?? Image.network('https://get-picto.com/wp-content/uploads/2024/01/logo-instagram-png.webp',
|
||||
height: imageSize, width: imageSize)),
|
||||
Container(
|
||||
width: widget.contextWidth - (imageSize + 20) - endWidth,
|
||||
child: Padding(padding: widget.contextWidth != MediaQuery.of(context).size.width ?
|
||||
const EdgeInsets.symmetric(horizontal: 10) : const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Row( children: [
|
||||
Container(padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
|
||||
margin: const EdgeInsets.only(right: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: isData(widget.item.topic) ? Colors.blue :
|
||||
isComputing(widget.item.topic) ? Colors.green :
|
||||
isDataCenter(widget.item.topic) ? Colors.orange :
|
||||
isStorage(widget.item.topic) ? Colors.red : Colors.grey,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text( MediaQuery.of(context).size.width < 600 ? "" : widget.item.type.toString(),
|
||||
style: const TextStyle(fontSize: 10, color: Colors.white, fontWeight: FontWeight.w600)),
|
||||
),
|
||||
Expanded( child: Text(widget.item.name?.toUpperCase() ?? "",
|
||||
style: const TextStyle(fontSize: 20, overflow: TextOverflow.ellipsis, fontWeight: FontWeight.w600, color: Color(0xFFF67C0B9))),
|
||||
)
|
||||
]),
|
||||
Text( "From ${widget.item.owner ?? "unknown owner"}",
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey, overflow: TextOverflow.ellipsis)),
|
||||
Text(widget.item.shortDescription ?? "", style: const TextStyle(fontSize: 12, overflow: TextOverflow.ellipsis)),
|
||||
],)
|
||||
)
|
||||
),
|
||||
Container(
|
||||
width: endWidth,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children : [
|
||||
InkWell(
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (WorkspaceLocal.hasItem(widget.item)) { WorkspaceLocal.removeItem(widget.item);
|
||||
} else { WorkspaceLocal.addItem(widget.item); }
|
||||
});
|
||||
for (var key in widget.keys) { key.currentState?.setState(() {}); }
|
||||
},
|
||||
child: Container(
|
||||
height: 40,
|
||||
constraints: const BoxConstraints(maxWidth: 80),
|
||||
width: itemWidth,
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [BoxShadow(color: Colors.grey.shade300, spreadRadius: 1, blurRadius: 1, offset: const Offset(0, 1))],
|
||||
color: (WorkspaceLocal.hasItem(widget.item) ? Colors.red : const Color.fromRGBO(38, 166, 154, 1)),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Icon(WorkspaceLocal.hasItem(widget.item) ? Icons.remove_shopping_cart : Icons.add_shopping_cart,
|
||||
color: Colors.white, size: 20 ))
|
||||
),
|
||||
...(ratio > 1 ? [Padding( padding: const EdgeInsets.only(left: 20),
|
||||
child: InkWell(
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () { },
|
||||
child: Container(
|
||||
height: 40,
|
||||
constraints: const BoxConstraints(maxWidth: 80, minWidth: 40),
|
||||
width: itemWidth,
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: [BoxShadow(color: Colors.grey.shade300, spreadRadius: 1, blurRadius: 1, offset: const Offset(0, 1))],
|
||||
color: Colors.grey.shade300,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: const Icon(Icons.favorite_border, color: Colors.white, size: 20 ))
|
||||
)
|
||||
)] : [])
|
||||
])
|
||||
)]),
|
||||
);
|
||||
return widget.readOnly ? w : InkWell( mouseCursor: SystemMouseCursors.click,
|
||||
onTap: () { AppRouter.zones.last.go(context, { "id" : widget.item.id ?? "" }); },
|
||||
child: w );
|
||||
}
|
||||
}
|
||||
27
lib/widgets/items_details/data_item.dart
Normal file
27
lib/widgets/items_details/data_item.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:oc_front/models/search.dart';
|
||||
|
||||
class DataItemWidget extends StatefulWidget {
|
||||
DataItem item;
|
||||
DataItemWidget ({ super.key, required this.item });
|
||||
@override DataItemWidgetState createState() => DataItemWidgetState();
|
||||
}
|
||||
class DataItemWidgetState extends State<DataItemWidget> {
|
||||
@override Widget build(BuildContext context) {
|
||||
return Wrap( children: [
|
||||
Padding(padding: EdgeInsets.symmetric(vertical: 20, horizontal: 100),
|
||||
child: Text("type : ${widget.item.dtype ?? "unknown type"}",
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600))),
|
||||
Padding(padding: EdgeInsets.symmetric(horizontal: 100, vertical: 20),
|
||||
child: Text("location : ${widget.item.location ?? "unknown location"}",
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600))),
|
||||
Padding(padding: EdgeInsets.symmetric(horizontal: 100, vertical: 20),
|
||||
child: Text("protocol : ${widget.item.protocol.isEmpty ? "no protocol founded" : widget.item.protocol.join(",")}",
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600))),
|
||||
Padding(padding: EdgeInsets.symmetric(horizontal: 100, vertical: 20),
|
||||
child: Text("ex : ${widget.item.example ?? "no example"}",
|
||||
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600))),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user