FIRST PROTO

This commit is contained in:
Robert Nasarek 2022-08-30 10:51:19 +02:00
parent 7c2d0ae96c
commit 141fc8dd51
18 changed files with 366 additions and 208 deletions

View file

@ -30,8 +30,8 @@ if (process.platform === 'win32') {
// Functions
async function handleFileOpen() {
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {properties: ['openDirectory']})
async function handleFileOpen() {
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {properties: ['openDirectory']})
if (canceled) {
return
} else {

View file

@ -34,7 +34,7 @@
"start": "cross-env NODE_ENV=development webpack serve --hot --host 0.0.0.0 --config=./webpack.dev.config.js --mode development",
"build": "cross-env NODE_ENV=production webpack --config webpack.build.config.js --mode production",
"package": "npm run build",
"postpackage": "electron-packager ./ --out=./builds"
"postpackage": "electron-packager ./ --out=./builds --overwrite"
},
"dependencies": {
"@emotion/react": "^11.10.0",
@ -49,7 +49,9 @@
"react": "^18.2.0",
"react-async-devtools": "^10.0.1",
"react-dom": "^18.2.0",
"react-loader-spinner": "^5.3.3",
"react-router-dom": "^6.3.0",
"replace-special-characters": "^1.2.6",
"write-json-file": "^5.0.0",
"xml-parse-from-string": "^1.0.1",
"xml2js": "^0.4.23"
@ -58,6 +60,7 @@
"@babel/core": "^7.18.10",
"@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"@electron-forge/plugin-webpack": "^6.0.0-beta.65",
"babel-loader": "^8.2.5",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",

View file

@ -0,0 +1,3 @@
{
"rootDir": "/home/rbrt/Schreibtisch/marvin"
}

View file

@ -1,11 +1,12 @@
// Config
import config from './config/config.json'
import config from '/resources/config/config.json'
// Modules
import createReport from 'docx-templates';
import fs from 'fs';
import { mkdir } from 'node:fs/promises';
import path from 'path';
import replaceSpecialCharacters from "replace-special-characters";
// Components
import ObjektkatalogApi from './ObjektkatalogApi';
@ -20,7 +21,7 @@ export async function fillTemplate(log, objectData) {
if (objectData.httpStatus === 200) {
try {
// Read template.
const template = fs.readFileSync('src/assets/templates/rp-template.docx');
const template = fs.readFileSync('resources/templates/rp-template.docx');
// Create report.
buffer = await createReport({
template,
@ -57,8 +58,10 @@ export async function fillTemplate(log, objectData) {
// Write document to disk.
if (buffer) {
console.log(objectData)
fs.writeFileSync(path.join(folderPath, objectData.titel), buffer)
const normCharacterTitle = replaceSpecialCharacters(objectData.titel)
const normSpacingTitle = normCharacterTitle.replace(/[^A-Z0-9]+/ig, "_");
const filename = objectData.datum + '_' + normSpacingTitle + '.docx'
fs.writeFileSync(path.join(folderPath, filename), buffer)
log = {
...log,
status: 'green',

View file

@ -5,8 +5,21 @@ const {parseString} = require("xml2js");
*/
class ObjektkatalogApi {
json;
short;
raw;
receivedData = {
datum: '',
hersteller: '',
herstellungsdatum: '',
herstellungsort: '',
httpStatus: 500,
inventarnummer: '',
masse: '',
materialTechnik: '',
titel: '',
};
visibility;
async getData(objectId) {
if (objectId) {
const response = await fetch('https://objektkatalog.gnm.de/rest_export/' + objectId);
@ -41,15 +54,38 @@ class ObjektkatalogApi {
zustandsbeschreibung: responseJson[0]['f54b6da6006ea444c33a348b8c4370a8']
}
} else {
return {httpStatus: 404};
// No JSON
this.receivedData = {
...this.receivedData,
httpStatus: 404,
}
this.visibility = false
return [this.receivedData, this.visibility]
}
} else {
return {httpStatus: response.status};
// No response
this.receivedData= {
...this.receivedData,
httpStatus: 404,
}
this.visibility = false
return [this.receivedData, this.visibility]
}
} else {
return {httpStatus: 400}
// No ObjectId
this.receivedData = {
...this.receivedData,
httpStatus: 500,
}
this.visibility = false
return [this.receivedData, this.visibility]
}
// Datum
// Default date of today
let today = new Date()
let todayFormat = today.getFullYear() + '-' + (String(today.getMonth() + 1).padStart(2, '0')) + '-' + String(today.getDate()).padStart(2, '0'); ;
// Inventarnummer
let inventarnummer;
if (this.raw.inventarnummer.length !== 0) {
@ -113,19 +149,22 @@ class ObjektkatalogApi {
})
}
return this.short = {
inventarnummer: inventarnummer,
titel: titel,
hersteller: hersteller,
herstellungsort: herstellungsort,
herstellungsdatum: herstellungsdatum,
materialTechnik: materialTechnik,
masse: masse,
httpStatus: 200,
}
this.receivedData ={
...this.receivedData,
datum: todayFormat,
titel: titel,
hersteller: hersteller,
herstellungsort: herstellungsort,
herstellungsdatum: herstellungsdatum,
inventarnummer: inventarnummer,
materialTechnik: materialTechnik,
masse: masse,
httpStatus: 200,
}
this.visibility = true
return [this.receivedData, this.visibility]
}
}
module.exports = ObjektkatalogApi;

View file

@ -1,7 +0,0 @@
import GnmObject from "../ObjektkatalogApi";
/*
*
*/

View file

@ -1,4 +1,4 @@
@import url('https://fonts.googleapis.com/css?family=Roboto');
@import url('https://fonts.googleapis.com/css?family=Open+Sans');
a {
color: #d5d5d5;
@ -9,10 +9,10 @@ a {
}
body {
background-color: #00152E ;
background-color: white ;
color: #d5d5d5;
font-family: 'Roboto', regular, serif;
font-size: larger;
font-family: 'Open Sans', sans-serif;
font-size: 1rem;
}
@ -26,7 +26,8 @@ body {
}
.closed{
height: 0;
height: 0px;
overflow: hidden;
position: relative;
}
@ -47,13 +48,13 @@ div {
}
.edit-button {
background-color: #d5d5d5;
font-family: roboto, sans-serif;
background-color: #e30f27;
color: #d5d5d5;
font-family: "Open Sans", sans-serif;
font-size: 1rem;
border-radius: 0 0.4rem 0.4rem 0;
border: unset;
padding: 0 4px;
height: 1.1rem;
height: 1.5rem;
}
.edit-input {
@ -64,6 +65,12 @@ div {
display: flex;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex-full {
flex: 1
}
@ -88,21 +95,23 @@ input {
border-top: none;
border-right: none;
border-left: none;
border-bottom: 2px solid darkred;
border-bottom: 2px solid #e30f27;
border-radius: 3px;
}
input[type="date"]::-webkit-calendar-picker-indicator {
color: #d5d5d5;
color: black;
width: 20px;
height: 20px;
border-width: thin;
filter: invert(1);
}
input:focus {
background-color: #d5d5d5;
color: #00152E;
font-size: .9rem;
}
input.edit-input:not(:disabled) {
@ -112,9 +121,9 @@ input.edit-input:not(:disabled) {
.input-field {
background-color: #00152E ;
color: #d5d5d5;
font-size: medium;
background-color: #f2f2f2 ;
color: black;
font-size: .9rem;
font-family: roboto, sans-serif;
}
@ -130,19 +139,34 @@ input.edit-input:not(:disabled) {
label {
padding-right: 5px;
color: #464646;
}
.loading-icon-canvas {
position: absolute;
height: 98%;
width: 98%;
left: 0%;
top: 0%;
background-color: #555555;
opacity: .5;
}
.loading-icon
.log {
font-size: 0.75em;
height: 5em;
}
.nav-icon svg {
fill: black;
}
.no-overflow {
overflow: hidden;
height: inherit;
}
.open {
height: 700px;
height: 500px;
}
p {
margin: 0;
@ -157,7 +181,8 @@ p {
}
.red {
background-color: crimson;
background-color: #e30f27;
color: white;
}
.round-box {
@ -172,12 +197,12 @@ select {
}
.send-button {
font-family: roboto, sans-serif;
background-color: #e30f27;
color: white;
font-family: "Open Sans", sans-serif;
font-size: 1rem;
border-radius: 0.4rem;
border: unset;
background-color: darkred;
color: #d5d5d5;
}
.scroll-y {
@ -191,6 +216,18 @@ select {
font-size: .85rem;
}
.settings-div {
background-color: #d5d5d5;
color: black;
width: 300px;
border-top: none;
border-right: none;
border-left: none;
border-bottom: 0.125rem solid #e30019;
border-radius: 0.1875rem;
overflow-x: scroll;
white-space:nowrap;
}
.sticky-edit {
display: flex;
@ -198,7 +235,7 @@ select {
}
textarea, textarea:focus, input:focus{
outline: none;
font-size: medium;
font-size: .9rem;
;
}
@ -215,6 +252,32 @@ textarea.select-folder-field, textarea.select-folder-field:focus, input.select-f
margin-bottom: 0.25em;
}
/* width */
::-webkit-scrollbar {
width: 10px;
height: 5px;
}
/* Track */
::-webkit-scrollbar-track {
background: #f1f1f1;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #888;
width: 10px;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: #555;
}
.yellow{
background-color: goldenrod;
}

View file

@ -3,13 +3,16 @@ import {DataForm} from "./DataForm";
import {Log} from "./Log"
import SettingsIcon from '@mui/icons-material/Settings';
// Modules
import ObjektkatalogApi from "../ObjektkatalogApi";
// React
import { Link } from "react-router-dom";
import {NavLink} from "react-router-dom";
import React, {useState} from 'react'
// CSS
import '../assets/css/styles.css'
import ObjektkatalogApi from "../ObjektkatalogApi";
import {LoadingIcon} from "./LoadingIcon";
////////////////////
@ -18,34 +21,34 @@ import ObjektkatalogApi from "../ObjektkatalogApi";
export const App = () => {
// Variables
const objektkatalogApi = new ObjektkatalogApi();
// States
/* Initialize state of the log.
* log: variables fpr logging message.
* logClass: for visibility of log <div>.
*/
const [logState, setLogState] = useState({
log: {
status: '',
message: '',
code: '',
tip:'',
}, // with message, code, tip
logClass: 'inactive'
});
const [isLoading, setIsLoading] = useState(false);
const [logState, setLogState] = useState({
log: {
status: '',
message: '',
code: '',
tip: '',
}, // with message, code, tip
logClass: 'inactive'
});
const [objectData, setObjectData] = useState(
{
datum: '',
inventarnummer: '',
titel: '',
hersteller: '',
herstellungsort: '',
herstellungsdatum: '',
materialTechnik: '',
masse: '',
herstellungsort: '',
httpStatus: 500,
inventarnummer: '',
masse: '',
materialTechnik: '',
titel: '',
}
);
@ -53,91 +56,115 @@ export const App = () => {
false
);
// Handler
async function getDataAndAskForCheckUpClickHandler(e) {
// Prevent page reload.
e.preventDefault();
// Handler
const getDataAndAskForCheckUpClickHandler = async (e) => {
setIsLoading(true)
// Prevent page reload.
e.preventDefault()
let objectId = ''
objectId = e.target.form.objectId.value
// Get objectId from user input.
// Get objectId from user input.
let objectId = document.getElementById('object-id').value
if (objectId) {
// Get object data from objektkatalog.gnm.de
let objektkatalogApi = new ObjektkatalogApi();
let receivedObjectData = await objektkatalogApi.getData(objectId);
if (receivedObjectData.httpStatus === 404) {
setLogState({
log: {
status: 'red',
message: 'Kein Objekt mit dieser Inventarnummer gefunden!',
code: '',
tip:'',
}, // with message, code, tip
logClass: 'active'
})
} else {
console.log(receivedObjectData)
// Fill and open check up form
setObjectData(receivedObjectData)
setCheckUpVisibility(true)
await setObjectData((prevState) => {
return {
...prevState,
inventarnummer: objectId,
}
} else {
setLogState({
}
)
if (objectId) {
// Get object data from objektkatalog.gnm.de
const [receivedObjectData, receivedVisibility] = await objektkatalogApi.getData(objectId);
if (receivedObjectData.httpStatus !== 200) {
await setLogState({
log: {
status: 'red',
message: 'Bitte Inventarnummer eingeben!',
message: 'Kein Objekt mit dieser Inventarnummer gefunden!',
code: '',
tip:'',
tip: '',
}, // with message, code, tip
logClass: 'active'
})
await setCheckUpVisibility(false)
} else {
// Fill and open check up form
await setObjectData(receivedObjectData)
await setCheckUpVisibility(true)
await setLogState({
log: {
status: '',
message: '',
code: '',
tip: '',
}, // with message, code, tip
logClass: 'inactive'
})
}
} else {
await setLogState({
log: {
status: 'red',
message: 'Bitte Inventarnummer eingeben!',
code: '',
tip: '',
}, // with message, code, tip
logClass: 'active'
})
await setCheckUpVisibility(false)
}
setIsLoading(false)
}
return (
<div className="App">
<header className="App-header">
<nav className={"flex justify-content-end"}>
<Link to="/settings"><SettingsIcon/></Link>
</nav>
</header>
<main>
<div className={"container"}>
<form id={"object-id-form"} className={"flex-wrap"}>
<div className={"center column flex justify-content-space-between "}>
<label htmlFor="document-type" className={"center cut v-distance"}>Dokumenttyp:</label>
<select name="document-type" id="document-type" className={"input-field center cut"}>
<option value="restaurierungsprotokoll">Restaurierungsprotokoll</option>
<option value="leihgabenbegleitblatt">Leihgabenbegleitblatt</option>
<option value="analyse">Analyse</option>
</select>
</div>
<div className={"center column flex full justify-content-space-between"}>
<label htmlFor={"object-id"} className={"center cut v-distance"}>Inventarnummer:</label>
<input id={"object-id"} type={"text"} className={"center cut input-field"}/>
</div>
return (
<div className="App">
<header className="App-header">
<nav className={"flex justify-content-end"}>
<NavLink className={'nav-icon'} to="/settings"><SettingsIcon/></NavLink>
</nav>
</header>
<main>
<div className={"container"}>
<form id={"object-id-form"} className={"flex-wrap"}>
<div className={"center column flex justify-content-space-between "}>
<label htmlFor="document-type" className={"center cut v-distance"}>Dokumenttyp:</label>
<select name="document-type" id="document-type" className={"input-field center cut"}>
<option value="restaurierungsprotokoll">Restaurierungsprotokoll</option>
<option value="leihgabenbegleitblatt">Leihgabenbegleitblatt</option>
<option value="analyse">Analyse</option>
</select>
</div>
<div className={"center column flex full justify-content-space-between"}>
<label htmlFor={"object-id"} className={"center cut v-distance"}>Inventarnummer:</label>
<input
id={"object-id"}
name="objectId"
type={"text"}
className={"center cut input-field"}
/>
</div>
<button className={'send-button center top-distance'} onClick={getDataAndAskForCheckUpClickHandler}>
Dokument vorbereiten
</button>
<button className={'send-button center top-distance'} onClick={getDataAndAskForCheckUpClickHandler}>
Dokument vorbereiten
</button>
</form>
</div>
<DataForm
className={checkUpVisibility ? 'open' : 'closed'}
objectData={objectData}
setObjectData={setObjectData}
logState={logState}
setLogState={setLogState}
/>
</main>
<footer>
{/* We give state and state setter of the parent as params to the child components, so that child events can change parent states */}
<Log logState={logState} setLogState={setLogState}/>
</footer>
</div>
)
</form>
</div>
<LoadingIcon isLoading={isLoading}/>
<DataForm
checkUpVisibility={checkUpVisibility}
objectData={objectData}
setObjectData={setObjectData}
logState={logState}
setLogState={setLogState}
/>
</main>
<footer>
{/* We give state and state setter of the parent as params to the child components, so that child events can change parent states */}
<Log logState={logState} setLogState={setLogState}/>
</footer>
</div>
)
}
export default App

View file

@ -8,13 +8,11 @@ import React from 'react'
// Main //
////////////////////
export const DataForm = ({className, objectData, setObjectData, logState, setLogState}) => {
export const DataForm = ({checkUpVisibility, objectData, setObjectData, logState, setLogState}) => {
let log;
// Handlers
const datumChangeHandler = (e) => {
console.log(e.target.form.datum.value)
setObjectData((prevState) => {
return {
...prevState,
@ -88,7 +86,6 @@ export const DataForm = ({className, objectData, setObjectData, logState, setLog
const fillTemplateClickHandler = async (e) => {
e.preventDefault();
console.log(objectData, 'parentobjectData')
log = await fillTemplate(logState, objectData);
// Set new state of log div with message and visibility class
setLogState({
@ -97,12 +94,8 @@ export const DataForm = ({className, objectData, setObjectData, logState, setLog
});
}
// Default date of today
let today = new Date()
let todayFormat = today.getFullYear() + '-' + (String(today.getMonth() + 1).padStart(2, '0')) + '-' + String(today.getDate()).padStart(2, '0'); ;
return (<div className={'checkup ' + className}>
<form className={'flex column no-overflow'}>
return (<div className={'checkup ' + (checkUpVisibility ? 'open' : 'closed')}>
<form className={'flex column '}>
<div >
<label htmlFor={'template-datum'} >Datum</label>
<input
@ -110,11 +103,11 @@ export const DataForm = ({className, objectData, setObjectData, logState, setLog
name={"datum"}
type={"date"}
className={"full input-field"}
defaultValue={todayFormat}
defaultValue={objectData.datum}
onChange={datumChangeHandler}
/>
</div>
<div >
<div>
<label htmlFor={'template-inventarnummer'} >Inventarnummer</label>
<input
id={"template-inventarnummer"}

6
src/components/Input.js Normal file
View file

@ -0,0 +1,6 @@
import React from "react";
export const Input = ({config}) => {
return (
<span className={'settings-div '}> {config.rootDir} </span>)
}

View file

@ -0,0 +1,22 @@
import {ThreeDots} from "react-loader-spinner";
import React from "react";
export const LoadingIcon = ({isLoading}) => {
const classes = "loading-icon-canvas flex-center " + (isLoading ? 'active' : 'inactive')
return (
<div className={classes}>
<div className={"loading-icon"}>
<ThreeDots
height="100"
width="100"
radius="9"
color="#e30019"
ariaLabel="three-dots-loading"
wrapperStyle={{}}
wrapperClassName=""
visible={true}
/>
</div>
</div>
)
}

View file

@ -9,7 +9,7 @@ export const Log = ({logState, setLogState}) => {
let code;
let tip;
const closeLog = (e, logClass) => {
const closeLog = (e) => {
e.preventDefault()
setLogState({log: logState.log,
logClass: 'inactive'});
@ -30,7 +30,7 @@ export const Log = ({logState, setLogState}) => {
<div className={'log '}>
<div id={'log-content'} className={classes}>
<div>
<button onClick={(e) => {closeLog(e)}}>x</button>
<button onClick={closeLog}>x</button>
</div>
<div className={'scroll-y flex-full'} style={{maxHeight: 4 + 'em'}}>
<p>{logState.log.message}</p>

View file

@ -1,3 +0,0 @@
{
"rootDir": "/home/rbrt/Dokumente/marvin"
}

View file

@ -1,15 +1,18 @@
import config from '../config/config.json'
// Config
import configImport from '/resources/config/config.json'
// Components
import {Log} from "../components/Log";
// Icons
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
import EditIcon from '@mui/icons-material/Edit';
// Modules
const {writeFile} = require('fs');
// React
import React, {useState} from "react";
import {Link} from "react-router-dom";
import {NavLink} from "react-router-dom";
import {Input} from "../components/Input";
import EditIcon from "@mui/icons-material/Edit";
import {writeFile} from "fs";
////////////////////
@ -17,54 +20,62 @@ import {Link} from "react-router-dom";
////////////////////
export default function Settings() {
let rootDir;
const [disableButton, setDisableButton] = useState(true)
// States
const [config, setConfig] = useState(
configImport
)
async function selectFolderHandler() {
return rootDir = await window.openFile();
const [restart, setRestart] = useState(
false
)
//Functions
async function selectFolderHandler(e) {
e.preventDefault()
const rootDirFromWindow = await window.openFile();
setConfig((prevState) => {
return {
...prevState,
rootDir: rootDirFromWindow
}
})
const saveResult = await saveInputClickHandler(rootDirFromWindow)
}
// Handler
function saveInputClickHandler(rootDir) {
if (rootDir) {
config.rootDir = rootDir
writeFile('src/config/config.json', JSON.stringify(config, null, 2), (error) => {
if (error) {
console.log('An error has occurred ', error);
return;
async function saveInputClickHandler(rootDirFromWindow) {
let config2Safe = {
rootDir: rootDirFromWindow
}
if (rootDirFromWindow) {
let message;
await writeFile('resources/config/config.json', JSON.stringify(config2Safe, null, 2), (err) => {
if (err) {
message = err;
} else {
message = 'saved file'
}
console.log('Data written successfully to disk');
});
return message
})
setRestart(true)
}
}
return (
<div className="App">
<header className="App-header">
<Link to="/"><ArrowBackIosIcon/></Link>
<NavLink className={'nav-icon'} to="/"><ArrowBackIosIcon/></NavLink>
</header>
<main>
<form>
<div className={'full red v-distance'}>Die App muss nach Veränderungen neu gestartet werden!</div>
<form onSubmit={selectFolderHandler}>
<label htmlFor={"output-root"}>
Wurzelverzeichnis
</label>
<div className="col-sm-10 d-flex align-items-center flex sticky-edit">
<input
id={"root-dir"}
className={"form-control select-folder-field edit-input"}
defaultValue={config.rootDir}
disabled={true}
/>
<button className="edit-button text-end" onClick={
(e) => {
console.log(e)
e.preventDefault()
selectFolderHandler().then((rootDir) => {saveInputClickHandler(rootDir)})
}
}>
<Input config={config} setConfig={setConfig}/>
<button type="submit" className="edit-button text-end">
<i className="fas fa-edit d-block">
<EditIcon sx={{fontSize: 16}} color={'#d5d5d5'}/>
</i>

View file

@ -1,7 +0,0 @@
module.exports = {
channels: {
GET_DATA: 'get_data',
},
log: {
}
};

View file

@ -20,24 +20,27 @@ module.exports = {
},
{
test: /\.jsx?$/,
use: [{ loader: 'babel-loader' }],
use: [{loader: 'babel-loader'}],
include: defaultInclude
},
{
test: /\.(jpe?g|png|gif)$/,
use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }],
use: [{loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]'}],
include: defaultInclude
},
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
use: [{ loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' }],
use: [{loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]'}],
include: defaultInclude
}
]
},
target: 'electron-renderer',
plugins: [
new HtmlWebpackPlugin({title: 'Marvin'}),
new HtmlWebpackPlugin({
title: 'Marvin',
template: 'src/index.html'
}),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
@ -49,12 +52,14 @@ module.exports = {
}),
// new MinifyPlugin()
],
/*
stats: {
colors: true,
children: false,
chunks: false,
modules: false
},
*/
optimization: {
minimize: true
}