oc-search porting to flutter (missing compose + workflow editor)

This commit is contained in:
mr
2024-07-05 09:24:40 +02:00
parent a7f34db2e0
commit 7e4687853f
220 changed files with 10528 additions and 119 deletions

66
lib/core/models/cart.dart Normal file
View 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();
}

View 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)
)),
])
)
);
}
}

View 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()
],);
}
}

View 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,
)
))
]
)
])
)
);
}
}

View 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)
);
}
}

View 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);
}
}

View 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() { }
}

View 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

View 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(); }
}

View 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(); }
}

View 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(); }
}

View 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();
}
}

View File

@@ -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
View 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
View 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
View 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
View 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
View 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() => {};
}

View 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
View 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) )
]
);
}
}

View 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
View 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
View 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
View 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
View 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)
)
)
)
],
),
])
)
]
)
);
}
}

View 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
View 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)
),
),
);
}),
),
),
),
),
],
),
),
)
]);
},
);
}
}

View 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,
),
),
),
);
}
}

View 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),)))]))
],)));
}
}

View 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
View 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) );
}
}

View 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
)
]
)
)
);
}
}

View 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 );
}
}

View 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))),
]);
}
}