Merge remote-tracking branch 'origin/main'

This commit is contained in:
Kim 2023-03-16 16:13:10 +01:00
commit 774291c1c9
9 changed files with 230 additions and 154 deletions

View File

@ -8,12 +8,14 @@ public static class CredentialsMethods
{ {
public static Session? Login(this Credentials credentials) public static Session? Login(this Credentials credentials)
{ {
if (credentials.Username.IsNullOrEmpty() || credentials.Password.IsNullOrEmpty()) var (username, password) = credentials;
if (username.IsNullOrEmpty() || password.IsNullOrEmpty())
return null; return null;
var user = Db.GetUserByEmail(credentials.Username); var user = Db.GetUserByEmail(username);
if (user is null || !user.VerifyPassword(credentials.Password)) if (user is null || !user.VerifyPassword(password))
return null; return null;
var session = new Session(user); var session = new Session(user);

View File

@ -41,7 +41,9 @@ public static class FolderMethods
public static IEnumerable<Folder> DescendantFolders(this Folder parent) public static IEnumerable<Folder> DescendantFolders(this Folder parent)
{ {
return parent.Traverse(ChildFolders); return parent
.TraverseDepthFirstPreOrder(ChildFolders)
.Skip(1); // skip self
} }
public static Boolean IsDescendantOf(this Folder folder, Folder ancestor) public static Boolean IsDescendantOf(this Folder folder, Folder ancestor)

View File

@ -73,7 +73,9 @@ public static class UserMethods
public static IEnumerable<User> DescendantUsers(this User parent) public static IEnumerable<User> DescendantUsers(this User parent)
{ {
return parent.Traverse(ChildUsers); return parent
.TraverseDepthFirstPreOrder(ChildUsers)
.Skip(1); // skip self
} }
public static Boolean IsDescendantOf(this User user, User ancestor) public static Boolean IsDescendantOf(this User user, User ancestor)

View File

@ -1,4 +1,3 @@
using System.Reactive.Linq;
using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Database;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
@ -8,7 +7,6 @@ public static class Program
{ {
public static void Main(String[] args) public static void Main(String[] args)
{ {
Db.CreateFakeRelations(); Db.CreateFakeRelations();
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);

View File

@ -0,0 +1,147 @@
namespace InnovEnergy.Lib.Utils;
public static class TreeTraversal
{
// public static IEnumerable<T> TraverseDepthFirstPreOrder<T>(this T root, Func<T, IEnumerable<T>> getChildren)
// {
// var stack = new Stack<IEnumerator<T>>();
//
// var iterator = root.AsSingleEnumerator();
//
// while (true)
// {
// while (iterator.MoveNext())
// {
// yield return iterator.Current;
//
// iterator = getChildren(iterator.Current).GetEnumerator();
// stack.Push(iterator);
// }
//
// iterator.Dispose();
// if (stack.Count == 0)
// yield break;
//
// iterator = stack.Pop();
// }
//
// }
public static IEnumerable<T> TraverseDepthFirstPreOrder<T>(this T root, Func<T, IEnumerable<T>> getChildren)
{
return Traverse(root,
getChildren,
branchOpen: true,
branchClose: false,
leaf: true);
}
public static IEnumerable<T> TraverseDepthFirstPostOrder<T>(this T root, Func<T, IEnumerable<T>> getChildren)
{
return Traverse(root,
getChildren,
branchOpen: false,
branchClose: true,
leaf: true);
}
public static IEnumerable<T> TraverseLeaves<T>(this T root, Func<T, IEnumerable<T>> getChildren)
{
return Traverse(root,
getChildren,
branchOpen: false,
branchClose: true,
leaf: false);
}
private static IEnumerable<T> Traverse<T>(T root,
Func<T, IEnumerable<T>> getChildren,
Boolean branchOpen,
Boolean branchClose,
Boolean leaf)
{
// the if-checks on the constant booleans are
// almost free because of modern branch predictors
var stack = new Stack<IEnumerator<T>>();
var it = root.AsSingleEnumerator();
it.MoveNext();
while (true)
{
//////// going down ////////
while (true)
{
var cit = getChildren(it.Current).GetEnumerator();
if (cit.MoveNext()) // node has children, must be a branch
{
if (branchOpen)
yield return it.Current;
stack.Push(it);
it = cit;
}
else // no children, hence a leaf
{
var node = it.Current;
if (leaf)
yield return node;
if (!it.MoveNext())
break; // no more siblings: goto parent
}
}
//////// going up ////////
while (true)
{
it.Dispose();
if (stack.Count == 0) yield break; // we got to the bottom of the stack, were done
it = stack.Pop();
var node = it.Current;
if (branchClose)
yield return node; // we've seen all its children: close the branch
if (it.MoveNext())
break;
}
}
}
public static IEnumerable<T> TraverseBreadthFirst<T>(this T node, Func<T, IEnumerable<T>> getChildren)
{
var queue = new Queue<IEnumerator<T>>();
var iterator = node.AsSingleEnumerator();
while(true)
{
while (iterator.MoveNext())
{
yield return iterator.Current;
iterator.Current
.Apply(getChildren)
.GetEnumerator()
.Apply(queue.Enqueue);
}
iterator.Dispose();
if (queue.Count == 0)
yield break;
iterator = queue.Dequeue();
}
}
}

View File

@ -1,4 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using static System.Runtime.CompilerServices.MethodImplOptions; using static System.Runtime.CompilerServices.MethodImplOptions;
@ -6,6 +7,7 @@ namespace InnovEnergy.Lib.Utils;
public static class Utils public static class Utils
{ {
public static Boolean IsNull<T>([NotNullWhen(returnValue: false)] this T t) => t is null;
public static IEnumerable<String> GetEnumStrings<T>(this T e) where T : Enum public static IEnumerable<String> GetEnumStrings<T>(this T e) where T : Enum
{ {
@ -20,7 +22,7 @@ public static class Utils
[DebuggerStepThrough][MethodImpl(AggressiveInlining)] [DebuggerStepThrough][MethodImpl(AggressiveInlining)]
public static T ConvertTo<T>(this IConvertible c) where T : IConvertible public static T ConvertTo<T>(this IConvertible c) where T : IConvertible
{ {
var t = typeof (T); var t = typeof(T);
var type = t.IsEnum var type = t.IsEnum
? Enum.GetUnderlyingType(t) ? Enum.GetUnderlyingType(t)
@ -50,13 +52,13 @@ public static class Utils
[DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)] [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
public static R Apply<T, R>(this T t, Func<T, R> f) => f(t); public static R Apply<T, R>(this T t, Func<T, R> f) => f(t);
[DebuggerStepThrough]
[MethodImpl(AggressiveInlining | AggressiveOptimization)] [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
public static R Apply<T1, T2, R>(this (T1 p1, T2 p2) t, Func<T1, T2, R> f) => f(t.p1, t.p2); public static R Apply<T1, T2, R>(this (T1 p1, T2 p2) t, Func<T1, T2, R> f) => f(t.p1, t.p2);
[DebuggerStepThrough]
[MethodImpl(AggressiveInlining | AggressiveOptimization)] [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
public static R Apply<T1, T2, T3, R>(this (T1 p1, T2 p2, T3 p3) t, Func<T1, T2, T3, R> f) => f(t.p1, t.p2, t.p3); public static R Apply<T1, T2, T3, R>(this (T1 p1, T2 p2, T3 p3) t, Func<T1, T2, T3, R> f) => f(t.p1, t.p2, t.p3);
[DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)] [DebuggerStepThrough][MethodImpl(AggressiveInlining | AggressiveOptimization)]
public static R ApplyOrDefault<T, R>(this T t, Func<T, R> f, R @default) public static R ApplyOrDefault<T, R>(this T t, Func<T, R> f, R @default)
@ -79,7 +81,7 @@ public static class Utils
? res ? res
: res + length; : res + length;
} }
public static Decimal Modulo(this Decimal dividend, Decimal divisor) public static Decimal Modulo(this Decimal dividend, Decimal divisor)
{ {
var res = dividend % divisor; var res = dividend % divisor;
@ -89,111 +91,39 @@ public static class Utils
: res + divisor; : res + divisor;
} }
public static IEnumerable<T> Traverse<T>(this T root, Func<T, IEnumerable<T>> getChildren)
{
var stack = new Stack<IEnumerator<T>>();
var it = root.AsSingleEnumerator();
it.MoveNext();
while (true)
{
//////// going down ////////
while (true)
{
var cit = getChildren(it.Current).GetEnumerator();
if (cit.MoveNext()) // node has children, must be a branch
{
yield return it.Current;
stack.Push(it);
it = cit;
}
else // no children, hence a leaf
{
var node = it.Current;
yield return node;
if (!it.MoveNext())
break; // no more siblings: goto parent
}
}
//////// going up ////////
while (true)
{
it.Dispose();
if (stack.Count == 0)
yield break; // we got to the bottom of the stack, were done
it = stack.Pop();
if (it.MoveNext())
break;
}
}
}
public static Int32 Clamp(this Int32 value, Int32 minValue, Int32 maxValue) public static Int32 Clamp(this Int32 value, Int32 minValue, Int32 maxValue)
{ {
var clamped = Math.Min(maxValue, value); var clamped = Math.Min(maxValue, value);
clamped = Math.Max(minValue, clamped); clamped = Math.Max(minValue, clamped);
return clamped; return clamped;
} }
public static Double Clamp(this Double value, Double minValue, Double maxValue) public static Double Clamp(this Double value, Double minValue, Double maxValue)
{ {
var clamped = Math.Min(maxValue, value); var clamped = Math.Min(maxValue, value);
clamped = Math.Max(minValue, clamped); clamped = Math.Max(minValue, clamped);
return clamped; return clamped;
} }
public static Decimal Clamp(this Decimal value, Decimal minValue, Decimal maxValue) public static Decimal Clamp(this Decimal value, Decimal minValue, Decimal maxValue)
{ {
var clamped = Math.Min(maxValue, value); var clamped = Math.Min(maxValue, value);
clamped = Math.Max(minValue, clamped); clamped = Math.Max(minValue, clamped);
return clamped; return clamped;
} }
public static R ValueOrDefault<T, R>(this Dictionary<T, R> dict, T key, R defaultValue) where T : notnull
#pragma warning disable 8714
public static async Task<R> ApplyOrDefault<T, R>(this T t, Func<T, Task<R>> f, R @default)
{
try
{
return await f(t);
}
catch
{
return @default;
}
}
public static R ValueOrDefault<T, R>(this Dictionary<T, R> dict, T key, R defaultValue)
{ {
return dict.TryGetValue(key, out var value) return dict.TryGetValue(key, out var value)
? value ? value
: defaultValue; : defaultValue;
} }
public static Dictionary<K, V> CombineDicts<K,V>(params IEnumerable<KeyValuePair<K,V>>[] dicts)
{
return dicts.Flatten().ToDictionary(kv => kv.Key, kv => kv.Value);
}
#pragma warning restore 8714
public static void CopyFilesRecursively(String source, String target) public static void CopyFilesRecursively(String source, String target)
{ {
CopyFilesRecursively(new DirectoryInfo(source), new DirectoryInfo(target)); CopyFilesRecursively(new DirectoryInfo(source), new DirectoryInfo(target));
@ -210,27 +140,4 @@ public static class Utils
public static String ExecutingProcessName => Process.GetCurrentProcess().ProcessName; public static String ExecutingProcessName => Process.GetCurrentProcess().ProcessName;
public static IEnumerable<IEnumerable<T>> TraverseWithPath<T>(this T root, Func<T, IEnumerable<T>> getChildren)
{
var stack = new Stack<IEnumerator<T>>();
stack.Push(root.AsSingleEnumerator());
do
{
for (var top = stack.Peek(); top.MoveNext(); top = Push(top))
yield return stack.Select(p => p.Current);
var popped = stack.Pop();
popped.Dispose();
}
while (stack.Count > 0);
IEnumerator<T> Push(IEnumerator<T> node)
{
var top = getChildren(node.Current).GetEnumerator();
stack.Push(top);
return top;
}
}
} }

View File

@ -1,3 +1,4 @@
import { NodeModel } from "@minoru/react-dnd-treeview";
import { createContext, ReactNode, useCallback, useState } from "react"; import { createContext, ReactNode, useCallback, useState } from "react";
import axiosConfig from "../../config/axiosConfig"; import axiosConfig from "../../config/axiosConfig";
import { I_Folder, I_Installation } from "../../util/types"; import { I_Folder, I_Installation } from "../../util/types";
@ -9,6 +10,8 @@ interface GroupContextProviderProps {
loading: boolean; loading: boolean;
setLoading: (value: boolean) => void; setLoading: (value: boolean) => void;
getError: boolean; getError: boolean;
tree: NodeModel<I_Folder | I_Installation>[];
setTree: (value: NodeModel<I_Folder | I_Installation>[]) => void;
} }
export const GroupContext = createContext<GroupContextProviderProps>({ export const GroupContext = createContext<GroupContextProviderProps>({
@ -18,19 +21,39 @@ export const GroupContext = createContext<GroupContextProviderProps>({
loading: false, loading: false,
setLoading: () => {}, setLoading: () => {},
getError: false, getError: false,
tree: [],
setTree: () => {},
}); });
const getTreeData = (
data: (I_Folder | I_Installation)[]
): NodeModel<I_Folder | I_Installation>[] => {
return data.map((element) => {
const isFolder = element.type === "Folder";
return {
id: isFolder ? element.id : "installation-" + element.id,
parent: element.parentId,
text: element.name,
droppable: isFolder,
data: element,
};
});
};
const GroupContextProvider = ({ children }: { children: ReactNode }) => { const GroupContextProvider = ({ children }: { children: ReactNode }) => {
const [data, setData] = useState<(I_Folder | I_Installation)[]>([]); const [data, setData] = useState<(I_Folder | I_Installation)[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [getError, setGetError] = useState(false); const [getError, setGetError] = useState(false);
const [tree, setTree] = useState<NodeModel<I_Folder | I_Installation>[]>([]);
const fetchData = useCallback(async () => { const fetchData = useCallback(async () => {
console.log("fetchData");
setLoading(true); setLoading(true);
return axiosConfig return axiosConfig
.get("/GetAllFoldersAndInstallations") .get("/GetAllFoldersAndInstallations")
.then((res) => { .then((res) => {
setData(res.data); setData(res.data);
setTree(getTreeData(res.data));
setLoading(false); setLoading(false);
}) })
.catch((err) => { .catch((err) => {
@ -41,7 +64,16 @@ const GroupContextProvider = ({ children }: { children: ReactNode }) => {
return ( return (
<GroupContext.Provider <GroupContext.Provider
value={{ data, setData, fetchData, loading, setLoading, getError }} value={{
data,
setData,
fetchData,
loading,
setLoading,
getError,
tree,
setTree,
}}
> >
{children} {children}
</GroupContext.Provider> </GroupContext.Provider>

View File

@ -1,12 +1,13 @@
import { NodeModel } from "@minoru/react-dnd-treeview";
import { Button, CircularProgress, Grid } from "@mui/material"; import { Button, CircularProgress, Grid } from "@mui/material";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../../config/axiosConfig"; import axiosConfig from "../../config/axiosConfig";
import { I_Folder } from "../../util/types"; import { I_Folder, I_Installation } from "../../util/types";
import { GroupContext } from "../Context/GroupContextProvider";
import InnovenergySnackbar from "../InnovenergySnackbar"; import InnovenergySnackbar from "../InnovenergySnackbar";
import InnovenergyTextfield from "../Layout/InnovenergyTextfield"; import InnovenergyTextfield from "../Layout/InnovenergyTextfield";
import { GroupContext } from "../Context/GroupContextProvider";
interface I_CustomerFormProps { interface I_CustomerFormProps {
values: I_Folder; values: I_Folder;
@ -16,9 +17,8 @@ interface I_CustomerFormProps {
const updateFolder = (data: I_Folder) => { const updateFolder = (data: I_Folder) => {
return axiosConfig.put("/UpdateFolder", data); return axiosConfig.put("/UpdateFolder", data);
}; };
const FolderForm = (props: I_CustomerFormProps) => {
const { fetchData } = useContext(GroupContext);
const FolderForm = (props: I_CustomerFormProps) => {
const { values, id } = props; const { values, id } = props;
const intl = useIntl(); const intl = useIntl();
@ -26,6 +26,19 @@ const FolderForm = (props: I_CustomerFormProps) => {
const [error, setError] = useState(); const [error, setError] = useState();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { setTree, tree } = useContext(GroupContext);
const updateTree = (newValues: I_Folder) => {
const elementToUpdate = tree.find(
(element) => element.data?.id.toString() === id
);
if (elementToUpdate) {
elementToUpdate.data = newValues;
elementToUpdate.text = newValues.name;
setTree([...tree]);
}
};
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
name: values.name, name: values.name,
@ -40,9 +53,9 @@ const FolderForm = (props: I_CustomerFormProps) => {
id: idAsNumber, id: idAsNumber,
}) })
.then((res) => { .then((res) => {
updateTree({ ...values, ...formikValues, id: idAsNumber });
setSnackbarOpen(true); setSnackbarOpen(true);
setLoading(false); setLoading(false);
fetchData();
}) })
.catch((err) => { .catch((err) => {
setSnackbarOpen(true); setSnackbarOpen(true);

View File

@ -17,23 +17,8 @@ import DragPreview from "./DragPreview";
import InnovenergySnackbar from "../../InnovenergySnackbar"; import InnovenergySnackbar from "../../InnovenergySnackbar";
import { GroupContext } from "../../Context/GroupContextProvider"; import { GroupContext } from "../../Context/GroupContextProvider";
const getTreeData = (
data: (I_Folder | I_Installation)[]
): NodeModel<I_Folder | I_Installation>[] => {
return data.map((element) => {
const isFolder = element.type === "Folder";
return {
id: isFolder ? element.id : "installation-" + element.id,
parent: element.parentId,
text: element.name,
droppable: isFolder,
data: element,
};
});
};
const GroupTree = () => { const GroupTree = () => {
const { data, fetchData, loading, setLoading, getError } = const { data, fetchData, loading, setLoading, getError, setTree, tree } =
useContext(GroupContext); useContext(GroupContext);
const [putError, setPutError] = useState(false); const [putError, setPutError] = useState(false);
const [snackbarOpen, setSnackbarOpen] = useState(false); const [snackbarOpen, setSnackbarOpen] = useState(false);
@ -44,18 +29,6 @@ const GroupTree = () => {
fetchData(); fetchData();
}, [fetchData]); }, [fetchData]);
/* const findParent = (element: I_Folder | I_Installation): any => {
if (data) {
const parent = data.find(
(el) => el.type === "Folder" && el.id === element.parentId
);
if (parent) {
return findParent(parent);
}
return element.parentId;
}
}; */
const handleDrop = ( const handleDrop = (
newTree: NodeModel<I_Folder | I_Installation>[], newTree: NodeModel<I_Folder | I_Installation>[],
{ dropTargetId, dragSource }: DropOptions<I_Folder | I_Installation> { dropTargetId, dragSource }: DropOptions<I_Folder | I_Installation>
@ -72,7 +45,7 @@ const GroupTree = () => {
) )
.then(() => { .then(() => {
setSnackbarOpen(true); setSnackbarOpen(true);
fetchData(); setTree(newTree);
}) })
.catch((err) => { .catch((err) => {
setPutError(err); setPutError(err);
@ -92,7 +65,7 @@ const GroupTree = () => {
<DndProvider backend={MultiBackend} options={getBackendOptions()}> <DndProvider backend={MultiBackend} options={getBackendOptions()}>
<ScrollingComponent className={styles.treeContainer}> <ScrollingComponent className={styles.treeContainer}>
<Tree<I_Installation | I_Folder> <Tree<I_Installation | I_Folder>
tree={getTreeData(data)} tree={tree}
rootId={0} rootId={0}
dragPreviewRender={(monitorProps) => ( dragPreviewRender={(monitorProps) => (
<DragPreview monitorProps={monitorProps} /> <DragPreview monitorProps={monitorProps} />