frontend, localization, bugfix
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {USER_GROUP_ADMIN} from "./constants";
|
||||
import {isInt} from "./util";
|
||||
|
||||
export default class API {
|
||||
constructor() {
|
||||
@@ -14,13 +15,22 @@ export default class API {
|
||||
|
||||
async apiCall(method, params) {
|
||||
params = params || { };
|
||||
params.csrfToken = this.csrfToken();
|
||||
let response = await fetch("/api/" + method, {
|
||||
method: 'post',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
const csrfToken = this.csrfToken();
|
||||
const config = {method: 'post'};
|
||||
if (params instanceof FormData) {
|
||||
if (csrfToken) {
|
||||
params.append("csrfToken", csrfToken);
|
||||
}
|
||||
config.body = params;
|
||||
} else {
|
||||
if (csrfToken) {
|
||||
params.csrfToken = csrfToken;
|
||||
}
|
||||
config.headers = {'Content-Type': 'application/json'};
|
||||
config.body = JSON.stringify(params);
|
||||
}
|
||||
|
||||
let response = await fetch("/api/" + method, config);
|
||||
let res = await response.json();
|
||||
if (!res.success && res.msg === "You are not logged in.") {
|
||||
this.loggedIn = false;
|
||||
@@ -35,7 +45,7 @@ export default class API {
|
||||
}
|
||||
|
||||
for (const permission of this.permissions) {
|
||||
if (method.endsWith("*") && permission.toLowerCase().startsWith(method.toLowerCase().substr(0, method.length - 1))) {
|
||||
if (method.endsWith("*") && permission.toLowerCase().startsWith(method.toLowerCase().substring(0, method.length - 1))) {
|
||||
return true;
|
||||
} else if (method.toLowerCase() === permission.toLowerCase()) {
|
||||
return true;
|
||||
@@ -48,7 +58,7 @@ export default class API {
|
||||
|
||||
hasGroup(groupIdOrName) {
|
||||
if (this.loggedIn && this.user?.groups) {
|
||||
if (!isNaN(groupIdOrName) && (typeof groupIdOrName === 'string' && groupIdOrName.match(/^\d+$/))) {
|
||||
if (isInt(groupIdOrName)) {
|
||||
return this.user.groups.hasOwnProperty(groupIdOrName);
|
||||
} else {
|
||||
let userGroups = Object.values(this.user.groups);
|
||||
|
||||
10
react/shared/elements/dialog.css
Normal file
10
react/shared/elements/dialog.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.modal-dialog {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 400px;
|
||||
border: 2px solid #000;
|
||||
padding: 8px;
|
||||
background-color: white;
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import {Box, Modal} from "@mui/material";
|
||||
import {Button, Typography} from "@material-ui/core";
|
||||
import "./dialog.css";
|
||||
|
||||
export default function Dialog(props) {
|
||||
|
||||
const show = props.show;
|
||||
const classes = ["modal", "fade"];
|
||||
const style = { paddingRight: "12px", display: (show ? "block" : "none") };
|
||||
const onClose = props.onClose || function() { };
|
||||
const onOption = props.onOption || function() { };
|
||||
const options = props.options || ["Close"];
|
||||
const type = props.type || "default";
|
||||
|
||||
let buttons = [];
|
||||
for (let name of options) {
|
||||
@@ -17,31 +19,27 @@ export default function Dialog(props) {
|
||||
else if(name === "No") type = "danger";
|
||||
|
||||
buttons.push(
|
||||
<button type="button" key={"button-" + name} className={"btn btn-" + type}
|
||||
<Button variant={"outlined"} size={"small"} type="button" key={"button-" + name}
|
||||
data-dismiss={"modal"} onClick={() => { onClose(); onOption(name); }}>
|
||||
{name}
|
||||
</button>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx(classes, show && "show")} style={style} aria-modal={"true"} onClick={() => onClose()}>
|
||||
<div className="modal-dialog" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">{props.title}</h4>
|
||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={() => onClose()}>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>{props.message}</p>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
{ buttons }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <Modal
|
||||
open={show}
|
||||
onClose={onClose}
|
||||
aria-labelledby="modal-title"
|
||||
aria-describedby="modal-description"
|
||||
>
|
||||
<Box className={clsx("modal-dialog", props.className)}>
|
||||
<Typography id="modal-title" variant="h6" component="h2">
|
||||
{props.title}
|
||||
</Typography>
|
||||
<Typography id="modal-description" sx={{ mt: 2 }}>
|
||||
{props.message}
|
||||
</Typography>
|
||||
{ buttons }
|
||||
</Box>
|
||||
</Modal>
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, {useReducer} from 'react';
|
||||
import {createContext, useCallback, useState} from "react";
|
||||
import { enUS as dateFnsEN, de as dateFnsDE } from 'date-fns/locale';
|
||||
|
||||
const LocaleContext = createContext(null);
|
||||
|
||||
@@ -29,11 +30,11 @@ function reducer(entries, action) {
|
||||
|
||||
function LocaleProvider(props) {
|
||||
|
||||
// const [entries, setEntries] = useState(window.languageEntries || {});
|
||||
const [entries, dispatch] = useReducer(reducer, window.languageEntries || {});
|
||||
const [currentLocale, setCurrentLocale] = useState(window.languageCode || "en_US");
|
||||
|
||||
const translate = useCallback((key, defaultTranslation = null) => {
|
||||
|
||||
if (currentLocale) {
|
||||
if (entries.hasOwnProperty(currentLocale)) {
|
||||
let [module, variable] = key.split(".");
|
||||
@@ -46,7 +47,7 @@ function LocaleProvider(props) {
|
||||
}
|
||||
}
|
||||
|
||||
return defaultTranslation || "[" + key + "]";
|
||||
return key ? defaultTranslation || "[" + key + "]" : "";
|
||||
}, [currentLocale, entries]);
|
||||
|
||||
const hasModule = useCallback((code, module) => {
|
||||
@@ -61,6 +62,16 @@ function LocaleProvider(props) {
|
||||
}
|
||||
}, [entries]);
|
||||
|
||||
const toDateFns = () => {
|
||||
switch (currentLocale) {
|
||||
case 'de_DE':
|
||||
return dateFnsDE;
|
||||
case 'en_US':
|
||||
default:
|
||||
return dateFnsEN;
|
||||
}
|
||||
}
|
||||
|
||||
/** API HOOKS **/
|
||||
const setLanguage = useCallback(async (api, params) => {
|
||||
let res = await api.setLanguage(params);
|
||||
|
||||
@@ -50,25 +50,25 @@ const getBaseUrl = () => {
|
||||
const formatDate = (L, apiDate) => {
|
||||
if (!(apiDate instanceof Date)) {
|
||||
if (!isNaN(apiDate)) {
|
||||
apiDate = new Date(apiDate);
|
||||
apiDate = new Date(apiDate * 1000);
|
||||
} else {
|
||||
apiDate = parse(apiDate, API_DATE_FORMAT, new Date());
|
||||
}
|
||||
}
|
||||
|
||||
return format(apiDate, L("general.date_format", "YYY/MM/dd"));
|
||||
return format(apiDate, L("general.datefns_date_format", "YYY/MM/dd"));
|
||||
}
|
||||
|
||||
const formatDateTime = (L, apiDate) => {
|
||||
if (!(apiDate instanceof Date)) {
|
||||
if (!isNaN(apiDate)) {
|
||||
apiDate = new Date(apiDate);
|
||||
apiDate = new Date(apiDate * 1000);
|
||||
} else {
|
||||
apiDate = parse(apiDate, API_DATETIME_FORMAT, new Date());
|
||||
}
|
||||
}
|
||||
|
||||
return format(apiDate, L("general.date_time_format", "YYY/MM/dd HH:mm:ss"));
|
||||
return format(apiDate, L("general.datefns_date_time_format", "YYY/MM/dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
const upperFirstChars = (str) => {
|
||||
@@ -77,5 +77,11 @@ const upperFirstChars = (str) => {
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
const isInt = (value) => {
|
||||
return !isNaN(value) &&
|
||||
parseInt(Number(value)) === value &&
|
||||
!isNaN(parseInt(value, 10));
|
||||
}
|
||||
|
||||
export { humanReadableSize, removeParameter, getParameter, encodeText, decodeText, getBaseUrl,
|
||||
formatDate, formatDateTime, upperFirstChars };
|
||||
formatDate, formatDateTime, upperFirstChars, isInt };
|
||||
Reference in New Issue
Block a user