mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-11 14:19:38 +00:00
Add "Tools" tab with chart issue scanner
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
"electron-updater": "6.2.1",
|
||||
"electron-window-state": "5.0.3",
|
||||
"eventemitter3": "5.0.1",
|
||||
"exceljs": "^4.4.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"lodash": "4.17.21",
|
||||
"parse-sng": "4.0.3",
|
||||
|
||||
231
pnpm-lock.yaml
generated
231
pnpm-lock.yaml
generated
@@ -59,6 +59,9 @@ importers:
|
||||
eventemitter3:
|
||||
specifier: 5.0.1
|
||||
version: 5.0.1
|
||||
exceljs:
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0
|
||||
fs-extra:
|
||||
specifier: 11.2.0
|
||||
version: 11.2.0
|
||||
@@ -810,6 +813,12 @@ packages:
|
||||
resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
'@fast-csv/format@4.3.5':
|
||||
resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==}
|
||||
|
||||
'@fast-csv/parse@4.3.6':
|
||||
resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==}
|
||||
|
||||
'@humanwhocodes/config-array@0.11.14':
|
||||
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
|
||||
engines: {node: '>=10.10.0'}
|
||||
@@ -1238,6 +1247,9 @@ packages:
|
||||
'@types/ms@0.7.34':
|
||||
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
|
||||
|
||||
'@types/node@14.18.63':
|
||||
resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==}
|
||||
|
||||
'@types/node@18.16.0':
|
||||
resolution: {integrity: sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==}
|
||||
|
||||
@@ -1576,6 +1588,10 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
big-integer@1.6.52:
|
||||
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
binary-extensions@2.2.0:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1584,12 +1600,18 @@ packages:
|
||||
resolution: {integrity: sha512-5ATpz/uPDgq5GgEDxTB4ouXCde7q2lqAQlSdBRQVl/AJnxmQmhIfyxJx+0MGu//D5rHQifkfGbWWlaysG0o9NA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
binary@0.3.0:
|
||||
resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==}
|
||||
|
||||
bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
||||
bluebird-lst@1.0.9:
|
||||
resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==}
|
||||
|
||||
bluebird@3.4.7:
|
||||
resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==}
|
||||
|
||||
bluebird@3.7.2:
|
||||
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
|
||||
|
||||
@@ -1630,9 +1652,17 @@ packages:
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
buffer-indexof-polyfill@1.0.2:
|
||||
resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
buffer@5.7.1:
|
||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||
|
||||
buffers@0.1.1:
|
||||
resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==}
|
||||
engines: {node: '>=0.2.0'}
|
||||
|
||||
builder-util-runtime@9.2.4:
|
||||
resolution: {integrity: sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -1670,6 +1700,9 @@ packages:
|
||||
caniuse-lite@1.0.30001640:
|
||||
resolution: {integrity: sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==}
|
||||
|
||||
chainsaw@0.1.0:
|
||||
resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==}
|
||||
|
||||
chalk@1.1.3:
|
||||
resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1984,6 +2017,9 @@ packages:
|
||||
resolution: {tarball: https://codeload.github.com/corbanbrook/dsp.js/tar.gz/219600bb0346ee9a00686c9875c81123e2d8780e}
|
||||
version: 1.0.0
|
||||
|
||||
duplexer2@0.1.4:
|
||||
resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
|
||||
|
||||
duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
|
||||
@@ -2225,6 +2261,10 @@ packages:
|
||||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
exceljs@4.4.0:
|
||||
resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==}
|
||||
engines: {node: '>=8.3.0'}
|
||||
|
||||
exifreader@4.23.3:
|
||||
resolution: {integrity: sha512-/Ii4jiNp/5BXdKOiWXZYrWmZFn/ANu3bMVGO7GFQufao5M52/fK2OsAPMH34PL4S79z1eZBzAoaYyBXit0zzVA==}
|
||||
|
||||
@@ -2244,6 +2284,10 @@ packages:
|
||||
resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==}
|
||||
engines: {'0': node >=0.6.0}
|
||||
|
||||
fast-csv@4.3.6:
|
||||
resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
@@ -2364,6 +2408,11 @@ packages:
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fstream@1.0.12:
|
||||
resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==}
|
||||
engines: {node: '>=0.6'}
|
||||
deprecated: This package is no longer supported.
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
@@ -2546,6 +2595,9 @@ packages:
|
||||
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
immediate@3.0.6:
|
||||
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
|
||||
|
||||
immutable@4.3.4:
|
||||
resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==}
|
||||
|
||||
@@ -2791,6 +2843,9 @@ packages:
|
||||
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
|
||||
engines: {'0': node >= 0.2.0}
|
||||
|
||||
jszip@3.10.1:
|
||||
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
|
||||
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
@@ -2805,6 +2860,9 @@ packages:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
lie@3.3.0:
|
||||
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
|
||||
|
||||
lilconfig@2.1.0:
|
||||
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2820,6 +2878,9 @@ packages:
|
||||
resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
listenercount@1.0.1:
|
||||
resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==}
|
||||
|
||||
lmdb@3.0.8:
|
||||
resolution: {integrity: sha512-9rp8JT4jPhCRJUL7vRARa2N06OLSYzLwQsEkhC6Qu5XbcLyM/XBLMzDlgS/K7l7c5CdURLdDk9uE+hPFIogHTQ==}
|
||||
hasBin: true
|
||||
@@ -2843,18 +2904,36 @@ packages:
|
||||
lodash.flatten@4.4.0:
|
||||
resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
|
||||
|
||||
lodash.groupby@4.6.0:
|
||||
resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==}
|
||||
|
||||
lodash.isboolean@3.0.3:
|
||||
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
||||
|
||||
lodash.isequal@4.5.0:
|
||||
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
|
||||
|
||||
lodash.isfunction@3.0.9:
|
||||
resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==}
|
||||
|
||||
lodash.isnil@4.0.0:
|
||||
resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==}
|
||||
|
||||
lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
|
||||
lodash.isundefined@3.0.1:
|
||||
resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==}
|
||||
|
||||
lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
|
||||
lodash.union@4.6.0:
|
||||
resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==}
|
||||
|
||||
lodash.uniq@4.5.0:
|
||||
resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
@@ -3223,6 +3302,9 @@ packages:
|
||||
engines: {node: ^16.14.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
|
||||
pako@1.0.11:
|
||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||
|
||||
parent-module@1.0.1:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3489,6 +3571,11 @@ packages:
|
||||
rfc4648@1.5.3:
|
||||
resolution: {integrity: sha512-MjOWxM065+WswwnmNONOT+bD1nXzY9Km6u3kzvnx8F8/HXGZdz3T6e6vZJ8Q/RIMUSp/nxqjH3GwvJDy8ijeQQ==}
|
||||
|
||||
rimraf@2.7.1:
|
||||
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
|
||||
rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
@@ -3540,6 +3627,10 @@ packages:
|
||||
sax@1.3.0:
|
||||
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
|
||||
|
||||
saxes@5.0.1:
|
||||
resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
scan-chart@4.1.4:
|
||||
resolution: {integrity: sha512-LzFMfPxLgT7X5TCYK5Xkags3XVu3V7JAoDg7Te0A5GkSivwKA4fnwMRpdtRLiThlnspIAO2L/+lrysz1pIagmg==}
|
||||
|
||||
@@ -3571,6 +3662,9 @@ packages:
|
||||
resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
setimmediate@1.0.5:
|
||||
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3828,6 +3922,9 @@ packages:
|
||||
resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==}
|
||||
hasBin: true
|
||||
|
||||
traverse@0.3.9:
|
||||
resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==}
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
@@ -3927,6 +4024,9 @@ packages:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
unzipper@0.10.14:
|
||||
resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==}
|
||||
|
||||
update-browserslist-db@1.1.0:
|
||||
resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==}
|
||||
hasBin: true
|
||||
@@ -3942,6 +4042,10 @@ packages:
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
uuid@8.3.2:
|
||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
||||
hasBin: true
|
||||
|
||||
validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
|
||||
@@ -4040,6 +4144,9 @@ packages:
|
||||
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
xmlchars@2.2.0:
|
||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -4735,6 +4842,25 @@ snapshots:
|
||||
|
||||
'@eslint/js@8.57.0': {}
|
||||
|
||||
'@fast-csv/format@4.3.5':
|
||||
dependencies:
|
||||
'@types/node': 14.18.63
|
||||
lodash.escaperegexp: 4.1.2
|
||||
lodash.isboolean: 3.0.3
|
||||
lodash.isequal: 4.5.0
|
||||
lodash.isfunction: 3.0.9
|
||||
lodash.isnil: 4.0.0
|
||||
|
||||
'@fast-csv/parse@4.3.6':
|
||||
dependencies:
|
||||
'@types/node': 14.18.63
|
||||
lodash.escaperegexp: 4.1.2
|
||||
lodash.groupby: 4.6.0
|
||||
lodash.isfunction: 3.0.9
|
||||
lodash.isnil: 4.0.0
|
||||
lodash.isundefined: 3.0.1
|
||||
lodash.uniq: 4.5.0
|
||||
|
||||
'@humanwhocodes/config-array@0.11.14':
|
||||
dependencies:
|
||||
'@humanwhocodes/object-schema': 2.0.3
|
||||
@@ -5131,6 +5257,8 @@ snapshots:
|
||||
|
||||
'@types/ms@0.7.34': {}
|
||||
|
||||
'@types/node@14.18.63': {}
|
||||
|
||||
'@types/node@18.16.0': {}
|
||||
|
||||
'@types/node@20.14.10':
|
||||
@@ -5553,10 +5681,17 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
big-integer@1.6.52: {}
|
||||
|
||||
binary-extensions@2.2.0: {}
|
||||
|
||||
binary-parser@2.2.1: {}
|
||||
|
||||
binary@0.3.0:
|
||||
dependencies:
|
||||
buffers: 0.1.1
|
||||
chainsaw: 0.1.0
|
||||
|
||||
bl@4.1.0:
|
||||
dependencies:
|
||||
buffer: 5.7.1
|
||||
@@ -5567,6 +5702,8 @@ snapshots:
|
||||
dependencies:
|
||||
bluebird: 3.7.2
|
||||
|
||||
bluebird@3.4.7: {}
|
||||
|
||||
bluebird@3.7.2: {}
|
||||
|
||||
boolbase@1.0.0: {}
|
||||
@@ -5604,11 +5741,15 @@ snapshots:
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer-indexof-polyfill@1.0.2: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
buffers@0.1.1: {}
|
||||
|
||||
builder-util-runtime@9.2.4:
|
||||
dependencies:
|
||||
debug: 4.3.5(supports-color@5.5.0)
|
||||
@@ -5682,6 +5823,10 @@ snapshots:
|
||||
|
||||
caniuse-lite@1.0.30001640: {}
|
||||
|
||||
chainsaw@0.1.0:
|
||||
dependencies:
|
||||
traverse: 0.3.9
|
||||
|
||||
chalk@1.1.3:
|
||||
dependencies:
|
||||
ansi-styles: 2.2.1
|
||||
@@ -6008,6 +6153,10 @@ snapshots:
|
||||
|
||||
dsp.js@https://codeload.github.com/corbanbrook/dsp.js/tar.gz/219600bb0346ee9a00686c9875c81123e2d8780e: {}
|
||||
|
||||
duplexer2@0.1.4:
|
||||
dependencies:
|
||||
readable-stream: 2.3.8
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
@@ -6403,6 +6552,18 @@ snapshots:
|
||||
|
||||
eventemitter3@5.0.1: {}
|
||||
|
||||
exceljs@4.4.0:
|
||||
dependencies:
|
||||
archiver: 5.3.2
|
||||
dayjs: 1.11.11
|
||||
fast-csv: 4.3.6
|
||||
jszip: 3.10.1
|
||||
readable-stream: 3.6.2
|
||||
saxes: 5.0.1
|
||||
tmp: 0.2.1
|
||||
unzipper: 0.10.14
|
||||
uuid: 8.3.2
|
||||
|
||||
exifreader@4.23.3:
|
||||
optionalDependencies:
|
||||
'@xmldom/xmldom': 0.8.10
|
||||
@@ -6428,6 +6589,11 @@ snapshots:
|
||||
extsprintf@1.4.1:
|
||||
optional: true
|
||||
|
||||
fast-csv@4.3.6:
|
||||
dependencies:
|
||||
'@fast-csv/format': 4.3.5
|
||||
'@fast-csv/parse': 4.3.6
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-diff@1.3.0: {}
|
||||
@@ -6553,6 +6719,13 @@ snapshots:
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
fstream@1.0.12:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
inherits: 2.0.4
|
||||
mkdirp: 0.5.6
|
||||
rimraf: 2.7.1
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
function.prototype.name@1.1.6:
|
||||
@@ -6773,6 +6946,8 @@ snapshots:
|
||||
|
||||
ignore@5.3.1: {}
|
||||
|
||||
immediate@3.0.6: {}
|
||||
|
||||
immutable@4.3.4: {}
|
||||
|
||||
import-fresh@3.3.0:
|
||||
@@ -6995,6 +7170,13 @@ snapshots:
|
||||
|
||||
jsonparse@1.3.1: {}
|
||||
|
||||
jszip@3.10.1:
|
||||
dependencies:
|
||||
lie: 3.3.0
|
||||
pako: 1.0.11
|
||||
readable-stream: 2.3.8
|
||||
setimmediate: 1.0.5
|
||||
|
||||
keyv@4.5.4:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
@@ -7010,6 +7192,10 @@ snapshots:
|
||||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
lie@3.3.0:
|
||||
dependencies:
|
||||
immediate: 3.0.6
|
||||
|
||||
lilconfig@2.1.0: {}
|
||||
|
||||
lilconfig@3.0.0: {}
|
||||
@@ -7018,6 +7204,8 @@ snapshots:
|
||||
|
||||
lines-and-columns@2.0.4: {}
|
||||
|
||||
listenercount@1.0.1: {}
|
||||
|
||||
lmdb@3.0.8:
|
||||
dependencies:
|
||||
msgpackr: 1.10.2
|
||||
@@ -7047,14 +7235,26 @@ snapshots:
|
||||
|
||||
lodash.flatten@4.4.0: {}
|
||||
|
||||
lodash.groupby@4.6.0: {}
|
||||
|
||||
lodash.isboolean@3.0.3: {}
|
||||
|
||||
lodash.isequal@4.5.0: {}
|
||||
|
||||
lodash.isfunction@3.0.9: {}
|
||||
|
||||
lodash.isnil@4.0.0: {}
|
||||
|
||||
lodash.isplainobject@4.0.6: {}
|
||||
|
||||
lodash.isundefined@3.0.1: {}
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lodash.union@4.6.0: {}
|
||||
|
||||
lodash.uniq@4.5.0: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
log-symbols@4.1.0:
|
||||
@@ -7526,6 +7726,8 @@ snapshots:
|
||||
- bluebird
|
||||
- supports-color
|
||||
|
||||
pako@1.0.11: {}
|
||||
|
||||
parent-module@1.0.1:
|
||||
dependencies:
|
||||
callsites: 3.1.0
|
||||
@@ -7771,6 +7973,10 @@ snapshots:
|
||||
|
||||
rfc4648@1.5.3: {}
|
||||
|
||||
rimraf@2.7.1:
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
|
||||
rimraf@3.0.2:
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
@@ -7848,6 +8054,10 @@ snapshots:
|
||||
|
||||
sax@1.3.0: {}
|
||||
|
||||
saxes@5.0.1:
|
||||
dependencies:
|
||||
xmlchars: 2.2.0
|
||||
|
||||
scan-chart@4.1.4:
|
||||
dependencies:
|
||||
'@noble/hashes': 1.4.0
|
||||
@@ -7892,6 +8102,8 @@ snapshots:
|
||||
functions-have-names: 1.2.3
|
||||
has-property-descriptors: 1.0.2
|
||||
|
||||
setimmediate@1.0.5: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
@@ -8193,6 +8405,8 @@ snapshots:
|
||||
dependencies:
|
||||
nopt: 1.0.10
|
||||
|
||||
traverse@0.3.9: {}
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
truncate-utf8-bytes@1.0.2:
|
||||
@@ -8295,6 +8509,19 @@ snapshots:
|
||||
|
||||
universalify@2.0.1: {}
|
||||
|
||||
unzipper@0.10.14:
|
||||
dependencies:
|
||||
big-integer: 1.6.52
|
||||
binary: 0.3.0
|
||||
bluebird: 3.4.7
|
||||
buffer-indexof-polyfill: 1.0.2
|
||||
duplexer2: 0.1.4
|
||||
fstream: 1.0.12
|
||||
graceful-fs: 4.2.11
|
||||
listenercount: 1.0.1
|
||||
readable-stream: 2.3.8
|
||||
setimmediate: 1.0.5
|
||||
|
||||
update-browserslist-db@1.1.0(browserslist@4.23.2):
|
||||
dependencies:
|
||||
browserslist: 4.23.2
|
||||
@@ -8309,6 +8536,8 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
uuid@8.3.2: {}
|
||||
|
||||
validate-npm-package-license@3.0.4:
|
||||
dependencies:
|
||||
spdx-correct: 3.2.0
|
||||
@@ -8411,6 +8640,8 @@ snapshots:
|
||||
|
||||
xmlbuilder@15.1.1: {}
|
||||
|
||||
xmlchars@2.2.0: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
@@ -3,11 +3,13 @@ import { RouteReuseStrategy, RouterModule, Routes } from '@angular/router'
|
||||
|
||||
import { BrowseComponent } from './components/browse/browse.component'
|
||||
import { SettingsComponent } from './components/settings/settings.component'
|
||||
import { ToolsComponent } from './components/tools/tools.component'
|
||||
import { TabPersistStrategy } from './core/tab-persist.strategy'
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'browse', component: BrowseComponent, data: { shouldReuse: true } },
|
||||
{ path: 'library', redirectTo: '/browse' },
|
||||
{ path: 'tools', component: ToolsComponent, data: { shouldReuse: true } },
|
||||
{ path: 'settings', component: SettingsComponent, data: { shouldReuse: true } },
|
||||
{ path: 'about', redirectTo: '/browse' },
|
||||
{ path: '**', redirectTo: '/browse' },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<div class="navbar p-0 min-h-0 bg-base-100" style="-webkit-app-region: drag">
|
||||
<div style="-webkit-app-region: no-drag">
|
||||
<button class="btn btn-ghost rounded-none" routerLinkActive="btn-active" routerLink="/browse">Browse</button>
|
||||
<button class="btn btn-ghost rounded-none" routerLinkActive="btn-active" routerLink="/tools">Tools</button>
|
||||
<button class="btn btn-ghost rounded-none flex flex-nowrap" routerLinkActive="btn-active" routerLink="/settings">
|
||||
<i *ngIf="updateAvailable === 'error'" class="bi bi-exclamation-triangle-fill text-warning"></i>
|
||||
Settings
|
||||
|
||||
62
src-angular/app/components/tools/tools.component.html
Normal file
62
src-angular/app/components/tools/tools.component.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<div class="p-8 flex flex-col gap-3">
|
||||
<div class="text-xl">Chart issue scanning</div>
|
||||
<div class="flex gap-3">
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text">Issue scan directory</span>
|
||||
</div>
|
||||
<div class="join w-full">
|
||||
<input
|
||||
[value]="settingsService.issueScanDirectory || 'No folder selected'"
|
||||
class="join-item input input-bordered cursor-default pointer-events-none flex-1"
|
||||
readonly
|
||||
type="text"
|
||||
placeholder="No directory selected!" />
|
||||
@if (settingsService.issueScanDirectory !== undefined) {
|
||||
<button (click)="openIssueScanDirectory()" class="join-item btn btn-neutral">Open Folder</button>
|
||||
}
|
||||
<button (click)="getIssueScanDirectory()" class="join-item btn btn-primary">Choose</button>
|
||||
</div>
|
||||
</label>
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text">Spreadsheet output directory</span>
|
||||
</div>
|
||||
<div class="join w-full">
|
||||
<input
|
||||
[value]="settingsService.spreadsheetOutputDirectory || 'No folder selected'"
|
||||
class="join-item input input-bordered cursor-default pointer-events-none flex-1"
|
||||
readonly
|
||||
type="text"
|
||||
placeholder="No directory selected!" />
|
||||
@if (settingsService.spreadsheetOutputDirectory !== undefined) {
|
||||
<button (click)="openSpreadsheetOutputDirectory()" class="join-item btn btn-neutral">Open Folder</button>
|
||||
}
|
||||
<button (click)="getSpreadsheetOutputDirectory()" class="join-item btn btn-primary">Choose</button>
|
||||
</div>
|
||||
</label>
|
||||
<button
|
||||
(click)="scanIssues()"
|
||||
class="btn btn-primary self-end"
|
||||
[attr.disabled]="
|
||||
settingsService.issueScanDirectory === undefined || settingsService.spreadsheetOutputDirectory === undefined || scanning ? true : null
|
||||
">
|
||||
<i class="bi bi-gear-wide-connected text-lg"></i>
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
<dialog #scanErrorModal class="modal">
|
||||
<div class="modal-box bg-base-100 text-base-content flex flex-col gap-2">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">
|
||||
<i class="bi bi-x-lg text-lg"></i>
|
||||
</button>
|
||||
</form>
|
||||
<h3 class="text-lg font-bold">Error scanning charts for issues:</h3>
|
||||
<p class="py-4">{{ scanErrorText }}</p>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
77
src-angular/app/components/tools/tools.component.ts
Normal file
77
src-angular/app/components/tools/tools.component.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Component, ElementRef, NgZone, ViewChild } from '@angular/core'
|
||||
|
||||
import { SettingsService } from 'src-angular/app/core/services/settings.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-tools',
|
||||
templateUrl: './tools.component.html',
|
||||
})
|
||||
export class ToolsComponent {
|
||||
@ViewChild('themeDropdown', { static: true }) themeDropdown: ElementRef
|
||||
@ViewChild('scanErrorModal') scanErrorModal: ElementRef<HTMLDialogElement>
|
||||
|
||||
public scanning = false
|
||||
public buttonText = 'Scan for issues'
|
||||
public scanErrorText = ''
|
||||
|
||||
constructor(
|
||||
zone: NgZone,
|
||||
public settingsService: SettingsService,
|
||||
) {
|
||||
window.electron.on.updateIssueScan(({ status, message }) => zone.run(() => {
|
||||
if (status === 'progress') {
|
||||
this.buttonText = message
|
||||
} else if (status === 'error') {
|
||||
this.scanning = false
|
||||
this.scanErrorText = message
|
||||
this.scanErrorModal.nativeElement.showModal()
|
||||
} else if (status === 'done') {
|
||||
this.scanning = false
|
||||
this.buttonText = 'Complete! (click to scan again)'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
openIssueScanDirectory() {
|
||||
if (this.settingsService.issueScanDirectory) {
|
||||
window.electron.emit.showFolder(this.settingsService.issueScanDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
async getIssueScanDirectory() {
|
||||
const result = await window.electron.invoke.showOpenDialog({
|
||||
title: 'Choose issue scan folder',
|
||||
defaultPath: this.settingsService.issueScanDirectory || '',
|
||||
properties: ['openDirectory'],
|
||||
})
|
||||
|
||||
if (result.canceled === false) {
|
||||
this.settingsService.issueScanDirectory = result.filePaths[0]
|
||||
}
|
||||
}
|
||||
|
||||
openSpreadsheetOutputDirectory() {
|
||||
if (this.settingsService.spreadsheetOutputDirectory) {
|
||||
window.electron.emit.showFolder(this.settingsService.spreadsheetOutputDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
async getSpreadsheetOutputDirectory() {
|
||||
const result = await window.electron.invoke.showOpenDialog({
|
||||
title: 'Choose spreadsheet output folder',
|
||||
defaultPath: this.settingsService.spreadsheetOutputDirectory || '',
|
||||
properties: ['openDirectory'],
|
||||
})
|
||||
|
||||
if (result.canceled === false) {
|
||||
this.settingsService.spreadsheetOutputDirectory = result.filePaths[0]
|
||||
}
|
||||
}
|
||||
|
||||
async scanIssues() {
|
||||
if (this.settingsService.issueScanDirectory && this.settingsService.spreadsheetOutputDirectory) {
|
||||
this.scanning = true
|
||||
window.electron.emit.scanIssues()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,20 @@ export class SettingsService {
|
||||
this.settings.libraryPath = value
|
||||
this.saveSettings()
|
||||
}
|
||||
get issueScanDirectory() {
|
||||
return this.settings.issueScanPath
|
||||
}
|
||||
set issueScanDirectory(value: string | undefined) {
|
||||
this.settings.issueScanPath = value
|
||||
this.saveSettings()
|
||||
}
|
||||
get spreadsheetOutputDirectory() {
|
||||
return this.settings.spreadsheetOutputPath
|
||||
}
|
||||
set spreadsheetOutputDirectory(value: string | undefined) {
|
||||
this.settings.spreadsheetOutputPath = value
|
||||
this.saveSettings()
|
||||
}
|
||||
get chartFolderName() {
|
||||
return this.settings.chartFolderName
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IpcInvokeHandlers, IpcToMainEmitHandlers } from '../src-shared/interfaces/ipc.interface.js'
|
||||
import { download } from './ipc/DownloadHandler.ipc.js'
|
||||
import { scanIssues } from './ipc/issue-scan/IssueScanHandler.ipc.js'
|
||||
import { getSettings, setSettings } from './ipc/SettingsHandler.ipc.js'
|
||||
import { downloadUpdate, getCurrentVersion, getUpdateAvailable, quitAndInstall, retryUpdate } from './ipc/UpdateHandler.ipc.js'
|
||||
import { getPlatform, getThemeColors, isMaximized, maximize, minimize, openUrl, quit, restore, showFile, showFolder, showOpenDialog, toggleDevTools } from './ipc/UtilHandlers.ipc.js'
|
||||
@@ -31,5 +32,6 @@ export function getIpcToMainEmitHandlers(): IpcToMainEmitHandlers {
|
||||
quit,
|
||||
showFile,
|
||||
showFolder,
|
||||
scanIssues,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { dataPath, settingsPath, tempPath, themesPath } from '../../src-shared/P
|
||||
import { defaultSettings, Settings } from '../../src-shared/Settings.js'
|
||||
import { mainWindow } from '../main.js'
|
||||
|
||||
console.log(settingsPath)
|
||||
export let settings = readSettings()
|
||||
|
||||
function readSettings() {
|
||||
|
||||
240
src-electron/ipc/issue-scan/ExcelBuilder.ts
Normal file
240
src-electron/ipc/issue-scan/ExcelBuilder.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import exceljs, { Borders } from 'exceljs'
|
||||
import _ from 'lodash'
|
||||
import { FolderIssueType, ScannedChart } from 'scan-chart'
|
||||
|
||||
export function getChartIssues(charts: { chart: ScannedChart; path: string }[]) {
|
||||
const chartIssues: {
|
||||
path: string
|
||||
artist: string
|
||||
name: string
|
||||
charter: string
|
||||
errorName: string
|
||||
errorDescription: string
|
||||
fixMandatory: boolean
|
||||
}[] = []
|
||||
|
||||
for (const chart of charts) {
|
||||
const addIssue = (
|
||||
errorName: string,
|
||||
errorDescription: string,
|
||||
fixMandatory: boolean,
|
||||
) => {
|
||||
|
||||
chartIssues.push({
|
||||
path: chart.path,
|
||||
artist: removeStyleTags(chart.chart.artist ?? ''),
|
||||
name: removeStyleTags(chart.chart.name ?? ''),
|
||||
charter: removeStyleTags(chart.chart.charter ?? ''),
|
||||
errorName,
|
||||
errorDescription,
|
||||
fixMandatory,
|
||||
})
|
||||
}
|
||||
|
||||
if (chart.chart.folderIssues.length > 0) {
|
||||
for (const folderIssue of chart.chart.folderIssues) {
|
||||
if (folderIssue.folderIssue === 'albumArtSize') {
|
||||
continue
|
||||
} // Ignored; .sng conversion fixes this
|
||||
addIssue(
|
||||
folderIssue.folderIssue,
|
||||
folderIssue.description,
|
||||
(
|
||||
[
|
||||
'noMetadata',
|
||||
'invalidMetadata',
|
||||
'noAudio',
|
||||
'badAudio',
|
||||
'noChart',
|
||||
'invalidChart',
|
||||
'badChart',
|
||||
] satisfies FolderIssueType[] as FolderIssueType[]
|
||||
).includes(folderIssue.folderIssue),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (const metadataIssue of chart.chart.metadataIssues) {
|
||||
addIssue(
|
||||
metadataIssue.metadataIssue,
|
||||
metadataIssue.description,
|
||||
['"name"', '"artist"', '"charter"'].some(property => metadataIssue.description.includes(property)),
|
||||
)
|
||||
}
|
||||
|
||||
if (chart.chart.notesData) {
|
||||
for (const issue of chart.chart.notesData.chartIssues) {
|
||||
addIssue(
|
||||
issue.noteIssue,
|
||||
`${issue.instrument ? `[${issue.instrument}]` : ''}${issue.difficulty ? `[${issue.difficulty}]` : ''} ${issue.description}`,
|
||||
issue.noteIssue === 'noNotes',
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chartIssues
|
||||
}
|
||||
|
||||
export async function getIssuesXLSX(
|
||||
chartIssues: Awaited<ReturnType<typeof getChartIssues>>,
|
||||
) {
|
||||
const chartIssueHeaders = [
|
||||
{ text: 'Artist', width: 160 / 7 },
|
||||
{ text: 'Name', width: 400 / 7 },
|
||||
{ text: 'Charter', width: 160 / 7 },
|
||||
{ text: 'Issue Name', width: 160 / 7 },
|
||||
{
|
||||
text: 'Issue Description (a more detailed description of issue types can be '
|
||||
+ 'found at https://drive.google.com/open?id=1UK7GsP4ZHJkOg8uREFRMY72svySaDlf0QRTGlk-ruYQ)',
|
||||
width: 650 / 7,
|
||||
},
|
||||
{ text: 'Fix Mandatory?', width: 120 / 7 },
|
||||
{ text: 'Path', width: 600 / 7 },
|
||||
]
|
||||
const chartIssueRows: (string | { text: string; hyperlink: string })[][] = []
|
||||
for (const issue of chartIssues) {
|
||||
chartIssueRows.push([
|
||||
issue.artist,
|
||||
issue.name,
|
||||
issue.charter,
|
||||
issue.errorName,
|
||||
issue.errorDescription,
|
||||
issue.fixMandatory ? 'yes' : 'no',
|
||||
issue.path,
|
||||
])
|
||||
}
|
||||
|
||||
const gridlineBorderStyle = {
|
||||
top: { style: 'thin', color: { argb: 'FFD0D0D0' } },
|
||||
left: { style: 'thin', color: { argb: 'FFD0D0D0' } },
|
||||
bottom: { style: 'thin', color: { argb: 'FFD0D0D0' } },
|
||||
right: { style: 'thin', color: { argb: 'FFD0D0D0' } },
|
||||
} satisfies Partial<Borders>
|
||||
const workbook = new exceljs.Workbook()
|
||||
workbook.creator = 'Chorus'
|
||||
workbook.created = new Date()
|
||||
workbook.modified = new Date()
|
||||
|
||||
const chartIssuesWorksheet = workbook.addWorksheet('Chart Issues', {
|
||||
views: [{ state: 'frozen', ySplit: 1 }], // Sticky header row
|
||||
})
|
||||
chartIssuesWorksheet.autoFilter = {
|
||||
from: { row: 1, column: 1 },
|
||||
to: { row: chartIssueRows.length + 1, column: chartIssueHeaders.length },
|
||||
}
|
||||
chartIssueHeaders.forEach((header, index) => {
|
||||
const cell = chartIssuesWorksheet.getCell(1, index + 1)
|
||||
cell.value = header.text
|
||||
cell.fill = {
|
||||
type: 'pattern',
|
||||
pattern: 'solid',
|
||||
fgColor: { argb: 'FFD3D3D3' },
|
||||
}
|
||||
cell.font = { bold: true }
|
||||
const column = chartIssuesWorksheet.getColumn(index + 1)
|
||||
column.width = header.width
|
||||
column.border = gridlineBorderStyle
|
||||
})
|
||||
chartIssuesWorksheet.addRows(chartIssueRows)
|
||||
chartIssuesWorksheet.addConditionalFormatting({
|
||||
ref: `A2:${columnNumberToLetter(chartIssueHeaders.length)}${chartIssueRows.length + 1
|
||||
}`,
|
||||
rules: [
|
||||
{
|
||||
type: 'expression',
|
||||
priority: 99999,
|
||||
formulae: ['MOD(ROW(),2)=0'],
|
||||
style: {
|
||||
fill: {
|
||||
type: 'pattern',
|
||||
pattern: 'solid',
|
||||
fgColor: { argb: 'FFF7F7F7' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
return await workbook.xlsx.writeBuffer({ useStyles: true })
|
||||
}
|
||||
|
||||
export function columnNumberToLetter(column: number) {
|
||||
let temp,
|
||||
letter = ''
|
||||
while (column > 0) {
|
||||
temp = (column - 1) % 26
|
||||
letter = String.fromCharCode(temp + 65) + letter
|
||||
column = (column - temp - 1) / 26
|
||||
}
|
||||
return letter
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns an string representation of `ms` that looks like HH:MM:SS.mm
|
||||
*/
|
||||
export function msToExactTime(ms: number) {
|
||||
const seconds = _.round((ms / 1000) % 60, 2)
|
||||
const minutes = Math.floor((ms / 1000 / 60) % 60)
|
||||
const hours = Math.floor((ms / 1000 / 60 / 60) % 24)
|
||||
return `${hours ? `${hours}:` : ''}${_.padStart(
|
||||
minutes + '',
|
||||
2,
|
||||
'0',
|
||||
)}:${_.padStart(seconds.toFixed(2), 5, '0')}`
|
||||
}
|
||||
|
||||
const allowedTags = [
|
||||
'align',
|
||||
'allcaps',
|
||||
'alpha',
|
||||
'b',
|
||||
'br',
|
||||
'color',
|
||||
'cspace',
|
||||
'font',
|
||||
'font-weight',
|
||||
'gradient',
|
||||
'i',
|
||||
'indent',
|
||||
'line-height',
|
||||
'line-indent',
|
||||
'link',
|
||||
'lowercase',
|
||||
'margin',
|
||||
'mark',
|
||||
'mspace',
|
||||
'nobr',
|
||||
'noparse',
|
||||
'page',
|
||||
'pos',
|
||||
'rotate',
|
||||
's',
|
||||
'size',
|
||||
'smallcaps',
|
||||
'space',
|
||||
'sprite',
|
||||
'strikethrough',
|
||||
'style',
|
||||
'sub',
|
||||
'sup',
|
||||
'u',
|
||||
'uppercase',
|
||||
'voffset',
|
||||
'width',
|
||||
]
|
||||
const tagPattern = allowedTags.map(tag => `\\b${tag}\\b`).join('|')
|
||||
/**
|
||||
* @returns `text` with all style tags removed. (e.g. "<color=#AEFFFF>Aren Eternal</color> & Geo" -> "Aren Eternal & Geo")
|
||||
*/
|
||||
export function removeStyleTags(text: string) {
|
||||
let oldText = text
|
||||
let newText = text
|
||||
do {
|
||||
oldText = newText
|
||||
newText = newText
|
||||
.replace(new RegExp(`<\\s*\\/?\\s*(?:#|${tagPattern})[^>]*>`, 'gi'), '')
|
||||
.trim()
|
||||
} while (newText !== oldText)
|
||||
return newText
|
||||
}
|
||||
183
src-electron/ipc/issue-scan/IssueScanHandler.ipc.ts
Normal file
183
src-electron/ipc/issue-scan/IssueScanHandler.ipc.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import Bottleneck from 'bottleneck'
|
||||
import dayjs from 'dayjs'
|
||||
import { shell } from 'electron'
|
||||
import { createReadStream } from 'fs'
|
||||
import pkg from 'fs-extra'
|
||||
import _ from 'lodash'
|
||||
import { SngHeader, SngStream } from 'parse-sng'
|
||||
import { scanChartFolder, ScannedChart } from 'scan-chart'
|
||||
import { Readable } from 'stream'
|
||||
import { inspect } from 'util'
|
||||
|
||||
import { appearsToBeChartFolder, getExtension, hasAlbumName, hasChartExtension, hasIniExtension, hasSngExtension } from '../../../src-shared/UtilFunctions.js'
|
||||
import { hasVideoExtension } from '../../ElectronUtilFunctions.js'
|
||||
import { emitIpcEvent } from '../../main.js'
|
||||
import { getSettings } from '../SettingsHandler.ipc.js'
|
||||
import { getChartIssues, getIssuesXLSX } from './ExcelBuilder.js'
|
||||
|
||||
const { readdir, readFile, writeFile } = pkg
|
||||
export async function scanIssues() {
|
||||
const settings = await getSettings()
|
||||
if (!settings.issueScanPath || !settings.spreadsheetOutputPath) {
|
||||
emitIpcEvent('updateIssueScan', {
|
||||
status: 'error',
|
||||
message: 'Scan path or output path were not properly defined.',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const chartFolders = await getChartFolders(settings.issueScanPath)
|
||||
|
||||
const limiter = new Bottleneck({ maxConcurrent: 20 }) // Ensures memory use stays bounded
|
||||
|
||||
const charts: { chart: ScannedChart; path: string }[] = []
|
||||
for (const chartFolder of chartFolders) {
|
||||
limiter.schedule(async () => {
|
||||
const isSng = chartFolder.files.length === 1 && hasSngExtension(chartFolder.files[0])
|
||||
const files = isSng ? await getFilesFromSng([chartFolder.path, chartFolder.files[0]].join('/')) : await getFilesFromFolder(chartFolder)
|
||||
|
||||
const result: { chart: ScannedChart; path: string } = {
|
||||
chart: scanChartFolder(files),
|
||||
path: chartFolder.path,
|
||||
}
|
||||
charts.push(result)
|
||||
emitIpcEvent('updateIssueScan', { status: 'progress', message: `${charts.length}/${chartFolders.length} scanned...` })
|
||||
})
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
limiter.on('error', err => {
|
||||
reject(err)
|
||||
limiter.stop()
|
||||
})
|
||||
|
||||
limiter.on('idle', async () => {
|
||||
const issues = getChartIssues(charts)
|
||||
const xlsx = await getIssuesXLSX(issues)
|
||||
const outputPath = [settings.spreadsheetOutputPath, `chart_issues_${dayjs().format('YYYY.MM.DD_HH.mm.ss')}.xlsx`].join('/')
|
||||
await writeFile(outputPath, new Uint8Array(xlsx))
|
||||
await new Promise<void>(resolve => setTimeout(resolve, 500)) // Delay for OS file processing
|
||||
await shell.openPath(outputPath)
|
||||
emitIpcEvent('updateIssueScan', {
|
||||
status: 'done',
|
||||
message: `${issues.length} issues found in ${charts.length} charts. Spreadsheet saved to ${outputPath}`,
|
||||
})
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
emitIpcEvent('updateIssueScan', { status: 'error', message: inspect(err) })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns valid chart folders in `path` and all its subdirectories.
|
||||
*/
|
||||
async function getChartFolders(path: string) {
|
||||
const chartFolders: { path: string; files: string[] }[] = []
|
||||
|
||||
const entries = await readdir(path, { withFileTypes: true })
|
||||
|
||||
const subfolders = _.chain(entries)
|
||||
.filter(entry => entry.isDirectory() && entry.name !== '__MACOSX') // Apple should follow the principle of least astonishment (smh)
|
||||
.map(folder => getChartFolders([path, folder.name].join('/')))
|
||||
.value()
|
||||
|
||||
chartFolders.push(..._.flatMap(await Promise.all(subfolders)))
|
||||
|
||||
const sngFiles = entries.filter(entry => !entry.isDirectory() && hasSngExtension(entry.name))
|
||||
chartFolders.push(...sngFiles.map(sf => ({ path, files: [sf.name] })))
|
||||
|
||||
if (
|
||||
subfolders.length === 0 && // Charts won't contain other charts
|
||||
appearsToBeChartFolder(entries.map(entry => getExtension(entry.name)))
|
||||
) {
|
||||
chartFolders.push({
|
||||
path,
|
||||
files: entries.filter(entry => !entry.isDirectory()).map(entry => entry.name),
|
||||
})
|
||||
emitIpcEvent('updateIssueScan', { status: 'progress', message: `${chartFolders} charts found...` })
|
||||
}
|
||||
|
||||
return chartFolders
|
||||
}
|
||||
|
||||
async function getFilesFromSng(sngPath: string) {
|
||||
const sngStream = new SngStream(Readable.toWeb(createReadStream(sngPath)) as ReadableStream<Uint8Array>, { generateSongIni: true })
|
||||
|
||||
let header: SngHeader
|
||||
sngStream.on('header', h => header = h)
|
||||
const isFileTruncated = (fileName: string) => {
|
||||
const MAX_FILE_MIB = 2048
|
||||
const MAX_FILES_MIB = 5000
|
||||
const sortedFiles = _.sortBy(header.fileMeta, f => f.contentsLen)
|
||||
let usedSizeMib = 0
|
||||
for (const sortedFile of sortedFiles) {
|
||||
usedSizeMib += Number(sortedFile.contentsLen / BigInt(1024) / BigInt(1024))
|
||||
if (sortedFile.filename === fileName) {
|
||||
return usedSizeMib > MAX_FILES_MIB || sortedFile.contentsLen / BigInt(1024) / BigInt(1024) >= MAX_FILE_MIB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const files: { fileName: string; data: Uint8Array }[] = []
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
sngStream.on('file', async (fileName, fileStream, nextFile) => {
|
||||
const matchingFileMeta = header.fileMeta.find(f => f.filename === fileName)
|
||||
if (hasVideoExtension(fileName) || isFileTruncated(fileName) || !matchingFileMeta) {
|
||||
const reader = fileStream.getReader()
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const result = await reader.read()
|
||||
if (result.done) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const data = new Uint8Array(Number(matchingFileMeta.contentsLen))
|
||||
let offset = 0
|
||||
const reader = fileStream.getReader()
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const result = await reader.read()
|
||||
if (result.done) {
|
||||
break
|
||||
}
|
||||
data.set(result.value, offset)
|
||||
offset += result.value.length
|
||||
}
|
||||
|
||||
files.push({ fileName, data })
|
||||
}
|
||||
|
||||
if (nextFile) {
|
||||
nextFile()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
||||
sngStream.on('error', error => reject(error))
|
||||
|
||||
sngStream.start()
|
||||
})
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
async function getFilesFromFolder(chartFolder: { path: string; files: string[] }): Promise<{ fileName: string; data: Uint8Array }[]> {
|
||||
const files: { fileName: string; data: Uint8Array }[] = []
|
||||
|
||||
for (const fileName of chartFolder.files) {
|
||||
if (hasChartExtension(fileName) || hasIniExtension(fileName) || hasAlbumName(fileName)) {
|
||||
files.push({ fileName, data: await readFile(chartFolder.path + '/' + fileName) })
|
||||
} else {
|
||||
files.push({ fileName, data: new Uint8Array() })
|
||||
}
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
@@ -40,6 +40,7 @@ const electronApi: ContextBridgeApi = {
|
||||
quit: getEmitter('quit'),
|
||||
showFolder: getEmitter('showFolder'),
|
||||
showFile: getEmitter('showFile'),
|
||||
scanIssues: getEmitter('scanIssues'),
|
||||
},
|
||||
on: {
|
||||
errorLog: getListenerAdder('errorLog'),
|
||||
@@ -51,6 +52,7 @@ const electronApi: ContextBridgeApi = {
|
||||
queueUpdated: getListenerAdder('queueUpdated'),
|
||||
maximized: getListenerAdder('maximized'),
|
||||
minimized: getListenerAdder('minimized'),
|
||||
updateIssueScan: getListenerAdder('updateIssueScan'),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -23,19 +23,21 @@ export const themes = [
|
||||
* Represents Bridge's user settings.
|
||||
*/
|
||||
export interface Settings {
|
||||
downloadVideos: boolean // If background videos should be downloaded
|
||||
theme: typeof themes[number] // The name of the currently enabled UI theme
|
||||
customTheme: ThemeColors | null // The colors of a custom theme
|
||||
customThemePath: string | null // The last folder that contained the `customTheme`'s file
|
||||
libraryPath: string | undefined // The path to the user's library
|
||||
chartFolderName: string // The relative path and name of the chart that is saved in `libraryPath`
|
||||
isSng: boolean // If the chart should be downloaded as a .sng file or as a chart folder
|
||||
isCompactTable: boolean // If the search result table should have reduced padding
|
||||
visibleColumns: string[] // The search result columns to include
|
||||
zoomFactor: number // How much the display should be zoomed
|
||||
instrument: Instrument | null // The instrument selected by default, or `null` for "Any Instrument"
|
||||
difficulty: Difficulty | null // The difficulty selected by default, or `null` for "Any Difficulty"
|
||||
volume: number // The volume of the chart preview (0-100)
|
||||
downloadVideos: boolean // If background videos should be downloaded
|
||||
theme: typeof themes[number] // The name of the currently enabled UI theme
|
||||
customTheme: ThemeColors | null // The colors of a custom theme
|
||||
customThemePath: string | null // The last folder that contained the `customTheme`'s file
|
||||
libraryPath: string | undefined // The path to the user's library
|
||||
issueScanPath: string | undefined // The path to use when scanning for issues
|
||||
spreadsheetOutputPath: string | undefined // The path to use when saving generated issues
|
||||
chartFolderName: string // The relative path and name of the chart that is saved in `libraryPath`
|
||||
isSng: boolean // If the chart should be downloaded as a .sng file or as a chart folder
|
||||
isCompactTable: boolean // If the search result table should have reduced padding
|
||||
visibleColumns: string[] // The search result columns to include
|
||||
zoomFactor: number // How much the display should be zoomed
|
||||
instrument: Instrument | null // The instrument selected by default, or `null` for "Any Instrument"
|
||||
difficulty: Difficulty | null // The difficulty selected by default, or `null` for "Any Difficulty"
|
||||
volume: number // The volume of the chart preview (0-100)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,6 +49,8 @@ export const defaultSettings: Settings = {
|
||||
customTheme: null,
|
||||
customThemePath: null,
|
||||
libraryPath: undefined,
|
||||
issueScanPath: undefined,
|
||||
spreadsheetOutputPath: undefined,
|
||||
chartFolderName: '{artist} - {name} ({charter})',
|
||||
isSng: false,
|
||||
isCompactTable: false,
|
||||
|
||||
@@ -207,6 +207,27 @@ export function hasChartExtension(fileName: string) {
|
||||
return ['chart', 'mid'].includes(getExtension(fileName).toLowerCase())
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` if `fileName` is a valid album fileName.
|
||||
*/
|
||||
export function hasAlbumName(fileName: string) {
|
||||
return ['album.jpg', 'album.jpeg', 'album.png'].includes(fileName)
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` if `name` has a valid sng file extension.
|
||||
*/
|
||||
export function hasSngExtension(name: string) {
|
||||
return 'sng' === getExtension(name).toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` if `fileName` has a valid ini file extension.
|
||||
*/
|
||||
export function hasIniExtension(fileName: string) {
|
||||
return 'ini' === getExtension(fileName).toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns `true` if `fileName` is a valid chart fileName.
|
||||
*/
|
||||
@@ -246,6 +267,16 @@ export function hasAudioName(fileName: string) {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if the list of filename `extensions` appears to be intended as a chart folder.
|
||||
*/
|
||||
export function appearsToBeChartFolder(extensions: string[]) {
|
||||
const ext = extensions.map(extension => extension.toLowerCase())
|
||||
const containsNotes = ext.includes('chart') || ext.includes('mid')
|
||||
const containsAudio = ext.includes('ogg') || ext.includes('mp3') || ext.includes('wav') || ext.includes('opus')
|
||||
return containsNotes || containsAudio
|
||||
}
|
||||
|
||||
export function resolveChartFolderName(
|
||||
chartFolderName: string,
|
||||
chart: { name: string; artist: string; album: string; genre: string; year: string; charter: string },
|
||||
|
||||
@@ -74,6 +74,7 @@ export interface IpcToMainEmitEvents {
|
||||
quit: void
|
||||
showFolder: string
|
||||
showFile: string
|
||||
scanIssues: void
|
||||
}
|
||||
|
||||
export type IpcToMainEmitHandlers = {
|
||||
@@ -93,6 +94,7 @@ export interface IpcFromMainEmitEvents {
|
||||
queueUpdated: number[]
|
||||
maximized: void
|
||||
minimized: void
|
||||
updateIssueScan: { status: 'progress' | 'error' | 'done'; message: string }
|
||||
}
|
||||
|
||||
export type IpcFromMainEmitHandlers = {
|
||||
|
||||
Reference in New Issue
Block a user