Merged in feature/IO-3205-Paint-Scale-Integrations (pull request #1)
[DO NOT MERGE] Feature/IO-3205 Paint Scale Integrations Approved-by: Patrick Fic
This commit is contained in:
17
.env.imex
17
.env.imex
@@ -1,7 +1,14 @@
|
||||
VITE_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
|
||||
VITE_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
||||
VITE_FIREBASE_CONFIG_TEST={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
|
||||
VITE_GRAPHQL_ENDPOINT_TEST=https://db.test.bodyshop.app/v1/graphql
|
||||
VITE_COMPANY=IMEX
|
||||
|
||||
# Fire Base Config
|
||||
VITE_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
|
||||
VITE_FIREBASE_CONFIG_TEST={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
|
||||
# GraphQL Config
|
||||
VITE_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
||||
VITE_GRAPHQL_ENDPOINT_TEST=https://db.test.bodyshop.app/v1/graphql
|
||||
# Front End URL
|
||||
VITE_FE_URL=https://imex.online
|
||||
VITE_FE_URL_TEST=https://test.imex.online
|
||||
VITE_FE_URL_TEST=https://test.imex.online
|
||||
# API Url
|
||||
VITE_API_URL="https://api.imex.online"
|
||||
VITE_API_TEST_URL="https://test.api.imex.online"
|
||||
@@ -4,4 +4,6 @@ VITE_FIREBASE_CONFIG_TEST={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c"
|
||||
VITE_GRAPHQL_ENDPOINT_TEST=https://db.test.bodyshop.app/v1/graphql
|
||||
VITE_COMPANY=IMEX
|
||||
VITE_FE_URL=https://imex.online
|
||||
VITE_FE_URL_TEST=https://test.imex.online
|
||||
VITE_FE_URL_TEST=https://test.imex.online
|
||||
VITE_API_URL="http://localhost:4000"
|
||||
VITE_API_TEST_URL="http://api.test.imex.online"
|
||||
@@ -1,2 +0,0 @@
|
||||
VITE_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
|
||||
VITE_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
||||
17
.env.rome
17
.env.rome
@@ -1,7 +1,14 @@
|
||||
VITE_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
VITE_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
|
||||
VITE_FIREBASE_CONFIG_TEST={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
VITE_GRAPHQL_ENDPOINT_TEST=https://db.test.romeonline.io/v1/graphql
|
||||
VITE_COMPANY=ROME
|
||||
|
||||
# Fire Base Config
|
||||
VITE_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
VITE_FIREBASE_CONFIG_TEST={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
# GraphQL Config
|
||||
VITE_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
|
||||
VITE_GRAPHQL_ENDPOINT_TEST=https://db.test.romeonline.io/v1/graphql
|
||||
# Front End URL
|
||||
VITE_FE_URL=https://romeonline.io
|
||||
VITE_FE_URL_TEST=https://test.romeonline.io
|
||||
VITE_FE_URL_TEST=https://test.romeonline.io
|
||||
# API Url
|
||||
VITE_API_URL="https://api.romeonline.io"
|
||||
VITE_API_TEST_URL="https://test.api.romeonline.io"
|
||||
4
.idea/material_theme_project_new.xml
generated
4
.idea/material_theme_project_new.xml
generated
@@ -3,7 +3,9 @@
|
||||
<component name="MaterialThemeProjectNewConfig">
|
||||
<option name="metadata">
|
||||
<MTProjectMetadataState>
|
||||
<option name="userId" value="-70fa916f:1961b191ca1:-748b" />
|
||||
<option name="migrated" value="true" />
|
||||
<option name="pristineConfig" value="false" />
|
||||
<option name="userId" value="-4002d172:18ee315e3ba:-7ffe" />
|
||||
</MTProjectMetadataState>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
@@ -7,6 +7,7 @@ directories:
|
||||
buildResources: build
|
||||
files:
|
||||
- "!**/.vscode/*"
|
||||
- "!**/.idea/*"
|
||||
- "!src/*"
|
||||
- "!electron.vite.config.{js,ts,mjs,cjs}"
|
||||
- "!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}"
|
||||
@@ -26,6 +27,7 @@ nsis:
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
include: "scripts/installer.nsh" # Reference NSIS script from scripts directory
|
||||
mac:
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
category: public.app-category.business
|
||||
@@ -34,6 +36,11 @@ mac:
|
||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
- CFBundleURLTypes:
|
||||
- CFBundleTypeRole: Viewer # More specific role for protocol handling
|
||||
CFBundleURLName: com.convenientbrands.bodyshop-desktop-imex
|
||||
CFBundleURLSchemes:
|
||||
- imexmedia
|
||||
target:
|
||||
- target: default
|
||||
arch:
|
||||
@@ -50,12 +57,11 @@ linux:
|
||||
- deb
|
||||
maintainer: electronjs.org
|
||||
category: Utility
|
||||
desktop: scripts/imex-shop-partner.desktop
|
||||
appImage:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: s3
|
||||
bucket: imex-partner
|
||||
region: ca-central-1
|
||||
# electronDownload:
|
||||
# mirror: https://npmmirror.com/mirrors/electron/
|
||||
region: ca-central-1
|
||||
@@ -7,6 +7,7 @@ directories:
|
||||
buildResources: build
|
||||
files:
|
||||
- "!**/.vscode/*"
|
||||
- "!**/.idea/*"
|
||||
- "!src/*"
|
||||
- "!electron.vite.config.{js,ts,mjs,cjs}"
|
||||
- "!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}"
|
||||
@@ -26,6 +27,7 @@ nsis:
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
include: "scripts/installer.nsh" # Reference NSIS script from scripts directory
|
||||
mac:
|
||||
entitlementsInherit: build/entitlements.mac.plist
|
||||
category: public.app-category.business
|
||||
@@ -34,6 +36,11 @@ mac:
|
||||
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
|
||||
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
|
||||
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
|
||||
- CFBundleURLTypes:
|
||||
- CFBundleTypeRole: Viewer # More specific role for protocol handling
|
||||
CFBundleURLName: com.convenientbrands.bodyshop-desktop-rome
|
||||
CFBundleURLSchemes:
|
||||
- imexmedia
|
||||
target:
|
||||
- target: default
|
||||
arch:
|
||||
@@ -50,12 +57,11 @@ linux:
|
||||
- deb
|
||||
maintainer: electronjs.org
|
||||
category: Utility
|
||||
desktop: scripts/rome-shop-partner.desktop
|
||||
appImage:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: s3
|
||||
bucket: rome-partner
|
||||
region: us-east-2
|
||||
# electronDownload:
|
||||
# mirror: https://npmmirror.com/mirrors/electron/
|
||||
region: us-east-2
|
||||
252
package-lock.json
generated
252
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bodyshop-desktop",
|
||||
"version": "0.0.1-alpha.6",
|
||||
"version": "0.0.1-alpha.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bodyshop-desktop",
|
||||
"version": "0.0.1-alpha.6",
|
||||
"version": "0.0.1-alpha.8",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.13.6",
|
||||
@@ -14,6 +14,7 @@
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@sentry/electron": "^6.5.0",
|
||||
"@sentry/vite-plugin": "^3.3.1",
|
||||
"axios": "^1.9.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"electron-log": "^5.3.3",
|
||||
"electron-store": "^8.2.0",
|
||||
@@ -31,8 +32,10 @@
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"antd": "^5.24.6",
|
||||
"chokidar": "^4.0.3",
|
||||
@@ -52,6 +55,7 @@
|
||||
"graphql-request": "^7.1.2",
|
||||
"i18next": "^24.2.3",
|
||||
"lodash": "^4.17.21",
|
||||
"node-cron": "^3.0.3",
|
||||
"playwright": "^1.51.1",
|
||||
"prettier": "^3.5.3",
|
||||
"react": "^19.1.0",
|
||||
@@ -62,7 +66,9 @@
|
||||
"react-router": "^7.5.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "6.2.6"
|
||||
"vite": "6.2.6",
|
||||
"xml2js": "^0.6.2",
|
||||
"xmlbuilder2": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -2695,6 +2701,58 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/dom": {
|
||||
"version": "1.15.10",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz",
|
||||
"integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/url": "1.0.4",
|
||||
"@oozcitak/util": "8.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/infra": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz",
|
||||
"integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oozcitak/util": "8.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/url": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz",
|
||||
"integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/util": "8.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oozcitak/util": {
|
||||
"version": "8.3.8",
|
||||
"resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz",
|
||||
"integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/api": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
@@ -4322,13 +4380,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
||||
@@ -4459,6 +4510,13 @@
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-cron": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz",
|
||||
"integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/pg": {
|
||||
"version": "8.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz",
|
||||
@@ -4587,6 +4645,16 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/xml2js": {
|
||||
"version": "0.4.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yauzl": {
|
||||
"version": "2.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||
@@ -5627,7 +5695,6 @@
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
@@ -5666,6 +5733,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -6138,7 +6216,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
@@ -6399,7 +6476,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
@@ -6943,7 +7019,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
@@ -7189,7 +7264,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
@@ -7706,7 +7780,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -7716,7 +7789,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -7754,7 +7826,6 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
@@ -7767,7 +7838,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
@@ -8144,6 +8214,20 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
"esvalidate": "bin/esvalidate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/esquery": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
|
||||
@@ -8528,6 +8612,26 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
@@ -8578,7 +8682,6 @@
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
@@ -8594,7 +8697,6 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@@ -8604,7 +8706,6 @@
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
@@ -8798,7 +8899,6 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
@@ -8823,7 +8923,6 @@
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
@@ -8977,7 +9076,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -9117,7 +9215,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -9130,7 +9227,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
@@ -10561,7 +10657,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -11026,6 +11121,19 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-cron": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
|
||||
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
@@ -12851,13 +12959,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.0.tgz",
|
||||
"integrity": "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==",
|
||||
"version": "7.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
|
||||
"integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"turbo-stream": "2.4.0"
|
||||
@@ -14773,6 +14880,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
@@ -15149,6 +15266,30 @@
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xml2js/node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "15.1.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
||||
@@ -15159,6 +15300,53 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder2": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.1.1.tgz",
|
||||
"integrity": "sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oozcitak/dom": "1.15.10",
|
||||
"@oozcitak/infra": "1.0.8",
|
||||
"@oozcitak/util": "8.3.8",
|
||||
"js-yaml": "3.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder2/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder2/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder2/node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
|
||||
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bodyshop-desktop",
|
||||
"version": "0.0.1-alpha.7",
|
||||
"version": "0.0.1-alpha.8",
|
||||
"description": "Shop Management System Partner",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Convenient Brands, LLC",
|
||||
@@ -17,11 +17,13 @@
|
||||
"build:rome": "electron-vite build --mode rome && electron-builder --config electron-builder.rome.yml",
|
||||
"build:imex:publish": "electron-vite build --mode imex && electron-builder --config electron-builder.imex.yml --publish always",
|
||||
"build:rome:publish": "electron-vite build --mode rome && electron-builder --config electron-builder.rome.yml --publish always",
|
||||
"build:imex:linux": "electron-vite build --mode imex && electron-builder --config electron-builder.imex.yml --linux",
|
||||
"build:rome:linux": "electron-vite build --mode rome && electron-builder --config electron-builder.rome.yml --linux",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"build:unpack": "npm run build && electron-builder --dir",
|
||||
"build:win": "npm run build && electron-builder --win",
|
||||
"build:mac": "npm run build && electron-builder --mac",
|
||||
"build:linux": "npm run build && electron-builder --linux"
|
||||
"build:unpack": "electron-vite build --mode imex && electron-builder --dir",
|
||||
"build:win": "electron-vite build --mode imex && electron-builder --win",
|
||||
"build:mac": "electron-vite build --mode imex && electron-builder --mac",
|
||||
"build:linux": "electron-vite build --mode imex && electron-builder --linux"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.13.6",
|
||||
@@ -29,6 +31,7 @@
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@sentry/electron": "^6.5.0",
|
||||
"@sentry/vite-plugin": "^3.3.1",
|
||||
"axios": "^1.9.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"electron-log": "^5.3.3",
|
||||
"electron-store": "^8.2.0",
|
||||
@@ -46,8 +49,10 @@
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"antd": "^5.24.6",
|
||||
"chokidar": "^4.0.3",
|
||||
@@ -77,6 +82,9 @@
|
||||
"react-router": "^7.5.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "6.2.6"
|
||||
"vite": "6.2.6",
|
||||
"xml2js": "^0.6.2",
|
||||
"xmlbuilder2": "^3.1.1",
|
||||
"node-cron": "^3.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
8
scripts/imex-shop-partner.desktop
Normal file
8
scripts/imex-shop-partner.desktop
Normal file
@@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Name=ImEX Shop Partner
|
||||
Exec=/opt/ImEX%20Shop%20Partner/ShopPartner %u
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Icon=imex-shop-partner
|
||||
Categories=Utility;
|
||||
MimeType=x-scheme-handler/imexmedia;
|
||||
13
scripts/installer.nsh
Normal file
13
scripts/installer.nsh
Normal file
@@ -0,0 +1,13 @@
|
||||
!macro customInstall
|
||||
; Register imexmedia protocol
|
||||
WriteRegStr HKCR "imexmedia" "" "URL:ImEX Shop Partner Protocol"
|
||||
WriteRegStr HKCR "imexmedia" "URL Protocol" ""
|
||||
WriteRegStr HKCR "imexmedia\\shell" "" ""
|
||||
WriteRegStr HKCR "imexmedia\\shell\\open" "" ""
|
||||
WriteRegStr HKCR "imexmedia\\shell\\open\\command" "" '"$INSTDIR\ShopPartner.exe" "%1"'
|
||||
!macroend
|
||||
|
||||
!macro customUnInstall
|
||||
; Remove imexmedia protocol
|
||||
DeleteRegKey HKCR "imexmedia"
|
||||
!macroend
|
||||
8
scripts/rome-shop-partner.desktop
Normal file
8
scripts/rome-shop-partner.desktop
Normal file
@@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Name=Rome Shop Partner
|
||||
Exec=/opt/Rome%20Shop%20Partner/ShopPartner %u
|
||||
Type=Application
|
||||
Terminal=false
|
||||
Icon=rome-shop-partner
|
||||
Categories=Utility;
|
||||
MimeType=x-scheme-handler/imexmedia;
|
||||
@@ -25,6 +25,14 @@ import store from "./store/store";
|
||||
import { checkForAppUpdates } from "./util/checkForAppUpdates";
|
||||
import { getMainWindow } from "./util/toRenderer";
|
||||
import { GetAllEnvFiles } from "./watcher/watcher";
|
||||
import {
|
||||
isKeepAliveAgentInstalled,
|
||||
setupKeepAliveAgent,
|
||||
} from "./setup-keep-alive-agent";
|
||||
import {
|
||||
isKeepAliveTaskInstalled,
|
||||
setupKeepAliveTask,
|
||||
} from "./setup-keep-alive-task";
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://ba41d22656999a8c1fd63bcb7df98650@o492140.ingest.us.sentry.io/4509074139447296",
|
||||
@@ -34,6 +42,7 @@ log.initialize();
|
||||
const isMac: boolean = process.platform === "darwin";
|
||||
const protocol: string = "imexmedia";
|
||||
let isAppQuitting = false; //Needed on Mac as an override to allow us to fully quit the app.
|
||||
let isKeepAliveLaunch = false; // Track if launched via keep-alive
|
||||
// Initialize the server
|
||||
const localServer = new LocalServer();
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
@@ -52,7 +61,7 @@ function createWindow(): void {
|
||||
height,
|
||||
x,
|
||||
y,
|
||||
show: false,
|
||||
show: false, // Start hidden, show later if not keep-alive
|
||||
minWidth: 600,
|
||||
minHeight: 400,
|
||||
//autoHideMenuBar: true,
|
||||
@@ -192,7 +201,14 @@ function createWindow(): void {
|
||||
label: "Open Log File",
|
||||
click: (): void => {
|
||||
/* action for item 1 */
|
||||
shell.openPath(log.transports.file.getFile().path);
|
||||
shell
|
||||
.openPath(log.transports.file.getFile().path)
|
||||
.catch((error) => {
|
||||
log.error(
|
||||
"Failed to open log file:",
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -204,7 +220,12 @@ function createWindow(): void {
|
||||
{
|
||||
label: "Open Config Folder",
|
||||
click: (): void => {
|
||||
shell.openPath(path.dirname(store.path));
|
||||
shell.openPath(path.dirname(store.path)).catch((error) => {
|
||||
log.error(
|
||||
"Failed to open config folder:",
|
||||
errorTypeCheck(error),
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -218,13 +239,38 @@ function createWindow(): void {
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
|
||||
// {
|
||||
// label: "Decode Hardcoded Estimate",
|
||||
// click: (): void => {
|
||||
// ImportJob(`C:\\EMS\\CCC\\9ee762f4.ENV`);
|
||||
// },
|
||||
// },
|
||||
{
|
||||
label: "Install Keep Alive",
|
||||
enabled: true, // Default to enabled, update dynamically
|
||||
click: async (): Promise<void> => {
|
||||
try {
|
||||
if (platform.isWindows) {
|
||||
log.debug("Creating Windows keep-alive task");
|
||||
await setupKeepAliveTask();
|
||||
log.info("Successfully installed Windows keep-alive task");
|
||||
} else if (platform.isMacOS) {
|
||||
log.debug("Creating macOS keep-alive agent");
|
||||
await setupKeepAliveAgent();
|
||||
log.info("Successfully installed macOS keep-alive agent");
|
||||
}
|
||||
// Wait to ensure task/agent is registered
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
// Rebuild menu and update enabled state
|
||||
await updateKeepAliveMenuItem();
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Failed to install keep-alive: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
// Optionally notify user (e.g., via dialog or log)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Add All Estimates in watched directories",
|
||||
click: (): void => {
|
||||
@@ -254,8 +300,45 @@ function createWindow(): void {
|
||||
},
|
||||
];
|
||||
|
||||
// Dynamically update Install Keep Alive enabled state
|
||||
const updateKeepAliveMenuItem = async (): Promise<void> => {
|
||||
try {
|
||||
const isInstalled = platform.isWindows
|
||||
? await isKeepAliveTaskInstalled()
|
||||
: platform.isMacOS
|
||||
? await isKeepAliveAgentInstalled()
|
||||
: false;
|
||||
const developmentMenu = template
|
||||
.find((item) => item.label === "Application")
|
||||
// @ts-ignore
|
||||
?.submenu?.find((item: { id: string }) => item.id === "development")
|
||||
?.submenu as Electron.MenuItemConstructorOptions[];
|
||||
const keepAliveItem = developmentMenu?.find(
|
||||
(item) => item.label === "Install Keep Alive",
|
||||
);
|
||||
if (keepAliveItem) {
|
||||
keepAliveItem.enabled = !isInstalled; // Enable if not installed, disable if installed
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
log.debug(
|
||||
`Updated Install Keep Alive menu item: enabled=${keepAliveItem.enabled}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const menu: Electron.Menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
// Update menu item enabled state on app start
|
||||
updateKeepAliveMenuItem().catch((error) => {
|
||||
log.error(
|
||||
`Error updating Keep Alive menu item: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
});
|
||||
|
||||
// Register a global shortcut to show the hidden item
|
||||
globalShortcut.register("CommandOrControl+Shift+T", () => {
|
||||
@@ -266,7 +349,7 @@ function createWindow(): void {
|
||||
const fileMenu = template.find((item) => item.label === "Application");
|
||||
// @ts-ignore
|
||||
const hiddenItem = fileMenu?.submenu?.find(
|
||||
(item) => item.id === "development",
|
||||
(item: { id: string }) => item.id === "development",
|
||||
);
|
||||
//Adjust the development menu as well.
|
||||
|
||||
@@ -289,7 +372,9 @@ function createWindow(): void {
|
||||
mainWindow.on("moved", storeWindowState);
|
||||
|
||||
mainWindow.on("ready-to-show", () => {
|
||||
mainWindow.show();
|
||||
if (!isKeepAliveLaunch) {
|
||||
mainWindow.show(); // Show only if not a keep-alive launch
|
||||
}
|
||||
//Start the HTTP server.
|
||||
// Start the local HTTP server
|
||||
try {
|
||||
@@ -307,16 +392,24 @@ function createWindow(): void {
|
||||
});
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url);
|
||||
shell.openExternal(details.url).catch((error) => {
|
||||
log.error("Failed to open external URL:", errorTypeCheck(error));
|
||||
});
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
|
||||
mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]).catch((error) => {
|
||||
log.error("Failed to load URL:", errorTypeCheck(error));
|
||||
});
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, "../renderer/index.html"));
|
||||
mainWindow
|
||||
.loadFile(join(__dirname, "../renderer/index.html"))
|
||||
.catch((error) => {
|
||||
log.error("Failed to load file:", errorTypeCheck(error));
|
||||
});
|
||||
}
|
||||
if (import.meta.env.DEV) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
@@ -341,7 +434,8 @@ app.whenReady().then(async () => {
|
||||
optimizer.watchWindowShortcuts(window);
|
||||
});
|
||||
|
||||
let isDefaultProtocolClient;
|
||||
let isDefaultProtocolClient: boolean;
|
||||
|
||||
// remove so we can register each time as we run the app.
|
||||
app.removeAsDefaultProtocolClient(protocol);
|
||||
|
||||
@@ -365,25 +459,30 @@ app.whenReady().then(async () => {
|
||||
|
||||
// Add this event handler for second instance
|
||||
app.on("second-instance", (_event: Electron.Event, argv: string[]) => {
|
||||
// Someone tried to run a second instance, we should focus our window
|
||||
openMainWindow();
|
||||
const url = argv.find((arg) => arg.startsWith(`${protocol}://`));
|
||||
if (url) {
|
||||
openInExplorer(url);
|
||||
if (url.startsWith(`${protocol}://keep-alive`)) {
|
||||
log.info("Keep-alive protocol received, app is already running.");
|
||||
// Do nothing if already running
|
||||
return; // Skip openMainWindow to avoid focusing the window
|
||||
} else {
|
||||
openInExplorer(url);
|
||||
}
|
||||
} else {
|
||||
openMainWindow(); // Focus window if no URL
|
||||
}
|
||||
});
|
||||
|
||||
//Dynamically load ipcMain handlers once ready.
|
||||
try {
|
||||
// Replace 'path/to/your/file' with the actual path to your file
|
||||
const module = await import("./ipc/ipcMainConfig");
|
||||
log.debug("Successfully loaded ipcMainConfig");
|
||||
|
||||
// You can now use anything exported from the module
|
||||
// For example:
|
||||
// module.someFunction()
|
||||
log.debug("Successfully loaded ipcMainConfig", module);
|
||||
// Initialize cron tasks after loading ipcMainConfig
|
||||
await module.initializeCronTasks();
|
||||
log.info("Cron tasks initialized successfully");
|
||||
} catch (error) {
|
||||
log.error("Failed to load ipcMainConfig", {
|
||||
log.error("Failed to load ipcMainConfig or initialize cron tasks", {
|
||||
...ErrorTypeCheck(error),
|
||||
});
|
||||
}
|
||||
@@ -453,6 +552,12 @@ app.whenReady().then(async () => {
|
||||
mainWindow?.webContents.send(ipcTypes.toRenderer.updates.downloaded, info);
|
||||
});
|
||||
|
||||
// Check if launched with keep-alive protocol (Windows)
|
||||
const args = process.argv.slice(1);
|
||||
if (args.some((arg) => arg.startsWith(`${protocol}://keep-alive`))) {
|
||||
isKeepAliveLaunch = true;
|
||||
}
|
||||
|
||||
//The update itself will run when the bodyshop record is queried to know what release channel to use.
|
||||
createWindow();
|
||||
|
||||
@@ -464,7 +569,17 @@ app.whenReady().then(async () => {
|
||||
app.on("open-url", (event: Electron.Event, url: string) => {
|
||||
event.preventDefault();
|
||||
//Don't do anything for now. We just want to open the app.
|
||||
openInExplorer(url);
|
||||
if (url.startsWith(`${protocol}://keep-alive`)) {
|
||||
log.info("Keep-alive protocol received.");
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
isKeepAliveLaunch = true;
|
||||
openMainWindow(); // Launch app if not running
|
||||
}
|
||||
// Do nothing if already running
|
||||
return; // Skip openMainWindow to avoid focusing the window
|
||||
} else {
|
||||
openInExplorer(url);
|
||||
}
|
||||
});
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
@@ -518,5 +633,7 @@ function openMainWindow(): void {
|
||||
function openInExplorer(url: string): void {
|
||||
const folderPath: string = decodeURIComponent(url.split(`${protocol}://`)[1]);
|
||||
log.info("Opening folder in explorer", folderPath);
|
||||
shell.openPath(folderPath);
|
||||
shell.openPath(folderPath).catch((error) => {
|
||||
log.error("Failed to open folder in explorer:", errorTypeCheck(error));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,6 +7,14 @@ import ImportJob from "../decoder/decoder";
|
||||
import store from "../store/store";
|
||||
import { StartWatcher, StopWatcher } from "../watcher/watcher";
|
||||
import {
|
||||
SettingEmsOutFilePathGet,
|
||||
SettingEmsOutFilePathSet,
|
||||
SettingsPaintScaleInputConfigsGet,
|
||||
SettingsPaintScaleInputConfigsSet,
|
||||
SettingsPaintScaleInputPathSet,
|
||||
SettingsPaintScaleOutputConfigsGet,
|
||||
SettingsPaintScaleOutputConfigsSet,
|
||||
SettingsPaintScaleOutputPathSet,
|
||||
SettingsPpcFilePathGet,
|
||||
SettingsPpcFilePathSet,
|
||||
SettingsWatchedFilePathsAdd,
|
||||
@@ -14,22 +22,38 @@ import {
|
||||
SettingsWatchedFilePathsRemove,
|
||||
SettingsWatcherPollingGet,
|
||||
SettingsWatcherPollingSet,
|
||||
SettingEmsOutFilePathSet,
|
||||
SettingEmsOutFilePathGet,
|
||||
} from "./ipcMainHandler.settings";
|
||||
import {
|
||||
ipcMainHandleAuthStateChanged,
|
||||
ipMainHandleResetPassword,
|
||||
} from "./ipcMainHandler.user";
|
||||
import cron from "node-cron";
|
||||
import { PaintScaleConfig, PaintScaleType } from "../../util/types/paintScale";
|
||||
import { ppgInputHandler, ppgOutputHandler } from "./paintScaleHandlers/PPG";
|
||||
|
||||
const initializeCronTasks = async () => {
|
||||
try {
|
||||
// Fetch input and output configurations
|
||||
const inputConfigs = await SettingsPaintScaleInputConfigsGet();
|
||||
const outputConfigs = await SettingsPaintScaleOutputConfigsGet();
|
||||
|
||||
// Start input cron tasks
|
||||
await handlePaintScaleInputCron(inputConfigs);
|
||||
log.info("Initialized input cron tasks on app startup");
|
||||
|
||||
// Start output cron tasks
|
||||
await handlePaintScaleOutputCron(outputConfigs);
|
||||
log.info("Initialized output cron tasks on app startup");
|
||||
} catch (error) {
|
||||
log.error("Error initializing cron tasks on app startup:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Log all IPC messages and their payloads
|
||||
const logIpcMessages = (): void => {
|
||||
// Get all message types from ipcTypes.toMain
|
||||
Object.keys(ipcTypes.toMain).forEach((key) => {
|
||||
const messageType = ipcTypes.toMain[key];
|
||||
|
||||
// Wrap the original handler with our logging
|
||||
const originalHandler = ipcMain.listeners(messageType)[0];
|
||||
const originalHandler = ipcMain.listeners(messageType)?.[0];
|
||||
if (originalHandler) {
|
||||
ipcMain.removeAllListeners(messageType);
|
||||
}
|
||||
@@ -40,7 +64,6 @@ const logIpcMessages = (): void => {
|
||||
"color: green",
|
||||
payload,
|
||||
);
|
||||
// Call original handler if it existed
|
||||
if (originalHandler) {
|
||||
originalHandler(event, payload);
|
||||
}
|
||||
@@ -48,21 +71,91 @@ const logIpcMessages = (): void => {
|
||||
});
|
||||
};
|
||||
|
||||
// Input handler map
|
||||
const inputTypeHandlers: Partial<
|
||||
Record<PaintScaleType, (config: PaintScaleConfig) => Promise<void>>
|
||||
> = {
|
||||
[PaintScaleType.PPG]: ppgInputHandler,
|
||||
// Add other input type handlers as needed
|
||||
};
|
||||
|
||||
// Output handler map
|
||||
const outputTypeHandlers: Partial<
|
||||
Record<PaintScaleType, (config: PaintScaleConfig) => Promise<void>>
|
||||
> = {
|
||||
[PaintScaleType.PPG]: ppgOutputHandler,
|
||||
// Add other output type handlers as needed
|
||||
};
|
||||
|
||||
// Default handler for unsupported types
|
||||
const defaultHandler = async (config: PaintScaleConfig) => {
|
||||
log.debug(
|
||||
`No handler defined for type ${config.type} in config ${config.id}`,
|
||||
);
|
||||
};
|
||||
|
||||
// Input cron job management
|
||||
let inputCronTasks: { [id: string]: cron.ScheduledTask } = {};
|
||||
|
||||
const handlePaintScaleInputCron = async (configs: PaintScaleConfig[]) => {
|
||||
Object.values(inputCronTasks).forEach((task) => task.stop());
|
||||
inputCronTasks = {};
|
||||
|
||||
const validConfigs = configs.filter(
|
||||
(config) => config.path && config.path.trim() !== "",
|
||||
);
|
||||
|
||||
validConfigs.forEach((config) => {
|
||||
const cronExpression = `*/${config.pollingInterval} * * * *`;
|
||||
inputCronTasks[config.id] = cron.schedule(cronExpression, async () => {
|
||||
const handler = inputTypeHandlers[config.type] || defaultHandler;
|
||||
await handler(config);
|
||||
});
|
||||
log.info(
|
||||
`Started input cron task for config ${config.id} (type: ${config.type}) with interval ${config.pollingInterval}m`,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Output cron job management
|
||||
let outputCronTasks: { [id: string]: cron.ScheduledTask } = {};
|
||||
|
||||
const handlePaintScaleOutputCron = async (configs: PaintScaleConfig[]) => {
|
||||
Object.values(outputCronTasks).forEach((task) => task.stop());
|
||||
outputCronTasks = {};
|
||||
|
||||
const validConfigs = configs.filter(
|
||||
(config) => config.path && config.path.trim() !== "",
|
||||
);
|
||||
|
||||
validConfigs.forEach((config) => {
|
||||
const cronExpression = `*/${config.pollingInterval} * * * *`;
|
||||
outputCronTasks[config.id] = cron.schedule(cronExpression, async () => {
|
||||
const handler = outputTypeHandlers[config.type] || defaultHandler;
|
||||
await handler(config);
|
||||
});
|
||||
log.info(
|
||||
`Started output cron task for config ${config.id} (type: ${config.type}) with interval ${config.pollingInterval}m`,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Existing IPC handlers...
|
||||
|
||||
ipcMain.on(ipcTypes.toMain.test, () =>
|
||||
console.log("** Verify that ipcMain is loaded and working."),
|
||||
);
|
||||
|
||||
//Auth handler
|
||||
// Auth handler
|
||||
ipcMain.on(ipcTypes.toMain.authStateChanged, ipcMainHandleAuthStateChanged);
|
||||
ipcMain.on(ipcTypes.toMain.user.resetPassword, ipMainHandleResetPassword);
|
||||
|
||||
//Add debug handlers if in development
|
||||
// Add debug handlers if in development
|
||||
if (import.meta.env.DEV) {
|
||||
log.debug("[IPC Debug Functions] Adding Debug Handlers");
|
||||
|
||||
ipcMain.on(ipcTypes.toMain.debug.decodeEstimate, async (): Promise<void> => {
|
||||
const relativeEmsFilepath = `_reference/ems/MPI_1/3698420.ENV`;
|
||||
// Get the app's root directory and create an absolute path
|
||||
const rootDir = app.getAppPath();
|
||||
const absoluteFilepath = path.join(rootDir, relativeEmsFilepath);
|
||||
|
||||
@@ -76,7 +169,7 @@ if (import.meta.env.DEV) {
|
||||
});
|
||||
}
|
||||
|
||||
//Settings Handlers
|
||||
// Settings Handlers
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.filepaths.get,
|
||||
SettingsWatchedFilePathsGet,
|
||||
@@ -109,22 +202,77 @@ ipcMain.handle(
|
||||
SettingEmsOutFilePathSet,
|
||||
);
|
||||
|
||||
// Paint Scale Input Settings Handlers
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.getInputConfigs,
|
||||
SettingsPaintScaleInputConfigsGet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.setInputConfigs,
|
||||
SettingsPaintScaleInputConfigsSet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.setInputPath,
|
||||
SettingsPaintScaleInputPathSet,
|
||||
);
|
||||
|
||||
// Paint Scale Output Settings Handlers
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.getOutputConfigs,
|
||||
SettingsPaintScaleOutputConfigsGet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.setOutputConfigs,
|
||||
SettingsPaintScaleOutputConfigsSet,
|
||||
);
|
||||
ipcMain.handle(
|
||||
ipcTypes.toMain.settings.paintScale.setOutputPath,
|
||||
SettingsPaintScaleOutputPathSet,
|
||||
);
|
||||
|
||||
// IPC handlers for updating paint scale cron
|
||||
ipcMain.on(
|
||||
ipcTypes.toMain.settings.paintScale.updateInputCron,
|
||||
(_event, configs: PaintScaleConfig[]) => {
|
||||
handlePaintScaleInputCron(configs).catch((error) => {
|
||||
log.error(`Error handling paint scale input cron for configs: ${error}`);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.on(
|
||||
ipcTypes.toMain.settings.paintScale.updateOutputCron,
|
||||
(_event, configs: PaintScaleConfig[]) => {
|
||||
handlePaintScaleOutputCron(configs).catch((error) => {
|
||||
log.error(`Error handling paint scale output cron for configs: ${error}`);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
ipcMain.handle(ipcTypes.toMain.user.getActiveShop, () => {
|
||||
return store.get("app.bodyshop.shopname");
|
||||
});
|
||||
|
||||
//Watcher Handlers
|
||||
// Watcher Handlers
|
||||
ipcMain.on(ipcTypes.toMain.watcher.start, () => {
|
||||
StartWatcher();
|
||||
StartWatcher().catch((error) => {
|
||||
log.error("Error starting watcher:", error);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on(ipcTypes.toMain.watcher.stop, () => {
|
||||
StopWatcher();
|
||||
StopWatcher().catch((error) => {
|
||||
log.error("Error stopping watcher:", error);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on(ipcTypes.toMain.updates.download, () => {
|
||||
log.info("Download update requested from renderer.");
|
||||
autoUpdater.downloadUpdate();
|
||||
autoUpdater.downloadUpdate().catch((error) => {
|
||||
log.error("Error downloading update:", error);
|
||||
});
|
||||
});
|
||||
|
||||
export { initializeCronTasks };
|
||||
|
||||
logIpcMessages();
|
||||
|
||||
67
src/main/ipc/ipcMainConfig.types.ts
Normal file
67
src/main/ipc/ipcMainConfig.types.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
export interface User {
|
||||
stsTokenManager?: {
|
||||
accessToken: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BodyShop {
|
||||
shopname: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface GraphQLResponse {
|
||||
bodyshops_by_pk?: {
|
||||
imexshopid: string;
|
||||
shopname: string;
|
||||
};
|
||||
jobs?: Array<{
|
||||
labhrs: any;
|
||||
larhrs: any;
|
||||
ro_number: string;
|
||||
ownr_ln: string;
|
||||
ownr_fn: string;
|
||||
plate_no: string;
|
||||
v_vin: string;
|
||||
v_model_yr: string;
|
||||
v_make_desc: string;
|
||||
v_model_desc: string;
|
||||
vehicle?: {
|
||||
v_paint_codes?: {
|
||||
paint_cd1: string;
|
||||
};
|
||||
};
|
||||
larhrs_aggregate?: {
|
||||
aggregate?: {
|
||||
sum?: {
|
||||
mod_lb_hrs: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
ins_co_nm: string;
|
||||
est_ct_ln: string;
|
||||
est_ct_fn: string;
|
||||
job_totals?: {
|
||||
rates?: {
|
||||
mapa?: {
|
||||
total?: {
|
||||
amount: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
totals?: {
|
||||
subtotal?: {
|
||||
amount: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
rate_mapa: number;
|
||||
labhrs_aggregate?: {
|
||||
aggregate?: {
|
||||
sum?: {
|
||||
mod_lb_hrs: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
rate_lab: number;
|
||||
}>;
|
||||
}
|
||||
@@ -1,9 +1,27 @@
|
||||
import {dialog, IpcMainInvokeEvent} from "electron";
|
||||
// main/ipcMainHandler.settings.ts
|
||||
import { dialog, IpcMainInvokeEvent } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import Store from "../store/store";
|
||||
import {getMainWindow} from "../util/toRenderer";
|
||||
import {addWatcherPath, removeWatcherPath, StartWatcher, StopWatcher,} from "../watcher/watcher";
|
||||
import { getMainWindow } from "../util/toRenderer";
|
||||
import {
|
||||
addWatcherPath,
|
||||
removeWatcherPath,
|
||||
StartWatcher,
|
||||
StopWatcher,
|
||||
} from "../watcher/watcher";
|
||||
import { PaintScaleConfig } from "../../util/types/paintScale";
|
||||
|
||||
|
||||
// Initialize paint scale input configs in store if not set
|
||||
if (!Store.get("settings.paintScaleInputConfigs")) {
|
||||
Store.set("settings.paintScaleInputConfigs", []);
|
||||
}
|
||||
|
||||
// Initialize paint scale output configs in store if not set
|
||||
if (!Store.get("settings.paintScaleOutputConfigs")) {
|
||||
Store.set("settings.paintScaleOutputConfigs", []);
|
||||
}
|
||||
|
||||
const SettingsWatchedFilePathsAdd = async (): Promise<string[]> => {
|
||||
const mainWindow = getMainWindow();
|
||||
@@ -17,21 +35,22 @@ const SettingsWatchedFilePathsAdd = async (): Promise<string[]> => {
|
||||
|
||||
if (!result.canceled) {
|
||||
Store.set(
|
||||
"settings.filepaths",
|
||||
_.union(result.filePaths, Store.get("settings.filepaths")),
|
||||
"settings.filepaths",
|
||||
_.union(result.filePaths, Store.get("settings.filepaths")),
|
||||
);
|
||||
addWatcherPath(result.filePaths);
|
||||
}
|
||||
|
||||
return Store.get("settings.filepaths");
|
||||
};
|
||||
|
||||
const SettingsWatchedFilePathsRemove = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
path: string,
|
||||
_event: IpcMainInvokeEvent,
|
||||
path: string,
|
||||
): Promise<string[]> => {
|
||||
Store.set(
|
||||
"settings.filepaths",
|
||||
_.without(Store.get("settings.filepaths"), path),
|
||||
"settings.filepaths",
|
||||
_.without(Store.get("settings.filepaths"), path),
|
||||
);
|
||||
removeWatcherPath(path);
|
||||
return Store.get("settings.filepaths");
|
||||
@@ -46,15 +65,16 @@ const SettingsWatcherPollingGet = async (): Promise<{
|
||||
interval: number;
|
||||
}> => {
|
||||
const pollingEnabled: { enabled: boolean; interval: number } =
|
||||
Store.get("settings.polling");
|
||||
Store.get("settings.polling");
|
||||
return { enabled: pollingEnabled.enabled, interval: pollingEnabled.interval };
|
||||
};
|
||||
|
||||
const SettingsWatcherPollingSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
pollingSettings: {
|
||||
enabled: boolean;
|
||||
interval: number;
|
||||
},
|
||||
_event: IpcMainInvokeEvent,
|
||||
pollingSettings: {
|
||||
enabled: boolean;
|
||||
interval: number;
|
||||
},
|
||||
): Promise<{
|
||||
enabled: boolean;
|
||||
interval: number;
|
||||
@@ -63,15 +83,16 @@ const SettingsWatcherPollingSet = async (
|
||||
const { enabled, interval } = pollingSettings;
|
||||
Store.set("settings.polling", { enabled, interval });
|
||||
|
||||
//Restart the watcher with these new settings.
|
||||
await StopWatcher();
|
||||
await StartWatcher();
|
||||
|
||||
return { enabled, interval };
|
||||
};
|
||||
|
||||
const SettingsPpcFilePathGet = async (): Promise<string> => {
|
||||
return Store.get("settings.ppcFilePath");
|
||||
};
|
||||
|
||||
const SettingsPpcFilePathSet = async (): Promise<string> => {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
@@ -83,14 +104,16 @@ const SettingsPpcFilePathSet = async (): Promise<string> => {
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
Store.set("settings.ppcFilePath", result.filePaths[0]); //There should only ever be on directory that was selected.
|
||||
Store.set("settings.ppcFilePath", result.filePaths[0]);
|
||||
}
|
||||
|
||||
return (Store.get("settings.ppcFilePath") as string) || "";
|
||||
};
|
||||
|
||||
const SettingEmsOutFilePathGet = async (): Promise<string> => {
|
||||
return Store.get("settings.emsOutFilePath");
|
||||
};
|
||||
|
||||
const SettingEmsOutFilePathSet = async (): Promise<string> => {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
@@ -102,12 +125,116 @@ const SettingEmsOutFilePathSet = async (): Promise<string> => {
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
Store.set("settings.emsOutFilePath", result.filePaths[0]); //There should only ever be on directory that was selected.
|
||||
Store.set("settings.emsOutFilePath", result.filePaths[0]);
|
||||
}
|
||||
|
||||
return (Store.get("settings.emsOutFilePath") as string) || "";
|
||||
};
|
||||
|
||||
const SettingsPaintScaleInputConfigsGet = async (
|
||||
_event?: IpcMainInvokeEvent,
|
||||
): Promise<PaintScaleConfig[]> => {
|
||||
try {
|
||||
const configs = Store.get("settings.paintScaleInputConfigs") as PaintScaleConfig[];
|
||||
log.debug("Retrieved paint scale input configs:", configs);
|
||||
return configs || [];
|
||||
} catch (error) {
|
||||
log.error("Error getting paint scale input configs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleInputConfigsSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
configs: PaintScaleConfig[],
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
Store.set("settings.paintScaleInputConfigs", configs);
|
||||
log.debug("Saved paint scale input configs:", configs);
|
||||
return true;
|
||||
} catch (error) {
|
||||
log.error("Error setting paint scale input configs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleInputPathSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
): Promise<string | null> => {
|
||||
try {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
log.error("No main window found when trying to open dialog");
|
||||
return null;
|
||||
}
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
if (result.canceled) {
|
||||
log.debug("Paint scale input path selection canceled");
|
||||
return null;
|
||||
}
|
||||
const path = result.filePaths[0];
|
||||
log.debug("Selected paint scale input path:", path);
|
||||
return path;
|
||||
} catch (error) {
|
||||
log.error("Error setting paint scale input path:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleOutputConfigsGet = async (
|
||||
_event?: IpcMainInvokeEvent,
|
||||
): Promise<PaintScaleConfig[]> => {
|
||||
try {
|
||||
const configs = Store.get("settings.paintScaleOutputConfigs") as PaintScaleConfig[];
|
||||
log.debug("Retrieved paint scale output configs:", configs);
|
||||
return configs || [];
|
||||
} catch (error) {
|
||||
log.error("Error getting paint scale output configs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleOutputConfigsSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
configs: PaintScaleConfig[],
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
Store.set("settings.paintScaleOutputConfigs", configs);
|
||||
log.debug("Saved paint scale output configs:", configs);
|
||||
return true;
|
||||
} catch (error) {
|
||||
log.error("Error setting paint scale output configs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const SettingsPaintScaleOutputPathSet = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
): Promise<string | null> => {
|
||||
try {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
log.error("No main window found when trying to open dialog");
|
||||
return null;
|
||||
}
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
if (result.canceled) {
|
||||
log.debug("Paint scale output path selection canceled");
|
||||
return null;
|
||||
}
|
||||
const path = result.filePaths[0];
|
||||
log.debug("Selected paint scale output path:", path);
|
||||
return path;
|
||||
} catch (error) {
|
||||
log.error("Error setting paint scale output path:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
SettingsPpcFilePathGet,
|
||||
SettingsPpcFilePathSet,
|
||||
@@ -118,4 +245,10 @@ export {
|
||||
SettingsWatcherPollingSet,
|
||||
SettingEmsOutFilePathGet,
|
||||
SettingEmsOutFilePathSet,
|
||||
};
|
||||
SettingsPaintScaleInputConfigsGet,
|
||||
SettingsPaintScaleInputConfigsSet,
|
||||
SettingsPaintScaleInputPathSet,
|
||||
SettingsPaintScaleOutputConfigsGet,
|
||||
SettingsPaintScaleOutputConfigsSet,
|
||||
SettingsPaintScaleOutputPathSet,
|
||||
};
|
||||
261
src/main/ipc/paintScaleHandlers/PPG.ts
Normal file
261
src/main/ipc/paintScaleHandlers/PPG.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import log from "electron-log/main";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import axios from "axios";
|
||||
import { create } from "xmlbuilder2";
|
||||
import { parseStringPromise } from "xml2js";
|
||||
import store from "../../store/store";
|
||||
import client from "../../graphql/graphql-client";
|
||||
import { PaintScaleConfig } from "../../../util/types/paintScale";
|
||||
|
||||
// PPG Input Handler
|
||||
export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
|
||||
try {
|
||||
log.info(
|
||||
`Polling input directory for PPG config ${config.id}: ${config.path}`,
|
||||
);
|
||||
|
||||
// Ensure archive and error directories exist
|
||||
const archiveDir = path.join(config.path!, "archive");
|
||||
const errorDir = path.join(config.path!, "error");
|
||||
await fs.mkdir(archiveDir, { recursive: true });
|
||||
await fs.mkdir(errorDir, { recursive: true });
|
||||
|
||||
// Check for files
|
||||
const files = await fs.readdir(config.path!);
|
||||
for (const file of files) {
|
||||
// Only process XML files
|
||||
if (!file.toLowerCase().endsWith(".xml")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = path.join(config.path!, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
if (!stats.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
log.debug(`Processing input file: ${filePath}`);
|
||||
|
||||
// Check file accessibility (e.g., not locked)
|
||||
try {
|
||||
await fs.access(filePath, fs.constants.R_OK);
|
||||
} catch (error) {
|
||||
log.warn(`File ${filePath} is inaccessible, skipping:`, error);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate XML structure
|
||||
let xmlContent : BlobPart;
|
||||
|
||||
try {
|
||||
xmlContent = await fs.readFile(filePath, "utf8");
|
||||
await parseStringPromise(xmlContent);
|
||||
} catch (error) {
|
||||
log.error(`Invalid XML in ${filePath}:`, error);
|
||||
const timestamp = Date.now().toString(); // similar to DateTime.Now.Ticks in C#
|
||||
const errorPath = path.join(errorDir, `${timestamp}.xml`);
|
||||
await fs.rename(filePath, errorPath);
|
||||
log.debug(`Moved invalid file to error: ${errorPath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get authentication token
|
||||
const token = (store.get("user") as any)?.stsTokenManager?.accessToken;
|
||||
if (!token) {
|
||||
log.error(`No authentication token for file: ${filePath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Upload file to API
|
||||
const formData = new FormData();
|
||||
formData.append("file", new Blob([xmlContent]), path.basename(filePath));
|
||||
formData.append(
|
||||
"shopId",
|
||||
(store.get("app.bodyshop") as any)?.shopname || "",
|
||||
);
|
||||
|
||||
const baseURL = store.get("app.isTest")
|
||||
? import.meta.env.VITE_API_TEST_URL
|
||||
: import.meta.env.VITE_API_URL;
|
||||
const finalUrl = `${baseURL}/mixdata/upload`;
|
||||
|
||||
log.debug(`Uploading file to ${finalUrl}`);
|
||||
|
||||
try {
|
||||
const response = await axios.post(finalUrl, formData, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
log.info(`Successful upload of ${filePath}`);
|
||||
// Move file to archive
|
||||
const timestamp = Date.now().toString(); // generate new timestamp
|
||||
const archivePath = path.join(archiveDir, `${timestamp}.xml`);
|
||||
await fs.rename(filePath, archivePath);
|
||||
log.debug(`Moved file to archive: ${archivePath}`);
|
||||
} else {
|
||||
log.error(
|
||||
`Failed to upload ${filePath}: ${response.status} ${response.statusText}`,
|
||||
response.data,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Error uploading ${filePath}:`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Error polling input directory ${config.path}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// PPG Output Handler
|
||||
export async function ppgOutputHandler(
|
||||
config: PaintScaleConfig,
|
||||
): Promise<void> {
|
||||
try {
|
||||
log.info(`Generating PPG output for config ${config.id}: ${config.path}`);
|
||||
|
||||
await fs.mkdir(config.path!, { recursive: true });
|
||||
|
||||
const query = `
|
||||
query PpgData($today: timestamptz!, $todayplus5: timestamptz!, $shopid: uuid!) {
|
||||
bodyshops_by_pk(id:$shopid) {
|
||||
id
|
||||
shopname
|
||||
imexshopid
|
||||
}
|
||||
jobs(where: {
|
||||
_or: [
|
||||
{
|
||||
_and: [
|
||||
{ scheduled_in: { _lte: $todayplus5 } },
|
||||
{ scheduled_in: { _gte: $today } }
|
||||
]
|
||||
},
|
||||
{ inproduction: { _eq: true } }
|
||||
]
|
||||
}) {
|
||||
id
|
||||
ro_number
|
||||
status
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
v_vin
|
||||
v_model_yr
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_color
|
||||
plate_no
|
||||
ins_co_nm
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
rate_mapa
|
||||
rate_lab
|
||||
job_totals
|
||||
vehicle {
|
||||
v_paint_codes
|
||||
}
|
||||
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables = {
|
||||
today: new Date().toISOString(),
|
||||
todayplus5: new Date(Date.now() + 5 * 86400000).toISOString(),
|
||||
shopid: (store.get("app.bodyshop") as any)?.id,
|
||||
};
|
||||
|
||||
const response = (await client.request(query, variables)) as any;
|
||||
const jobs = response.jobs ?? [];
|
||||
|
||||
const header = {
|
||||
PPG: {
|
||||
Header: {
|
||||
Protocol: {
|
||||
Message: "PaintShopInterface",
|
||||
Name: "PPG",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
Transaction: {
|
||||
TransactionID: "",
|
||||
TransactionDate: (() => {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = ("0" + (now.getMonth() + 1)).slice(-2);
|
||||
const day = ("0" + now.getDate()).slice(-2);
|
||||
const hours = ("0" + now.getHours()).slice(-2);
|
||||
const minutes = ("0" + now.getMinutes()).slice(-2);
|
||||
return `${year}-${month}-${day}:${hours}:${minutes}`;
|
||||
})(),
|
||||
},
|
||||
Product: {
|
||||
Name: "ImEX Online",
|
||||
Version: "",
|
||||
},
|
||||
},
|
||||
DataInterface: {
|
||||
ROData: {
|
||||
ShopInfo: {
|
||||
ShopID: response.bodyshops_by_pk?.imexshopid || "",
|
||||
ShopName: response.bodyshops_by_pk?.shopname || "",
|
||||
},
|
||||
RepairOrders: {
|
||||
ROCount: jobs.length.toString(),
|
||||
RO: jobs.map((job: any) => ({
|
||||
RONumber: job.ro_number || "",
|
||||
ROStatus: "Open",
|
||||
Customer: `${job.ownr_ln || ""}, ${job.ownr_fn || ""}`,
|
||||
ROPainterNotes: "",
|
||||
LicensePlateNum: job.plate_no || "",
|
||||
VIN: job.v_vin || "",
|
||||
ModelYear: job.v_model_yr || "",
|
||||
MakeDesc: job.v_make_desc || "",
|
||||
ModelName: job.v_model_desc || "",
|
||||
OEMColorCode: job.vehicle?.v_paint_codes?.paint_cd1 || "",
|
||||
RefinishLaborHours: job.larhrs?.aggregate?.sum?.mod_lb_hrs || 0,
|
||||
InsuranceCompanyName: job.ins_co_nm || "",
|
||||
EstimatorName: `${job.est_ct_ln || ""}, ${job.est_ct_fn || ""}`,
|
||||
PaintMaterialsRevenue: (
|
||||
(job.job_totals?.rates?.mapa?.total?.amount || 0) / 100
|
||||
).toFixed(2),
|
||||
PaintMaterialsRate: job.rate_mapa || 0,
|
||||
BodyHours: job.labhrs?.aggregate?.sum?.mod_lb_hrs || 0,
|
||||
BodyLaborRate: job.rate_lab || 0,
|
||||
TotalCostOfRepairs: (
|
||||
(job.job_totals?.totals?.subtotal?.amount || 0) / 100
|
||||
).toFixed(2),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const xml = create({ version: "1.0" }, header).end({ prettyPrint: true });
|
||||
const outputPath = path.join(config.path!, `PPGPaint.xml`);
|
||||
await fs.writeFile(outputPath, xml);
|
||||
log.info(`Saved PPG output XML to ${outputPath}`);
|
||||
} catch (error) {
|
||||
log.error(`Error generating PPG output for config ${config.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
71
src/main/setup-keep-alive-agent.ts
Normal file
71
src/main/setup-keep-alive-agent.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { promises as fs } from "fs";
|
||||
import { join } from "path";
|
||||
import { homedir } from "os";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import log from "electron-log/main";
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
// Define the interval as a variable (in seconds)
|
||||
const KEEP_ALIVE_INTERVAL_SECONDS = 15 * 60; // 15 minutes
|
||||
|
||||
export async function setupKeepAliveAgent(): Promise<void> {
|
||||
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.convenientbrands.bodyshop-desktop.keepalive</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>open</string>
|
||||
<string>imexmedia://keep-alive</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>StartInterval</key>
|
||||
<integer>${KEEP_ALIVE_INTERVAL_SECONDS}</integer>
|
||||
</dict>
|
||||
</plist>`;
|
||||
|
||||
const plistPath = join(
|
||||
homedir(),
|
||||
"Library/LaunchAgents/com.convenientbrands.bodyshop-desktop.keepalive.plist",
|
||||
);
|
||||
|
||||
try {
|
||||
await fs.writeFile(plistPath, plistContent);
|
||||
const { stdout, stderr } = await execPromise(`launchctl load ${plistPath}`);
|
||||
log.info(`Launch agent created and loaded: ${stdout}`);
|
||||
if (stderr) log.warn(`Launch agent stderr: ${stderr}`);
|
||||
} catch (error) {
|
||||
log.error(`Error setting up launch agent: ${error instanceof Error ? error.message : String(error)}`);
|
||||
throw error; // Rethrow to allow caller to handle
|
||||
}
|
||||
}
|
||||
|
||||
export async function isKeepAliveAgentInstalled(): Promise<boolean> {
|
||||
const plistPath = join(
|
||||
homedir(),
|
||||
"Library/LaunchAgents/com.convenientbrands.bodyshop-desktop.keepalive.plist",
|
||||
);
|
||||
const maxRetries = 3;
|
||||
const retryDelay = 500; // 500ms delay between retries
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
await fs.access(plistPath, fs.constants.F_OK);
|
||||
const { stdout } = await execPromise(`launchctl list | grep com.convenientbrands.bodyshop-desktop.keepalive`);
|
||||
return !!stdout; // Return true if plist exists and agent is loaded
|
||||
} catch (error) {
|
||||
log.debug(`Launch agent not found (attempt ${attempt}/${maxRetries}): ${error instanceof Error ? error.message : String(error)}`);
|
||||
if (attempt === maxRetries) {
|
||||
return false; // Return false after all retries fail
|
||||
}
|
||||
// Wait before retrying
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
return false; // Fallback return
|
||||
}
|
||||
53
src/main/setup-keep-alive-task.ts
Normal file
53
src/main/setup-keep-alive-task.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import log from "electron-log/main";
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
|
||||
// Define the interval as a variable (in minutes)
|
||||
const KEEP_ALIVE_INTERVAL_MINUTES = 15;
|
||||
|
||||
export async function setupKeepAliveTask(): Promise<void> {
|
||||
const taskName = "ImEXShopPartnerKeepAlive";
|
||||
const protocolUrl = "imexmedia://keep-alive";
|
||||
// Use rundll32.exe to silently open the URL as a protocol
|
||||
const command = `rundll32.exe url.dll,OpenURL "${protocolUrl}"`;
|
||||
// Escape quotes for schtasks /tr parameter
|
||||
const escapedCommand = command.replace(/"/g, '\\"');
|
||||
|
||||
const schtasksCommand = `schtasks /create /tn "${taskName}" /tr "${escapedCommand}" /sc minute /mo ${KEEP_ALIVE_INTERVAL_MINUTES} /f`;
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execPromise(schtasksCommand);
|
||||
log.info(`Scheduled task created: ${stdout}`);
|
||||
if (stderr) log.warn(`Scheduled task stderr: ${stderr}`);
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Error creating scheduled task: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
throw error; // Rethrow to allow caller to handle
|
||||
}
|
||||
}
|
||||
|
||||
export async function isKeepAliveTaskInstalled(): Promise<boolean> {
|
||||
const taskName = "ImEXShopPartnerKeepAlive";
|
||||
const maxRetries = 3;
|
||||
const retryDelay = 500; // 500ms delay between retries
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const { stdout } = await execPromise(`schtasks /query /tn "${taskName}"`);
|
||||
return !!stdout; // Return true if task exists
|
||||
} catch (error) {
|
||||
log.debug(
|
||||
`Scheduled task ${taskName} not found (attempt ${attempt}/${maxRetries}): ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
if (attempt === maxRetries) {
|
||||
return false; // Return false after all retries fail
|
||||
}
|
||||
// Wait before retrying
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
return false; // Fallback return
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import ipcTypes from '../../../../../util/ipcTypes.json';
|
||||
import { PaintScaleConfig, PaintScaleType } from '../../../../../util/types/paintScale';
|
||||
import { message } from "antd";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
type ConfigType = 'input' | 'output';
|
||||
|
||||
export const usePaintScaleConfig = (configType: ConfigType) => {
|
||||
const [paintScaleConfigs, setPaintScaleConfigs] = useState<PaintScaleConfig[]>([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Get the appropriate IPC methods based on config type
|
||||
const getConfigsMethod = configType === 'input'
|
||||
? ipcTypes.toMain.settings.paintScale.getInputConfigs
|
||||
: ipcTypes.toMain.settings.paintScale.getOutputConfigs;
|
||||
|
||||
const setConfigsMethod = configType === 'input'
|
||||
? ipcTypes.toMain.settings.paintScale.setInputConfigs
|
||||
: ipcTypes.toMain.settings.paintScale.setOutputConfigs;
|
||||
|
||||
const setPathMethod = configType === 'input'
|
||||
? ipcTypes.toMain.settings.paintScale.setInputPath
|
||||
: ipcTypes.toMain.settings.paintScale.setOutputPath;
|
||||
|
||||
// Load paint scale configs on mount
|
||||
useEffect(() => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(getConfigsMethod)
|
||||
.then((configs: PaintScaleConfig[]) => {
|
||||
// Ensure all configs have a pollingInterval and type (for backward compatibility)
|
||||
const updatedConfigs = configs.map(config => ({
|
||||
...config,
|
||||
pollingInterval: config.pollingInterval || 1440, // Default to 1440 seconds
|
||||
type: config.type || PaintScaleType.PPG, // Default type if missing
|
||||
}));
|
||||
setPaintScaleConfigs(updatedConfigs || []);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Failed to load paint scale ${configType} configs:`, error);
|
||||
});
|
||||
}, [getConfigsMethod]);
|
||||
|
||||
// Save configs to store and notify main process of config changes
|
||||
const saveConfigs = (configs: PaintScaleConfig[]) => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(setConfigsMethod, configs)
|
||||
.then(() => {
|
||||
// Notify main process to update cron job
|
||||
if (configType === 'input') {
|
||||
window.electron.ipcRenderer.send(ipcTypes.toMain.settings.paintScale.updateInputCron, configs);
|
||||
} else if (configType === 'output') {
|
||||
window.electron.ipcRenderer.send(ipcTypes.toMain.settings.paintScale.updateOutputCron, configs);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Failed to save paint scale ${configType} configs:`, error);
|
||||
});
|
||||
};
|
||||
|
||||
// New helper to check if a path is unique across input and output configs
|
||||
const checkPathUnique = async (newPath: string): Promise<boolean> => {
|
||||
try {
|
||||
const inputConfigs: PaintScaleConfig[] = await window.electron.ipcRenderer.invoke(ipcTypes.toMain.settings.paintScale.getInputConfigs);
|
||||
const outputConfigs: PaintScaleConfig[] = await window.electron.ipcRenderer.invoke(ipcTypes.toMain.settings.paintScale.getOutputConfigs);
|
||||
const allConfigs = [...inputConfigs, ...outputConfigs];
|
||||
// Allow updating the current config even if its current value equals newPath.
|
||||
return !allConfigs.some(config => config.path === newPath);
|
||||
} catch (error) {
|
||||
console.error("Failed to check unique path:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Handle adding a new paint scale config
|
||||
const handleAddConfig = (type: PaintScaleType) => {
|
||||
const newConfig: PaintScaleConfig = {
|
||||
id: Date.now().toString(),
|
||||
type,
|
||||
pollingInterval: 1440, // Default to 1440 seconds
|
||||
};
|
||||
const updatedConfigs = [...paintScaleConfigs, newConfig];
|
||||
setPaintScaleConfigs(updatedConfigs);
|
||||
saveConfigs(updatedConfigs);
|
||||
};
|
||||
|
||||
// Handle removing a config
|
||||
const handleRemoveConfig = (id: string) => {
|
||||
const updatedConfigs = paintScaleConfigs.filter((config) => config.id !== id);
|
||||
setPaintScaleConfigs(updatedConfigs);
|
||||
saveConfigs(updatedConfigs);
|
||||
};
|
||||
|
||||
// Handle path selection (modified to check directory uniqueness)
|
||||
const handlePathChange = async (id: string) => {
|
||||
try {
|
||||
const path: string | null = await window.electron.ipcRenderer.invoke(setPathMethod, id);
|
||||
if (path) {
|
||||
const isUnique = await checkPathUnique(path);
|
||||
if (!isUnique) {
|
||||
message.error(t("settings.errors.duplicatePath"));
|
||||
return;
|
||||
}
|
||||
const updatedConfigs = paintScaleConfigs.map((config) =>
|
||||
config.id === id ? { ...config, path } : config,
|
||||
);
|
||||
setPaintScaleConfigs(updatedConfigs);
|
||||
saveConfigs(updatedConfigs);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to set paint scale ${configType} path:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle polling interval change
|
||||
const handlePollingIntervalChange = (id: string, pollingInterval: number) => {
|
||||
const updatedConfigs = paintScaleConfigs.map((config) =>
|
||||
config.id === id ? { ...config, pollingInterval } : config,
|
||||
);
|
||||
setPaintScaleConfigs(updatedConfigs);
|
||||
saveConfigs(updatedConfigs);
|
||||
};
|
||||
|
||||
return {
|
||||
paintScaleConfigs,
|
||||
handleAddConfig,
|
||||
handleRemoveConfig,
|
||||
handlePathChange,
|
||||
handlePollingIntervalChange,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,180 @@
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
FileAddFilled,
|
||||
FolderOpenFilled,
|
||||
WarningFilled,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import { FC, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
PaintScaleConfig,
|
||||
PaintScaleType,
|
||||
paintScaleTypeOptions,
|
||||
} from "../../../../util/types/paintScale";
|
||||
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
||||
|
||||
const SettingsPaintScaleInputPaths: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
paintScaleConfigs,
|
||||
handleAddConfig,
|
||||
handleRemoveConfig,
|
||||
handlePathChange,
|
||||
handlePollingIntervalChange,
|
||||
} = usePaintScaleConfig("input");
|
||||
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<PaintScaleType | null>(null);
|
||||
|
||||
// Show modal when adding a new path
|
||||
const showAddPathModal = () => {
|
||||
setSelectedType(null);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
// Handle modal confirmation
|
||||
const handleModalOk = () => {
|
||||
if (selectedType) {
|
||||
handleAddConfig(selectedType);
|
||||
setIsModalVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle modal cancellation
|
||||
const handleModalCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
// Table columns for paint scale configs
|
||||
const columns = [
|
||||
{
|
||||
title: t("settings.labels.paintScaleType"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
render: (type: PaintScaleType) => {
|
||||
const typeOption = paintScaleTypeOptions.find(
|
||||
(option) => option.value === type,
|
||||
);
|
||||
const label = typeOption ? typeOption.label : type;
|
||||
const colorMap: Partial<Record<PaintScaleType, string>> = {
|
||||
[PaintScaleType.PPG]: "blue",
|
||||
// Add other types and colors as needed
|
||||
};
|
||||
return <Tag color={colorMap[type] || "default"}>{label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.paintScalePath"),
|
||||
dataIndex: "path",
|
||||
key: "path",
|
||||
render: (path: string | null, record: PaintScaleConfig) => {
|
||||
const isValid = path && path.trim() !== "";
|
||||
return (
|
||||
<Space>
|
||||
<Input
|
||||
value={path || ""}
|
||||
placeholder={t("settings.labels.paintScalePath")}
|
||||
disabled
|
||||
style={{
|
||||
borderColor: isValid ? "#52c41a" : "#d9d9d9",
|
||||
}}
|
||||
suffix={
|
||||
<Tooltip
|
||||
title={
|
||||
isValid
|
||||
? t("settings.labels.validPath")
|
||||
: t("settings.labels.invalidPath")
|
||||
}
|
||||
>
|
||||
{isValid ? (
|
||||
<CheckCircleFilled style={{ color: "#52c41a" }} />
|
||||
) : (
|
||||
<WarningFilled style={{ color: "#faad14" }} />
|
||||
)}
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handlePathChange(record.id)}
|
||||
icon={<FolderOpenFilled />}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.pollingInterval"),
|
||||
dataIndex: "pollingInterval",
|
||||
key: "pollingInterval",
|
||||
render: (pollingInterval: number, record: PaintScaleConfig) => (
|
||||
<Input
|
||||
type="number"
|
||||
value={pollingInterval}
|
||||
onChange={(e) =>
|
||||
handlePollingIntervalChange(record.id, Number(e.target.value))
|
||||
}
|
||||
style={{ width: 100 }}
|
||||
placeholder={t("settings.labels.pollingInterval")}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.actions"),
|
||||
key: "actions",
|
||||
render: (_: any, record: PaintScaleConfig) => (
|
||||
<Button danger onClick={() => handleRemoveConfig(record.id)}>
|
||||
{t("settings.labels.remove")}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
title={t("settings.labels.paintScaleSettingsInput")}
|
||||
extra={
|
||||
<Button onClick={showAddPathModal} icon={<FileAddFilled />}>
|
||||
{t("settings.actions.addpath")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
dataSource={paintScaleConfigs}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title={t("settings.labels.selectPaintScaleType")}
|
||||
open={isModalVisible}
|
||||
onOk={handleModalOk}
|
||||
onCancel={handleModalCancel}
|
||||
okButtonProps={{ disabled: !selectedType }}
|
||||
>
|
||||
<Select
|
||||
value={selectedType}
|
||||
options={paintScaleTypeOptions}
|
||||
onChange={(value) => setSelectedType(value)}
|
||||
style={{ width: "100%" }}
|
||||
placeholder={t("settings.labels.selectPaintScaleType")}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsPaintScaleInputPaths;
|
||||
@@ -0,0 +1,162 @@
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
FileAddFilled,
|
||||
FolderOpenFilled,
|
||||
WarningFilled,
|
||||
} from "@ant-design/icons";
|
||||
import { Button, Card, Input, Modal, Select, Space, Table, Tag } from "antd";
|
||||
import { FC, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
PaintScaleConfig,
|
||||
PaintScaleType,
|
||||
paintScaleTypeOptions,
|
||||
} from "../../../../util/types/paintScale";
|
||||
import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig";
|
||||
|
||||
const SettingsPaintScaleOutputPaths: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
paintScaleConfigs,
|
||||
handleAddConfig,
|
||||
handleRemoveConfig,
|
||||
handlePathChange,
|
||||
handlePollingIntervalChange,
|
||||
} = usePaintScaleConfig("output");
|
||||
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<PaintScaleType | null>(null);
|
||||
|
||||
// Show modal when adding a new path
|
||||
const showAddPathModal = () => {
|
||||
setSelectedType(null);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
// Handle modal confirmation
|
||||
const handleModalOk = () => {
|
||||
if (selectedType) {
|
||||
handleAddConfig(selectedType);
|
||||
setIsModalVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle modal cancellation
|
||||
const handleModalCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
// Table columns for paint scale configs
|
||||
const columns = [
|
||||
{
|
||||
title: t("settings.labels.paintScaleType"),
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
render: (type: PaintScaleType) => {
|
||||
const typeOption = paintScaleTypeOptions.find(
|
||||
(option) => option.value === type,
|
||||
);
|
||||
const label = typeOption ? typeOption.label : type;
|
||||
const colorMap: Partial<Record<PaintScaleType, string>> = {
|
||||
[PaintScaleType.PPG]: "blue",
|
||||
// Add other types and colors as needed
|
||||
};
|
||||
return <Tag color={colorMap[type] || "default"}>{label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.paintScalePath"),
|
||||
dataIndex: "path",
|
||||
key: "path",
|
||||
render: (path: string | null, record: PaintScaleConfig) => {
|
||||
const isValid = path && path.trim() !== "";
|
||||
return (
|
||||
<Space>
|
||||
<Input
|
||||
value={path || ""}
|
||||
placeholder={t("settings.labels.paintScalePath")}
|
||||
disabled
|
||||
style={{
|
||||
borderColor: isValid ? "#52c41a" : "#d9d9d9",
|
||||
}}
|
||||
suffix={
|
||||
isValid ? (
|
||||
<CheckCircleFilled style={{ color: "#52c41a" }} />
|
||||
) : (
|
||||
<WarningFilled style={{ color: "#faad14" }} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handlePathChange(record.id)}
|
||||
icon={<FolderOpenFilled />}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.pollingInterval"),
|
||||
dataIndex: "pollingInterval",
|
||||
key: "pollingInterval",
|
||||
render: (pollingInterval: number, record: PaintScaleConfig) => (
|
||||
<Input
|
||||
type="number"
|
||||
value={pollingInterval}
|
||||
onChange={(e) =>
|
||||
handlePollingIntervalChange(record.id, Number(e.target.value))
|
||||
}
|
||||
style={{ width: 100 }}
|
||||
placeholder={t("settings.labels.pollingInterval")}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("settings.labels.actions"),
|
||||
key: "actions",
|
||||
render: (_: any, record: PaintScaleConfig) => (
|
||||
<Button danger onClick={() => handleRemoveConfig(record.id)}>
|
||||
{t("settings.labels.remove")}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
title={t("settings.labels.paintScaleSettingsOutput")}
|
||||
extra={
|
||||
<Button onClick={showAddPathModal} icon={<FileAddFilled />}>
|
||||
{t("settings.actions.addpath")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
dataSource={paintScaleConfigs}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
title={t("settings.labels.selectPaintScaleType")}
|
||||
open={isModalVisible}
|
||||
onOk={handleModalOk}
|
||||
onCancel={handleModalCancel}
|
||||
okButtonProps={{ disabled: !selectedType }}
|
||||
>
|
||||
<Select
|
||||
value={selectedType}
|
||||
options={paintScaleTypeOptions}
|
||||
onChange={(value) => setSelectedType(value)}
|
||||
style={{ width: "100%" }}
|
||||
placeholder={t("settings.labels.selectPaintScaleType")}
|
||||
/>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsPaintScaleOutputPaths;
|
||||
@@ -1,3 +1,4 @@
|
||||
// renderer/Settings.tsx
|
||||
import { Col, Row } from "antd";
|
||||
import { FC } from "react";
|
||||
import SettingsWatchedPaths from "./Settings.WatchedPaths";
|
||||
@@ -5,30 +6,40 @@ import SettingsWatcher from "./Settings.Watcher";
|
||||
import Welcome from "../Welcome/Welcome";
|
||||
import SettingsPpcFilepath from "./Settings.PpcFilePath";
|
||||
import SettingsEmsOutFilePath from "./Settings.EmsOutFilePath";
|
||||
import SettingsPaintScaleInputPaths from "./Settings.PaintScaleInputPaths";
|
||||
import SettingsPaintScaleOutputPaths from "./Settings.PaintScaleOutputPaths";
|
||||
|
||||
const colSpans = {
|
||||
md: 12,
|
||||
sm: 24,
|
||||
md: 12, // Two columns on medium screens and above
|
||||
sm: 24, // One column on small screens
|
||||
};
|
||||
|
||||
const Settings: FC = () => {
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Welcome />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsWatchedPaths />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsWatcher />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsPpcFilepath />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsEmsOutFilePath />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Welcome />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsWatchedPaths />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsWatcher />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsPpcFilepath />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsEmsOutFilePath />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsPaintScaleInputPaths />
|
||||
</Col>
|
||||
<Col {...colSpans}>
|
||||
<SettingsPaintScaleOutputPaths />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
export default Settings;
|
||||
|
||||
export default Settings;
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
ApolloClient,
|
||||
ApolloLink,
|
||||
HttpLink,
|
||||
// HttpLink,
|
||||
InMemoryCache,
|
||||
} from "@apollo/client";
|
||||
|
||||
const httpLink: HttpLink = new HttpLink({
|
||||
uri: import.meta.env.VITE_GRAPHQL_URL,
|
||||
});
|
||||
// const httpLink: HttpLink = new HttpLink({
|
||||
// uri: import.meta.env.VITE_GRAPHQL_URL,
|
||||
// });
|
||||
|
||||
const middlewares = [];
|
||||
|
||||
|
||||
@@ -27,6 +27,16 @@
|
||||
"watcher": {
|
||||
"getpolling": "toMain_settings_watcher_getpolling",
|
||||
"setpolling": "toMain_settings_watcher_setpolling"
|
||||
},
|
||||
"paintScale": {
|
||||
"getInputConfigs": "toMain_settings_paintScale_getInputConfigs",
|
||||
"setInputConfigs": "toMain_settings_paintScale_setInputConfigs",
|
||||
"setInputPath": "toMain_settings_paintScale_setInputPath",
|
||||
"getOutputConfigs": "toMain_settings_paintScale_getOutputConfigs",
|
||||
"setOutputConfigs": "toMain_settings_paintScale_setOutputConfigs",
|
||||
"setOutputPath": "toMain_settings_paintScale_setOutputPath",
|
||||
"updateInputCron": "toMain_settings_paintScale_updateInputCron",
|
||||
"updateOutputCron": "toMain_settings_paintScale_updateOutputCron"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
@@ -58,4 +68,4 @@
|
||||
"showErrorMessage": "toRenderer_general_showErrorMessage"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,9 @@
|
||||
"startwatcher": "Start Watcher",
|
||||
"stopwatcher": "Stop Watcher\n"
|
||||
},
|
||||
"errors": {
|
||||
"duplicatePath": "The selected directory is already used in another configuration."
|
||||
},
|
||||
"labels": {
|
||||
"emsOutFilePath": "EMS Out File Path (Parts Order, etc.)",
|
||||
"pollinginterval": "Polling Interval (ms)",
|
||||
@@ -34,7 +37,18 @@
|
||||
"watchedpaths": "Watched Paths",
|
||||
"watchermodepolling": "Polling",
|
||||
"watchermoderealtime": "Real Time",
|
||||
"watcherstatus": "Watcher Status"
|
||||
"watcherstatus": "Watcher Status",
|
||||
"paintScaleSettingsInput": "BSMS To Paint Scale",
|
||||
"paintScaleSettingsOutput": "Paint Scale To BSMS",
|
||||
"paintScalePath": "Paint Scale Path",
|
||||
"paintScaleType": "Paint Scale Type",
|
||||
"addPaintScalePath": "Add Paint Scale Path",
|
||||
"remove": "Remove",
|
||||
"actions": "Actions",
|
||||
"pollingInterval": "Polling Interval (m)",
|
||||
"validPath": "Valid path",
|
||||
"invalidPath": "Path not set or invalid",
|
||||
"selectPaintScaleType": "Select Paint Scale Type"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
|
||||
17
src/util/types/paintScale.ts
Normal file
17
src/util/types/paintScale.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export enum PaintScaleType {
|
||||
PPG = "PPG",
|
||||
}
|
||||
|
||||
export interface PaintScaleConfig {
|
||||
id: string;
|
||||
path?: string;
|
||||
type: PaintScaleType;
|
||||
pollingInterval: number;
|
||||
}
|
||||
|
||||
export const paintScaleTypeOptions = Object.values(PaintScaleType).map(
|
||||
(type) => ({
|
||||
value: type,
|
||||
label: type,
|
||||
}),
|
||||
);
|
||||
Reference in New Issue
Block a user