first working packed version

This commit is contained in:
rnsrk 2022-09-25 13:08:57 +02:00
parent 11b0d9fb6c
commit 8fc852b2b4
14 changed files with 227 additions and 197 deletions

View file

@ -1,7 +1,7 @@
# Marvin
<div style="text-align: center;">
<img src="marvin.ico"/>
<img src="src/assets/images/marvin.ico"/>
<p>
Document manager for Institute for Art Technology and Conservation at <a href="https://gnm.de" _targer="blank">Germanic National Museum Nuremberg </a>
</p>

View file

@ -2,18 +2,18 @@
"appId": "org.nasarek.marvin",
"win": {
"target": "NSIS",
"icon": "marvin256x256.ico"
"icon": "src/assets/images/marvin256x256.ico"
},
"nsis": {
"oneClick": false,
"installerIcon": "marvin256x256.ico",
"uninstallerIcon": "marvin256x256.ico",
"installerIcon": "src/assets/images/marvin256x256.ico",
"uninstallerIcon": "src/assets/images/marvin256x256.ico",
"allowToChangeInstallationDirectory": true
},
"linux": {
"target": "deb",
"category": "Utility",
"icon": "marvin256x256.png"
"icon": "src/assets/images/marvin256x256.png"
},
"files": [
"*.js",
@ -23,10 +23,5 @@
"./dist/main.js",
"./dist/main.js.LICENSE.txt",
"node_modules"
],
"extraResources": [{
"from": "resources/files",
"to": "files"
}
]
}

62
main.js
View file

@ -3,18 +3,41 @@
// Import parts of electron to use
const {app, BrowserWindow, dialog, ipcMain, Menu, screen, Tray} = require('electron')
const openAboutWindow = require('about-window').default
// Modules
const FileLoader = require('./src/FileLoader');
const path = require('path');
const fs = require('fs');
const Store = require('electron-store');
const url = require('url');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow, tray;
let devBrowserProperties = {};
let devWebPreferences = {};
const ASSET_PATH = app.isPackaged
? path.resolve(process.resourcesPath, 'app/src/assets')
: path.resolve(__dirname, 'src/assets');
const fileLoader = new FileLoader();
const configDirPath = fileLoader.getConfigDirPath();
const imageDirPath = fileLoader.getImageDirPath();
const templateDirPath = fileLoader.getTemplateDirPath();
// Create store.
const store = new Store()
store.set({
"configDirPath": configDirPath,
"imageDirPath": imageDirPath,
"templateDirPath": templateDirPath,
});
// CONFIGJSON UNSET
/*
store.set(
"configJson", '',
)
*/
let trayMenu = Menu.buildFromTemplate([
{
@ -32,10 +55,10 @@ let trayMenu = Menu.buildFromTemplate([
}
])
const RESSOURCE_PATH= fs.existsSync(ASSET_PATH) ? ASSET_PATH : 'resources/files' ;
function createTray() {
tray = new Tray(path.resolve(RESSOURCE_PATH, 'images/marvin16x16.png'));
tray = new Tray(path.resolve(imageDirPath, 'marvin16x16.png'));
tray.setToolTip('Marvin')
tray.setContextMenu(trayMenu);
tray.on('click', function() {
@ -73,11 +96,6 @@ async function handleFileOpen() {
}
}
// Constants
const getAssetPath = async (assetPath) => {
return path.resolve(ASSET_PATH, assetPath);
};
// Build main menu from file.
let mainMenu = Menu.buildFromTemplate(
@ -140,7 +158,7 @@ let mainMenu = Menu.buildFromTemplate(
label: 'Über Marvin',
click: () => {
openAboutWindow({
icon_path: path.join(__dirname, 'marvin.ico'),
icon_path: path.resolve(imageDirPath, 'marvin.ico'),
bug_report_url: 'mailto:r.nasarek@gnm.de',
bug_link_text: 'Einen Fehler melden',
license: 'MIT',
@ -159,14 +177,16 @@ function createWindow(dimensions) {
if (dev !== true) {
devBrowserProperties = {
fullscreenable: false,
resizable: true,
resizable: false,
}
devWebPreferences = {
devTools: false
}
}
// Create tray icon.
createTray()
let appWidth = 400;
let appHeight = 720;
let appWidth = 520;
let appHeight = 860;
let xPosition = (dimensions.width - appWidth)
// Create the browser window.
mainWindow = new BrowserWindow({
@ -175,12 +195,13 @@ function createWindow(dimensions) {
y: 0,
width: appWidth,
height: appHeight,
icon: path.resolve(RESSOURCE_PATH, 'images/marvin.ico'),
icon: path.resolve(imageDirPath, 'marvin.ico'),
show: false,
webPreferences: {
...devWebPreferences,
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: false
contextIsolation: false,
}
})
@ -223,7 +244,6 @@ function createWindow(dimensions) {
.catch(err => console.log('Error loading React DevTools: ', err))
mainWindow.webContents.openDevTools()
}
mainWindow.webContents.openDevTools()
})
mainWindow.on('close', function (e) {
@ -247,10 +267,6 @@ function createWindow(dimensions) {
// Some APIs can only be used after this event occurs.
app.on('ready', () => {
ipcMain.handle('dialog:openFile', handleFileOpen)
ipcMain.handle('assetPath:getAssetPath', async (event, assetPath) => {
return await getAssetPath(assetPath)
})
const display = screen.getPrimaryDisplay();
const dimensions = display.size;
createWindow(dimensions);

View file

@ -33,9 +33,9 @@
"prod": "cross-env NODE_ENV=production webpack --mode production --config webpack.build.config.js && electron --noDevServer .",
"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",
"pack:installer": "electron-builder build -l",
"pack:installer": "electron-builder build -w",
"package": "npm run build",
"postpackage": "electron-packager ./ --ignore \"(resources|builds|installer)\" --out=./builds --overwrite --platform linux --icon marvin.ico"
"postpackage": "electron-packager ./ --ignore \"(resources|builds|installer)\" --out=./builds --overwrite --platform win32 --icon src/assets/images/marvin.ico"
},
"dependencies": {
"@emotion/react": "^11.10.0",
@ -44,6 +44,7 @@
"@mui/material": "^5.10.2",
"about-window": "^1.15.2",
"docx-templates": "^4.9.2",
"electron-store": "^8.1.0",
"image-resize-compress": "^1.0.8",
"postcss": "^8.4.16",
"react": "^18.2.0",

View file

@ -3,9 +3,10 @@ import createReport from 'docx-templates';
import {mkdir} from 'node:fs/promises';
import path from 'path';
import replaceSpecialCharacters from "replace-special-characters";
import Store from 'electron-store'
import {parseString, Builder} from 'xml2js';
import {FileLoader} from "./FileLoader";
import fs from "fs";
////////////////////
@ -24,12 +25,11 @@ class DocCreator {
// Get DateTime.
this.today = new Date();
// FileLoader
this.fileLoader = new FileLoader()
// Get config file and parse to json.
this.configFile = this.fileLoader.getConfigFile()
// Electron config storage
this.store = new Store();
// Get config json
this.configJson = this.store.get('configJson')
// Choose the template for selected document type.
switch (objectData.dokumenttyp) {
case 'rp':
@ -68,14 +68,12 @@ class DocCreator {
const folderName = this.normObjectId + '__' + this.normTitle
this.objectPath = path.join(this.configFile.rootDir, folderName);
this.objectPath = path.join(this.configJson.rootDir, folderName);
this.documentPath = path.join(this.objectPath, this.documentInfo.documentType, objectData.datum);
this.filename = this.normObjectId + '__' + this.normTitle + '__' + this.objectData.datum + '__' + this.objectData.dokumenttyp;
this.filenameWithExtension = this.filename + '.docx'
this.temporaryWorkDirPath = path.join(this.objectPath, 'werkstatt');
}
// Fills the docx template.
@ -87,7 +85,7 @@ class DocCreator {
if (this.objectData.httpStatus === 200) {
try {
// Read template.
const template = fs.readFileSync(path.resolve(this.fileLoader.getTemplateDir(), this.documentInfo.templateFile));
const template = fs.readFileSync(path.resolve(this.store.get('templateDirPath'), this.documentInfo.templateFile));
// Create report.
buffer = await createReport({

View file

@ -1,35 +1,54 @@
import fs from "fs";
import path from "path";
const path = require('path');
const fs = require('fs');
const {app} = require('electron');
class FileLoader {
assetPath;
devAssetPath = 'src/assets';
buildAssetPath = 'resources/app/src/assets';
packedAssetPath = 'resources/files/';
assetBuildPath;
assetInstallerPath;
assetDevPath;
getPackedStructure() {
if (fs.existsSync(this.packedAssetPath)) {
this.assetPath = this.packedAssetPath;
} else if (fs.existsSync(this.buildAssetPath)) {
this.assetPath = this.packedAssetPath;
constructor(props) {
this.getAssetPath()
}
getAssetPath() {
this.assetBuildPath = path.resolve(__dirname, '../assets');
this.assetInstallerPath = path.resolve(__dirname, 'assets');
this.assetDevPath = path.resolve(app.getAppPath(), 'assets');
if (fs.existsSync(this.assetInstallerPath)) {
this.assetPath = this.assetInstallerPath
} else if (fs.existsSync(this.assetBuildPath)) {
this.assetPath = this.assetBuildPath
} else {
this.assetPath = this.devAssetPath;
this.assetPath = this.assetDevPath
}
}
getConfigFile() {
this.getPackedStructure()
console.log(this.assetPath)
const ConfigFile = fs.readFileSync(path.resolve(this.assetPath, 'config/config.json'))
return JSON.parse(ConfigFile.toString())
const fileBuffer = fs.readFileSync(path.resolve(this.assetPath, 'config/config.json'))
return JSON.parse(fileBuffer.toString())
}
getTemplateDir() {
getTemplateDirPath() {
return path.resolve(this.assetPath, 'templates');
}
getConfigDirPath() {
return path.resolve(this.assetPath, 'config');
}
getImageDirPath() {
return path.resolve(this.assetPath, 'images');
}
whereAmI () {
console.log(__dirname , '__dirname');
console.log(process.resourcesPath, 'resourcePath');
console.log(process.cwd(), 'cwd');
console.log(process.env.PWD, 'pwd');
console.log(app.getAppPath(), 'getAppPath')
}
}
export {FileLoader}
module.exports = FileLoader;

View file

@ -86,7 +86,7 @@ class ObjektkatalogApi {
httpStatus: 404,
}
this.visibility = false
return [this.receivedData, this.visibility]
return this.receivedData
}
case 408:
// No response, no data form.
@ -95,7 +95,7 @@ class ObjektkatalogApi {
httpStatus: 408,
}
this.visibility = false
return [this.receivedData, this.visibility]
return this.receivedData
case 503:
// Objektkatalog not reachable.
this.receivedData = {
@ -103,7 +103,7 @@ class ObjektkatalogApi {
httpStatus: 503,
}
this.visibility = false
return [this.receivedData, this.visibility]
return this.receivedData
default:
// Any other error
this.receivedData = {
@ -111,7 +111,7 @@ class ObjektkatalogApi {
httpStatus: 500,
}
this.visibility = false
return [this.receivedData, this.visibility]
return this.receivedData
}
} else {
// No ObjectId, no data form.
@ -219,7 +219,7 @@ class ObjektkatalogApi {
httpStatus: 200,
}
this.visibility = true
return [this.receivedData, this.visibility]
return this.receivedData
}
}

View file

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

View file

@ -178,10 +178,12 @@ label {
opacity: .5;
}
.loading-icon
.log {
font-size: 0.75em;
height: 5em;
position: fixed;
top: 0
}
.nav-icon svg {

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

View file

@ -3,6 +3,7 @@ import {Galary} from './Galary'
// Modules
import {DocCreator} from "../DocCreator";
import Store from 'electron-store';
// React
import React from 'react'
@ -18,6 +19,9 @@ export const DataForm = ({checkUpVisibility, objectData, setObjectData, logState
// Variables
let log;
// Store
const store = new Store()
// Handlers
// Change handler response to every keystroke!
@ -101,15 +105,29 @@ export const DataForm = ({checkUpVisibility, objectData, setObjectData, logState
// Fill the template and save it.
const fillTemplateClickHandler = async (e) => {
e.preventDefault();
const docCreator = new DocCreator(logState, objectData)
log = await docCreator.fillTemplate();
const metsMods = await docCreator.createMetsMods();
const configJson = store.get('configJson')
if (configJson.rootDir == null) {
setLogState(
{
log: {
status: 'red',
message: 'Keine Hauptverzeichnis für die Ordnerstruktur angegeben!',
tip: 'Bitte zu den Einstellungen wechseln, und dort ein Verzeichnis auswählen!'
},
logStatus: 'active'
}
)
} else {
const docCreator = new DocCreator(logState, objectData)
log = await docCreator.fillTemplate();
await docCreator.createMetsMods();
// Set new state of log div with message and visibility class
setLogState({
log: log,
logStatus: 'active'
});
// Set new state of log div with message and visibility class
setLogState({
log: log,
logStatus: 'active'
});
}
}
// Renders data form
@ -118,101 +136,102 @@ export const DataForm = ({checkUpVisibility, objectData, setObjectData, logState
<form className={'flex column pd-05rem'}>
<Galary objectData={objectData}/>
<div>
<div >
<label htmlFor={'template-datum'} >Datum</label>
<input
id={"template-datum"}
name={"datum"}
type={"date"}
className={"full input-field"}
defaultValue={objectData.datum}
onChange={datumChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-inventarnummer'} >Inventarnummer</label>
<input
id={"template-inventarnummer"}
name={"inventarnummer"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.inventarnummer}
onChange={inventarnummerChangeHandler}
/>
</div>
<div >
<label htmlFor={'template-titel'}>Titel</label>
<input
id={"template-titel"}
name={"titel"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.titel}
onChange={titelChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-hersteller'}>Hersteller</label>
<input
id={"template-hersteller"}
name={"hersteller"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.hersteller}
onChange={herstellerChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-herstellungsort'}>Herstellungsort</label>
<input
id={"template-herstellungsort"}
name={"herstellungsort"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.herstellungsort}
onChange={herstellungsortChangeHandler}
/>
</div>
<div >
<label htmlFor={'template-herstellungsdatum'}>Herstellungsdatum</label>
<input
id={"template-herstellungsdatum"}
name={"herstellungsdatum"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.herstellungsdatum}
onChange={herstellungsdatumChangeHandler}
/>
<div>
<label htmlFor={'template-datum'}>Datum</label>
<input
id={"template-datum"}
name={"datum"}
type={"date"}
className={"full input-field"}
defaultValue={objectData.datum}
onChange={datumChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-inventarnummer'}>Inventarnummer</label>
<input
id={"template-inventarnummer"}
name={"inventarnummer"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.inventarnummer}
onChange={inventarnummerChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-titel'}>Titel</label>
<input
id={"template-titel"}
name={"titel"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.titel}
onChange={titelChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-hersteller'}>Hersteller</label>
<input
id={"template-hersteller"}
name={"hersteller"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.hersteller}
onChange={herstellerChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-herstellungsort'}>Herstellungsort</label>
<input
id={"template-herstellungsort"}
name={"herstellungsort"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.herstellungsort}
onChange={herstellungsortChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-herstellungsdatum'}>Herstellungsdatum</label>
<input
id={"template-herstellungsdatum"}
name={"herstellungsdatum"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.herstellungsdatum}
onChange={herstellungsdatumChangeHandler}
/>
</div>
<div >
<label htmlFor={'template-materialTechnik'}>Material und Technik</label>
<input
id={"template-materialTechnik"}
name={"materialTechnik"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.materialTechnik}
onChange={materialTechnikChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-masse'}>Maße</label>
<input
id={"template-masse"}
name={"masse"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.masse}
onChange={masseChangeHandler}
/>
</div>
</div>
<div>
<label htmlFor={'template-materialTechnik'}>Material und Technik</label>
<input
id={"template-materialTechnik"}
name={"materialTechnik"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.materialTechnik}
onChange={materialTechnikChangeHandler}
/>
</div>
<div>
<label htmlFor={'template-masse'}>Maße</label>
<input
id={"template-masse"}
name={"masse"}
type={"text"}
className={"full input-field"}
defaultValue={objectData.masse}
onChange={masseChangeHandler}
/>
</div>
</div>
<button
type={"submit"}
className={'send-button center top-distance'}
onClick={fillTemplateClickHandler}
>Dokument erstellen</button>
>Dokument erstellen
</button>
</form>
</div>
)

View file

@ -1,9 +1,5 @@
// Modules
import {FileLoader} from "../FileLoader";
// Electron
const electron = window.require('electron');
const ipcRenderer = electron.ipcRenderer;
import Store from 'electron-store';
// Icons
import ArrowBackIosIcon from '@mui/icons-material/ArrowBackIos';
@ -13,7 +9,6 @@ import React, {useState} from "react";
import {NavLink} from "react-router-dom";
import {Input} from "../components/Input";
import EditIcon from "@mui/icons-material/Edit";
import {writeFile} from "fs";
////////////////////
@ -21,16 +16,11 @@ import {writeFile} from "fs";
////////////////////
export default function Settings() {
const fileLoader = new FileLoader()
const initialConfig = fileLoader.getConfigFile()
console.log(initialConfig)
const getConfigPath = async (path) => {
return await ipcRenderer.invoke('assetPath:getAssetPath', path)
}
const store = new Store()
const initialConfigJson = store.get('configJson')
// States
const [configFile, setConfigFile] = useState(
initialConfig
initialConfigJson
)
const [restart, setRestart] = useState(
false
@ -46,25 +36,18 @@ export default function Settings() {
rootDir: rootDirFromWindow
}
})
await saveInputClickHandler(rootDirFromWindow)
saveInputClickHandler(rootDirFromWindow)
}
// Handler
async function saveInputClickHandler(rootDirFromWindow) {
function saveInputClickHandler(rootDirFromWindow) {
let config2Safe = {
rootDir: rootDirFromWindow
}
if (rootDirFromWindow) {
let message;
const configPath = await getConfigPath('config/config.json')
await writeFile(configPath, JSON.stringify(config2Safe, null, 2), (err) => {
if (err) {
message = err;
} else {
message = 'saved file'
}
return message
})
store.set(
'configJson', config2Safe
)
setRestart(true)
return restart;
}